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 f7b907cd authored by Hermann's avatar Hermann Committed by Hermann
Browse files

move participantsTable to new file, add controller for data handling

parent 75053809
import m from 'mithril';
import { ListSelect, DatalistController, Form } from 'amiv-web-ui-components';
import { Toolbar, ToolbarTitle, Dialog, Card, Button } from 'polythene-mithril';
import { ResourceHandler } from '../auth';
import RelationlistController from '../relationlistcontroller';
import TableView from '../views/tableView';
import { dateFormatter } from '../utils';
export class ParticipantsController {
constructor() {
this.signupHandler = new ResourceHandler('eventsignups');
this.signupCtrl = new DatalistController((query, search) => this.signupHandler.get({
search, ...query,
}));
this.userHandler = new ResourceHandler('users');
this.acceptedUserController = new RelationlistController({
primary: 'eventsignups',
secondary: 'users',
query: { where: { accepted: true } },
searchKeys: ['email'],
includeWithoutRelation: true,
});
this.waitingUserController = new RelationlistController({
primary: 'eventsignups',
secondary: 'users',
query: { where: { accepted: false } },
searchKeys: ['email'],
includeWithoutRelation: true,
});
this.allParticipants = [];
}
setEventId(eventId) {
this.signupCtrl.setQuery({ where: { event: eventId } });
this.acceptedUserController.setQuery({ where: { event: eventId, accepted: true } });
this.waitingUserController.setQuery({ where: { event: eventId, accepted: false } });
}
refresh() {
this.signupCtrl.getFullList().then((list) => {
this.allParticipants = list;
});
this.acceptedUserController.refresh();
this.waitingUserController.refresh();
}
}
// Helper class to either display the signed up participants or those on the
// waiting list.
export class ParticipantsTable {
constructor({
attrs: {
participantsCtrl,
waitingList,
additional_fields_schema: additionalFieldsSchema,
},
}) {
this.participantsCtrl = participantsCtrl;
if (waitingList) {
this.ctrl = this.participantsCtrl.waitingUserController;
} else {
this.ctrl = this.participantsCtrl.acceptedUserController;
}
this.add_fields_schema = additionalFieldsSchema
? JSON.parse(additionalFieldsSchema).properties : null;
// true while in the modus of adding a signup
this.addmode = false;
this.userHandler = new ResourceHandler('users');
this.userController = new DatalistController(
(query, search) => this.userHandler.get({ search, ...query }).then(data => ({
...data,
_items: data._items.map(user => ({
hasSignup: this.participantsCtrl.allParticipants.some(
signupUser => user._id === signupUser.user,
),
...user,
})),
})),
);
}
exportAsCSV(filePrefix) {
this.ctrl.getFullList().then((list) => {
const csvData = (list.map((item) => {
const additionalFields = item.additional_fields && JSON.parse(item.additional_fields);
return [
item.position,
item._created,
item.user ? item.user.firstname : '',
item.user ? item.user.lastname : '',
item.user ? item.user.membership : 'none',
item.email,
item.accepted,
item.confirmed,
...Object.keys(this.add_fields_schema || {}).map(key => (
additionalFields && additionalFields[key] ? additionalFields[key] : '')),
].join(',');
})).join('\n');
const header = [
'Position', 'Date', 'Firstname', 'Lastname', 'Membership', 'Email', 'Accepted', 'Confirmed',
...Object.keys(this.add_fields_schema || {}).map(key => this.add_fields_schema[key].title),
].join(',');
const filename = `${filePrefix}_participants_export.csv`;
const fileContent = `data:text/csv;charset=utf-8,${header}\n${csvData}`;
const link = document.createElement('a');
link.setAttribute('href', encodeURI(fileContent));
link.setAttribute('download', filename);
link.click();
});
}
itemRow(data) {
// TODO list should not have hardcoded size outside of stylesheet
const hasPatchRights = data._links.self.methods.indexOf('PATCH') > -1;
const additionalFields = data.additional_fields && JSON.parse(data.additional_fields);
const canBeAccepted = !data.accepted;
return [
m('div', { style: { width: '9em' } }, dateFormatter(data._created)),
m('div', { style: { width: '16em' } }, [
...data.user ? [`${data.user.firstname} ${data.user.lastname}`, m('br')] : '',
data.email,
]),
m(
'div', { style: { width: '14em' } },
m('div', ...data.user ? `Membership: ${data.user.membership}` : ''),
(additionalFields && this.add_fields_schema) ? Object.keys(additionalFields).map(
key => m('div', `${this.add_fields_schema[key].title}: ${additionalFields[key]}`),
) : '',
),
m('div', { style: { 'flex-grow': '100' } }),
canBeAccepted ? m('div', m(Button, {
// Button to accept this eventsignup
className: 'blue-row-button',
style: {
margin: '0px 4px',
},
borders: false,
label: 'accept',
events: {
onclick: () => {
// preapare data for patch request
const patch = (({ _id, _etag }) => ({ _id, _etag }))(data);
patch.accepted = true;
this.ctrl.handler.patch(patch).then(() => {
this.participantsCtrl.refresh();
m.redraw();
});
},
},
})) : '',
hasPatchRights ? m('div', m(Button, {
// Button to remove this eventsignup
className: 'red-row-button',
borders: false,
label: 'remove',
events: {
onclick: () => {
this.ctrl.handler.delete(data).then(() => {
this.participantsCtrl.refresh();
m.redraw();
});
},
},
})) : '',
];
}
editEventSignup(user, event) {
const form = new Form();
const schema = JSON.parse(event.additional_fields);
if (schema && schema.$schema) {
// ajv fails to verify the v4 schema of some resources
schema.$schema = 'http://json-schema.org/draft-06/schema#';
form.setSchema(schema);
}
const elements = form.renderSchema();
Dialog.show({
body: m('form', { onsubmit: () => false }, elements),
backdrop: true,
footerButtons: [
m(Button, {
label: 'Cancel',
events: { onclick: () => Dialog.hide() },
}),
m(Button, {
label: 'Submit',
events: {
onclick: () => {
const additionalFieldsString = JSON.stringify(form.getData());
const data = {
event: event._id,
additional_fields: additionalFieldsString,
};
data.user = user._id;
this.ctrl.handler.post(data).then(() => {
Dialog.hide();
this.participantsCtrl.refresh();
m.redraw();
});
},
},
})],
});
}
view({ attrs: { title, filePrefix, event, waitingList } }) {
return m(Card, {
style: { height: '400px', 'margin-bottom': '10px' },
content: m('div', [
this.addmode ? m(ListSelect, {
controller: this.userController,
listTileAttrs: user => Object.assign({}, {
title: `${user.firstname} ${user.lastname}`,
style: (user.hasSignup ? { color: 'rgba(0, 0, 0, 0.2)' } : {}),
hoverable: !user.hasSignup,
}),
selectedText: user => `${user.firstname} ${user.lastname}`,
onSubmit: (user) => {
this.addmode = false;
if (event.additional_fields) {
this.editEventSignup(user, event);
} else {
this.ctrl.handler.post({
user: user._id,
event: event._id,
}).then(() => {
this.participantsCtrl.refresh();
m.redraw();
});
}
},
onCancel: () => { this.addmode = false; m.redraw(); },
}) : '',
m(Toolbar, { compact: true }, [
m(ToolbarTitle, { text: title }),
waitingList && m(Button, {
style: { margin: '0px 4px' },
className: 'blue-button',
borders: true,
label: 'add',
events: { onclick: () => { this.addmode = true; } },
}),
m(Button, {
className: 'blue-button',
borders: true,
label: 'export CSV',
events: { onclick: () => this.exportAsCSV(filePrefix) },
}),
]),
m(TableView, {
tableHeight: '275px',
controller: this.ctrl,
tileContent: data => this.itemRow(data),
clickOnRows: false,
titles: [
{ text: 'Date of Signup', width: '9em' },
{ text: 'Participant', width: '16em' },
{ text: 'Additional Info', width: '16em' },
],
}),
]),
});
}
}
import m from 'mithril';
import { Toolbar, ToolbarTitle, Dialog, Card, Button } from 'polythene-mithril';
import { Button } from 'polythene-mithril';
import Stream from 'mithril/stream';
import { styler } from 'polythene-core-css';
import { DropdownCard, ListSelect, DatalistController, Form, Chip } from 'amiv-web-ui-components';
import { DropdownCard, Chip } from 'amiv-web-ui-components';
// eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig';
import ItemView from '../views/itemView';
import TableView from '../views/tableView';
import RelationlistController from '../relationlistcontroller';
import { ParticipantsController, ParticipantsTable } from './participants';
import { dateFormatter } from '../utils';
import { Property, FilterChip, icons } from '../views/elements';
import { ResourceHandler } from '../auth';
import { colors } from '../style';
const viewLayout = [
......@@ -144,245 +142,18 @@ class ParticipantsSummary {
}
}
// Helper class to either display the signed up participants or those on the
// waiting list.
class ParticipantsTable {
constructor({ attrs: { where, additional_fields_schema: additionalFieldsSchema, parent } }) {
this.ctrl = new RelationlistController({
primary: 'eventsignups',
secondary: 'users',
query: { where },
searchKeys: ['email'],
includeWithoutRelation: true,
});
this.parent = parent;
this.add_fields_schema = additionalFieldsSchema
? JSON.parse(additionalFieldsSchema).properties : null;
// true while in the modus of adding a signup
this.addmode = false;
this.userHandler = new ResourceHandler('users');
this.userController = new DatalistController(
(query, search) => this.userHandler.get({ search, ...query }).then(data => ({
...data,
_items: data._items.map(user => ({
hasSignup: parent.allParticipants.some(signupUser => user._id === signupUser.user),
...user,
})),
})),
);
}
oncreate(vnode) {
// passe reference to participantsTable to parent class
vnode.attrs.onRef(this);
}
exportAsCSV(filePrefix) {
this.ctrl.getFullList().then((list) => {
const csvData = (list.map((item) => {
const additionalFields = item.additional_fields && JSON.parse(item.additional_fields);
return [
item.position,
item._created,
item.user ? item.user.firstname : '',
item.user ? item.user.lastname : '',
item.user ? item.user.membership : 'none',
item.email,
item.accepted,
item.confirmed,
...Object.keys(this.add_fields_schema || {}).map(key => (
additionalFields && additionalFields[key] ? additionalFields[key] : '')),
].join(',');
})).join('\n');
const header = [
'Position', 'Date', 'Firstname', 'Lastname', 'Membership', 'Email', 'Accepted', 'Confirmed',
...Object.keys(this.add_fields_schema || {}).map(key => this.add_fields_schema[key].title),
].join(',');
const filename = `${filePrefix}_participants_export.csv`;
const fileContent = `data:text/csv;charset=utf-8,${header}\n${csvData}`;
const link = document.createElement('a');
link.setAttribute('href', encodeURI(fileContent));
link.setAttribute('download', filename);
link.click();
});
}
itemRow(data) {
// TODO list should not have hardcoded size outside of stylesheet
const hasPatchRights = data._links.self.methods.indexOf('PATCH') > -1;
const additionalFields = data.additional_fields && JSON.parse(data.additional_fields);
const canBeAccepted = !data.accepted;
return [
m('div', { style: { width: '9em' } }, dateFormatter(data._created)),
m('div', { style: { width: '16em' } }, [
...data.user ? [`${data.user.firstname} ${data.user.lastname}`, m('br')] : '',
data.email,
]),
m(
'div', { style: { width: '14em' } },
m('div', ...data.user ? `Membership: ${data.user.membership}` : ''),
(additionalFields && this.add_fields_schema) ? Object.keys(additionalFields).map(
key => m('div', `${this.add_fields_schema[key].title}: ${additionalFields[key]}`),
) : '',
),
m('div', { style: { 'flex-grow': '100' } }),
canBeAccepted ? m('div', m(Button, {
// Button to accept this eventsignup
className: 'blue-row-button',
style: {
margin: '0px 4px',
},
borders: false,
label: 'accept',
events: {
onclick: () => {
// preapare data for patch request
const patch = (({ _id, _etag }) => ({ _id, _etag }))(data);
patch.accepted = true;
this.ctrl.handler.patch(patch).then(() => {
this.parent.dataChanged();
});
},
},
})) : '',
hasPatchRights ? m('div', m(Button, {
// Button to remove this eventsignup
className: 'red-row-button',
borders: false,
label: 'remove',
events: {
onclick: () => {
this.ctrl.handler.delete(data).then(() => {
this.parent.dataChanged();
});
},
},
})) : '',
];
}
editEventSignup(user, event) {
const form = new Form();
const schema = JSON.parse(event.additional_fields);
if (schema && schema.$schema) {
// ajv fails to verify the v4 schema of some resources
schema.$schema = 'http://json-schema.org/draft-06/schema#';
form.setSchema(schema);
}
const elements = form.renderSchema();
Dialog.show({
body: m('form', { onsubmit: () => false }, elements),
backdrop: true,
footerButtons: [
m(Button, {
label: 'Cancel',
events: { onclick: () => Dialog.hide() },
}),
m(Button, {
label: 'Submit',
events: {
onclick: () => {
const additionalFieldsString = JSON.stringify(form.getData());
const data = {
event: event._id,
additional_fields: additionalFieldsString,
};
data.user = user._id;
this.ctrl.handler.post(data).then(() => {
Dialog.hide();
this.parent.dataChanged();
});
},
},
})],
});
}
view({ attrs: { title, filePrefix, event, waitingList } }) {
return m(Card, {
style: { height: '400px', 'margin-bottom': '10px' },
content: m('div', [
this.addmode ? m(ListSelect, {
controller: this.userController,
listTileAttrs: user => Object.assign({}, {
title: `${user.firstname} ${user.lastname}`,
style: (user.hasSignup ? { color: 'rgba(0, 0, 0, 0.2)' } : {}),
hoverable: !user.hasSignup,
}),
selectedText: user => `${user.firstname} ${user.lastname}`,
onSubmit: (user) => {
this.addmode = false;
if (event.additional_fields) {
this.editEventSignup(user, event);
} else {
this.ctrl.handler.post({
user: user._id,
event: event._id,
}).then(() => {
this.parent.dataChanged();
});
}
},
onCancel: () => { this.addmode = false; m.redraw(); },
}) : '',
m(Toolbar, { compact: true }, [
m(ToolbarTitle, { text: title }),
waitingList && m(Button, {
style: { margin: '0px 4px' },
className: 'blue-button',
borders: true,
label: 'add',
events: { onclick: () => { this.addmode = true; } },
}),
m(Button, {
className: 'blue-button',
borders: true,
label: 'export CSV',
events: { onclick: () => this.exportAsCSV(filePrefix) },
}),
]),
m(TableView, {
tableHeight: '275px',
controller: this.ctrl,
tileContent: data => this.itemRow(data),
clickOnRows: false,
titles: [
{ text: 'Date of Signup', width: '9em' },
{ text: 'Participant', width: '16em' },
{ text: 'Additional Info', width: '16em' },
],
}),
]),
});
}
}
export default class viewEvent extends ItemView {
constructor(vnode) {
super(vnode);
this.signupHandler = new ResourceHandler('eventsignups');
this.signupCtrl = new DatalistController((query, search) => this.signupHandler.get({
search, ...query,
}));
this.participantsCtrl = new ParticipantsController();
this.description = false;
this.advertisement = false;
this.registration = false;
this.allParticipants = [];
this.modalDisplay = Stream('none');
}
oninit() {
this.signupCtrl.setQuery({ where: { event: this.data._id } });
this.signupCtrl.getFullList().then((list) => { this.allParticipants = list; });
this.participantsCtrl.setEventId(this.data._id);
}
cloneEvent() {
......@@ -417,16 +188,6 @@ export default class viewEvent extends ItemView {
this.controller.data = event;
}
dataChanged() {
this.signupCtrl.getFullList().then((list) => {
this.allParticipants = list;
this.participantsAccepted.ctrl.refresh();
this.participantsWaiting.ctrl.refresh();
this.participantsWaiting.userController.refresh();
m.redraw();
});
}
view() {
let displaySpots = '-';
const stdMargin = { margin: '5px' };
......@@ -543,7 +304,7 @@ export default class viewEvent extends ItemView {
title: 'Participants Summary',
style: { margin: '10px 0' },
}, m(ParticipantsSummary, {
participants: this.allParticipants,
participants: this.participantsCtrl.allParticipants,
additionalFields: this.data.additional_fields,
})) : '',
......@@ -592,24 +353,20 @@ export default class viewEvent extends ItemView {
]),
m('div.viewcontainercolumn', { style: { width: '50em' } }, [
this.data.time_register_start ? m(ParticipantsTable, {
onRef: (ref) => { this.participantsAccepted = ref; m.redraw(); },
where: { accepted: true, event: this.data._id },
title: 'Accepted Participants',
filePrefix: 'accepted',
event: this.data,
waitingList: false,
additional_fields_schema: this.data.additional_fields,
parent: this,
participantsCtrl: this.participantsCtrl,
}) : '',
this.data.time_register_start ? m(ParticipantsTable, {
onRef: (ref) => { this.participantsWaiting = ref; m.redraw(); },
where: { accepted: false, event: this.data._id },
title: 'Participants on Waiting List',
filePrefix: 'waitinglist',
event: this.data,
waitingList: true,
additional_fields_schema: this.data.additional_fields,
parent: this,
participantsCtrl: this.participantsCtrl,
}) : '',
]),
]),