To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit a98dce9c authored by Hermann's avatar Hermann Committed by Sandro Lutz
Browse files

More Information about event participants

parent ebb8fea4
......@@ -170,16 +170,33 @@ export class ResourceHandler {
// translate search into where, we just look if any field contains search
// The search-string may match any of the keys in the object specified in the
// constructor
const searchQuery = {
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = {
$regex: `${query.search}`,
$options: 'i',
};
return fieldQuery;
}),
};
let searchQuery;
if (query.search.split(' ').length > 1) {
searchQuery = {
$and: query.search.split(' ').map(searchPhrase => ({
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = {
$regex: `${searchPhrase}`,
$options: 'i',
};
return fieldQuery;
}),
})),
};
} else {
searchQuery = {
$or: this.searchKeys.map((key) => {
const fieldQuery = {};
fieldQuery[key] = {
$regex: `${query.search}`,
$options: 'i',
};
return fieldQuery;
}),
};
}
// if there exists already a where-filter, AND them together
if ('where' in query) {
......
......@@ -14,7 +14,7 @@ import { ResourceHandler } from '../auth';
export default class EventTable {
constructor() {
this.handler = new ResourceHandler('events', config.tableKeys);
this.handler = new ResourceHandler('events');
this.ctrl = new DatalistController((query, search) => this.handler.get({ search, ...query }));
}
......
import m from 'mithril';
import {
Switch,
Toolbar,
ToolbarTitle,
Card,
TextField,
Button,
} from 'polythene-mithril';
import { Toolbar, ToolbarTitle, Card, Button } from 'polythene-mithril';
import Stream from 'mithril/stream';
import { styler } from 'polythene-core-css';
import { DropdownCard, DatalistController } from 'amiv-web-ui-components';
import { DropdownCard, DatalistController, Chip } from 'amiv-web-ui-components';
// eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig';
import ItemView from '../views/itemView';
......@@ -17,7 +10,7 @@ import { eventsignups as signupConfig } from '../resourceConfig.json';
import TableView from '../views/tableView';
import RelationlistController from '../relationlistcontroller';
import { dateFormatter } from '../utils';
import { icons, Property, chip } from '../views/elements';
import { Property, FilterChip, icons } from '../views/elements';
import { ResourceHandler } from '../auth';
import { colors } from '../style';
......@@ -70,6 +63,80 @@ class DuoLangProperty {
}
}
class ParticipantsSummary {
constructor() {
this.onlyAccepted = true;
}
view({ attrs: { participants, additionalFields = "{'properties': {}}" } }) {
// Parse the JSON from additional fields into an object
const parsedParticipants = participants.map(signup => ({
...signup,
additional_fields: signup.additional_fields ?
JSON.parse(signup.additional_fields) : {},
}));
// Filter if only accepted participants should be shown
const filteredParticipants = parsedParticipants.filter(participant =>
(this.onlyAccepted ? participant.accepted : true));
// check which additional fields should get summarized
let hasSBB = false;
let hasFood = false;
if (additionalFields) {
hasSBB = 'sbb_abo' in JSON.parse(additionalFields).properties;
hasFood = 'food' in JSON.parse(additionalFields).properties;
}
return m('div', [
m('div', {
style: {
height: '50px',
'overflow-x': 'auto',
'overflow-y': 'hidden',
'white-space': 'nowrap',
padding: '0px 5px',
},
}, [].concat(['Filters: '], ...[
m(FilterChip, {
selected: this.onlyAccepted,
onclick: () => { this.onlyAccepted = !this.onlyAccepted; },
}, 'accepted users'),
])),
hasSBB ? m('div', { style: { display: 'flex' } }, [
m(Property, { title: 'No SBB', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.sbb_abo === 'None').length),
m(Property, { title: 'GA', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.sbb_abo === 'GA').length),
m(Property, { title: 'Halbtax', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.sbb_abo === 'Halbtax').length),
m(Property, { title: 'Gleis 7', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.sbb_abo === 'Gleis 7').length),
]) : '',
hasFood ? m('div', { style: { display: 'flex' } }, [
m(Property, { title: 'Omnivors', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.food === 'Omnivor').length),
m(Property, { title: 'Vegis', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.food === 'Vegi').length),
m(Property, { title: 'Vegans', leftAlign: false }, filteredParticipants.filter(signup =>
signup.additional_fields.food === 'Vegan').length),
]) : '',
m('textarea', {
style: { opacity: '0', width: '0px' },
id: 'participantsemails',
}, filteredParticipants.map(signup => signup.email).toString().replace(/,/g, '; ')),
m(Button, {
label: 'Copy Emails',
events: {
onclick: () => {
document.getElementById('participantsemails').select();
document.execCommand('copy');
},
},
}),
]);
}
}
// Helper class to either display the signed up participants or those on the
// waiting list.
class ParticipantsTable {
......@@ -182,15 +249,6 @@ class ParticipantsTable {
}
}
class EmailList {
view({ attrs: { list } }) {
const emails = list.toString().replace(/,/g, '; ');
return m(Card, {
content: m(TextField, { value: emails, label: '', multiLine: true }, ''),
});
}
}
export default class viewEvent extends ItemView {
constructor(vnode) {
super(vnode);
......@@ -201,31 +259,15 @@ export default class viewEvent extends ItemView {
this.description = false;
this.advertisement = false;
this.registration = false;
this.emailAdresses = false;
this.emaillist = [''];
this.showAllEmails = false;
this.allParticipants = [];
this.modalDisplay = Stream('none');
}
oninit() {
this.setUpEmailList(this.showAllEmails);
this.signupCtrl.setQuery({ where: { event: this.data._id } });
this.signupCtrl.getFullList().then((list) => { this.allParticipants = list; });
}
setUpEmailList(showAll) {
// setup where query
const where = { event: this.data._id };
if (!showAll) {
// only show accepted
where.accepted = true;
}
this.signupCtrl.setQuery({ where });
this.signupCtrl.getFullList().then((list) => {
this.emaillist = (list.map(item => item.email));
m.redraw();
});
}
cloneEvent() {
const event = Object.assign({}, this.data);
console.log(event);
......@@ -285,10 +327,12 @@ export default class viewEvent extends ItemView {
]),
// below the title, most important details are listed
m('div.maincontainer', { style: { display: 'flex' } }, [
('signup_count' in this.data && this.data.signup_count !== null) && m(Property, {
style: stdMargin,
title: 'Signups',
}, `${this.data.signup_count} / ${displaySpots}`),
(this.data.spots !== null && 'signup_count' in this.data
&& this.data.signup_count !== null) ?
m(Property, {
style: stdMargin,
title: 'Signups',
}, `${this.data.signup_count} / ${displaySpots}`) : '',
this.data.location && m(Property, {
style: stdMargin,
title: 'Location',
......@@ -316,16 +360,16 @@ export default class viewEvent extends ItemView {
m(DropdownCard, { title: 'advertisement', style: { margin: '10px 0' } }, [
[
m(chip, {
m(Chip, {
svg: this.data.show_annonce ? icons.checked : icons.clear,
border: '1px #aaaaaa solid',
}, 'announce'),
m(chip, {
m(Chip, {
svg: this.data.show_infoscreen ? icons.checked : icons.clear,
border: '1px #aaaaaa solid',
margin: '4px',
}, 'infoscreen'),
m(chip, {
m(Chip, {
svg: this.data.show_website ? icons.checked : icons.clear,
border: '1px #aaaaaa solid',
}, 'website'),
......@@ -365,17 +409,13 @@ export default class viewEvent extends ItemView {
]),
// a list of email adresses of all participants, easy to copy-paste
m(DropdownCard, { title: 'Email Adresses', style: { margin: '10px 0' } }, [
m(Switch, {
defaultChecked: false,
label: 'show unaccepted',
onChange: () => {
this.showAllEmails = !this.showAllEmails;
this.setUpEmailList(this.showAllEmails);
},
}),
m(EmailList, { list: this.emaillist }),
]),
this.data.spots !== null ? m(DropdownCard, {
title: 'Participants Summary',
style: { margin: '10px 0' },
}, m(ParticipantsSummary, {
participants: this.allParticipants,
additionalFields: this.data.additional_fields,
})) : '',
m(DropdownCard, { title: 'Images' }, [
m('div', {
......
......@@ -7,8 +7,8 @@ import {
TextField,
Icon,
} from 'polythene-mithril';
import { DatalistController, ListSelect, DropdownCard } from 'amiv-web-ui-components';
import { icons, Property, chip } from '../views/elements';
import { DatalistController, ListSelect, DropdownCard, Chip } from 'amiv-web-ui-components';
import { icons, Property } from '../views/elements';
import { colors } from '../style';
import ItemView from '../views/itemView';
import TableView from '../views/tableView';
......@@ -200,7 +200,7 @@ export default class viewGroup extends ItemView {
// this div is the title line
m('div.maincontainer', [
m('h1', this.data.name),
this.data.requires_storage && m(chip, {
this.data.requires_storage && m(Chip, {
svg: icons.cloud,
svgColor: '#ffffff',
svgBackground: colors.orange,
......
......@@ -124,7 +124,8 @@
"lecture",
"professor",
"author",
"uploader"
"uploader",
"department"
],
"notPatchableKeys": ["uploader"]
}
......
import m from 'mithril';
import { Card, Toolbar, ToolbarTitle, Button, Snackbar } from 'polythene-mithril';
import { ListSelect, DatalistController } from 'amiv-web-ui-components';
import { ListSelect, DatalistController, Chip } from 'amiv-web-ui-components';
import ItemView from '../views/itemView';
import TableView from '../views/tableView';
import RelationlistController from '../relationlistcontroller';
import { ResourceHandler } from '../auth';
import { chip, icons, Property } from '../views/elements';
import { icons, Property } from '../views/elements';
import { colors } from '../style';
export default class UserView extends ItemView {
......@@ -44,25 +44,25 @@ export default class UserView extends ItemView {
view() {
const stdMargin = { margin: '5px' };
let membership = m(chip, {
let membership = m(Chip, {
svg: icons.clear,
svgBackground: colors.amiv_red,
...stdMargin,
}, 'No Member');
if (this.data.membership === 'regular') {
membership = m(chip, {
membership = m(Chip, {
svg: icons.checked,
svgBackground: colors.green,
...stdMargin,
}, 'Regular Member');
} else if (this.data.membership === 'extraordinary') {
membership = m(chip, {
membership = m(Chip, {
svg: icons.checked,
svgBackground: colors.green,
...stdMargin,
}, 'Extraordinary Member');
} else if (this.data.membership === 'honorary') {
membership = m(chip, {
membership = m(Chip, {
svg: icons.star,
svgBackground: colors.orange,
...stdMargin,
......@@ -95,11 +95,11 @@ export default class UserView extends ItemView {
m('h1', `${this.data.firstname} ${this.data.lastname}`),
membership,
this.data.department && m(
chip,
Chip,
{ svg: icons.department, ...stdMargin },
this.data.department,
),
this.data.gender && m(chip, { margin: '5px' }, this.data.gender),
this.data.gender && m(Chip, { margin: '5px' }, this.data.gender),
m('div', { style: { display: 'flex' } }, [
this.data.nethz && m(Property, { title: 'NETHZ', style: stdMargin }, this.data.nethz),
this.data.email && m(Property, { title: 'Email', style: stdMargin }, this.data.email),
......
import m from 'mithril';
import { Icon } from 'polythene-mithril';
import { Chip } from 'amiv-web-ui-components';
export const icons = {
search: '<svg width="24" height="24" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>',
......@@ -28,8 +28,8 @@ export const icons = {
// attrs is the title, children the text
// therefore, you can call it with m(Property, title, text)
export class Property {
view({ attrs: { title, ...restAttrs }, children }) {
return m('div', restAttrs, [
view({ attrs: { title, leftAlign = true, ...restAttrs }, children }) {
return m('div', { style: { margin: '5px' }, ...restAttrs }, [
m('span', {
style: {
'margin-top': '10px',
......@@ -37,7 +37,12 @@ export class Property {
color: 'rgba(0, 0, 0, 0.54)',
},
}, m.trust(title)),
m('p', { style: { color: 'rgba(0, 0, 0, 0.87)' } }, children),
m('p', {
style: {
color: 'rgba(0, 0, 0, 0.87)',
'text-align': leftAlign ? 'left' : 'right',
},
}, children),
]);
}
}
......@@ -55,46 +60,6 @@ export class selectGroup {
}
}
export class chip {
view({
attrs: {
svg,
background = '#ffffff',
textColor = '#000000',
svgColor = '#000000',
svgBackground = '#dddddd',
...attrs
},
children,
}) {
return m('div', {
style: {
height: '32px',
'background-color': background,
color: textColor,
'border-radius': '16px',
// if there is a border, things are weirdly shifted
padding: attrs.border ? '3px 8px 4px 6px' : '4px 8px',
display: 'inline-flex',
...attrs,
},
...attrs.onclick ? { onclick: attrs.onclick } : {},
}, [
svg && m('div', {
style: {
'background-color': svgBackground,
'border-radius': '12px',
margin: '0px 4px 0px -2px',
height: '24px',
width: '24px',
padding: '2px 2px 2px 4px',
},
}, m(Icon, { svg: { content: m.trust(svg) }, size: 'small', style: { svgColor } })),
m('div', { style: { 'line-height': '24px' } }, children),
]);
}
}
export class submitButton {
view({ attrs: { args, active, text } }) {
const argsCopy = args;
......@@ -104,3 +69,19 @@ export class submitButton {
return m('div.btn', argsCopy, text);
}
}
export class FilterChip {
view({ attrs: { selected = false, onclick = () => { } }, children }) {
return m(Chip, {
'margin-left': '5px',
'margin-right': '5px',
background: selected ? '#aaaaaa' : '#dddddd',
svgBackground: '#aaaaaa',
textColor: selected ? '#000000' : '#999999',
svgColor: '#000000',
svg: selected ? icons.checked : null,
onclick,
onClick: onclick,
}, children);
}
}
......@@ -3,7 +3,7 @@ import infinite from 'mithril-infinite';
import { List, ListTile, Toolbar, Search, Button } from 'polythene-mithril';
import 'polythene-css';
import { styler } from 'polythene-core-css';
import { chip, icons } from './elements';
import { FilterChip } from './elements';
const tableStyles = [
{
......@@ -21,22 +21,6 @@ const tableStyles = [
styler.add('tableview', tableStyles);
class FilterChip {
view({ attrs: { selected = false, onclick = () => {} }, children }) {
return m(chip, {
'margin-left': '5px',
'margin-right': '5px',
background: selected ? '#aaaaaa' : '#dddddd',
svgBackground: '#aaaaaa',
textColor: selected ? '#000000' : '#999999',
svgColor: '#000000',
svg: selected ? icons.checked : null,
onclick,
}, children);
}
}
export default class TableView {
/* Shows a table of objects for a given API resource.
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment