Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • maspect/amiv-admintool
  • emustafa/amiv-admintool
  • dvruette/amiv-admintool
  • amiv/amiv-admintool
4 results
Show changes
Showing
with 456 additions and 770 deletions
{
"apiUrl": "https://api-dev.amiv.ethz.ch",
"ownUrl": "https://admin-dev.amiv.ethz.ch",
"oAuthID": "AMIV Admintool"
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool Dev"
}
{
"apiUrl": "https://api-dev.amiv.ethz.ch",
"ownUrl": "http://localhost:9000",
"hookUrl": "http://0.0.0.0:5000/hook/websitepipeline",
"oAuthID": "Local Tool"
}
{
"apiUrl": "http://127.0.0.1:5000",
"ownUrl": "http://localhost:9000",
"hookUrl": "http://0.0.0.0:6000/hook/websitepipeline",
"oAuthID": "Local Tool"
}
{
"apiUrl": "https://api.amiv.ethz.ch",
"ownUrl": "https://admin.amiv.ethz.ch",
"oAuthID": "Admintools"
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool"
}
{
"apiUrl": "https://api-staging.amiv.ethz.ch",
"ownUrl": "https://admin-staging.amiv.ethz.ch",
"hookUrl": "https://webhooks.amiv.ethz.ch/hook/websitepipeline",
"oAuthID": "AMIV Admintool Staging"
}
......@@ -2,21 +2,35 @@ import m from 'mithril';
import Stream from 'mithril/stream';
import { ResourceHandler } from './auth';
import { debounce } from './utils';
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
// IS NOT IN USE
export default class RelationlistController {
/*
* Controller for a list of data embedding a relationship.
* The secondary api endpoint is embedded into the list items of the primary endpoint results.
* Searches are applied to both resources, queries and filters need to be specified for each.
*
* @param {bool} includeWithoutRelation - Specifies what to do in case the relation is undefined.
* By default, such items are excluded, if true they will be included into the list.
*/
constructor(
constructor({
primary,
secondary,
query = {},
searchKeys = false,
searchKeys = [],
secondaryQuery = {},
secondarySearchKeys = false,
) {
secondarySearchKeys = [],
includeWithoutRelation = false,
}) {
this.handler = new ResourceHandler(primary, searchKeys);
this.handler2 = new ResourceHandler(secondary, secondarySearchKeys);
this.secondaryKey = secondary.slice(0, -1);
......@@ -24,6 +38,8 @@ export default class RelationlistController {
this.query2 = secondaryQuery || {};
this.filter = null;
this.filter2 = null;
this.sort = null;
this.includeWithoutRelation = includeWithoutRelation;
// state pointer that is counted up every time the table is refreshed so
// we can tell infinite scroll that the data-version has changed.
this.stateCounter = Stream(0);
......@@ -46,6 +62,7 @@ export default class RelationlistController {
item,
pageData: pageNum => this.getPageData(pageNum),
pageKey: pageNum => `${pageNum}-${this.stateCounter()}`,
maxPages: this.totalPages ? this.totalPages : undefined,
};
}
......@@ -54,22 +71,21 @@ export default class RelationlistController {
// resource for the items specified by the relation in the primary resource
// We apply Queries for both resources seperately.
const query = Object.assign({}, this.query);
query.max_results = 10;
query.max_results = 50;
query.page = pageNum;
query.where = { ...this.filter, ...this.query.where };
console.log(query.search);
query.sort = this.sort || query.sort;
return new Promise((resolve) => {
this.handler.get(query).then((data) => {
// update total number of pages
this.totalPages = Math.ceil(data._meta.total / 10);
this.totalPages = Math.ceil(data._meta.total / 50);
console.log(data._items.map(item => item._id));
const itemsWithoutRelation = data._items.filter(item => !(this.secondaryKey in item));
const itemsWithRelation = data._items.filter(item => (this.secondaryKey in item));
const query2 = Object.assign({}, this.query2);
query2.where = {
_id: { $in: data._items.map(item => item[this.secondaryKey]) },
_id: { $in: itemsWithRelation.map(item => item[this.secondaryKey]) },
...this.filter2,
...this.query2.where,
};
......@@ -78,15 +94,20 @@ export default class RelationlistController {
const secondaryIds = secondaryData._items.map(item => item._id);
// filter the primary list to only include those items that have a relation to
// the queried secondary IDs
const filteredPrimaries = data._items.filter(item =>
secondaryIds.includes(item[this.secondaryKey]));
// now return the list of filteredPrimaries with the secondary data embedded
resolve(filteredPrimaries.map((item) => {
const filteredPrimaries = itemsWithRelation.filter(
item => secondaryIds.includes(item[this.secondaryKey]),
);
// embed the secondary data
const embeddedList = filteredPrimaries.map((item) => {
const itemCopy = Object.assign({}, item);
itemCopy[this.secondaryKey] = secondaryData._items.find(relItem =>
relItem._id === item[this.secondaryKey]);
itemCopy[this.secondaryKey] = secondaryData._items.find(
relItem => relItem._id === item[this.secondaryKey],
);
return itemCopy;
}));
});
// now return the list of filteredPrimaries with the secondary data embedded
if (this.includeWithoutRelation) resolve([...embeddedList, ...itemsWithoutRelation]);
else resolve(embeddedList);
});
});
});
......@@ -103,7 +124,6 @@ export default class RelationlistController {
// save totalPages as a constant to avoid race condition with pages added during this
// process
const { totalPages } = this;
console.log(totalPages);
if (totalPages === 1) {
resolve(firstPage);
......@@ -114,8 +134,10 @@ export default class RelationlistController {
this.getPageData(pageNum).then((newPage) => {
pages[pageNum] = newPage;
// look if all pages were collected
const missingPages = Array.from(new Array(totalPages), (x, i) => i + 1).filter(i =>
!(i in pages));
const missingPages = Array.from(new Array(totalPages), (x, i) => i + 1).filter(
i => !(i in pages),
);
// eslint-disable-next-line no-console
console.log('missingPages', missingPages);
if (missingPages.length === 0) {
// collect all the so-far loaded pages in order (sorted keys)
......@@ -151,5 +173,9 @@ export default class RelationlistController {
this.query = Object.assign({}, query, { search: this.query.search });
this.refresh();
}
}
setSort(sort) {
this.sort = sort;
this.refresh();
}
}
{
"apiUrl": "https://api-dev.amiv.ethz.ch/",
"events": {
"keyDescriptors": {
"title_de": "German Title",
"title_en": "English Title",
"location": "Location",
"show_website": "Event is shown on the website",
"priority": "Priority",
"time_end": "Ending time",
"time_register_end": "Deadline for registration",
"time_start": "Starting time",
"spots": "Spots available",
"allow_email_signup": "Event open for non-AMIV members",
"price": "Price",
"signup_count": "Signed-up participants",
"catchphrase_en": "Catchphrase in English. Announce and Website.",
"catchphrase_de": "Schlagwort auf Deutsch",
"description_de": "Beschreibung auf Deutsch",
"description_en": "Description in English",
"img_banner": "Banner as png",
"img_poster": "Poster as png",
"img_thumbnail": "Thumbnail as png",
"show_infoscreen": "Does the event show on the infoscreen?",
"img_infoscreen": "Infoscreen as png",
"time_advertising_end": "Advertisment ends on",
"time_advertising_start": "Advertisement starts on",
"selection_strategy": "TODO what is this?",
"show_announce": "Does it belong to announce?"
},
"tableKeys": [
"title_de",
"time_start",
"time_end",
"time_register_end",
"show_website",
"priority"
],
"notPatchableKeys": [
"signup_count"
],
"searchKeys": [
"title_de",
"title_en",
"location"
]
},
"users": {
"keyDescriptors": {
"legi": "Legi Number",
"firstname": "First Name",
"lastname": "Last Name",
"rfid": "RFID",
"phone": "Phone",
"nethz": "nethz Account",
"gender": "Gender",
"department": "Department",
"email": "Email"
},
"tableKeys": [
"firstname",
"lastname",
"nethz",
"legi",
"membership"
],
"searchKeys": [
"firstname",
"lastname",
"nethz",
"legi",
"email"
],
"notPatchableKeys": [
"password_set"
]
},
"joboffers":{
"keyDescriptors": {
"company": "Company",
"email": "Email",
"description_en": "Job description",
"description_de": "Job Beschreibung",
"logo": "Logo as png",
"pdf": "PDF provided by company",
"time_end": "Application deadline",
"title_de": "Stelle auf Deutsch",
"title_de": "Position title in English",
"show_website": "Is the job listed on the website?",
"_id":"Job ID."
},
"tableKeys": [
"title_de",
"time_end",
"show_website"
]
},
"groups": {
"keyDescriptors": {
"name": "Name"
},
"searchKeys": ["name"],
"patchableKeys": ["name"]
},
"groupmemberships": {
"patchableKeys": ["user", "group"]
},
"eventsignups": {
"patchableKeys": ["event"],
"tableKeys": [
"_created",
"user.lastname",
"user.firstname",
"email"
],
"searchKeys": []
}
}
import m from 'mithril';
import { FileInput } from 'amiv-web-ui-components';
import { Button, List, ListTile, Snackbar } from 'polythene-mithril';
import EditView from '../views/editView';
import { getSchema } from '../auth';
export default class editDoc extends EditView {
// constructor zu file upload
constructor(vnode) {
// remove the files list as it is impossible to validate
const docSchema = getSchema().definitions['Study Document'];
delete docSchema.properties.files;
super(vnode);
if (!('files' in this.form.data)) {
this.form.data.files = [{ name: 'add file' }];
}
}
beforeSubmit() {
// check if there are files uploaded
const files = [];
Object.keys(this.form.data).forEach((key) => {
if (key.startsWith('new_file_') && this.form.data[key]) {
files.push(this.form.data[key]);
delete this.form.data[key];
}
});
// in case that there are no files, eject an error
if (this.controller.modus === 'new' && files.length === 0) {
Snackbar.show({ title: 'You need to upload at least one file.' });
this.form.valid = false;
return;
}
// now post all together as FormData
const submitData = new FormData();
Object.keys(this.form.data).forEach((key) => {
if (key !== 'files') submitData.append(key, this.form.data[key]);
});
files.forEach((file) => { submitData.append('files', file); });
this.submit(submitData).then(() => this.controller.changeModus('view'));
}
view() {
return this.layout([
m('h3', 'Add a New Studydocument'),
this.form._renderField('semester', {
...this.form.schema.properties.semester,
style: { width: '100px' },
}),
...this.form.renderSchema(['type', 'lecture', 'title', 'course_year', 'professor', 'author']),
// file upload: work in progress, so far all files get deleted with a patch
m('div', [
'WARNING: Files added here will remove all files currently uploaded. If you want to add',
'/edit a file in this studydoc, reupload all other files as well.',
m(List, {
tiles: [...this.form.data.files.entries()].map(numAndFile => m(ListTile, {
content: [
m(FileInput, this.form.bind({
name: `new_file_${numAndFile[0]}`,
label: numAndFile[1].name,
})),
],
})),
}),
// additional file
m(Button, {
label: 'Additional File',
className: 'blue-button',
border: true,
events: { onclick: () => { this.form.data.files.push({ name: 'add file' }); } },
}),
]),
]);
}
}
import m from 'mithril';
import viewDoc from './viewDoc';
import editDoc from './editDoc';
import ItemController from '../itemcontroller';
import { loadingScreen } from '../layout';
export default class studydocItem {
constructor() {
this.controller = new ItemController('studydocuments');
}
view() {
if (!this.controller || (!this.controller.data && this.controller.modus !== 'new')) {
return m(loadingScreen);
}
if (this.controller.modus !== 'view') return m(editDoc, { controller: this.controller });
return m(viewDoc, { controller: this.controller });
}
}
import m from 'mithril';
import { DatalistController } from 'amiv-web-ui-components';
import TableView from '../views/tableView';
import { ResourceHandler } from '../auth';
/* Table of all studydocuments */
export default class StudydocTable {
constructor() {
this.handler = new ResourceHandler('studydocuments');
this.ctrl = new DatalistController((query, search) => this.handler.get({ search, ...query }));
}
getItemData(data) {
return [
m('div', { style: { width: 'calc(100% - 36em)' } }, data.title),
m('div', { style: { width: '8em' } }, data.author),
m('div', { style: { width: '4em' } }, data.course_year),
m('div', { style: { width: '4em' } }, data.semester),
m('div', { style: { width: '10em' } }, data.lecture),
m('div', { style: { width: '10em' } }, data.files.map((file) => {
const splittedFilenames = file.name.split('.');
return `.${splittedFilenames[splittedFilenames.length - 1]} `;
})),
];
}
view() {
return m(TableView, {
controller: this.ctrl,
keys: ['title', 'author', 'course_year', 'semester', 'lecture'],
tileContent: this.getItemData,
titles: [
{ text: 'Title', width: 'calc(100% - 36em)' },
{ text: 'Author', width: '8em' },
{ text: 'Year', width: '4em' },
{ text: 'Sem.', width: '4em' },
{ text: 'Lecture', width: '10em' },
{ text: 'Files', width: '10em' },
],
onAdd: () => { m.route.set('/newstudydocument'); },
});
}
}
import m from 'mithril';
import ItemView from '../views/itemView';
import { Property } from '../views/elements';
export default class viewDoc extends ItemView {
view() {
const stdMarg = { margin: '5px' };
return this.layout(m('div.maincontainer', [
m('h3', {
style: { 'margin-top': '0px', 'margin-bottom': '0px' },
}, this.data.title),
// below the title, most important details are listed
m('div', { style: { display: 'flex' } }, [
this.data.lecture && m(Property, {
title: 'Lecture',
style: stdMarg,
}, `${this.data.lecture} ${this.data.department.toUpperCase()}`),
this.data.semster && m(Property, {
title: 'Semester',
style: stdMarg,
}, this.data.semester),
this.data.department && !this.data.lecture && m(Property, {
title: 'Department',
style: stdMarg,
}, this.data.department.toUpperCase()),
this.data.professor && m(Property, {
title: 'Professor',
style: stdMarg,
}, this.data.professor),
this.data.author && m(Property, {
title: 'Author',
style: stdMarg,
}, this.data.author),
this.data.uploader && m(Property, {
title: 'Uploader',
style: stdMarg,
}, this.data.uploader),
]),
]));
}
}
......@@ -5,12 +5,13 @@ addTypography();
// https://material.io/tools/color/#!/?view.left=0&view.right=1
// &secondary.color=e8462b&primary.color=274284
// eslint-disable-next-line import/prefer-default-export
export const colors = {
amiv_blue: '#1F2D54',
amiv_red: '#e8462b',
green: '#4ef599',
blue: '#274284',
//light_blue: '#5378E1',
// light_blue: '#5378E1',
light_blue: '#5a6db4',
orange: 'orange',
};
......@@ -32,6 +33,14 @@ ButtonCSS.addStyle('.red-row-button', {
margin_h: 0,
});
ButtonCSS.addStyle('.blue-row-button', {
color_light_text: 'white',
color_light_background: colors.light_blue,
padding_h: 0,
font_size: 12,
margin_h: 0,
});
CardCSS.addStyle('.pe-card', {
border_radius: '4',
});
......@@ -63,6 +72,9 @@ const style = [
p: {
margin: '0',
},
a: {
color: 'rgba(0, 0, 0, 0.87)',
},
},
];
styler.add('containers', style);
import m from 'mithril';
import infinite from 'mithril-infinite';
const pageSize = 5;
const getIndex = pageNum => (pageNum - 1) * pageSize;
function item(data, opts, itemIndex){
return m('div', data.firstname);
}
function pageData(pageNum) {
return new Promise((resolve, reject) => {
m.request({
method: 'GET',
dataType: 'jsonp',
headers: {
'Authorization': 'root'
},
url: `https://amiv-api.ethz.ch/users?max_results=5&page=${pageNum}`,
}).then((response) => {
resolve(response._items);
});
});
}
export default {
view: function() {
return m('div', {
style: {
height: '400px'
}
//className: 'experiment_list',
/*header: {
title: 'Users'
},*/
}, m(infinite, {
item,
pageData,
}),
);
}
}
import m from 'mithril';
import { RadioGroup } from 'polythene-mithril';
import { TextInput } from 'amiv-web-ui-components';
import EditView from '../views/editView';
export default class UserEdit extends EditView {
beforeSubmit() {
if ('rfid' in this.form.data && !this.form.data.rfid) delete this.form.data.rfid;
this.submit(this.form.data).then(() => this.controller.changeModus('view'));
}
view() {
return this.layout([
...this.renderPage({
lastname: { type: 'text', label: 'Last Name' },
firstname: { type: 'text', label: 'First Name' },
email: { type: 'text', label: 'Email' },
nethz: { type: 'text', label: 'NETHZ' },
rfid: { type: 'text', label: 'RFID Code' },
}),
m(RadioGroup, {
name: 'Membership',
buttons: [
{
value: 'none',
label: 'No Member',
defaultChecked: this.data.membership === 'none',
},
{
value: 'regular',
label: 'Regular AMIV Member',
defaultChecked: this.data.membership === 'regular',
},
{
value: 'extraordinary',
label: 'Extraordinary Member',
defaultChecked: this.data.membership === 'extraordinary',
},
{
value: 'honorary',
label: 'Honorary Member',
defaultChecked: this.data.membership === 'honorary',
},
],
onChange: ({ value }) => { this.data.membership = value; },
}),
m(RadioGroup, {
name: 'Sex',
buttons: [
{ value: 'female', label: 'Female', defaultChecked: this.data.gender === 'female' },
{ value: 'male', label: 'Male', defaultChecked: this.data.gender === 'male' },
],
onChange: ({ value }) => { console.log(value); this.data.gender = value; },
}),
m(RadioGroup, {
name: 'Departement',
buttons: [
{ value: 'itet', label: 'ITET', defaultChecked: this.data.department === 'itet' },
{ value: 'mavt', label: 'MAVT', defaultChecked: this.data.department === 'mavt' },
],
onChange: ({ value }) => { this.data.department = value; },
}),
...this.form.renderSchema(['lastname', 'firstname', 'email', 'phone', 'nethz', 'legi']),
m(TextInput, this.form.bind({
type: 'password',
name: 'password',
label: 'New password',
floatingLabel: true,
})),
...this.form.renderSchema(['rfid', 'send_newsletter', 'membership', 'department']),
]);
}
}
import m from 'mithril';
import { DatalistController } from 'amiv-web-ui-components';
import EditUser from './editUser';
import ViewUser from './viewUser';
import TableView from '../views/tableView';
import { users as config } from '../resourceConfig.json';
import DatalistController from '../listcontroller';
import ItemController from '../itemcontroller';
import { loadingScreen } from '../layout';
import { ResourceHandler } from '../auth';
export class UserItem {
constructor() {
......@@ -13,7 +13,9 @@ export class UserItem {
}
view() {
if (!this.controller || !this.controller.data) return m(loadingScreen);
if (!this.controller || (!this.controller.data && this.controller.modus !== 'new')) {
return m(loadingScreen);
}
if (this.controller.modus !== 'view') return m(EditUser, { controller: this.controller });
return m(ViewUser, { controller: this.controller });
}
......@@ -21,13 +23,19 @@ export class UserItem {
export class UserTable {
constructor() {
this.ctrl = new DatalistController('users', { sort: [['lastname', 1]] });
this.handler = new ResourceHandler('users');
this.ctrl = new DatalistController(
(query, search) => this.handler.get({ search, ...query }),
{ sort: [['lastname', 1]] },
);
}
view() {
const tableKeys = ['firstname', 'lastname', 'nethz', 'legi', 'membership'];
return m(TableView, {
controller: this.ctrl,
keys: config.tableKeys,
titles: config.tableKeys.map(key => config.keyDescriptors[key] || key),
keys: tableKeys,
titles: tableKeys.map(key => this.handler.schema.properties[key].title || key),
filters: [[
{ name: 'not members', query: { membership: 'none' } },
{ name: 'regular members', query: { membership: 'regular' } },
......
import m from 'mithril';
import { Card, Toolbar, ToolbarTitle, Button } from 'polythene-mithril';
import { Card, Toolbar, ToolbarTitle, Button, Snackbar } from 'polythene-mithril';
import { ListSelect, DatalistController, Chip } from 'amiv-web-ui-components';
import ItemView from '../views/itemView';
import TableView from '../views/tableView';
import SelectList from '../views/selectList';
import DatalistController from '../listcontroller';
import RelationlistController from '../relationlistcontroller';
import { chip, icons, Property } from '../views/elements';
import { ResourceHandler } from '../auth';
import { icons, Property } from '../views/elements';
import { colors } from '../style';
export default class UserView extends ItemView {
constructor(vnode) {
super(vnode);
// a controller to handle the groupmemberships of this user
this.groupmemberships = new RelationlistController('groupmemberships', 'groups', {
where: { user: this.data._id },
this.groupmemberships = new RelationlistController({
primary: 'groupmemberships', secondary: 'groups', query: { where: { user: this.data._id } },
});
// a controller to handle the eventsignups of this user
this.eventsignups = new RelationlistController('eventsignups', 'events', {
where: { user: this.data._id },
this.eventsignups = new RelationlistController({
primary: 'eventsignups', secondary: 'events', query: { where: { user: this.data._id } },
});
// initially, don't display the choice field for a new group
// (this will be displayed once the user clicks on 'new')
this.groupchoice = false;
// a controller to handle the list of possible groups to join
this.groupcontroller = new DatalistController('groups', {}, ['name']);
this.groupHandler = new ResourceHandler('groups', ['name']);
this.groupController = new DatalistController(
(query, search) => this.groupHandler.get({ search, ...query }),
);
// exclude the groups where the user is already a member
this.groupmemberships.handler.get({ where: { user: this.data._id } })
.then((data) => {
const groupIds = data._items.map(item => item.group);
this.groupcontroller.setQuery({
this.groupController.setQuery({
where: { _id: { $nin: groupIds } },
});
});
this.sessionsHandler = new ResourceHandler('sessions');
}
oninit() {
......@@ -41,35 +45,35 @@ 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,
style: stdMargin,
}, 'No Member');
if (this.data.membership === 'regular') {
membership = m(chip, {
membership = m(Chip, {
svg: icons.checked,
svgBackground: colors.green,
...stdMargin,
style: stdMargin,
}, 'Regular Member');
} else if (this.data.membership === 'extraordinary') {
membership = m(
chip,
{ svg: icons.checked, svgBackground: colors.green, ...stdMargin },
'Extraordinary Member',
);
membership = m(Chip, {
svg: icons.checked,
svgBackground: colors.green,
style: stdMargin,
}, 'Extraordinary Member');
} else if (this.data.membership === 'honorary') {
membership = m(
chip,
{ svg: icons.star, svgBackground: colors.orange, ...stdMargin },
'Honorary Member',
);
membership = m(Chip, {
svg: icons.star,
svgBackground: colors.orange,
style: stdMargin,
}, 'Honorary Member');
}
// Selector that is only displayed if "new" is clicked in the
// groupmemberships. Selects a group to request membership for.
const groupSelect = m(SelectList, {
controller: this.groupcontroller,
const groupSelect = m(ListSelect, {
controller: this.groupController,
listTileAttrs: group => Object.assign({}, { title: group.name }),
selectedText: group => group.name,
onSubmit: (group) => {
......@@ -79,6 +83,7 @@ export default class UserView extends ItemView {
group: group._id,
}).then(() => {
this.groupmemberships.refresh();
m.redraw();
});
},
onCancel: () => { this.groupchoice = false; m.redraw(); },
......@@ -91,16 +96,19 @@ export default class UserView extends ItemView {
m('h1', `${this.data.firstname} ${this.data.lastname}`),
membership,
this.data.department && m(
chip,
{ svg: icons.department, ...stdMargin },
Chip,
{ svg: icons.department, style: stdMargin },
this.data.department,
),
this.data.gender && m(chip, { margin: '5px' }, this.data.gender),
m(Chip, {
svg: this.data.send_newsletter ? icons.checked : icons.clear,
style: stdMargin,
}, 'newsletter'),
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),
this.data.legi && m(Property, { title: 'Legi', style: stdMargin }, this.data.legi),
this.data.rfid && m(Property, { title: 'RFID', style: stdMargin }, this.data.rfid),
m(Property, { title: 'Legi', style: stdMargin }, this.data.legi ? this.data.legi : '-'),
m(Property, { title: 'RFID', style: stdMargin }, this.data.rfid ? this.data.rfid : '-'),
this.data.phone && m(Property, { title: 'Phone', style: stdMargin }, this.data.phone),
]),
]),
......@@ -115,7 +123,7 @@ export default class UserView extends ItemView {
tableHeight: '175px',
controller: this.eventsignups,
tileContent: item => m('div', item.event.title_en || item.event.title_de),
titles: ['event'],
titles: ['Event'],
clickOnRows: (data) => { m.route.set(`/events/${data.event._id}`); },
filters: [[{
name: 'upcoming',
......@@ -145,12 +153,33 @@ export default class UserView extends ItemView {
tableHeight: '225px',
controller: this.groupmemberships,
keys: ['group.name', 'expiry'],
titles: ['groupname', 'expiry'],
titles: ['Group Name', 'Expires'],
clickOnRows: (data) => { m.route.set(`/groups/${data.group._id}`); },
}),
]),
})),
]),
], [
m(Button, {
label: 'log out all Sessions',
className: 'itemView-delete-button',
border: true,
events: {
onclick: () => {
this.sessionsHandler.get({
where: { user: this.data._id },
}).then((response) => {
if (response._items.length === 0) {
Snackbar.show({ title: 'No active sessions for this user.' });
} else {
response._items.forEach((session) => {
this.sessionsHandler.delete(session);
});
}
});
},
},
}),
]);
}
}
......@@ -18,10 +18,11 @@ export function debounce(func, wait, immediate) {
};
}
export function dateFormatter(datestring) {
export function dateFormatter(datestring, time = true) {
// converts an API datestring into the standard format 01.01.1990, 10:21
if (!datestring) return '';
const date = new Date(datestring);
if (!time) return date.toLocaleDateString('de-DE');
return date.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
......
This diff is collapsed.
This diff is collapsed.
import m from 'mithril';
import { Toolbar, Dialog, Button } from 'polythene-mithril';
import { IconButton, Toolbar, Dialog, Button } from 'polythene-mithril';
import { ButtonCSS } from 'polythene-css';
import { colors } from '../style';
import { loadingScreen } from '../layout';
import { icons } from './elements';
ButtonCSS.addStyle('.itemView-edit-button', {
color_light_background: colors.light_blue,
......@@ -50,23 +51,36 @@ export default class ItemView {
});
}
layout(children) {
layout(children, buttons = []) {
if (!this.controller || !this.controller.data) return m(loadingScreen);
// update the data reference
this.data = this.controller.data;
return m('div', [
m(Toolbar, m('div.pe-button-row', [
m(Button, {
element: 'div',
className: 'itemView-edit-button',
label: `Edit ${this.resource.charAt(0).toUpperCase()}${this.resource.slice(1, -1)}`,
events: { onclick: () => { this.controller.changeModus('edit'); } },
m(Toolbar, [
this.data._links.self.methods.indexOf('PATCH') > -1 && m('div', {
style: { width: 'calc(100% - 48px)' },
}, m('div.pe-button-row', [
m(Button, {
element: 'div',
className: 'itemView-edit-button',
label: `Edit ${this.resource.charAt(0).toUpperCase()}${this.resource.slice(1, -1)}`,
events: { onclick: () => { this.controller.changeModus('edit'); } },
}),
m(Button, {
label: `Delete ${this.resource.charAt(0).toUpperCase()}${this.resource.slice(1, -1)}`,
className: 'itemView-delete-button',
border: true,
events: { onclick: () => this.delete() },
}),
...buttons,
])),
m(IconButton, {
style: { 'margin-left': 'auto', 'margin-right': '0px' },
icon: { svg: { content: m.trust(icons.clear) } },
events: { onclick: () => { this.controller.cancel(); } },
}),
m(Button, {
label: `Delete ${this.resource.charAt(0).toUpperCase()}${this.resource.slice(1, -1)}`,
className: 'itemView-delete-button',
border: true,
events: { onclick: () => this.delete() },
}),
])),
]),
m('div', {
style: { height: 'calc(100vh - 130px)', 'overflow-y': 'scroll' },
}, children),
......