rowt/frontend/main.ts
philipp 09d4c5d958
All checks were successful
CI/CD Pipeline / test (push) Successful in 9m40s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped
Keep selected rowers during boat change, Fixes #48
2024-08-15 15:42:06 +02:00

798 lines
23 KiB
TypeScript

import Choices from "choices.js";
import { Sidebar } from "./js/sidebar";
import "./scss/app.scss";
export interface choiceMap {
[details: string]: Choices;
}
declare var loggedin_user_id: string;
let choiceObjects: choiceMap = {};
let boat_in_ottensheim = true;
let boat_reserved_today = true;
document.addEventListener("DOMContentLoaded", function () {
changeTheme();
initcolorTheme();
initSearch();
initSidebar();
initToggle();
replaceStrings();
initChoices();
initBoatActions();
selectBoatChange();
addRelationMagic(<HTMLElement>document.querySelector("body"));
reloadPage();
setCurrentdate(<HTMLInputElement>document.querySelector("#departure"));
});
function changeTheme() {
let toggleBtn = <HTMLElement>document.querySelector("#theme-toggle-js");
if (toggleBtn) {
toggleBtn.addEventListener("click", function () {
if (toggleBtn.dataset.theme === "light") {
setTheme("dark", true);
} else {
setTheme("light", true);
}
});
}
}
/***
* init javascript
* 1) detect native color scheme or use set theme in local storage
* 2) detect view (desktop or responsive) if on mobile device with touch screen
* 3) set base font size to 112.5% -> 18px
*/
function initcolorTheme() {
colorThemeWatcher();
let theme = localStorage.getItem("theme");
if (theme == null || theme === "auto") {
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
setTheme("dark", false);
} else {
setTheme("light", false);
}
} else {
setTheme(theme);
}
}
/***
* Listener operating system native color configuration
*/
function colorThemeWatcher() {
try {
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (e) => {
setTheme(e.matches ? "dark" : "light");
});
} catch {
console.warn("color theme watcher not supported");
}
}
/**
* Define color scheme, colors without losing the base font size configuration
* and add data-theme to html tag
* @param theme
*/
function setTheme(theme: string, setLocalStorage = true) {
let toggleBtn = document.querySelector("#theme-toggle-js");
if (setLocalStorage) {
localStorage.setItem("theme", theme);
}
if (toggleBtn) toggleBtn.setAttribute("data-theme", theme);
if (
document.documentElement.classList.contains("dark") &&
theme === "light"
) {
document.documentElement.classList.remove("dark");
} else if (theme === "dark") {
document.documentElement.classList.add("dark");
}
}
function setCurrentdate(input: HTMLInputElement) {
if (input) {
const now = new Date();
const formattedDateTime = `${now.getFullYear()}-${String(
now.getMonth() + 1
).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}T${String(
now.getHours()
).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
input.value = formattedDateTime;
}
}
interface ChoiceBoatEvent extends Event {
detail: {
value: string;
label: string;
customProperties: {
amount_seats: number;
owner: number;
default_destination: string;
boat_in_ottensheim: boolean;
boat_reserved_today: boolean;
default_handoperated: boolean;
convert_handoperated_possible: boolean;
};
};
}
function selectBoatChange() {
const boatSelect = <HTMLSelectElement>document.querySelector("#boat_id");
if (boatSelect) {
const boatChoice = new Choices(boatSelect, {
loadingText: "Wird geladen...",
noResultsText: "Keine Ergebnisse gefunden",
noChoicesText: "Keine Ergebnisse gefunden",
itemSelectText: "Zum Auswählen klicken",
} as any);
boatSelect.addEventListener(
"addItem",
function (e) {
const event = e as ChoiceBoatEvent;
boat_reserved_today = event.detail.customProperties.boat_reserved_today;
if (boat_reserved_today) {
alert(
event.detail.label.trim() +
" wurde heute reserviert. Bitte kontrolliere, dass du die Reservierung nicht störst."
);
}
boat_in_ottensheim = event.detail.customProperties.boat_in_ottensheim;
const amount_seats = event.detail.customProperties.amount_seats;
setMaxAmountRowers("newrower", amount_seats);
let only_steering = <HTMLSelectElement>(
document.querySelector("#shipmaster_only_steering")
);
if (event.detail.customProperties.default_handoperated) {
only_steering.setAttribute("checked", "true");
} else {
only_steering.removeAttribute("checked");
}
if (event.detail.customProperties.convert_handoperated_possible) {
only_steering.removeAttribute("readonly");
} else {
only_steering.setAttribute("readonly", "readonly");
}
const destination = <HTMLSelectElement>(
document.querySelector("#destination")
);
destination.value = event.detail.customProperties.default_destination;
if (event.detail.customProperties.owner) {
choiceObjects["newrower"].setChoiceByValue(
event.detail.customProperties.owner.toString()
);
if (event.detail.value === "36") {
/** custom code for Etsch */
choiceObjects["newrower"].setChoiceByValue("81");
}
} else if (typeof loggedin_user_id !== "undefined") {
const currentSelection = choiceObjects["newrower"].getValue();
let selectedItemsCount: number;
if (Array.isArray(currentSelection)) {
selectedItemsCount = currentSelection.length;
} else {
selectedItemsCount = currentSelection !== undefined ? 1 : 0;
}
if (selectedItemsCount == 0) {
choiceObjects["newrower"].setChoiceByValue(loggedin_user_id);
}
}
const inputElement = document.getElementById(
"departure"
) as HTMLInputElement;
const now = new Date();
const formattedDateTime = `${now.getFullYear()}-${String(
now.getMonth() + 1
).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}T${String(
now.getHours()
).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
inputElement.value = formattedDateTime;
const destinput = <HTMLInputElement>(
document.querySelector("#destination")
);
destinput.dispatchEvent(new Event("input"));
},
false
);
choiceObjects[boatSelect.id] = boatChoice;
choiceObjects["boat_id"] = boatChoice;
}
}
function reloadPage() {
if (!window.location.href.includes("ergo")) {
let pageTitle = document.title;
let attentionMessage = "Riemen- und Dollenbruch";
document.addEventListener("visibilitychange", function () {
let isPageActive = !document.hidden;
if (!isPageActive) {
document.title = attentionMessage;
} else {
document.title = pageTitle;
location.reload();
}
});
}
}
function setMaxAmountRowers(name: string, rowers: number) {
if (choiceObjects[name]) {
//choiceObjects[name].removeActiveItems(-1);
let curSelection = choiceObjects[name].getValue(true);
let amount_to_delete = (<any>curSelection).length - rowers;
if (amount_to_delete > 0) {
let to_delete = (<any>curSelection).slice(-amount_to_delete);
for (let del of to_delete) {
choiceObjects[name].removeActiveItemsByValue(del);
}
}
let input = <HTMLElement>document.querySelector("#" + name);
if (input) {
choiceObjects[name].config.maxItemCount = rowers;
if (rowers === 0) {
choiceObjects[name].disable();
input.parentElement?.parentElement?.parentElement?.classList.add(
"hidden"
);
input.parentElement?.parentElement?.parentElement?.classList.add(
"md:block"
);
input.parentElement?.parentElement?.parentElement?.classList.add(
"opacity-50"
);
} else {
choiceObjects[name].enable();
input.parentElement?.parentElement?.parentElement?.classList.remove(
"hidden"
);
input.parentElement?.parentElement?.parentElement?.classList.remove(
"md:block"
);
input.parentElement?.parentElement?.parentElement?.classList.remove(
"opacity-50"
);
}
}
let shipmaster = <HTMLElement>(
document.querySelector("#shipmaster-" + name + "js")
);
let steering_person = <HTMLElement>(
document.querySelector("#steering_person-" + name + "js")
);
if (rowers == 1) {
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.add("hidden");
}
shipmaster.removeAttribute("required");
if (steering_person.parentNode) {
(<HTMLElement>steering_person.parentNode).classList.add("hidden");
}
steering_person.removeAttribute("required");
} else {
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.remove("hidden");
}
shipmaster.setAttribute("required", "required");
if (steering_person.parentNode) {
(<HTMLElement>steering_person.parentNode).classList.remove("hidden");
}
steering_person.setAttribute("required", "required");
}
}
}
function initBoatActions() {
const boatSelects = document.querySelectorAll(
'.boats-js[data-onclick="true"]'
);
if (boatSelects) {
Array.prototype.forEach.call(boatSelects, (select: HTMLInputElement) => {
select.addEventListener("click", function () {
if (select.dataset.seats) {
if (select.dataset.id) {
choiceObjects["boat_id"].setChoiceByValue(select.dataset.id);
}
window.scrollTo(0, 0);
}
});
});
}
}
function initChoices() {
const selects = document.querySelectorAll('select[data-init="true"]');
if (selects) {
Array.prototype.forEach.call(selects, (select: HTMLInputElement) => {
initNewChoice(select);
});
}
}
interface ChoiceEvent extends Event {
detail: {
value: string;
label: string;
customProperties: {
is_cox: boolean;
steers: boolean;
cox_on_boat: boolean;
is_racing: boolean;
};
};
}
function initNewChoice(select: HTMLInputElement) {
let seats = 0;
if (select.dataset && select.dataset.seats) {
seats = +select.dataset.seats;
}
let shipmaster = <HTMLElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
let steering_person = <HTMLElement>(
document.querySelector("#steering_person-" + select.id + "js")
);
if (seats == 1) {
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.add("hidden");
}
shipmaster.removeAttribute("required");
if (steering_person.parentNode) {
(<HTMLElement>steering_person.parentNode).classList.add("hidden");
}
steering_person.removeAttribute("required");
} else {
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.remove("hidden");
}
shipmaster.setAttribute("required", "required");
if (steering_person.parentNode) {
(<HTMLElement>steering_person.parentNode).classList.remove("hidden");
}
steering_person.setAttribute("required", "required");
}
const choice = new Choices(select, {
searchFields: ["label", "value", "customProperties.searchableText"],
removeItemButton: true,
loadingText: "Wird geladen...",
noResultsText: "Keine Ergebnisse gefunden",
noChoicesText: "Keine Ergebnisse gefunden",
itemSelectText: "Zum Auswählen klicken",
placeholderValue: "Ruderer auswählen",
maxItemCount: seats,
maxItemText: (maxItemCount) => {
return `Nur ${maxItemCount} Ruderer können hinzugefügt werden`;
},
callbackOnInit: function () {
this._currentState.items.forEach(function (obj) {
if (boat_in_ottensheim && obj.customProperties) {
if (obj.customProperties.is_racing) {
const coxSelect = <HTMLSelectElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
var new_option = new Option(obj.label, obj.value);
if (obj.customProperties.cox_on_boat) {
new_option.selected = true;
}
coxSelect.add(new_option);
}
}
if (obj.customProperties && obj.customProperties.is_cox) {
const coxSelect = <HTMLSelectElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
var new_option = new Option(obj.label, obj.value);
if (obj.customProperties.cox_on_boat) {
new_option.selected = true;
}
coxSelect.add(new_option);
}
const steeringSelect = <HTMLSelectElement>(
document.querySelector("#steering_person-" + select.id + "js")
);
if (steeringSelect) {
var new_option = new Option(obj.label, obj.value);
if (obj.customProperties && obj.customProperties.steers) {
new_option.selected = true;
}
steeringSelect.add(new_option);
}
});
},
});
choiceObjects[select.id] = choice;
select.addEventListener(
"addItem",
function (e) {
const event = e as ChoiceEvent;
const user_id = event.detail.value;
const name = event.detail.label;
if (boat_in_ottensheim && event.detail.customProperties.is_racing) {
if (event.detail.customProperties.is_racing) {
const coxSelect = <HTMLSelectElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
if (coxSelect) {
coxSelect.add(new Option(name, user_id));
}
}
}
if (event.detail.customProperties.is_cox) {
const coxSelect = <HTMLSelectElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
if (coxSelect) {
coxSelect.add(new Option(name, user_id));
}
}
const steeringSelect = <HTMLSelectElement>(
document.querySelector("#steering_person-" + select.id + "js")
);
if (steeringSelect) {
steeringSelect.add(new Option(name, user_id));
}
},
false
);
select.addEventListener(
"removeItem",
function (e) {
const event = e as ChoiceEvent;
const user_id = event.detail.value;
const coxSelect = <HTMLSelectElement>(
document.querySelector("#shipmaster-" + select.id + "js")
);
if (coxSelect) {
for (var i = 0; i < coxSelect.length; i++) {
if (coxSelect.options[i].value == user_id) coxSelect.remove(i);
}
}
const steeringSelect = <HTMLSelectElement>(
document.querySelector("#steering_person-" + select.id + "js")
);
if (steeringSelect) {
for (var i = 0; i < steeringSelect.length; i++) {
if (steeringSelect.options[i].value == user_id)
steeringSelect.remove(i);
}
}
},
false
);
choiceObjects[select.id] = choice;
}
function initToggle() {
// get filter btns & set object sessionStorage
const btns = <NodeListOf<HTMLButtonElement>>(
document.querySelectorAll(".filter-trips-js")
);
let filterObject = new Map();
if (btns) {
Array.prototype.forEach.call(btns, (btn: HTMLButtonElement) => {
filterObject.set(btn.dataset.action, btn.ariaPressed);
btn.addEventListener("click", () => {
let filter = sessionStorage.getItem("tripsFilter");
if (filter) {
let filterMap = new Map(JSON.parse(filter));
for (let entry of filterMap.entries()) {
if (entry[0] === btn.dataset.action && entry[1] !== "true") {
filterMap.set(entry[0], "true");
} else {
filterMap.set(entry[0], "false");
}
}
sessionStorage.setItem(
"tripsFilter",
JSON.stringify(Array.from(filterMap.entries()))
);
}
resetFilteredElements();
if (btn.getAttribute("aria-pressed") === "false") {
Array.prototype.forEach.call(btns, (b: HTMLButtonElement) => {
b.setAttribute("aria-pressed", "false");
});
triggerFilterAction(btn.dataset.action);
} else {
btn.setAttribute("aria-pressed", "false");
}
});
});
}
let filter = sessionStorage.getItem("tripsFilter");
if (filter) {
let filterMap = new Map(JSON.parse(filter));
for (let entry of filterMap.entries()) {
if (entry[1] === "true") {
triggerFilterAction(entry[0]);
}
}
} else {
sessionStorage.setItem(
"tripsFilter",
JSON.stringify(Array.from(filterObject.entries()))
);
}
}
function resetFilteredElements() {
const hiddenElements = document.querySelectorAll(".reset-js.hidden");
if (hiddenElements) {
Array.prototype.forEach.call(
hiddenElements,
(hiddenElement: HTMLButtonElement) => {
hiddenElement.classList.remove("hidden");
}
);
}
}
function triggerFilterAction(activeFilter: any) {
const activeBtn = document.querySelector(
'button[data-action="' + activeFilter + '"]'
);
if (activeBtn) {
activeBtn.setAttribute("aria-pressed", "true");
filterAction(activeFilter);
}
}
function filterAction(activeFilter: string) {
switch (activeFilter) {
case "filter-days": {
filterDays();
break;
}
case "filter-coxs": {
filterCoxs();
break;
}
}
}
function filterDays() {
const daysNoTrips = document.querySelectorAll('div[data-trips="0"]');
Array.prototype.forEach.call(daysNoTrips, (day: HTMLElement) => {
day.classList.toggle("hidden");
});
}
function filterCoxs() {
const noCoxNeeded = document.querySelectorAll('div[data-coxneeded="false"]');
Array.prototype.forEach.call(noCoxNeeded, (notNeeded: HTMLElement) => {
notNeeded.classList.toggle("hidden");
});
}
function initSearch() {
const input = <HTMLInputElement>document.querySelector("#filter-js");
if (input) {
filterElements(input.value);
input.addEventListener("input", () => {
filterElements(input.value);
});
}
}
function filterElements(input: string) {
const elements = document.querySelectorAll('div[data-filterable="true"]');
let resultWrapper = <HTMLElement>document.querySelector("#filter-result-js"),
amountShownElements = 0;
Array.prototype.forEach.call(elements, (element: HTMLElement) => {
// set both strings (input & dataset filter) to lowercase to not be case sensitive
let filterString = element.dataset.filter?.toLocaleLowerCase();
// bulk hide all elements
element.style.display = "none";
// show if input matches
if (filterString?.includes(input.toLocaleLowerCase())) {
element.style.display = "flex";
amountShownElements++;
}
});
if (resultWrapper) {
resultWrapper.innerHTML =
amountShownElements === 0
? "Kein Ergebnis gefunden"
: "<strong>" +
amountShownElements +
"</strong>" +
(amountShownElements > 1 ? " Ergebnisse" : " Ergebnis") +
" gefunden";
}
}
function initSidebar() {
const sidebarTrigger = <NodeListOf<HTMLElement>>(
document.querySelectorAll("[data-trigger]")
);
if (sidebarTrigger) {
Array.prototype.forEach.call(
sidebarTrigger,
(triggerElement: HTMLElement) => {
if (triggerElement.dataset.trigger) {
const sidebar = new Sidebar(triggerElement.dataset.trigger);
triggerElement.addEventListener("click", (e) => {
e.preventDefault();
if (triggerElement.dataset.trigger === "sidebar") {
initTripSidebar(triggerElement);
}
sidebar.toggle();
});
}
}
);
}
}
function initTripSidebar(triggerElement: HTMLElement) {
const sidebarElement = <HTMLElement>document.querySelector("#sidebar");
if (
sidebarElement &&
triggerElement.dataset.body &&
triggerElement.dataset.header
) {
let body = <HTMLElement>document.querySelector(triggerElement.dataset.body);
let bodyElement = <HTMLElement>body.cloneNode(true);
let bodyContainerElement = <HTMLElement>(
sidebarElement.querySelector(".body-js")
);
/* Quickfix duplicate ids checkboxes */
const checkboxes = <NodeListOf<HTMLElement>>(
bodyElement.querySelectorAll('input[type="checkbox"]')
);
Array.prototype.forEach.call(checkboxes, (checkbox: HTMLElement) => {
if (checkbox) {
checkbox.parentElement?.setAttribute("for", checkbox.id + "js");
checkbox.id += "js";
}
});
const prefixedContent = <NodeListOf<HTMLElement>>(
bodyElement.querySelectorAll(".change-id-js")
);
Array.prototype.forEach.call(prefixedContent, (content: HTMLElement) => {
if (content) {
content.id += "js";
if (content.dataset.relation) {
content.dataset.relation += "js";
}
}
});
if (bodyContainerElement) {
bodyContainerElement.innerHTML = "";
bodyContainerElement.append(bodyElement);
addRelationMagic(bodyElement);
}
if (triggerElement.dataset.day) {
let hiddenElement = <HTMLInputElement>(
bodyElement.querySelector(".day-js")
);
if (hiddenElement) {
hiddenElement.value = triggerElement.dataset.day;
}
}
let headerElement = sidebarElement.querySelector(".header-js");
if (headerElement) {
headerElement.innerHTML = triggerElement.dataset.header;
}
const selects = bodyElement.querySelectorAll("select[multiple]");
if (selects) {
Array.prototype.forEach.call(selects, (select: HTMLInputElement) => {
initNewChoice(select);
});
}
}
const defaultDateTimes = <NodeListOf<HTMLInputElement>>(
document.querySelectorAll(".current-date-time")
);
defaultDateTimes.forEach((defaultDateTime) => {
setCurrentdate(defaultDateTime);
});
}
function addRelationMagic(bodyElement: HTMLElement) {
const fields = bodyElement.querySelectorAll(".set-distance-js");
if (fields) {
Array.prototype.forEach.call(fields, (field: HTMLInputElement) => {
if (field.dataset.relation) {
const relatedField = <HTMLInputElement>(
bodyElement.querySelector("#" + field.dataset.relation)
);
if (relatedField) {
field.addEventListener("input", (e) => {
e.preventDefault();
const dataList = <HTMLDataListElement>(
document.querySelector("#destinations")
);
if (dataList) {
var option = Array.prototype.find.call(
dataList.options,
function (option) {
return option.value === field.value;
}
);
if (option && option.value !== "") {
// Get distance
const distance = option.getAttribute("distance");
if (distance && relatedField.value === "")
relatedField.value = distance;
}
}
});
}
}
});
}
}
function replaceStrings() {
const weekdays = document.querySelectorAll(".weekday-js");
Array.prototype.forEach.call(weekdays, (weekday: HTMLElement) => {
weekday.innerHTML = weekday.innerHTML.replace("Freitag", "Markttag");
});
}