Merge branch 'eventlineup-availability-reminders'
# Conflicts: # src/public/js/eventlineup.js
This commit is contained in:
@@ -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'))
|
||||
}
|
||||
@@ -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'))
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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};
|
||||
|
||||
38
src/views/event/partials/modal_availability_reminders.hbs
Normal file
38
src/views/event/partials/modal_availability_reminders.hbs
Normal file
@@ -0,0 +1,38 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">Send Reminders</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
<div class="u-padSidesMd">
|
||||
<strong>Send to players who have selected:</strong>
|
||||
<div class="u-spaceTopSm"><div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="undecidedCheckBox" id="undecidedCheckBox" checked="" value>
|
||||
<label class="Checkbox-label" for="undecidedCheckBox">Undecided</label>
|
||||
</div>
|
||||
<div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="maybeCheckbox" id="maybeCheckbox" value="2">
|
||||
<label class="Checkbox-label" for="maybeCheckbox">Maybe</label>
|
||||
</div>
|
||||
<div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="attendingCheckbox" id="attendingCheckbox" value="1">
|
||||
<label class="Checkbox-label" for="attendingCheckbox">Attending</label>
|
||||
</div>
|
||||
<div class="Checkbox u-padBottomNone">
|
||||
<input class="Checkbox-input" type="checkbox" name="notAttendingCheckbox" id="notAttendingCheckbox" value="0">
|
||||
<label class="Checkbox-label" for="notAttendingCheckbox">Not Attending</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +39,21 @@
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}}
|
||||
<span>Insert previous lineup</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="openAvailabilityReminderModal(this, {{team.id}}, {{event.id}})">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
|
||||
<span>Availability Reminders</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal({title:'Reset Availabilities',body:'Are sure you want to reset availabilities?'}, submitResetAvailabilities, {team_id:{{team.id}}, event_id:{{event.id}} })";>
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/refresh.svg"}}}
|
||||
<span>Reset All Availabilities</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal({title:'Clear Lineup',body:'Are sure you want to clear lineup?'}, submitClearLineup, {team_id:{{team.id}}, event_id:{{event.id}}, event_lineup_id:{{event_lineup.id}} })">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/trash.svg"}}}
|
||||
<span>Clear Lineup</span>
|
||||
</a>
|
||||
<div class="u-hidden">
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div id="availability-reminder-modal-{{event.id}}" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">Send Reminders</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
19
src/views/modal_confirm.hbs
Normal file
19
src/views/modal_confirm.hbs
Normal file
@@ -0,0 +1,19 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">{{title}}</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
{{body}}
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user