Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Add validation to detect events which overlap in time
Browse files Browse the repository at this point in the history
  • Loading branch information
flenny committed Apr 11, 2023
1 parent d1f6fa0 commit 6b32c3a
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
.clasp.json
.clasprc.json
node_modules
2 changes: 1 addition & 1 deletion src/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const EVENTS_SHEET_ID_COLUMN = 'A:A'
const EVENTS_SHEET_NAME_COLUMN = 'B:B'
const EVENTS_SHEET_COSTS_COLUMN = 'E:E'
const EVENTS_SHEET_ADDITIONAL_COSTS_COLUMN = 'F:F'
const EVENTS_SHEET_PARTICIPANT_LIMIT_COLUMN = 'M:M'
const EVENTS_SHEET_PARTICIPANT_LIMIT_COLUMN = 'K:K'
const BOOKINGS_SHEET_EVENT_ID_COLUMN = 'L'

/**
Expand Down
6 changes: 3 additions & 3 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ <h2 class="mb-3 display-6">🏸 Kursangebot
</h2>

<div class="col-sm-9">
<input class="form-control form-control-lg no-validate" id="search" type="search"
<input class="form-control form-control no-validate" id="search" type="search"
placeholder="Filtern nach Kursbeschreibung oder Wochentag..." />
</div>
<div class="btn-group col-sm-3">
<button id="search-reset-btn" class="btn btn-lg btn-block btn-custom" type="button" disabled>
<i class="bi bi-trash"></i>&ensp;Filter zurücksetzen
<button id="search-reset-btn" class="btn btn btn-block btn-custom" type="button" disabled>
<i class="bi-trash"></i>&ensp;Filter zurücksetzen
</button>
</div>

Expand Down
59 changes: 45 additions & 14 deletions src/javascript.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script>
const POLL_INTERVAL = 10000
const EVENTS_CONTAINER_ELEMENT_ID = 'events'
let eventData

const refreshEvents = () => {
google.script.run.withSuccessHandler(events => {
updateEvents(events)
eventData = Object.values(events)
updateEvents(eventData)
setTimeout(refreshEvents, POLL_INTERVAL)
}).getEvents()
}
Expand All @@ -13,12 +15,13 @@
google.script.run.withSuccessHandler(events => {
const container = document.getElementById(EVENTS_CONTAINER_ELEMENT_ID)
while (container.hasChildNodes()) container.removeChild(container.lastChild)
Object.values(events).forEach(event => addEventToForm(event))
eventData = Object.values(events)
eventData.forEach(event => addEventToForm(event))
}).getEvents()
}

const updateEvents = events => {
Object.values(events).forEach(event => {
events.forEach(event => {
const [id] = event
const container = document.getElementById(`event-${id}-status`)
if (container) container.innerHTML = getEventStatus(event)
Expand All @@ -27,25 +30,25 @@
}

const getEventStatus = event => {
const [id, , , , , , , , , , , , participantLimit, bookings, availableSeats] = event
const [id, , , , , , , , , , participantLimit, bookings, availableSeats] = event
let [badgeState, badgeText] = ['success', 'freie Plätze']
if (availableSeats <= 0) [badgeState, badgeText] = ['danger', 'Warteliste']
else if (availableSeats <= 5) [badgeState, badgeText] = ['warning', 'wenige Plätze']
return `
<br class="d-none d-md-block">
<p>
<small class="d-none d-lg-block">
<i class="bi bi-people-fill"></i>&ensp;Anzahl Plätze: ${participantLimit}<br>
<i class="bi bi-pencil-square"></i>&ensp;Anmeldungen: ${bookings}<br>
<i class="bi bi-check2-square"></i>&ensp;Freie Plätze: ${availableSeats}
<i class="bi-people-fill"></i>&ensp;Anzahl Plätze: ${participantLimit}<br>
<i class="bi-pencil-square"></i>&ensp;Anmeldungen: ${bookings}<br>
<i class="bi-check2-square"></i>&ensp;Freie Plätze: ${availableSeats}
</small>
<span class="badge text-bg-${badgeState} bg-${badgeState}">${badgeText}</span>
</p>
`
}

const addEventToForm = event => {
const [id, name, description, , costs, additionalCosts, day, date, startTime, endTime, gradeFrom, gradeTo, participantLimit, , availableSeats] = event
const [id, name, description, , costs, additionalCosts, start, end, gradeFrom, gradeTo, participantLimit, , availableSeats] = event
const events = document.getElementById(EVENTS_CONTAINER_ELEMENT_ID)

// Create a string to display the costs of the event
Expand All @@ -54,6 +57,11 @@
else if (additionalCosts === 0) costsDisplaySring = `${costs} CHF`
else costsDisplaySring = `${costs} CHF (+ ${additionalCosts} CHF)`

const weekday = new Date(start).toLocaleDateString('de-CH', { weekday: 'long' })
const date = new Date(start).toLocaleDateString('de-CH', { day: 'numeric', month: 'long' })
const from = new Date(start).toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' })
const to = new Date(end).toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' })

// Create meta data which is used to filter the events by grade
const gradeMetaData = []
for (let i = gradeFrom; i <= gradeTo; i++) gradeMetaData.push(`grade-${i}`)
Expand All @@ -72,10 +80,10 @@
<br class="d-none d-md-block">
<div id="event-${id}-details">
<p style="white-space: nowrap;" data-search-meta="${gradeMetaData.join(' ')}"><small>
<i class="bi bi-calendar"></i>&ensp;${day}, ${date}<br>
<i class="bi bi-clock"></i>&ensp;${startTime} - ${endTime} Uhr<br>
<i class="bi bi-tencent-qq"></i>&ensp;${gradeFrom}.-${gradeTo}. Klasse<br>
<i class="bi bi-piggy-bank"></i>&ensp;${costsDisplaySring}</small>
<i class="bi-calendar"></i>&ensp;${weekday}, ${date}<br>
<i class="bi-clock"></i>&ensp;${from} - ${to} Uhr<br>
<i class="bi-tencent-qq"></i>&ensp;${gradeFrom}.-${gradeTo}. Klasse<br>
<i class="bi-piggy-bank"></i>&ensp;${costsDisplaySring}</small>
</p>
</div>
</td>
Expand Down Expand Up @@ -141,8 +149,10 @@ <h4 class="alert-heading">${head}</h4>
return
}

// Check if selected events match the selected grade
let valid = true
const selectedEvents = [...new FormData(form).getAll('eventId')]

// Validate if selected events match the selected grade
const selectedGrade = document.getElementById('grade').value
const nonMatchingEvents = []
selectedEvents.forEach(id => {
Expand All @@ -151,9 +161,30 @@ <h4 class="alert-heading">${head}</h4>
})
if (nonMatchingEvents.length > 0) {
alert('warning', 'Ungültige Kurse', `Bitte wähle nur Kurse aus, die für die ${selectedGrade}. Klasse bestimmt sind. Du hast folgende Kurse ausgewählt, die nicht für die ${selectedGrade}. Klasse sind: ${nonMatchingEvents.join(', ')}`)
return
valid = false
}

// Validate if selected events do not overlap in time
const overlappingEvents = []
selectedEvents.forEach(eventId1 => {
const [, , , , , , start, end] = eventData.find(e => e[0] == eventId1)
const event1Start = new Date(start)
const event1End = new Date(end)
selectedEvents.forEach(eventId2 => {
if (eventId1 === eventId2) return
const [, , , , , , start, end] = eventData.find(e => e[0] == eventId2)
const event2Start = new Date(start)
const event2End = new Date(end)
if (event1Start < event2End && event1End > event2Start) overlappingEvents.push(eventId1)
})
})
if (overlappingEvents.length > 0) {
alert('warning', 'Überlappende Kurse', `Bitte wähle nur Kurse aus, die nicht zeitgleich stattfinden. Du hast folgende Kurse ausgewählt, die zeitgleich stattfinden: ${overlappingEvents.join(', ')}`)
valid = false
}

if (!valid) return

const modalDialogSend = new bootstrap.Modal('#modal-send')
const modalDialogSuccess = new bootstrap.Modal('#modal-success')

Expand Down
9 changes: 6 additions & 3 deletions src/success.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
<div class="text-center">
<h1 class="display-3">Deine Anmeldung war erfolgreich 🎉</h1>
<p class="lead">Vielen herzlichen Dank für deine Anmeldung beim Ferienpass Seeberg. Wir freuen uns sehr, dass du
beim Ferienpass dabei bist. 🥳 Wir haben dir per E-Mail soeben einen Link geschickt. Dort kannst du jederzeit
den Status deiner gebuchten Kurse überprüfen.</p>
dabei bist 🥳.</p>
<p class="lead">Wir haben dir per E-Mail soeben einen Link geschickt. Dort kannst du jederzeit den Status
deiner gebuchten Kurse überprüfen. Falls du keine E-Mail bekommen hast, schau doch zur Sicherheit mal noch
kurz im Spam-Ordner nach.</p>
<p class="lead"><a href="<?!= getPreFilledFormUrl(data.origin, data.bookingId) ?>" target="_top">Hier</a> kannst
du weitere Kurse buchen.</p>
du Kurse für weitere Familienmitglieder buchen. Die Kontaktangabgen werden übernommen, damit du nicht nochmal
alles neu eingeben musst 🤞🏻.</p>
<p class="lead">Tschüss und bis bald 👋</p>
<p class="lead">Dein Ferienpass Seeberg Team</p>
</div>
Expand Down

0 comments on commit 6b32c3a

Please sign in to comment.