Merge pull request #2592 from FinnStutzenstein/UserTable

New table design for users
This commit is contained in:
Emanuel Schütze 2016-11-18 09:49:23 +01:00 committed by GitHub
commit 7ad81274d0
9 changed files with 711 additions and 440 deletions

View File

@ -866,23 +866,23 @@ img {
}
/** Motion **/
#motion-table .row {
border: 1px solid #ddd;
border-top: 0px;
#motion-os-table .identifier-col {
width: 50px;
min-height: 1px;
}
#motion-table .data-row:hover {
background-color: #f5f5f5;
#motion-os-table .identifier-col > div {
text-align: center;
}
#motion-table .data-row > div {
padding: 10px 0px 10px 8px;
#motion-os-table .title-col {
width: calc(100% - 50px);
}
#motion-table .header-row {
border-top: 1px solid #ddd;
background-color: #f5f5f5;
#motion-os-table .title-col, .os-table small {
color: #555;
}
.motion-toolbar, .speakers-toolbar {
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
@ -890,6 +890,7 @@ img {
height: 54px;
margin: -20px -5px 50px -5px;
}
.motion-toolbar:first-child {
margin-bottom: 20px;
}
@ -899,77 +900,78 @@ img {
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;
}
#motion-table .main-header {
.os-table .main-header {
width: calc(100% - 50px);
float: right;
}
#motion-table .main-header .form-inline {
.os-table .main-header .form-inline {
margin-left: 15px;
}
#motion-table .content > div {
.os-table .content > div { /* horizontal blocks */
display: inline-block;
float: left;
}
#motion-table .identifier-col {
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 {
.os-table .content > div > div { /* vertival blocks */
margin-bottom: 3px;
}
#motion-table .content > div > div:last-child {
.os-table .content > div > div:last-child {
margin-bottom: 0px;
}
#motion-table .row .centered {
.os-table .row .centered {
text-align: center;
}
#motion-table .row .col-xs-1 {
.os-table .row .col-xs-1 {
width: 50px;
}
#motion-table .row .col-xs-4 {
.os-table .projector {
width: 70px !important;
}
.os-table .row .col-xs-4 {
padding-right: 10px;
}
#motion-table .dropdown {
.os-table .dropdown {
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;
}
#motion-table .dropdown-entry {
padding: 5px 10px 5px 10px;
display: inline-block;
width: 100%;
}
#motion-table .title {
font-size: 110%;
.os-table .title {
font-size: 115%;
margin-right: 10px;
padding: 0;
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"
.filter('trusted', [
'$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
.run([
'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).
.value('MotionPollDetailCtrlCache', {})
@ -723,9 +750,11 @@ angular.module('OpenSlidesApp.motions.site', [
'HTMLValidizer',
'Projector',
'ProjectionDefault',
'MotionCsvExport',
'Multiselect',
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock,
MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) {
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault, MotionCsvExport, Multiselect) {
Motion.bindAll({}, $scope, 'motions');
Category.bindAll({}, $scope, 'categories');
MotionBlock.bindAll({}, $scope, 'motionBlocks');
@ -743,35 +772,65 @@ angular.module('OpenSlidesApp.motions.site', [
});
$scope.alert = {};
// setup table sorting
$scope.sortColumn = 'identifier';
$scope.filterPresent = '';
$scope.reverse = false;
$scope.multiselectFilter = {
$scope.multiselect = Multiselect.instance();
$scope.multiselect.filters = {
state: [],
category: [],
motionBlock: [],
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 = {
state: function (motion) {return motion.state_id;},
category: function (motion) {return motion.category_id;},
motionBlock: function (motion) {return motion.motion_block_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
$scope.operateMultiselectFilter = function (filter, id) {
if (!$scope.isDeleteMode) {
if (_.indexOf($scope.multiselectFilter[filter], id) > -1) {
// remove id
$scope.multiselectFilter[filter] = _.filter($scope.multiselectFilter[filter], function (_id) {
return _id != id;
});
} else {
// add id
$scope.multiselectFilter[filter].push(id);
}
$scope.multiselect.operate(filter, id);
}
};
// function to sort by clicked column
@ -781,61 +840,15 @@ angular.module('OpenSlidesApp.motions.site', [
}
$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
$scope.reset_filters = function () {
$scope.multiselectFilter = {
state: [],
category: [],
motionBlock: [],
tag: []
};
$scope.resetFilters = function () {
$scope.multiselect.resetFilters();
if ($scope.filter) {
$scope.filter.search = '';
}
};
$scope.are_filters_set = function () {
return $scope.multiselectFilter.state.length > 0 ||
$scope.multiselectFilter.category.length > 0 ||
$scope.multiselectFilter.motionBlock.length > 0 ||
$scope.multiselectFilter.tag.length > 0 ||
$scope.areFiltersSet = function () {
return $scope.multiselect.areFiltersSet() ||
($scope.filter ? $scope.filter.search : false);
};
@ -910,7 +923,7 @@ angular.module('OpenSlidesApp.motions.site', [
};
// Export as a pdf file
$scope.pdf_export = function() {
$scope.pdfExport = function() {
var filename = gettextCatalog.getString("Motions") + ".pdf";
var image_sources = [];
@ -940,31 +953,12 @@ angular.module('OpenSlidesApp.motions.site', [
};
// Export as a csv file
$scope.csv_export = function () {
$scope.csvExport = function () {
var element = document.getElementById('downloadLinkCSV');
var csvRows = [
['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';
MotionCsvExport(element, $scope.motionsFiltered);
};
// Export as docx file
$scope.docx_export = function () {
$scope.docxExport = function () {
MotionDocxExport.export($scope.motionsFiltered, $scope.categories);
};

View File

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

View File

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

View File

@ -101,7 +101,7 @@ angular.module('OpenSlidesApp.users', [])
firstName = _.trim(this.first_name),
lastName = _.trim(this.last_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) {
name += [lastName, firstName].join(', ');
} 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', [
'$scope',
'$state',
@ -432,8 +461,11 @@ angular.module('OpenSlidesApp.users.site', [
'UserAccessDataListContentProvider',
'PdfMakeDocumentProvider',
'gettextCatalog',
'UserCsvExport',
'Multiselect',
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');
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
$scope.$watch(function () {
@ -445,10 +477,40 @@ angular.module('OpenSlidesApp.users.site', [
}
});
$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
$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.reverse = false;
// function to sort by clicked column
@ -458,6 +520,29 @@ angular.module('OpenSlidesApp.users.site', [
}
$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
$scope.currentPage = 1;
@ -467,6 +552,17 @@ angular.module('OpenSlidesApp.users.site', [
$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
$scope.openDialog = function (user) {
ngDialog.open(UserForm.getDialog(user));
@ -490,7 +586,8 @@ angular.module('OpenSlidesApp.users.site', [
$scope.isSelectMode = false;
// check all checkboxes
$scope.checkAll = function () {
angular.forEach($scope.users, function (user) {
$scope.selectedAll = !$scope.selectedAll;
_.forEach($scope.usersFiltered, function (user) {
user.selected = $scope.selectedAll;
});
};
@ -559,20 +656,25 @@ angular.module('OpenSlidesApp.users.site', [
$scope.uncheckAll();
};
$scope.makePDF_userList = function () {
// Export as PDF
$scope.pdfExportUserList = function () {
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);
pdfMake.createPdf(documentProvider.getDocument()).download(filename);
};
$scope.makePDF_userAccessDataList = function () {
$scope.pdfExportUserAccessDataList = function () {
var filename = gettextCatalog.getString("List of access data")+".pdf";
var userAccessDataListContentProvider = UserAccessDataListContentProvider.createInstance(
$scope.users, $scope.groups, Config);
$scope.usersFiltered, $scope.groups, Config);
var documentProvider = PdfMakeDocumentProvider.createInstance(userAccessDataListContentProvider);
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>
<translate>Import</translate>
</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>
<h1 translate>Participants</h1>
</div>
@ -50,37 +30,38 @@
</button>
</div>
<div class="col-sm-6">
<div class="form-inline text-right">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" ng-model="filter.search" ng-model-options="{debounce: 500}" class="form-control"
placeholder="{{ 'Search' | translate}}">
</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>
<div class="dropdown pull-right" uib-dropdown>
<button os-perms="users.can_manage" class="btn btn-default" uib-dropdown-toggle
id="user-pdf-dropdown">
<i class="fa fa-upload"></i>
<span ng-if="usersFiltered.length == users.length" translate>
Export all
</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 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>
</div>
</div>
<div uib-collapse="!isSelectMode" class="row spacer">
<div os-perms="users.can_manage" ng-show="isSelectMode" class="col-sm-12 text-left form-inline">
<!-- actions -->
@ -134,94 +115,311 @@
<!-- filter users (for user with 'can_see_extra_data' permission) - consider present filter -->
<div os-perms="users.can_see_extra_data">
<span ng-repeat="user in $parent.usersFiltered = (users | filter: filter.search | filter: {groups_id: groupFilter} |
filter: {is_present: filterPresent} | orderBy: sortColumn:reverse)"></span>
<span ng-repeat="user in $parent.usersFiltered = (users
| 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>
<!-- filter users (for user without 'can_see_extra_data' permission) -->
<div os-perms="!users.can_see_extra_data"
ng-repeat="user in $parent.usersFiltered = (users | filter: filter.search | filter: {groups_id: groupFilter} |
orderBy: sortColumn:reverse)"></div>
ng-repeat="user in $parent.usersFiltered = (users
| 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">
<thead>
<tr>
<!-- projector column -->
<th ng-show="!isSelectMode" os-perms="core.can_manage_projector" class="minimum">
<!-- 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 ng-if="user.number"> {{ user.number }} </div>
<div os-perms="users.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !user.hover}">
<a href="" ng-click="openDialog(user)" translate>Edit</a> |
<a ui-sref="users.user.change-password({id: user.id})" translate>Change password</a> |
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ user.get_short_name() }}</b>"
ng-bootbox-confirm-action="delete(user)" translate>Delete</a>
</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>
<td os-perms="users.can_see_extra_data">
<span os-perms="!users.can_manage">
<i ng-if="user.is_present" class="fa fa-check-square-o"></i>
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode" os-perms="users.can_manage">
<i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- reset Filters -->
<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>
<input os-perms="users.can_manage" type="checkbox" ng-model="user.is_present" ng-click="save(user)">
</table>
<!-- 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"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ user.get_short_name() }}</b>"
ng-bootbox-confirm-action="delete(user)" translate>Delete</a>
</small>
</div>
</div>
</div>
<!-- additional content column -->
<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 ng-if="user.groups_id.length">
<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
total-items="usersFiltered.length"
items-per-page="itemsPerPage"