190 lines
7.8 KiB
JavaScript
190 lines
7.8 KiB
JavaScript
"use strict";
|
|
|
|
window.CLIENT = window.CLIENT || null;
|
|
window.CLIENTS = window.CLIENTS || {};
|
|
|
|
function getStoredUsername(client) {
|
|
return window.localStorage.getItem(`${CLIENT}/${client}/username`);
|
|
}
|
|
|
|
function getStoredToken(client) {
|
|
return window.localStorage.getItem(`${CLIENT}/${client}/token`);
|
|
}
|
|
|
|
function getAuthorizationHeader(client) {
|
|
return {
|
|
'Authorization': 'Bearer ' + window.localStorage.getItem(`${CLIENT}/${client}/token`),
|
|
};
|
|
}
|
|
|
|
async function authenticate(client, username, password) {
|
|
const res = await fetch(`${CLIENTS[client]['api']}/auth`, {
|
|
method: 'GET',
|
|
headers: {'Authorization': 'Basic ' + btoa(username + ':' + password)},
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok) throw new ApiError(res.status, json['message']);
|
|
return json['token'];
|
|
}
|
|
|
|
async function get(client, path) {
|
|
const res = await fetch(`${CLIENTS[client]['api']}${path}`, {
|
|
method: 'GET',
|
|
headers: {...getAuthorizationHeader(client)},
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok) throw new ApiError(res.status, json['message']);
|
|
return json;
|
|
}
|
|
|
|
async function getDeliverySchedules(client, filters, limit, offset) {
|
|
const query = [];
|
|
if (!!filters) query.push(`filters=${filters.join(',')}`);
|
|
if (!!limit) query.push(`limit=${limit}`);
|
|
if (!!offset) query.push(`offset=${offset}`);
|
|
return await get(client, `/delivery_schedules${!!query ? '?' : ''}${query.join('&')}`);
|
|
}
|
|
|
|
async function init() {
|
|
render();
|
|
}
|
|
|
|
async function updateOverview(client) {
|
|
const [schedules] = await Promise.all([getDeliverySchedules(client, [`year=${getCurrentLastSeason()}`])]);
|
|
const rows = [];
|
|
const days = groupBy(schedules['data'], 'date');
|
|
const now = new Date();
|
|
for (const [dateString, day] of Object.entries(days)) {
|
|
const date = new Date(dateString);
|
|
const row = document.createElement('div');
|
|
row.className = 'day';
|
|
if (now.getFullYear() === date.getFullYear() && now.getMonth() === date.getMonth() && now.getDate() === date.getDate())
|
|
row.classList.add('today');
|
|
row.innerHTML = `<div><span style="font-size: 0.75em; display: block">${fmtDateWeekday(date)}</span>${fmtDate(date)}</div>`;
|
|
const container = document.createElement('div');
|
|
container.className = 'schedule-container';
|
|
for (const schedule of day) {
|
|
const from = schedule.announcement_from !== null ? new Date(schedule.announcement_from) : null;
|
|
const to = schedule.announcement_to !== null ? new Date(schedule.announcement_to) : null;
|
|
const status = from === null && to === null ? 'Anmeldung offen' : from > now ? `Anmeldung ab ${fmtDateTime(from)}` : to > now ? `Anmeldung bis ${fmtDateTime(new Date(to - 1))}` : 'Anmeldefrist vorbei';
|
|
const link = document.createElement('a');
|
|
if (schedule.is_cancelled) link.className = 'cancelled';
|
|
link.innerHTML += `<div><span style="font-size: 0.75em; display: block;">${escapeHTML(schedule.branch.name)}</span><span>${escapeHTML(schedule.description)}</span><span style="font-size: 0.75em; display: block;">${status}</span></div>`;
|
|
if (schedule.delivered_weight > 0) {
|
|
link.innerHTML += `
|
|
<span>
|
|
<span><strong>${fmtInt(schedule.delivered_weight)} kg</strong></span> /
|
|
<span class="min-kg">${fmtInt(schedule.announced_weight)} kg</span>
|
|
(<span class="min-percent">${fmtInt(Math.round(schedule.delivered_weight / schedule.announced_weight * 100))}%</span>)
|
|
</span>`;
|
|
} else if (schedule.max_weight !== null) {
|
|
link.innerHTML += `
|
|
<span>
|
|
<span>${fmtInt(schedule.announced_weight)} kg</span> /
|
|
<span class="min-kg">${fmtInt(schedule.max_weight)} kg</span>
|
|
(<span class="min-percent">${fmtInt(Math.round(schedule.announced_weight / schedule.max_weight * 100))}%</span>)
|
|
</span>`;
|
|
} else {
|
|
link.innerHTML += `<span><span>${fmtInt(schedule.announced_weight)} kg</span></span>`;
|
|
}
|
|
container.append(link);
|
|
}
|
|
row.appendChild(container);
|
|
rows.push(row);
|
|
}
|
|
|
|
const main = document.getElementsByTagName('main')[0];
|
|
main.replaceChildren(main.firstElementChild, ...rows);
|
|
}
|
|
|
|
function render() {
|
|
const hash = window.location.hash;
|
|
const main = document.getElementById("access");
|
|
const nav = document.getElementsByTagName("nav")[0].getElementsByTagName("ul")[0];
|
|
for (const li of nav.children) li.className = '';
|
|
|
|
const client = Object.keys(CLIENTS).find(id => hash.startsWith(`#/${id}/`) || hash === `#/${id}`);
|
|
if (client === undefined) {
|
|
window.location.hash = `#/${Object.keys(CLIENTS).find(id => !!getStoredUsername(id) && !!getStoredToken(id)) || Object.keys(CLIENTS)[0]}`;
|
|
return;
|
|
}
|
|
nav.children[Object.keys(CLIENTS).indexOf(client)].className = 'active';
|
|
|
|
if ((!getStoredUsername(client) || !getStoredToken(client)) && window.location.hash !== `#/${client}/login`) {
|
|
window.location.hash = `#/${client}/login`;
|
|
return;
|
|
}
|
|
|
|
if (hash === `#/${client}/login`) {
|
|
main.className = 'login';
|
|
main.innerHTML = `
|
|
<form onsubmit="return actionLogin(this);">
|
|
<h1>Anmelden</h1>
|
|
<input type="text" name="username" placeholder="Benutzername" value="${getStoredUsername(client) ?? ''}"/>
|
|
<input type="password" name="password" placeholder="Kennwort"/>
|
|
<input type="hidden" name="client" value="${client}"/>
|
|
<button type="submit">Anmelden</button>
|
|
</form>`;
|
|
} else if (hash === `#/${client}`) {
|
|
main.className = 'overview';
|
|
main.innerHTML = `
|
|
<h1>${CLIENTS[client].name}</h1>`;
|
|
update();
|
|
} else {
|
|
window.location.hash = `#/${client}`;
|
|
}
|
|
}
|
|
|
|
function update() {
|
|
const hash = window.location.hash;
|
|
const client = Object.keys(CLIENTS).find(id => hash.startsWith(`#/${id}/`) || hash === `#/${id}`);
|
|
if (document.hidden || client === undefined) {
|
|
// do nothing
|
|
} else {
|
|
if (hash === `#/${client}`) {
|
|
updateOverview(client)
|
|
.then()
|
|
.catch(e => {
|
|
if (e instanceof ApiError && e.statusCode === 401) {
|
|
window.localStorage.removeItem(`${CLIENT}/${client}/token`);
|
|
window.location.hash = `#/${client}/login`;
|
|
} else {
|
|
alert(e.localizedMessage ?? ERROR_MESSAGES[e.message] ?? `Unbekannter Fehler: ${e.message}`);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await init();
|
|
setInterval(update, 60_000);
|
|
});
|
|
|
|
window.addEventListener('hashchange', render);
|
|
|
|
window.addEventListener('pageshow', update)
|
|
|
|
document.addEventListener('visibilitychange', update);
|
|
|
|
function actionLogin(form) {
|
|
const elements = form.getElementsByClassName('error');
|
|
for (const e of elements) form.removeChild(e);
|
|
|
|
const client = form['client'].value;
|
|
window.localStorage.setItem(`${CLIENT}/${client}/username`, form['username'].value);
|
|
|
|
authenticate(client, form['username'].value, form['password'].value)
|
|
.then(token => {
|
|
window.localStorage.setItem(`${CLIENT}/${client}/token`, token);
|
|
window.location.hash = `#/${client}`;
|
|
}).catch(e => {
|
|
const error = document.createElement('div');
|
|
error.className = 'error';
|
|
error.innerText = e.localizedMessage ?? ERROR_MESSAGES[e.message] ?? `Unbekannter Fehler\n(${e.message})`;
|
|
form.insertBefore(error, form.lastChild.previousSibling);
|
|
});
|
|
|
|
return false;
|
|
}
|