Commit 5a05be2b authored by adietmue's avatar adietmue
Browse files

Frontend: Selection of single courses

parent 330d3af0
const m = require('mithril'); const m = require('mithril');
const { courses, userCourses } = require('./backend.js'); const { courses, userCourses } = require('./backend.js');
function isSelected(course) { function isSelectedOrReserved(course) {
return userCourses.selected.some(sel => sel === course._id); return userCourses.selected.some(sel => sel.course === course._id) ||
userCourses.signups.some(signup => signup.course === course._id);
} }
function isBusy() { return userCourses.resources.selections.isBusy(); }
module.exports = { module.exports = {
oninit() { courses.get(); }, oninit() { courses.get().then((d) => { console.log(d); }); },
view() { view() {
return m('table', [ return m('table', [
...@@ -17,6 +16,9 @@ module.exports = { ...@@ -17,6 +16,9 @@ module.exports = {
m('th', 'Course'), m('th', 'Course'),
m('th', 'Department'), m('th', 'Department'),
m('th', 'Name'), m('th', 'Name'),
m('th', 'Spots'),
m('th', 'Signup Start'),
m('th', 'Signup End'),
m('th', 'Starting time'), m('th', 'Starting time'),
m('th', 'Ending time'), m('th', 'Ending time'),
]), ]),
...@@ -26,6 +28,9 @@ module.exports = { ...@@ -26,6 +28,9 @@ module.exports = {
m('td', course.lecture.title), m('td', course.lecture.title),
m('td', course.lecture.department), m('td', course.lecture.department),
m('td', course.assistant), m('td', course.assistant),
m('td', course.spots),
m('td', course.signup.start),
m('td', course.signup.end),
course.datetimes.map(timeslot => [ course.datetimes.map(timeslot => [
m('td', timeslot.start), m('td', timeslot.start),
m('td', timeslot.end), m('td', timeslot.end),
...@@ -35,8 +40,8 @@ module.exports = { ...@@ -35,8 +40,8 @@ module.exports = {
m( m(
'button', 'button',
{ {
onclick() { userCourses.selectCourse(course._id); }, onclick() { userCourses.select(course._id); },
disabled: isSelected(course) || isBusy(), disabled: isSelectedOrReserved(course),
// ? // ?
// true : false, // true : false,
// return false; // return false;
......
// User Sidebar // User Sidebar
const m = require('mithril'); const m = require('mithril');
const { userCourses } = require('./backend.js'); const { userCourses, courses } = require('./backend.js');
function courseView(course) { const courseView = {
return m('li', course); view({ attrs: { _id, courseId, remove } }) {
} // Get Lecture of Course
const course = courses.list.find(item => item._id === courseId);
// Otherwise display loading
return m('li', [
m('span', course ? course.lecture.title : 'Loading...'),
// If there is no id, the element is not yet created,
// so a delete button does not make any sense
m('button', { onclick: remove, disabled: !_id }, 'X'),
]);
},
};
function asList(courses) { return courses.map(courseView); }
module.exports = { module.exports = {
oninit() { userCourses.get(); }, oninit() { userCourses.get(); },
...@@ -15,31 +24,33 @@ module.exports = { ...@@ -15,31 +24,33 @@ module.exports = {
return [ return [
m('h1', 'Selected Courses'), m('h1', 'Selected Courses'),
userCourses.selected.length ? [ userCourses.selected.length ? [
userCourses.selected.map(selId => userCourses.selected.map(({ _id, course: courseId }) =>
m('li', [ m(
m('span', selId), courseView,
m( { _id, courseId, remove() { userCourses.deselect(_id); } },
'button', )),
{
onclick() { userCourses.deselectCourse(selId); },
disabled: userCourses.resources.selections.isBusy(),
},
'X',
),
])),
m('button', { onclick() { userCourses.reserve(); } }, 'reserve'), m('button', { onclick() { userCourses.reserve(); } }, 'reserve'),
] : m('p', 'No courses selected.'), ] : m('p', 'No courses selected.'),
m('h1', 'Reserved Courses'), m('h1', 'Reserved Courses'),
userCourses.reserved.length ? [ userCourses.reserved.length ? [
asList(userCourses.reserved), userCourses.reserved.map(({ _id, course: courseId }) =>
m('button', { onclick() { userCourses.pay(); } }, 'Pay'), m(
courseView,
{ _id, courseId, remove() { userCourses.free(_id); } },
)),
// m('button', { onclick() { userCourses.pay(); } }, 'Pay'),
] : m('p', 'No courses reserved.'), ] : m('p', 'No courses reserved.'),
userCourses.waiting.length ? [
m('h4', 'Waiting'),
userCourses.waiting.map(({ _id, course: courseId }) =>
m(
courseView,
{ _id, courseId, remove() { userCourses.free(_id); } },
)),
] : [],
m('h1', 'Accepted Courses'), m('h1', 'Accepted Courses'),
userCourses.accepted.length ? [ m('p', 'No courses accepted.'), // TODO: Implement
asList(userCourses.accepted),
] : m('p', 'No courses accepted.'),
]; ];
}, },
}; };
...@@ -194,59 +194,66 @@ const userCourses = { ...@@ -194,59 +194,66 @@ const userCourses = {
}, },
get selected() { get selected() {
// We are only interested in the first (only) item return this.resources.selections.list;
const sel = this.resources.selections.list[0];
return sel ? sel.courses : [];
}, },
get signups() { return this.resources.signups.list; },
get reserved() { get reserved() {
return this.resources.signups.list.filter(({ status }) => return this.resources.signups.list.filter(({ status }) =>
status === 'reserved'); status === 'reserved');
}, },
get waiting() {
return this.resources.signups.list.filter(({ status }) =>
status !== 'reserved' && status !== 'accepted');
},
get accepted() { get accepted() {
return this.resources.signups.list.filter(({ status }) => return this.resources.signups.list.filter(({ status }) =>
status === 'accepted'); status === 'accepted');
}, },
selectCourse(courseId) { // Select a course
// If user already has a selection and update it select(courseId) {
const currentSelection = this.resources.selections.list[0] || [];
if (this.selected.length > 0) {
const selectionId = currentSelection._id;
const newSelection = [
...currentSelection.courses,
courseId,
];
return this.resources.selections.patchItem(
selectionId,
{ courses: newSelection },
);
// TODO: Provide error feedback to user in .catch
}
// Otherwise create a new selection
return this.resources.selections.post({ return this.resources.selections.post({
nethz: session.user.nethz, nethz: session.user.nethz,
courses: [courseId], course: courseId,
}); });
// TODO: Provide error feedback to user in .catch
}, },
deselectCourse(courseId) { // Remove a selection
const selection = this.resources.selections.list[0] || []; deselect(selectionId) {
const newSelection = selection.courses.filter(item => item !== courseId); return this.resources.selections.deleteItem(selectionId);
const selectionId = selection._id;
if (newSelection.length === 0) {
this.resources.selections.deleteItem(selectionId);
} else {
this.resources.selections.patchItem(
selectionId,
{ courses: newSelection },
);
}
// TODO: Provide error feedback to user in .catch // TODO: Provide error feedback to user in .catch
}, },
reserve() {}, // Reserve all selected course
reserve() {
// Apply only to selections that actually have been created
const selections = Object.values(this.resources.selections._items);
selections.forEach(({ _id, course }) => {
// Delete selection
this.deselect(_id);
// Create signup
return this.resources.signups.post({
nethz: session.user.nethz,
course,
}).catch((err) => {
// Restore selection
this.select(course);
throw err;
// Todo: Provide feedback to user
});
});
},
// Give up a reserved signup
free(signupId) {
return this.resources.signups.deleteItem(signupId);
// TODO: Provide error feedback to user
},
// TODO: Implement
pay() {}, pay() {},
}; };
......
Supports Markdown
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