New table design for users

This commit is contained in:
FinnStutzenstein 2016-11-04 11:57:23 +01:00
parent f84c381f38
commit e0f78b16a0
9 changed files with 711 additions and 440 deletions

View File

@ -866,23 +866,23 @@ img {
} }
/** Motion **/ /** Motion **/
#motion-table .row { #motion-os-table .identifier-col {
border: 1px solid #ddd; width: 50px;
border-top: 0px; min-height: 1px;
} }
#motion-table .data-row:hover { #motion-os-table .identifier-col > div {
background-color: #f5f5f5; text-align: center;
} }
#motion-table .data-row > div { #motion-os-table .title-col {
padding: 10px 0px 10px 8px; width: calc(100% - 50px);
} }
#motion-table .header-row { #motion-os-table .title-col, .os-table small {
border-top: 1px solid #ddd; color: #555;
background-color: #f5f5f5;
} }
.motion-toolbar, .speakers-toolbar { .motion-toolbar, .speakers-toolbar {
background-color: #f5f5f5; background-color: #f5f5f5;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
@ -890,6 +890,7 @@ img {
height: 54px; height: 54px;
margin: -20px -5px 50px -5px; margin: -20px -5px 50px -5px;
} }
.motion-toolbar:first-child { .motion-toolbar:first-child {
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -899,77 +900,78 @@ img {
padding: 12px 15px 10px 15px; padding: 12px 15px 10px 15px;
} }
.header-row > div {
/** OS-Table **/
.os-table .row {
border: 1px solid #ddd;
border-top: 0px;
}
.os-table .data-row:hover {
background-color: #f5f5f5;
}
.os-table .data-row > div {
padding: 10px 0px 10px 0px;
}
.os-table .header-row {
border-top: 1px solid #ddd;
background-color: #f5f5f5;
}
.os-table .header-row > div {
padding: 10px; padding: 10px;
} }
#motion-table .main-header { .os-table .main-header {
width: calc(100% - 50px); width: calc(100% - 50px);
float: right; float: right;
} }
#motion-table .main-header .form-inline { .os-table .main-header .form-inline {
margin-left: 15px; margin-left: 15px;
} }
#motion-table .content > div { .os-table .content > div { /* horizontal blocks */
display: inline-block; display: inline-block;
float: left; float: left;
} }
#motion-table .identifier-col { .os-table .content > div > div { /* vertival blocks */
width: 50px;
min-height: 1px;
}
#motion-table .identifier-col > div {
text-align: center;
}
#motion-table .title-col {
width: calc(100% - 50px);
}
#motion-table .title-col, #motion-table small {
color: #555;
}
#motion-table .content > div > div {
margin-bottom: 3px; margin-bottom: 3px;
} }
#motion-table .content > div > div:last-child { .os-table .content > div > div:last-child {
margin-bottom: 0px; margin-bottom: 0px;
} }
#motion-table .row .centered { .os-table .row .centered {
text-align: center; text-align: center;
} }
#motion-table .row .col-xs-1 { .os-table .row .col-xs-1 {
width: 50px; width: 50px;
} }
#motion-table .row .col-xs-4 { .os-table .projector {
width: 70px !important;
}
.os-table .row .col-xs-4 {
padding-right: 10px; padding-right: 10px;
} }
#motion-table .dropdown { .os-table .dropdown {
display: inline-block; display: inline-block;
} }
#motion-table .header-row .dropdown > span, #motion-table .sort-spacer { .os-table .header-row .dropdown > span, .os-table .sort-spacer {
padding: 5px 10px 5px 10px; padding: 5px 10px 5px 10px;
} }
#motion-table .dropdown-entry { .os-table .title {
padding: 5px 10px 5px 10px; font-size: 115%;
display: inline-block;
width: 100%;
}
#motion-table .title {
font-size: 110%;
margin-right: 10px; margin-right: 10px;
padding: 0; padding: 0;
background-color: transparent; background-color: transparent;

View File

@ -699,32 +699,6 @@ angular.module('OpenSlidesApp.core', [
} }
]) ])
/*
* This filter filters all items in an array. If the filterArray is empty, the
* array is passed. The filterArray contains numbers of the multiselect, e. g. [1, 3, 4].
* Then, all items in the array are passed, if the item_id (get with id_function) matches
* one of the ids in filterArray. id_function could also return a list of ids. Example:
* Item 1 has two tags with ids [1, 4]. filterArray == [3, 4] --> match
*/
.filter('SelectMultipleFilter', [
function () {
return function (array, filterArray, idFunction) {
if (filterArray.length === 0) {
return array;
}
return Array.prototype.filter.call(array, function (item) {
var id = idFunction(item);
if (!id) {
return false;
} else if (typeof id === 'number') {
id = [id];
}
return _.intersection(id, filterArray).length > 0;
});
};
}
])
// mark HTML as "trusted" // mark HTML as "trusted"
.filter('trusted', [ .filter('trusted', [
'$sce', '$sce',

View File

@ -342,6 +342,91 @@ angular.module('OpenSlidesApp.core.site', [
} }
]) ])
.factory('Multiselect', [
function () {
var instance = function () {
var areFiltersSet = function () {
var areFiltersSet = false;
_.forEach(self.filters, function (filterList, filter) {
if (filterList.length > 0) {
areFiltersSet = true;
}
});
return areFiltersSet;
};
var resetFilters = function () {
_.forEach(self.filters, function (filterList, filter) {
self.filters[filter] = [];
});
};
var getFilterString = function (obj) {
var newList = [];
_.forEach(self.propertyList, function (property) {
newList.push(obj[property]);
});
_.forEach(self.propertyFunctionList, function (fn) {
newList.push(fn(obj));
});
_.forEach(self.propertyDict, function (idFunction, property) {
newList.push(_.map(obj[property], idFunction).join(' '));
});
return newList.join(' ');
};
var operate = function (filter, id) {
if (_.indexOf(self.filters[filter], id) > -1) {
// remove id
self.filters[filter] = _.filter(self.filters[filter], function (_id) {
return _id != id;
});
} else {
// add id
self.filters[filter].push(id);
}
};
var self = {
filters: {},
areFiltersSet: areFiltersSet,
resetFilters: resetFilters,
operate: operate,
getFilterString: getFilterString,
};
resetFilters(); //Initiate filters
return self;
};
return {
instance: instance
};
}
])
/*
* This filter filters all items in an array. If the filterArray is empty, the
* array is passed. The filterArray contains numbers of the multiselect, e. g. [1, 3, 4].
* Then, all items in the array are passed, if the item_id (get with id_function) matches
* one of the ids in filterArray. id_function could also return a list of ids. Example:
* Item 1 has two tags with ids [1, 4]. filterArray == [3, 4] --> match
*/
.filter('MultiselectFilter', [
function () {
return function (array, filterArray, idFunction) {
if (filterArray.length === 0) {
return array;
}
return Array.prototype.filter.call(array, function (item) {
var id = idFunction(item);
if (!id) {
return false;
} else if (typeof id === 'number') {
id = [id];
}
return _.intersection(id, filterArray).length > 0;
});
};
}
])
// Load the django url patterns // Load the django url patterns
.run([ .run([
'runtimeStates', 'runtimeStates',

View File

@ -653,6 +653,33 @@ angular.module('OpenSlidesApp.motions.site', [
} }
]) ])
.factory('MotionCsvExport', [
function () {
return function (element, motions) {
var csvRows = [
['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'],
];
_.forEach(motions, function (motion) {
var row = [];
row.push('"' + motion.identifier + '"');
row.push('"' + motion.getTitle() + '"');
row.push('"' + motion.getText() + '"');
row.push('"' + motion.getReason() + '"');
row.push('"' + motion.submitters[0].get_full_name() + '"');
var category = motion.category ? motion.category.name : '';
row.push('"' + category + '"');
row.push('"' + motion.origin + '"');
csvRows.push(row);
});
var csvString = csvRows.join("%0A");
element.href = 'data:text/csv;charset=utf-8,' + csvString;
element.download = 'motions-export.csv';
element.target = '_blank';
};
}
])
// Cache for MotionPollDetailCtrl so that users choices are keeped during user actions (e. g. save poll form). // Cache for MotionPollDetailCtrl so that users choices are keeped during user actions (e. g. save poll form).
.value('MotionPollDetailCtrlCache', {}) .value('MotionPollDetailCtrlCache', {})
@ -723,9 +750,11 @@ angular.module('OpenSlidesApp.motions.site', [
'HTMLValidizer', 'HTMLValidizer',
'Projector', 'Projector',
'ProjectionDefault', 'ProjectionDefault',
'MotionCsvExport',
'Multiselect',
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock, function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock,
MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider, MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) { gettextCatalog, HTMLValidizer, Projector, ProjectionDefault, MotionCsvExport, Multiselect) {
Motion.bindAll({}, $scope, 'motions'); Motion.bindAll({}, $scope, 'motions');
Category.bindAll({}, $scope, 'categories'); Category.bindAll({}, $scope, 'categories');
MotionBlock.bindAll({}, $scope, 'motionBlocks'); MotionBlock.bindAll({}, $scope, 'motionBlocks');
@ -743,35 +772,65 @@ angular.module('OpenSlidesApp.motions.site', [
}); });
$scope.alert = {}; $scope.alert = {};
// setup table sorting $scope.multiselect = Multiselect.instance();
$scope.sortColumn = 'identifier'; $scope.multiselect.filters = {
$scope.filterPresent = '';
$scope.reverse = false;
$scope.multiselectFilter = {
state: [], state: [],
category: [], category: [],
motionBlock: [], motionBlock: [],
tag: [] tag: []
}; };
$scope.multiselect.propertyList = ['identifier', 'origin'];
$scope.multiselect.propertyFunctionList = [
function (motion) {return motion.getTitle();},
function (motion) {return motion.getText();},
function (motion) {return motion.getReason();},
function (motion) {return motion.category ? motion.category.name : '';},
function (motion) {return motion.motionBlock ? motion.motionBlock.name : '';},
];
$scope.multiselect.PropertyDict = {
'submitters' : function (submitter) {
return submitter.get_short_name();
},
'supporters' : function (submitter) {
return supporter.get_short_name();
},
'tags' : function (tag) {
return tag.name;
},
};
$scope.getItemId = { $scope.getItemId = {
state: function (motion) {return motion.state_id;}, state: function (motion) {return motion.state_id;},
category: function (motion) {return motion.category_id;}, category: function (motion) {return motion.category_id;},
motionBlock: function (motion) {return motion.motion_block_id;}, motionBlock: function (motion) {return motion.motion_block_id;},
tag: function (motion) {return motion.tags_id;} tag: function (motion) {return motion.tags_id;}
}; };
// setup table sorting
$scope.sortOptions = [
{name: 'identifier',
display_name: 'Identifier'},
{name: 'getTitle()',
display_name: 'Title'},
{name: 'submitters',
display_name: 'Submitters'},
{name: 'category.name',
display_name: 'Category'},
{name: 'motionBlock.title',
display_name: 'Motion block'},
{name: 'state.name',
display_name: 'State'},
{name: 'log_messages[log_messages.length-1].time',
display_name: 'Creation date'},
{name: 'log_messages[0].time',
display_name: 'Last modified'},
];
$scope.sortColumn = 'identifier';
$scope.filterPresent = '';
$scope.reverse = false;
// function to operate the multiselectFilter // function to operate the multiselectFilter
$scope.operateMultiselectFilter = function (filter, id) { $scope.operateMultiselectFilter = function (filter, id) {
if (!$scope.isDeleteMode) { if (!$scope.isDeleteMode) {
if (_.indexOf($scope.multiselectFilter[filter], id) > -1) { $scope.multiselect.operate(filter, id);
// remove id
$scope.multiselectFilter[filter] = _.filter($scope.multiselectFilter[filter], function (_id) {
return _id != id;
});
} else {
// add id
$scope.multiselectFilter[filter].push(id);
}
} }
}; };
// function to sort by clicked column // function to sort by clicked column
@ -781,61 +840,15 @@ angular.module('OpenSlidesApp.motions.site', [
} }
$scope.sortColumn = column; $scope.sortColumn = column;
}; };
// define custom search filter string
$scope.getFilterString = function (motion) {
var category = '';
if (motion.category) {
category = motion.category.name;
}
var motionBlock = '';
if (motion.motionBlock) {
motionBlock = motion.motionBlock.title;
}
return [
motion.identifier,
motion.getTitle(),
motion.getText(),
motion.getReason(),
motion.origin,
_.map(
motion.submitters,
function (submitter) {
return submitter.get_short_name();
}
).join(" "),
_.map(
motion.supporters,
function (supporter) {
return supporter.get_short_name();
}
).join(" "),
_.map(
motion.tags,
function (tag) {
return tag.name;
}
).join(" "),
category,
motionBlock
].join(" ");
};
// for reset-button // for reset-button
$scope.reset_filters = function () { $scope.resetFilters = function () {
$scope.multiselectFilter = { $scope.multiselect.resetFilters();
state: [],
category: [],
motionBlock: [],
tag: []
};
if ($scope.filter) { if ($scope.filter) {
$scope.filter.search = ''; $scope.filter.search = '';
} }
}; };
$scope.are_filters_set = function () { $scope.areFiltersSet = function () {
return $scope.multiselectFilter.state.length > 0 || return $scope.multiselect.areFiltersSet() ||
$scope.multiselectFilter.category.length > 0 ||
$scope.multiselectFilter.motionBlock.length > 0 ||
$scope.multiselectFilter.tag.length > 0 ||
($scope.filter ? $scope.filter.search : false); ($scope.filter ? $scope.filter.search : false);
}; };
@ -910,7 +923,7 @@ angular.module('OpenSlidesApp.motions.site', [
}; };
// Export as a pdf file // Export as a pdf file
$scope.pdf_export = function() { $scope.pdfExport = function() {
var filename = gettextCatalog.getString("Motions") + ".pdf"; var filename = gettextCatalog.getString("Motions") + ".pdf";
var image_sources = []; var image_sources = [];
@ -940,31 +953,12 @@ angular.module('OpenSlidesApp.motions.site', [
}; };
// Export as a csv file // Export as a csv file
$scope.csv_export = function () { $scope.csvExport = function () {
var element = document.getElementById('downloadLinkCSV'); var element = document.getElementById('downloadLinkCSV');
var csvRows = [ MotionCsvExport(element, $scope.motionsFiltered);
['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'],
];
angular.forEach($scope.motionsFiltered, function (motion) {
var row = [];
row.push('"' + motion.identifier + '"');
row.push('"' + motion.getTitle() + '"');
row.push('"' + motion.getText() + '"');
row.push('"' + motion.getReason() + '"');
row.push('"' + motion.submitters[0].get_full_name() + '"');
var category = motion.category ? motion.category.name : '';
row.push('"' + category + '"');
row.push('"' + motion.origin + '"');
csvRows.push(row);
});
var csvString = csvRows.join("%0A");
element.href = 'data:text/csv;charset=utf-8,' + csvString;
element.download = 'motions-export.csv';
element.target = '_blank';
}; };
// Export as docx file // Export as docx file
$scope.docx_export = function () { $scope.docxExport = function () {
MotionDocxExport.export($scope.motionsFiltered, $scope.categories); MotionDocxExport.export($scope.motionsFiltered, $scope.categories);
}; };

View File

@ -51,7 +51,7 @@
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownExport"> <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownExport">
<!-- PDF export --> <!-- PDF export -->
<li> <li>
<a href="" ng-click="pdf_export()"> <a href="" ng-click="pdfExport()">
<i class="fa fa-file-pdf-o fa-lg"></i> <i class="fa fa-file-pdf-o fa-lg"></i>
PDF PDF
</a> </a>
@ -59,14 +59,14 @@
<!-- CSV export --> <!-- CSV export -->
<li> <li>
<a href="" id="downloadLinkCSV" <a href="" id="downloadLinkCSV"
ng-click="csv_export()"> ng-click="csvExport()">
<i class="fa fa-file-text-o fa-lg"></i> <i class="fa fa-file-text-o fa-lg"></i>
CSV CSV
</a> </a>
</li> </li>
<!-- DOCX export --> <!-- DOCX export -->
<li> <li>
<a href="" ng-click="docx_export()"> <a href="" ng-click="docxExport()">
<i class="fa fa-file-word-o fa-lg"></i> <i class="fa fa-file-word-o fa-lg"></i>
DOCX DOCX
</a> </a>
@ -95,7 +95,7 @@
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span> {{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div> </div>
<div id="motion-table" class="container-fluid"> <div id="motion-os-table" class="os-table container-fluid">
<div class="row header-row"> <div class="row header-row">
<div class="col-xs-1 centered" ng-show="isDeleteMode"> <div class="col-xs-1 centered" ng-show="isDeleteMode">
<i class="fa text-danger pointer" ng-class=" selectedAll ? 'fa-check-square-o' : 'fa-square-o'" <i class="fa text-danger pointer" ng-class=" selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
@ -103,8 +103,8 @@
</div> </div>
<div class="col-xs-11 main-header"> <div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right"> <span class="form-inline text-right pull-right">
<span class="sort-spacer pointer" ng-click="reset_filters()" <span class="sort-spacer pointer" ng-click="resetFilters()"
ng-if="are_filters_set()" ng-disabled="isDeleteMode" ng-if="areFiltersSet()" ng-disabled="isDeleteMode"
ng-class="{'disabled': isDeleteMode}"> ng-class="{'disabled': isDeleteMode}">
<i class="fa fa-times-circle"></i> <i class="fa fa-times-circle"></i>
<translate>Filter</translate> <translate>Filter</translate>
@ -112,19 +112,19 @@
<!-- State filter --> <!-- State filter -->
<span class="dropdown" uib-dropdown> <span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownState" uib-dropdown-toggle <span class="pointer" id="dropdownState" uib-dropdown-toggle
ng-class="{'bold': multiselectFilter.state.length > 0, 'disabled': isDeleteMode}" ng-class="{'bold': multiselect.filters.state.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode"> ng-disabled="isDeleteMode">
<translate>State</translate> <translate>State</translate>
<span class="caret"></span> <span class="caret"></span>
</span> </span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownState"> <ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownState">
<li ng-repeat="state in states" ng-class="state.workflowHeader ? 'dropdown-header' : ''"> <li ng-repeat="state in states" ng-class="state.workflowHeader ? 'dropdown-header' : ''">
<div class="dropdown-entry pointer" ng-if="state.workflowHeader"> <div ng-if="state.workflowHeader">
{{ state.name | translate }} {{ state.name | translate }}
</div> </div>
<div class="dropdown-entry pointer" ng-if="!state.workflowHeader" <div ng-if="!state.workflowHeader"
ng-click="operateMultiselectFilter('state', state.id)"> ng-click="operateMultiselectFilter('state', state.id)">
<i class="fa fa-check" ng-if="multiselectFilter.state.indexOf(state.id) > -1"></i> <i class="fa fa-check" ng-if="multiselect.filters.state.indexOf(state.id) > -1"></i>
{{ state.name | translate }} {{ state.name | translate }}
</div> </div>
</li> </li>
@ -133,17 +133,16 @@
<!-- Category filter --> <!-- Category filter -->
<span class="dropdown" uib-dropdown ng-if="categories.length > 0"> <span class="dropdown" uib-dropdown ng-if="categories.length > 0">
<span class="pointer" id="dropdownCategory" uib-dropdown-toggle <span class="pointer" id="dropdownCategory" uib-dropdown-toggle
ng-class="{'bold': multiselectFilter.category.length > 0, 'disabled': isDeleteMode}" ng-class="{'bold': multiselect.filters.category.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode"> ng-disabled="isDeleteMode">
<translate>Category</translate> <translate>Category</translate>
<span class="caret"></span> <span class="caret"></span>
</span> </span>
<ul class="dropdown-menu dropdown-menu-right" <ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownCategory"> aria-labelledby="dropdownCategory">
<li ng-repeat="category in categories"> <li ng-repeat="category in categories">
<div class="dropdown-entry pointer" <div ng-click="operateMultiselectFilter('category', category.id)">
ng-click="operateMultiselectFilter('category', category.id)"> <i class="fa fa-check" ng-if="multiselect.filters.category.indexOf(category.id) > -1"></i>
<i class="fa fa-check" ng-if="multiselectFilter.category.indexOf(category.id) > -1"></i>
{{ category.name }} {{ category.name }}
</div> </div>
</li> </li>
@ -152,17 +151,16 @@
<!-- Motion block filter --> <!-- Motion block filter -->
<span class="dropdown" uib-dropdown ng-if="motionBlocks.length > 0"> <span class="dropdown" uib-dropdown ng-if="motionBlocks.length > 0">
<span class="pointer" id="dropdownBlock" uib-dropdown-toggle <span class="pointer" id="dropdownBlock" uib-dropdown-toggle
ng-class="{'bold': multiselectFilter.motionBlock.length > 0, 'disabled': isDeleteMode}" ng-class="{'bold': multiselect.filters.motionBlock.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode"> ng-disabled="isDeleteMode">
<translate>Motion block</translate> <translate>Motion block</translate>
<span class="caret"></span> <span class="caret"></span>
</span> </span>
<ul class="dropdown-menu dropdown-menu-right" <ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownBlock"> aria-labelledby="dropdownBlock">
<li ng-repeat="block in motionBlocks"> <li ng-repeat="block in motionBlocks">
<div class="dropdown-entry pointer" <div ng-click="operateMultiselectFilter('motionBlock', block.id)">
ng-click="operateMultiselectFilter('motionBlock', block.id)"> <i class="fa fa-check" ng-if="multiselect.filters.motionBlock.indexOf(block.id) > -1"></i>
<i class="fa fa-check" ng-if="multiselectFilter.motionBlock.indexOf(block.id) > -1"></i>
{{ block.title }} {{ block.title }}
</div> </div>
</li> </li>
@ -171,17 +169,16 @@
<!-- Tag filter --> <!-- Tag filter -->
<span class="dropdown" uib-dropdown ng-if="tags.length > 0"> <span class="dropdown" uib-dropdown ng-if="tags.length > 0">
<span class="pointer" id="dropdownTag" uib-dropdown-toggle <span class="pointer" id="dropdownTag" uib-dropdown-toggle
ng-class="{'bold': multiselectFilter.tag.length > 0, 'disabled': isDeleteMode}" ng-class="{'bold': multiselect.filters.tag.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode"> ng-disabled="isDeleteMode">
<translate>Tag</translate> <translate>Tag</translate>
<span class="caret"></span> <span class="caret"></span>
</span> </span>
<ul class="dropdown-menu dropdown-menu-right" <ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownTag"> aria-labelledby="dropdownTag">
<li ng-repeat="tag in tags"> <li ng-repeat="tag in tags">
<div class="dropdown-entry pointer" <div ng-click="operateMultiselectFilter('tag', tag.id)">
ng-click="operateMultiselectFilter('tag', tag.id)"> <i class="fa fa-check" ng-if="multiselect.filters.tag.indexOf(tag.id) > -1"></i>
<i class="fa fa-check" ng-if="multiselectFilter.tag.indexOf(tag.id) > -1"></i>
{{ tag.name }} {{ tag.name }}
</div> </div>
</li> </li>
@ -195,106 +192,29 @@
<translate>Sort</translate> <translate>Sort</translate>
<span class="caret"></span> <span class="caret"></span>
</span> </span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownSort"> <ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownSort">
<li>
<!-- item --> <!-- item -->
<div class="pointer dropdown-entry" ng-click="toggleSort('agenda_item.getItemNumberWithAncestors()')"> <li>
<div ng-click="toggleSort('agenda_item.getItemNumberWithAncestors()')">
<translate translate-comment="short form of agenda item">Item</translate> <translate translate-comment="short form of agenda item">Item</translate>
<span class="spacer-right pull-right"></span> <span class="spacer-right pull-right"></span>
<i class="pull-right fa" <i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'agenda_item.getItemNumberWithAncestors()' && header.sortable != false ? 'visible' : 'hidden'}" ng-style="{'visibility': sortColumn === 'agenda_item.getItemNumberWithAncestors()' ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
</div> </div>
</li> </li>
<li> <!-- all other sortOptions -->
<!-- indentifier --> <li ng-repeat="option in sortOptions">
<div class="pointer dropdown-entry" ng-click="toggleSort('identifier')"> <div ng-click="toggleSort(option.name)">
<translate>Identifier</translate> {{ option.display_name | translate }}
<span class="spacer-right pull-right"></span> <span class="spacer-right pull-right"></span>
<i class="pull-right fa" <i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'identifier' && header.sortable != false ? 'visible' : 'hidden'}" ng-style="{'visibility': sortColumn === option.name ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
</div> </div>
</li> </li>
<li>
<!-- title -->
<div class="pointer dropdown-entry" ng-click="toggleSort('getTitle()')">
<translate>Title</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'getTitle()' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- submitters -->
<div class="pointer dropdown-entry" ng-click="toggleSort('submitters')">
<translate>Submitters</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'submitters' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- category -->
<div class="pointer dropdown-entry" ng-click="toggleSort('category.name')">
<translate>Category</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'category.name' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- motion block -->
<div class="pointer dropdown-entry" ng-click="toggleSort('motionBlock.title')">
<translate>Motion block</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'motionBlock.title' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- state -->
<div class="pointer dropdown-entry" ng-click="toggleSort('state.name')">
<translate>State</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'state.name' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- creation date -->
<div class="pointer dropdown-entry" ng-click="toggleSort('log_messages[log_messages.length-1].time')">
<translate>Creation date</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'log_messages[log_messages.length-1].time' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- last modified -->
<div class="pointer dropdown-entry" ng-click="toggleSort('log_messages[0].time')">
<translate>Last modified</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'log_messages[0].time' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-asc' : 'fa-sort-desc'">
</i>
</div>
</li>
</ul> </ul>
</span> </span>
<!-- search field --> <!-- search field -->
@ -309,7 +229,7 @@
<!-- show all selected multiselectoptions --> <!-- show all selected multiselectoptions -->
<span> <span>
<span ng-repeat="state in states" class="pointer spacer-left-lg" <span ng-repeat="state in states" class="pointer spacer-left-lg"
ng-if="!state.workflowHeader && multiselectFilter.state.indexOf(state.id) > -1" ng-if="!state.workflowHeader && multiselect.filters.state.indexOf(state.id) > -1"
ng-click="operateMultiselectFilter('state', state.id)" ng-click="operateMultiselectFilter('state', state.id)"
ng-class="{'disabled': isDeleteMode}"> ng-class="{'disabled': isDeleteMode}">
<span class="nobr"> <span class="nobr">
@ -318,7 +238,7 @@
</span> </span>
</span> </span>
<span ng-repeat="category in categories" class="pointer spacer-left-lg" <span ng-repeat="category in categories" class="pointer spacer-left-lg"
ng-if="multiselectFilter.category.indexOf(category.id) > -1" ng-if="multiselect.filters.category.indexOf(category.id) > -1"
ng-click="operateMultiselectFilter('category', category.id)" ng-click="operateMultiselectFilter('category', category.id)"
ng-class="{'disabled': isDeleteMode}"> ng-class="{'disabled': isDeleteMode}">
<span class="nobr"> <span class="nobr">
@ -327,7 +247,7 @@
</span> </span>
</span> </span>
<span ng-repeat="motionBlock in motionBlocks" class="pointer spacer-left-lg" <span ng-repeat="motionBlock in motionBlocks" class="pointer spacer-left-lg"
ng-if="multiselectFilter.motionBlock.indexOf(motionBlock.id) > -1" ng-if="multiselect.filters.motionBlock.indexOf(motionBlock.id) > -1"
ng-click="operateMultiselectFilter('motionBlock', motionBlock.id)" ng-click="operateMultiselectFilter('motionBlock', motionBlock.id)"
ng-class="{'disabled': isDeleteMode}"> ng-class="{'disabled': isDeleteMode}">
<span class="nobr"> <span class="nobr">
@ -336,7 +256,7 @@
</span> </span>
</span> </span>
<span ng-repeat="tag in tags" class="pointer spacer-left-lg" <span ng-repeat="tag in tags" class="pointer spacer-left-lg"
ng-if="multiselectFilter.tag.indexOf(tag.id) > -1" ng-if="multiselect.flters.tag.indexOf(tag.id) > -1"
ng-click="operateMultiselectFilter('tag', tag.id)" ng-click="operateMultiselectFilter('tag', tag.id)"
ng-class="{'disabled': isDeleteMode}"> ng-class="{'disabled': isDeleteMode}">
<span class="nobr"> <span class="nobr">
@ -349,27 +269,25 @@
</div> </div>
<!-- main table --> <!-- main table -->
<!-- data row -->
<div class="row data-row" ng-mouseover="motion.hover=true" <div class="row data-row" ng-mouseover="motion.hover=true"
ng-mouseleave="motion.hover=false" ng-mouseleave="motion.hover=false"
ng-class="{'projected': motion.isProjected().length}" ng-class="{'projected': motion.isProjected().length}"
ng-repeat="motion in motionsFiltered = (motions ng-repeat="motion in motionsFiltered = (motions
| osFilter: filter.search : getFilterString | osFilter: filter.search : multiselect.getFilterString
| SelectMultipleFilter: multiselectFilter.state : getItemId.state | MultiselectFilter: multiselect.filters.state : getItemId.state
| SelectMultipleFilter: multiselectFilter.category : getItemId.category | MultiselectFilter: multiselect.filters.category : getItemId.category
| SelectMultipleFilter: multiselectFilter.motionBlock : getItemId.motionBlock | MultiselectFilter: multiselect.filters.motionBlock : getItemId.motionBlock
| SelectMultipleFilter: multiselectFilter.tag : getItemId.tag | MultiselectFilter: multiselect.filters.tag : getItemId.tag
| toArray | toArray
| orderBy: sortColumn : reverse)"> | orderBy: sortColumn : reverse)">
<!-- select column --> <!-- select column -->
<div ng-show="isDeleteMode" os-perms="motions.can_manage" <div ng-show="isDeleteMode" os-perms="motions.can_manage" class="col-xs-1 centered">
class="col-xs-1 centered" ng-class="{'deleteColumn' : motion.selected}">
<i class="fa text-danger pointer" ng-click="motion.selected=!motion.selected" <i class="fa text-danger pointer" ng-click="motion.selected=!motion.selected"
ng-class="motion.selected ? 'fa-check-square-o' : 'fa-square-o'"></i> ng-class="motion.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div> </div>
<!-- projector column --> <!-- projector column -->
<div class="col-xs-1 centered" os-perms="core.can_manage_projector"> <div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<projector-button model="motion", default-projector-id="defaultProjectorId"> <projector-button model="motion", default-projector-id="defaultProjectorId">
</projector-button> </projector-button>
</div> </div>
@ -437,7 +355,7 @@
<!-- additional content column --> <!-- additional content column -->
<style> <style>
#motion-table .row .col-xs-4 { #motion-table .row .col-xs-4 {
width: calc(50% - {{ isDeleteMode ? '100' : '50' }}px); width: calc(50% - {{ isDeleteMode ? '120' : '70' }}px);
} }
</style> </style>
<div class="col-xs-4 content"> <div class="col-xs-4 content">
@ -461,10 +379,9 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.categoryHover"></i> <i class="fa fa-cog fa-lg spacer-left" ng-show="motion.categoryHover"></i>
</span> </span>
</span> </span>
<ul class="dropdown-menu" aria-labelledby="dropdown-category{{ motion.id }}"> <ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-category{{ motion.id }}">
<li ng-repeat="category in categories"> <li ng-repeat="category in categories">
<div class="dropdown-entry pointer" <div ng-click="toggle_category(motion, category)">
ng-click="toggle_category(motion, category)">
<i class="fa fa-check" ng-if="category.id == motion.category.id"></i> <i class="fa fa-check" ng-if="category.id == motion.category.id"></i>
{{ category.name }} {{ category.name }}
</div> </div>
@ -496,10 +413,9 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.motionBlockHover"></i> <i class="fa fa-cog fa-lg spacer-left" ng-show="motion.motionBlockHover"></i>
</span> </span>
</span> </span>
<ul class="dropdown-menu" aria-labelledby="dropdown-motionBlock{{ motion.id }}"> <ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-motionBlock{{ motion.id }}">
<li ng-repeat="motionBlock in motionBlocks"> <li ng-repeat="motionBlock in motionBlocks">
<div class="dropdown-entry pointer" <div ng-click="toggle_motionBlock(motion, motionBlock)">
ng-click="toggle_motionBlock(motion, motionBlock)">
<i class="fa fa-check" ng-if="motionBlock.id == motion.motionBlock.id"></i> <i class="fa fa-check" ng-if="motionBlock.id == motion.motionBlock.id"></i>
{{ motionBlock.title }} {{ motionBlock.title }}
</div> </div>
@ -533,9 +449,9 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.tagHover"></i> <i class="fa fa-cog fa-lg spacer-left" ng-show="motion.tagHover"></i>
</span> </span>
</span> </span>
<ul class="dropdown-menu" aria-labelledby="dropdown-tags{{ motion.id }}"> <ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-tags{{ motion.id }}">
<li ng-repeat="tag in tags"> <li ng-repeat="tag in tags">
<div class="dropdown-entry pointer" ng-click="toggle_tag(motion, tag)"> <div ng-click="toggle_tag(motion, tag)">
<i class="fa fa-check" ng-if="has_tag(motion, tag)"></i> <i class="fa fa-check" ng-if="has_tag(motion, tag)"></i>
{{ tag.name }} {{ tag.name }}
</div> </div>

View File

@ -11,12 +11,12 @@ def get_config_variables():
# Sorting # Sorting
yield ConfigVariable( yield ConfigVariable(
name='users_sort_by', name='users_sort_by',
default_value='firstName', default_value='first_name',
input_type='choice', input_type='choice',
label='Sort name of participants by', label='Sort name of participants by',
choices=( choices=(
{'value': 'firstName', 'display_name': 'First name'}, {'value': 'first_name', 'display_name': 'First name'},
{'value': 'lastName', 'display_name': 'Last name'}), {'value': 'last_name', 'display_name': 'Last name'}),
weight=510, weight=510,
group='Participants', group='Participants',
subgroup='General') subgroup='General')

View File

@ -101,7 +101,7 @@ angular.module('OpenSlidesApp.users', [])
firstName = _.trim(this.first_name), firstName = _.trim(this.first_name),
lastName = _.trim(this.last_name), lastName = _.trim(this.last_name),
name = ''; name = '';
if (Config.get('users_sort_by') && Config.get('users_sort_by').value == 'lastName') { if (Config.get('users_sort_by') && Config.get('users_sort_by').value == 'last_name') {
if (lastName && firstName) { if (lastName && firstName) {
name += [lastName, firstName].join(', '); name += [lastName, firstName].join(', ');
} else { } else {

View File

@ -416,6 +416,35 @@ angular.module('OpenSlidesApp.users.site', [
} }
]) ])
.factory('UserCsvExport', [
function () {
return function (element, users) {
var csvRows = [
['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'],
];
_.forEach(users, function (user) {
var row = [];
row.push('"' + user.title + '"');
row.push('"' + user.first_name + '"');
row.push('"' + user.last_name + '"');
row.push('"' + user.structure_level + '"');
row.push('"' + user.number + '"');
row.push('"' + user.groups_id.join(',') + '"');
row.push('"' + user.comment + '"');
row.push(user.is_active ? '1' : '0');
row.push(user.is_present ? '1' : '0');
row.push(user.is_committee ? '1' : '0');
csvRows.push(row);
});
var csvString = csvRows.join("%0A");
element.href = 'data:text/csv;charset=utf-8,' + csvString;
element.download = 'users-export.csv';
element.target = '_blank';
};
}
])
.controller('UserListCtrl', [ .controller('UserListCtrl', [
'$scope', '$scope',
'$state', '$state',
@ -432,8 +461,11 @@ angular.module('OpenSlidesApp.users.site', [
'UserAccessDataListContentProvider', 'UserAccessDataListContentProvider',
'PdfMakeDocumentProvider', 'PdfMakeDocumentProvider',
'gettextCatalog', 'gettextCatalog',
'UserCsvExport',
'Multiselect',
function($scope, $state, $http, ngDialog, UserForm, User, Group, PasswordGenerator, Projector, ProjectionDefault, function($scope, $state, $http, ngDialog, UserForm, User, Group, PasswordGenerator, Projector, ProjectionDefault,
UserListContentProvider, Config, UserAccessDataListContentProvider, PdfMakeDocumentProvider, gettextCatalog) { UserListContentProvider, Config, UserAccessDataListContentProvider, PdfMakeDocumentProvider, gettextCatalog,
UserCsvExport, Multiselect) {
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups'); Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
$scope.$watch(function () { $scope.$watch(function () {
@ -445,10 +477,40 @@ angular.module('OpenSlidesApp.users.site', [
} }
}); });
$scope.alert = {}; $scope.alert = {};
$scope.groupFilter = undefined;
$scope.multiselect = Multiselect.instance();
$scope.multiselect.filters = {
group: [],
};
$scope.multiselect.propertyList = ['first_name', 'last_name', 'title', 'number', 'comment', 'structure_level'];
$scope.multiselect.PropertyDict = {
'groups_id' : function (group_id) {
return Group.get(group_id).name;
},
};
$scope.getItemId = {
group: function (user) {return user.groups_id;},
};
// setup table sorting // setup table sorting
$scope.sortColumn = 'first_name'; //TODO: sort by first OR last name $scope.sortOptions = [
{name: 'first_name',
display_name: 'First name'},
{name: 'last_name',
display_name: 'Last name'},
{name: 'is_present',
display_name: 'Present'},
{name: 'is_active',
display_name: 'Active'},
{name: 'is_committee',
display_name: 'Committee'},
{name: 'number',
display_name: 'Number'},
{name: 'structure_level',
display_name: 'Structure level'},
{name: 'comment',
display_name: 'Comment'},
];
$scope.sortColumn = $scope.config('users_sort_by');
$scope.filterPresent = ''; $scope.filterPresent = '';
$scope.reverse = false; $scope.reverse = false;
// function to sort by clicked column // function to sort by clicked column
@ -458,6 +520,29 @@ angular.module('OpenSlidesApp.users.site', [
} }
$scope.sortColumn = column; $scope.sortColumn = column;
}; };
// function to operate the multiselectFilter
$scope.operateMultiselectFilter = function (filter, id) {
if (!$scope.isSelectMode) {
$scope.multiselect.operate(filter, id);
}
};
// for reset-button
$scope.resetFilters = function () {
$scope.multiselect.resetFilters();
if ($scope.filter) {
$scope.filter.search = '';
}
$scope.isPresentFilter = undefined;
$scope.isActiveFilter = undefined;
$scope.isCommitteeFilter = undefined;
};
$scope.areFiltersSet = function () {
return $scope.multiselect.areFiltersSet() ||
$scope.isPresentFilter ||
$scope.isActiveFilter ||
$scope.isCommitteeFilter ||
($scope.filter ? $scope.filter.search : false);
};
// pagination // pagination
$scope.currentPage = 1; $scope.currentPage = 1;
@ -467,6 +552,17 @@ angular.module('OpenSlidesApp.users.site', [
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage; $scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
}; };
// Toggle group from user
$scope.toggleGroup = function (user, group) {
if (_.indexOf(user.groups_id, group.id) > -1) {
user.groups_id = _.filter(user.groups_id, function (group_id) {
return group_id != group.id;
});
} else {
user.groups_id.push(group.id);
}
$scope.save(user);
};
// open new/edit dialog // open new/edit dialog
$scope.openDialog = function (user) { $scope.openDialog = function (user) {
ngDialog.open(UserForm.getDialog(user)); ngDialog.open(UserForm.getDialog(user));
@ -490,7 +586,8 @@ angular.module('OpenSlidesApp.users.site', [
$scope.isSelectMode = false; $scope.isSelectMode = false;
// check all checkboxes // check all checkboxes
$scope.checkAll = function () { $scope.checkAll = function () {
angular.forEach($scope.users, function (user) { $scope.selectedAll = !$scope.selectedAll;
_.forEach($scope.usersFiltered, function (user) {
user.selected = $scope.selectedAll; user.selected = $scope.selectedAll;
}); });
}; };
@ -559,20 +656,25 @@ angular.module('OpenSlidesApp.users.site', [
$scope.uncheckAll(); $scope.uncheckAll();
}; };
$scope.makePDF_userList = function () { // Export as PDF
$scope.pdfExportUserList = function () {
var filename = gettextCatalog.getString("List of participants")+".pdf"; var filename = gettextCatalog.getString("List of participants")+".pdf";
var userListContentProvider = UserListContentProvider.createInstance($scope.users, $scope.groups); var userListContentProvider = UserListContentProvider.createInstance($scope.usersFiltered, $scope.groups);
var documentProvider = PdfMakeDocumentProvider.createInstance(userListContentProvider); var documentProvider = PdfMakeDocumentProvider.createInstance(userListContentProvider);
pdfMake.createPdf(documentProvider.getDocument()).download(filename); pdfMake.createPdf(documentProvider.getDocument()).download(filename);
}; };
$scope.pdfExportUserAccessDataList = function () {
$scope.makePDF_userAccessDataList = function () {
var filename = gettextCatalog.getString("List of access data")+".pdf"; var filename = gettextCatalog.getString("List of access data")+".pdf";
var userAccessDataListContentProvider = UserAccessDataListContentProvider.createInstance( var userAccessDataListContentProvider = UserAccessDataListContentProvider.createInstance(
$scope.users, $scope.groups, Config); $scope.usersFiltered, $scope.groups, Config);
var documentProvider = PdfMakeDocumentProvider.createInstance(userAccessDataListContentProvider); var documentProvider = PdfMakeDocumentProvider.createInstance(userAccessDataListContentProvider);
pdfMake.createPdf(documentProvider.getDocument()).download(filename); pdfMake.createPdf(documentProvider.getDocument()).download(filename);
}; };
// Export as a csv file
$scope.csvExport = function () {
var element = document.getElementById('downloadLinkCSV');
UserCsvExport(element, $scope.usersFiltered);
};
} }
]) ])

View File

@ -13,26 +13,6 @@
<i class="fa fa-download fa-lg"></i> <i class="fa fa-download fa-lg"></i>
<translate>Import</translate> <translate>Import</translate>
</a> </a>
<div class="dropdown pull-right" uib-dropdown>
<button os-perms="users.can_manage" class="btn btn-default btn-sm" uib-dropdown-toggle
id="user-pdf-dropdown">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" dropdown-menu-right aria-labelledby="user-pdf-dropdown">
<li>
<a href="" ng-click="makePDF_userList()">
<i class="fa fa-list fa-fw"></i>
<translate>List of participants</translate>
</a>
<li os-perms="users.can_manage">
<a href="" ng-click="makePDF_userAccessDataList()">
<i class="fa fa-qrcode fa-fw"></i>
<translate>List of access data</translate>
</a>
</ul>
</div>
</div> </div>
<h1 translate>Participants</h1> <h1 translate>Participants</h1>
</div> </div>
@ -50,35 +30,36 @@
</button> </button>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-inline text-right"> <div class="dropdown pull-right" uib-dropdown>
<div class="form-group"> <button os-perms="users.can_manage" class="btn btn-default" uib-dropdown-toggle
<div class="input-group"> id="user-pdf-dropdown">
<div class="input-group-addon"><i class="fa fa-search"></i></div> <i class="fa fa-upload"></i>
<input type="text" ng-model="filter.search" ng-model-options="{debounce: 500}" class="form-control" <span ng-if="usersFiltered.length == users.length" translate>
placeholder="{{ 'Search' | translate}}"> Export all
</div>
</div>
<button class="btn btn-default" ng-click="$parent.isFilterOpen = !$parent.isFilterOpen"
ng-class="$parent.isFilterOpen ? 'btn-primary' : 'btn-default'">
<i class="fa fa-filter"></i>
<translate>Filter ...</translate>
</button>
</div>
</div>
</div>
<div uib-collapse="!$parent.isFilterOpen" class="row spacer">
<div class="col-sm-6 text-right"></div>
<div class="col-sm-6 text-right form-inline">
<!-- group filter -->
<select ng-model="groupFilter" class="form-control" id="groupFilter">
<option ng-click="groupFilter = undefined" value="" translate>--- Select group ---</option>
<option ng-repeat="group in groups" value="{{ group.id }}">{{ group.name | translate }}</option>
</select>
<!-- isPresent filter -->
<span os-perms="users.can_see_extra_data">
<input type="checkbox" ng-model="$parent.filterPresent" ng-false-value="''">
<translate>Is present</translate>
</span> </span>
<span ng-if="usersFiltered.length != users.length" translate>
Export filtered
</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" dropdown-menu-right aria-labelledby="user-pdf-dropdown">
<li>
<a href="" ng-click="pdfExportUserList()">
<i class="fa fa-list fa-fw"></i>
<translate>List of participants</translate> (PDF)
</a>
<li os-perms="users.can_manage">
<a href="" ng-click="pdfExportUserAccessDataList()">
<i class="fa fa-qrcode fa-fw"></i>
<translate>List of access data</translate> (PDF)
</a>
<li>
<a href="" id="downloadLinkCSV" ng-click="csvExport()">
<i class="fa fa-file-text-o fa-fw"></i>
CSV
</a>
</ul>
</div>
</div> </div>
</div> </div>
<div uib-collapse="!isSelectMode" class="row spacer"> <div uib-collapse="!isSelectMode" class="row spacer">
@ -134,94 +115,311 @@
<!-- filter users (for user with 'can_see_extra_data' permission) - consider present filter --> <!-- filter users (for user with 'can_see_extra_data' permission) - consider present filter -->
<div os-perms="users.can_see_extra_data"> <div os-perms="users.can_see_extra_data">
<span ng-repeat="user in $parent.usersFiltered = (users | filter: filter.search | filter: {groups_id: groupFilter} | <span ng-repeat="user in $parent.usersFiltered = (users
filter: {is_present: filterPresent} | orderBy: sortColumn:reverse)"></span> | osFilter: filter.search : multiselect.getFilterString
| filter: {is_present: isPresentFilter}
| filter: {is_active: isActiveFilter}
| filter: {is_committee: isCommitteeFilter}
| MultiselectFilter: multiselect.filters.group : getItemId.group
| orderBy: sortColumn:reverse)"></span>
</div> </div>
<!-- filter users (for user without 'can_see_extra_data' permission) --> <!-- filter users (for user without 'can_see_extra_data' permission) -->
<div os-perms="!users.can_see_extra_data" <div os-perms="!users.can_see_extra_data"
ng-repeat="user in $parent.usersFiltered = (users | filter: filter.search | filter: {groups_id: groupFilter} | ng-repeat="user in $parent.usersFiltered = (users
orderBy: sortColumn:reverse)"></div> | osFilter: filter.search : multiselect.getFilterString
| filter: {is_committee: isCommitteeFilter}
| MultiselectFilter: multiselect.filters.group : getItemId.group
| orderBy: sortColumn:reverse)"></div>
<table class="table table-striped table-bordered table-hover"> <div class="os-table container-fluid">
<thead> <div class="row header-row">
<tr> <div class="col-xs-1 centered" ng-show="isSelectMode" os-perms="users.can_manage">
<!-- projector column --> <i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
<th ng-show="!isSelectMode" os-perms="core.can_manage_projector" class="minimum"> ng-click="checkAll()"></i>
<!-- selection column -->
<th ng-show="isSelectMode" os-perms="users.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- name column -->
<th ng-click="toggleSort('first_name')" class="sortable">
<translate>Name</translate>
<!-- TODO: sort by first OR last name -->
<i class="pull-right fa" ng-show="sortColumn === 'first_name' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- structure level column -->
<th ng-click="toggleSort('structure_level')" class="sortable optional">
<translate>Structure level</translate>
<i class="pull-right fa" ng-show="sortColumn === 'structure_level' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- groups column -->
<th ng-click="toggleSort('groups')" class="sortable optional">
<translate>Groups</translate>
<i class="pull-right fa" ng-show="sortColumn === 'groups' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- present column -->
<th os-perms="users.can_see_extra_data" ng-click="toggleSort('is_present')" class="sortable minimum">
<translate>Present</translate>
<i class="pull-right fa" ng-show="sortColumn === 'is_present' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<tbody>
<tr ng-repeat="user in usersFiltered | limitTo : itemsPerPage : limitBegin" class="animate-item"
ng-class="{ 'activeline': user.isProjected().length, 'selected': user.selected }">
<!-- projector column -->
<td ng-show="!isSelectMode" os-perms="core.can_manage_projector">
<projector-button model="user" default-projector-id="defaultProjectorId">
</projector-button>
<!-- selection column -->
<td ng-show="isSelectMode" os-perms="users.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="user.selected">
<!-- user data colums -->
<td ng-mouseover="user.hover=true" ng-mouseleave="user.hover=false">
<strong>
<i ng-show="user.is_active === false" class="fa fa-ban"
title="{{ 'Is inactive' | translate }}"></i>
<i ng-show="user.is_committee" class="fa fa-users"
title="{{ 'Is a committee' | translate }}"></i>
<a ui-sref="users.user.detail({id: user.id})">{{ user.get_short_name() }}</a>
</strong>
<div ng-if="user.comment">
<small><i class="fa fa-info-circle"></i> {{ user.comment }}</small>
</div> </div>
<div ng-if="user.number"> {{ user.number }} </div> <div class="col-xs-11 main-header">
<div os-perms="users.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !user.hover}"> <span class="form-inline text-right pull-right">
<a href="" ng-click="openDialog(user)" translate>Edit</a> | <!-- reset Filters -->
<a ui-sref="users.user.change-password({id: user.id})" translate>Change password</a> | <span class="sort-spacer pointer" ng-click="resetFilters()"
ng-if="areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>Filter</translate>
</span>
<!-- Group filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownGroup" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.group.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Groups</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownGroup">
<li ng-repeat="group in groups">
<div ng-click="operateMultiselectFilter('group', group.id)">
<i class="fa fa-check" ng-if="multiselect.filters.group.indexOf(group.id) > -1"></i>
{{ group.name | translate }}
</div>
</li>
</ul>
</span>
<!-- Present filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownPresent" uib-dropdown-toggle
ng-class="{'bold': isPresentFilter !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Present</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownPresent">
<li>
<div ng-click="isPresentFilter = (isPresentFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isPresentFilter === true}"></i>
<translate>Is present</translate>
</div>
</li>
<li>
<div ng-click="isPresentFilter = (isPresentFilter === false) ? undefined : false">
<i class="fa" ng-class="{'fa-check': isPresentFilter === false}"></i>
<translate>Is not present</translate>
</div>
</li>
</ul>
</span>
<!-- Active filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownActive" uib-dropdown-toggle
ng-class="{'bold': isActiveFilter !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelecteMode">
<translate>Active</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownActive">
<li>
<div ng-click="isActiveFilter = (isActiveFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isActiveFilter === true}"></i>
<translate>Is active</translate>
</div>
</li>
<li>
<div ng-click="isActiveFilter = (isActiveFilter === false ? undefined : false)">
<i class="fa" ng-class="{'fa-check': isActiveFilter === false}"></i>
<translate>Is not active</translate>
</div>
</li>
</ul>
</span>
<!-- Committee filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownCommittee" uib-dropdown-toggle
ng-class="{'bold': isCommitteeFilter !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelecteMode">
<translate>Committee</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownCommittee">
<li>
<div ng-click="isCommitteeFilter = (isCommitteeFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isCommitteeFilter === true}"></i>
<translate>Yes</translate>
</div>
</li>
<li>
<div ng-click="isCommitteeFilter = (isCommitteeFilter === false ? undefined : false)">
<i class="fa" ng-class="{'fa-check': isCommitteeFilter === false}"></i>
<translate>No</translate>
</div>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Sort</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownSort">
<li ng-repeat="option in sortOptions">
<div ng-click="toggleSort(option.name)">
{{ option.display_name | translate }}
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === option.name ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
</ul>
</span>
<!-- search field -->
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.search" class="form-control" ng-model-options="{debounce: 500}"
placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span>
<span ng-repeat="group in groups" class="pointer spacer-left-lg"
ng-if="multiselect.filters.group.indexOf(group.id) > -1"
ng-click="operateMultiselectFilter('group', group.id)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ group.name | translate }}
</span>
</span>
<span ng-hide="isPresentFilter === undefined"
class="pointer spacer-left-lg"
ng-click="isPresentFilter = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isPresentFilter ? 'Is present' : 'Is not present' | translate }}
</span>
</span>
<span ng-hide="isActiveFilter === undefined"
class="pointer spacer-left-lg"
ng-click="isActiveFilter = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isActiveFilter ? 'Is active' : 'Is not active' | translate }}
</span>
</span>
<span ng-hide="isCommitteeFilter === undefined"
class="pointer spacer-left-lg"
ng-click="isCommitteeFilter = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isCommitteeFilter ? 'Is committee' : 'Is not committee' | translate }}
</span>
</span>
</span>
</div>
</div>
<!-- main table -->
<div class="row data-row" ng-mouseover="user.hover=true"
ng-mouseleave="user.hover=false"
ng-class="{'projected': user.isProjected().length}"
ng-repeat="user in usersFiltered
| limitTo : itemsPerPage : limitBegin">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="users.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="user.selected=!user.selected"
ng-class="user.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- projector column -->
<div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<projector-button model="user", default-projector-id="defaultProjectorId">
</projector-button>
</div>
<!-- main content column -->
<div class="col-xs-6 content">
<div class="spacer-right"> <!-- horizontal block -->
<strong>
<i ng-style="{'visibility': user.is_active === false ? 'visible' : 'hidden'}" class="fa fa-ban"
title="{{ 'Is inactive' | translate }}"></i>
<i ng-style="{'visibility': user.is_committee ? 'visible' : 'hidden'}" class="fa fa-university"
title="{{ 'Is a committee' | translate }}"></i>
</strong>
</div>
<div>
<div> <!-- vertical block -->
<strong>
<a ui-sref="users.user.detail({id: user.id})" class="title">{{ user.get_short_name() }}</a>
</strong>
</div>
<div ng-if="user.number"><translate>No.</translate> {{ user.number }} </div>
<div os-perms="users.can_manage" ng-class="{'hiddenDiv': !user.hover}">
<small>
<a href="" ng-click="openDialog(user)" translate>Edit</a> &middot;
<a ui-sref="users.user.change-password({id: user.id})" translate>Change password</a> &middot;
<a href="" class="text-danger" <a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br> ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ user.get_short_name() }}</b>" <b>{{ user.get_short_name() }}</b>"
ng-bootbox-confirm-action="delete(user)" translate>Delete</a> ng-bootbox-confirm-action="delete(user)" translate>Delete</a>
</small>
</div> </div>
<td class="optional">{{ user.structure_level }}
<td class="optional">
<div ng-repeat="group in user.groups_id">
{{ (groups | filter: {id: group})[0].name | translate }}
</div> </div>
<td os-perms="users.can_see_extra_data"> </div>
<span os-perms="!users.can_manage"> <!-- additional content column -->
<i ng-if="user.is_present" class="fa fa-check-square-o"></i> <style>
.os-table .row .col-xs-4 {
width: calc(50% - {{ isSelectMode ? '120' : '70' }}px);
}
</style>
<div class="col-xs-4 content">
<div style="width: 60%;" class="optional">
<small>
<!-- Group dropdown for manage user -->
<div os-perms="users.can_manage" ng-show="groups.length > 0"
uib-tooltip="{{ 'Groups' | translate }}" tooltip-placement="top-left"
ng-mouseover="user.groupHover=true"
ng-mouseleave="user.groupHover=false">
<span uib-dropdown>
<span id="dropdown-group{{ user.id }}" class="pointer" uib-dropdown-toggle>
<span ng-if="!user.groups_id.length" ng-show="user.hover">
<i class="fa fa-users"></i>
<i class="fa fa-plus"></i>
</span> </span>
<input os-perms="users.can_manage" type="checkbox" ng-model="user.is_present" ng-click="save(user)"> <span ng-if="user.groups_id.length">
</table> <i class="fa fa-users"></i>
<span ng-repeat="group in user.groups_id | limitTo:2">
{{ (groups | filter: {id: group})[0].name | translate }}<span ng-if="!$last">,</span></span><span ng-if="user.groups_id.length > 2">,
... [+{{ user.groups_id.length - 2}}]</span>
<i class="fa fa-cog fa-lg spcaer-left" ng-show="user.groupHover"></i>
</span>
</span>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-group{{ user.id }}">
<li ng-repeat="group in groups">
<div ng-click="toggleGroup(user, group)">
<i class="fa fa-check" ng-if="inArray(user.groups_id, group.id)"></i>
{{ group.name | translate }}
</div>
</li>
</ul>
</span>
</div>
<!-- Group dropdown for normal user -->
<div os-perms="!users.can_manage" ng-show="user.groups_id.length"
uib-tooltip="{{ 'Groups' | translate }}" tooltip-placement="top-left">
<i class="fa fa-users"></i>
<span ng-repeat="group in user.groups_id | limitTo:2">
{{ (groups | filter: {id: group})[0].name | translate }}<span ng-if="!$last">,</span></span><span ng-if="user.groups_id.length > 2">,
... [+{{ user.groups_id.length - 2}}]</span>
<!-- sorry for merging them together, but otherwise there would be a whitespace because of the new line -->
</div>
<div ng-if="user.structure_level" uib-tooltip="{{ 'Structure level' | translate }}" tooltip-placement="top-left">
<i class="fa fa-flag"></i>
{{ user.structure_level }}
</div>
<div ng-if="user.comment" uib-tooltip="{{ 'Comment' | translate }}" tooltip-placement="top-left">
<i class="fa fa-info-circle"></i>
{{ user.comment | limitTo:25}}{{ user.comment.length > 25 ? '...' : '' }}
</div>
</small>
</div>
<div style="width: 40%;" class="pull-right" os-perms="users.can_see_extra_data">
<div os-perms="users.can_manage">
<span class="pointer" ng-click="user.is_present = !user.is_present; save(user);">
<i class="fa" ng-class="user.is_present ? 'fa-check-square-o' : 'fa-square-o'"></i>
<span class="spacer-left" translate>Present</span>
</span>
</div>
</div>
</div>
</div><!-- end data row -->
</div><!-- end os-table -->
<ul uib-pagination <ul uib-pagination
total-items="usersFiltered.length" total-items="usersFiltered.length"
items-per-page="itemsPerPage" items-per-page="itemsPerPage"