diff --git a/src/controllers/event.js b/src/controllers/event.js index 4c61301..5011b05 100644 --- a/src/controllers/event.js +++ b/src/controllers/event.js @@ -21,6 +21,10 @@ exports.helpers = { exports.partials = path.join(__dirname, "../views/event/partials") +exports.confirmModalAvailabilityReminders = async (req, res) => { + res.status(200).render("event/partials/modal_availability_reminders") +} + exports.getEvents = async (req, res, next) => { const {user, team, layout} = req const bulkLoadTypes = ["event", "availabilitySummary"] @@ -70,19 +74,49 @@ exports.getEvent = async (req, res, next) => { exports.sendAvailabilityReminders = async (req,res,next) => { await Promise.all(req.promises) + if (!req.body || ! (req.body.event_id && req.body.memberIds)) { + res.status(400).send('Malformed post') + } if (req.params.event_id != req.body.eventId) { // Load actual event. Do I want this to be an error? probably - res.status(500).send() + res.status(400).send('Event ID parameter does not match the POST body') + return; } const {event} = req const {eventId, memberIds} = req.body const sendingMember = req.members.find(m=>m.userId==req.user.id) try { - await teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds) + // await teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds) res.status(200).send('OK') } catch (err) { res.status(500).send() } return +} + +exports.submitResetAvailabilities = async (req,res,next) => { + await Promise.all(req.promises) + if (!req.body || ! (req.body.event_id && req.body.memberIds)) { + res.status(400).send('Malformed post') + } + + if (req.params.event_id != req.body.event_id) { + // Load actual event. Do I want this to be an error? probably + res.status(400).send('Event ID parameter does not match the POST body'); + return + } + + const {event_id, memberIds} = req.body + const reset_promises = [] + + const availabilities = await teamsnap.loadAvailabilities({eventId: event_id}, teamsnapCallback); + + availabilities.filter(availability =>memberIds.includes(availability.memberId.toString())).forEach( availability => { + availability.statusCode = teamsnap.AVAILABILITIES.NONE + const promise = teamsnap.saveAvailability(availability, teamsnapCallback) + reset_promises.push(promise) + }) + await Promise.all(reset_promises) + .then(res.status(200).send('OK')) } \ No newline at end of file diff --git a/src/controllers/eventlineup.js b/src/controllers/eventlineup.js index 08aca98..4314330 100644 --- a/src/controllers/eventlineup.js +++ b/src/controllers/eventlineup.js @@ -88,6 +88,10 @@ exports.getEventLineupEmail = async (req, res)=>{ res.status(200).render("eventlineup/partials/email_modal.hbs", {layout:null, user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary}) } +exports.getAvailabilityRemindersModal = (req, res) => { + res.status(200).render("eventlineup/partials/availability_reminder_modal.hbs") +} + exports.getEventLineupEntries = async (req, res)=>{ const {event_lineup, event_lineup_entries} = req res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries)) @@ -160,4 +164,33 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup) } }) return {newEventLineupEntries, eventLineupEntries, deleteEventLineupEntries} +} + +exports.submitDeleteEventLineupEntries = async (req,res) => { + await Promise.all(req.promises); + const {event_lineup, event_lineup_entries} = req + let event_id + let memberIds + + if (!req.body || ! (req.body.event_id && req.body.memberIds)) { + res.status(400).send('Malformed post') + } else if (req.params.event_id != req.body.event_id) { + // Load actual event. Do I want this to be an error? probably + res.status(400).send('Event ID parameter does not match the POST body'); + return + } else { + event_id = req.body.event_id + memberIds = req.body.memberIds + } + + const deletion_promises = [] + + event_lineup_entries.filter(entry =>memberIds.includes(entry.memberId.toString())).forEach( entry => { + const promise = teamsnap.deleteEventLineupEntry(entry, teamsnapCallback) + deletion_promises.push(promise) + }) + + await Promise.all(deletion_promises) + .then(res.status(202).send('OK')) + } \ No newline at end of file diff --git a/src/public/js/eventlineup.js b/src/public/js/eventlineup.js index a8670c9..0226599 100644 --- a/src/public/js/eventlineup.js +++ b/src/public/js/eventlineup.js @@ -110,6 +110,101 @@ function refreshFlags(){ } +function openAvailabilityReminderModal (el, team_id, event_id) { + const url = `/${team_id}/event/${event_id}/modal-confirm-availability-reminders/` + const form = el.closest('form') + const form_data = new FormData (form) + fetch(url) + .then((response) => { + if (response.ok) { + return response.text(); + } else { + return Promise.reject(response.text()); + } + }) + .then((html) => { + const parser = new DOMParser() + const modal = parser.parseFromString(html, 'text/html') + const modal_node = modal.firstElementChild.querySelector('#modal') + modal_node.classList.add('is-open') + const modal_node_accept = modal.querySelector('Button[data-confirm=yes]') + const checked = Array.from(el.querySelectorAll('input:checked')).map + const body = document.querySelector('body') + body.appendChild(modal_node) + modal_node_accept.addEventListener( + "click", ()=>{ + // const memberIds = form_data.getAll('memberId') + const csrf_token = form_data.get('csrfToken') + const selected_status_codes = Array.from(document.querySelectorAll('input:checked')).map(e=>e.value) + const slots = Array.from(document.querySelectorAll('.lineup-slot')).filter( + slot =>{ + const slot_status_code = slot.querySelector('input[name=availabilityStatusCode]').value + return selected_status_codes.includes(slot_status_code) + } + ) + const memberIds = slots.map( + slot => slot.querySelector('input[name=memberId]').value + ) + + console.log("sending reminders", el, event_id, memberIds, csrf_token) + sendAvailabilityReminder(el, event_id, memberIds, csrf_token) + body.removeChild(modal_node) + } + ) + }) + +} + +function confirmModal(prompt, fn, options) { + const url = "/modal-confirm" + const params = new URLSearchParams(prompt) + url.search = params.toString() + + fetch(url+"?"+params.toString(), {method:"GET"}) + .then((response) => { + if (response.ok) { + return response.text(); + } else { + return Promise.reject(response.text()); + } + }) + .then((html) => { + const parser = new DOMParser() + const modal = parser.parseFromString(html, 'text/html') + const modal_node = modal.firstElementChild.querySelector('#modal') + modal_node.classList.add('is-open') + const modal_node_accept = modal.querySelector('Button[data-confirm=yes]') + const body = document.querySelector('body') + body.appendChild(modal_node) + modal_node_accept.addEventListener("click", ()=>{fn(options);body.removeChild(modal_node)}) + }) +} + +function submitClearLineup(options){ + console.log('clearing lineup...') + const {team_id, event_id, event_lineup_id} = options + const url = `/${team_id}/event/${event_id}/lineup/${event_lineup_id}/delete` + const form = document.querySelector(`#event-lineup-${event_id} form`); + const data = new FormData(form); + const memberIds = data.getAll('memberId') + + console.log(url) + fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}}) + .finally(()=>{location.reload()});//refresh page +} + +function submitResetAvailabilities(options){ + const {team_id, event_id} = options + const url = `/${team_id}/event/${event_id}/reset_availabilities` + const form = document.querySelector(`#event-lineup-${event_id} form`); + const data = new FormData(form); + const memberIds = data.getAll('memberId') + + console.log('submitting...', url) + fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}}) + .finally(()=>{location.reload()});//refresh page +} + function emailModal(el, url) { form = el.closest("form"); console.log(form) diff --git a/src/routes/event.js b/src/routes/event.js index 7d1c787..0ecdbe3 100644 --- a/src/routes/event.js +++ b/src/routes/event.js @@ -72,4 +72,7 @@ router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents); router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders) +router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/modal-confirm-availability-reminders/", eventsController.confirmModalAvailabilityReminders) +router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/reset_availabilities",upload.none(), eventsController.submitResetAvailabilities) + module.exports = {router, loadEvent, loadEvents} \ No newline at end of file diff --git a/src/routes/eventlineup.js b/src/routes/eventlineup.js index 7274f0e..df9345b 100644 --- a/src/routes/eventlineup.js +++ b/src/routes/eventlineup.js @@ -57,6 +57,7 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/adjacent", doubleCs router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/email", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineupEmail ) router.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup); +router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries); module.exports = {router, loadEventLineup} \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js index b006e7a..80fb442 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -27,4 +27,10 @@ router.get("/", (req,res,next) => { router.get("/:team_id([0-9]+)/members", membersController.getMembers); +router.get("/modal-confirm/", (req,res) => { + const {title, body} = req.query + res.render('modal_confirm', {title, body} ) +} +) + module.exports = {router, partials}; diff --git a/src/views/event/partials/modal_availability_reminders.hbs b/src/views/event/partials/modal_availability_reminders.hbs new file mode 100644 index 0000000..d7ab9ce --- /dev/null +++ b/src/views/event/partials/modal_availability_reminders.hbs @@ -0,0 +1,38 @@ +