331 lines
13 KiB
JavaScript
331 lines
13 KiB
JavaScript
"use strict";
|
|
|
|
window.CLIENT = window.CLIENT || null;
|
|
window.ELWIG_API = window.ELWIG_API || null;
|
|
|
|
function getCredentialsUsername() {
|
|
return window.localStorage.getItem(`${CLIENT}/username`);
|
|
}
|
|
|
|
function getCredentialsPassword() {
|
|
return window.localStorage.getItem(`${CLIENT}/password`);
|
|
}
|
|
|
|
function getBasicAuth() {
|
|
return {
|
|
'Authorization': 'Basic ' + btoa(getCredentialsUsername() + ':' + getCredentialsPassword()),
|
|
};
|
|
}
|
|
|
|
async function _get(path) {
|
|
const res = await fetch(`${ELWIG_API}${path}`, {
|
|
method: 'GET',
|
|
headers: {...getBasicAuth()},
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok) throw new ApiError(res.status, json['message']);
|
|
return json;
|
|
}
|
|
|
|
async function get(path) {
|
|
return (await _get(path))['data'];
|
|
}
|
|
|
|
async function getMember(mgnr) {
|
|
return await get(`/members/${mgnr}`);
|
|
}
|
|
|
|
async function getWineVarieties() {
|
|
return Object.fromEntries((await get('/wine/varieties')).map(item => [item['sortid'], item]));
|
|
}
|
|
|
|
async function getWineQualityLevels() {
|
|
return Object.fromEntries((await get('/wine/quality_levels')).map(item => [item['qualid'], item]));
|
|
}
|
|
|
|
async function getWineAttributes() {
|
|
return Object.fromEntries((await get('/wine/attributes')).map(item => [item['attrid'], item]));
|
|
}
|
|
|
|
async function getWineCultivations() {
|
|
return Object.fromEntries((await get('/wine/cultivations')).map(item => [item['cultid'], item]));
|
|
}
|
|
|
|
async function getModifiers() {
|
|
const list = await get('/modifiers');
|
|
const dict = {};
|
|
for (const item of list) {
|
|
if (!dict[item['year']]) dict[item['year']] = {};
|
|
dict[item['year']][item['modid']] = item;
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
async function getDeliveries(filters, limit, offset) {
|
|
const query = ['sort=reverse'];
|
|
if (!!filters) query.push(`filters=${filters.join(',')}`);
|
|
if (!!limit) query.push(`limit=${limit}`);
|
|
if (!!offset) query.push(`offset=${offset}`);
|
|
return await _get(`/deliveries${!!query ? '?' : ''}${query.join('&')}`);
|
|
}
|
|
|
|
async function getDeliveryStats(filters, detail) {
|
|
const query = [];
|
|
if (!!filters) query.push(`filters=${filters.join(',')}`);
|
|
if (!!detail) query.push(`detail=${detail}`);
|
|
return await _get(`/deliveries/stat${!!query ? '?' : ''}${query.join('&')}`);
|
|
}
|
|
|
|
async function getDeliverySchedules(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(`/delivery_schedules${!!query ? '?' : ''}${query.join('&')}`);
|
|
}
|
|
|
|
async function getDeliveryAnnouncements(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(`/delivery_announcements${!!query ? '?' : ''}${query.join('&')}`);
|
|
}
|
|
|
|
async function load() {
|
|
const main = document.getElementById("access");
|
|
const form = main.getElementsByTagName("form")[0];
|
|
if (form) {
|
|
const elements = form.getElementsByClassName('error');
|
|
for (const e of elements) form.removeChild(e);
|
|
}
|
|
try {
|
|
window.MEMBER = await getMember(getCredentialsUsername());
|
|
const txt = document.getElementById('usertext');
|
|
txt.innerHTML = `${MEMBER.prefix ?? ''} ${MEMBER.given_name ?? ''} ${MEMBER.middle_names ?? ''} ${MEMBER.name ?? ''} ${MEMBER.suffix ?? ''}<br/><div>MgNr. ${MEMBER.mgnr}</div>`;
|
|
window.WINE_VARIETIES = await getWineVarieties();
|
|
window.WINE_QUALITY_LEVELS = await getWineQualityLevels();
|
|
window.WINE_ATTRIBUTES = await getWineAttributes();
|
|
window.WINE_CULTIVATIONS = await getWineCultivations();
|
|
window.MODIFIERS = await getModifiers();
|
|
return true;
|
|
} catch (e) {
|
|
if (form) {
|
|
window.localStorage.removeItem(`${CLIENT}/password`);
|
|
const error = document.createElement('div');
|
|
error.className = 'error';
|
|
error.innerText = e.localizedMessage ?? ERROR_MESSAGES[e.message] ?? 'Unbekannter Fehler';
|
|
form.insertBefore(error, form.lastChild.previousSibling);
|
|
} else {
|
|
window.location.hash = '#/login';
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function init() {
|
|
if (!getCredentialsUsername() || !getCredentialsPassword()) {
|
|
window.location.hash = '#/login';
|
|
render();
|
|
return;
|
|
}
|
|
await load();
|
|
render();
|
|
}
|
|
|
|
async function updateOverview() {
|
|
const [schedules] = await Promise.all([getDeliverySchedules([`year=${getCurrentLastSeason()}`])]);
|
|
|
|
const main = document.getElementsByTagName('main')[0];
|
|
const days = groupBy(schedules.data, 'date');
|
|
for (const [dateString, day] of Object.entries(days)) {
|
|
const row = document.createElement('div');
|
|
row.className = 'day';
|
|
const date = new Date(dateString);
|
|
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 now = new Date();
|
|
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');
|
|
link.href = `#/anmelden/${schedule.year}/${schedule.dsnr}`
|
|
link.innerHTML += `<div><span>${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 {
|
|
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>`;
|
|
}
|
|
container.append(link);
|
|
}
|
|
row.appendChild(container);
|
|
main.appendChild(row);
|
|
}
|
|
}
|
|
|
|
async function updateDeliveries(year) {
|
|
const filters = [`mgnr=${MEMBER.mgnr}`, `year=${year}`];
|
|
const [deliveries, stat] = await Promise.all([getDeliveries(filters), getDeliveryStats(filters)]);
|
|
|
|
const tbody = document.getElementById('delivery-list');
|
|
tbody.innerHTML = '';
|
|
for (const delivery of deliveries.data) {
|
|
const tr = document.createElement('tr');
|
|
tr.style.background = '#C0C0C0';
|
|
tr.innerHTML = `
|
|
<th colspan="2">${delivery.lsnr}</th>
|
|
<td>${fmtDate(new Date(delivery.date))}</td>
|
|
<td>${delivery.time.substring(0, 5)}</td>
|
|
<td colspan="2">${delivery.branch.name}</td>
|
|
<td colspan="7">${delivery.comment ?? ''}</td>`;
|
|
tbody.appendChild(tr);
|
|
for (const part of delivery.parts) {
|
|
const tr = document.createElement('tr');
|
|
const defaultQualityLevel = getDefaultQualityLevel(part.gradation.kmw);
|
|
tr.innerHTML = `
|
|
<th>${part.dpnr}</th>
|
|
<td colspan="2">${WINE_VARIETIES[part.variety.sortid]?.name ?? ''}</td>
|
|
<td colspan="2" style="font-weight: bold;">${WINE_CULTIVATIONS[part.cultivation?.cultid]?.name ?? ''}</td>
|
|
<td colspan="2" style="font-weight: bold;">${WINE_ATTRIBUTES[part.attribute?.attrid]?.name ?? ''}</td>
|
|
<td class="${defaultQualityLevel['qualid'] != part.quality_level.qualid ? 'abgewertet' : ''}">${WINE_QUALITY_LEVELS[part.quality_level.qualid]?.name ?? ''}</td>
|
|
<td class="center">${fmtOe(part.gradation.oe)}</td>
|
|
<td class="center">${fmtKmw(part.gradation.kmw)}</td>
|
|
<td class="number">${fmtInt(part.weight)}</td>
|
|
<td>${part.modifiers.map(m => MODIFIERS[delivery.year][m.modid]).sort(m => m.ordering).map(m => m.name).join(' / ')}</td>
|
|
<td>${part.comment ?? ''}</td>`;
|
|
tbody.appendChild(tr);
|
|
}
|
|
}
|
|
|
|
const element = document.getElementById('delivery-stat');
|
|
element.innerText = `(Teil-)Lieferungen: ${fmtInt(stat.data.total.count)} (${fmtInt(stat.data.total.parts)}), Gewicht: ${fmtInt(stat.data.total.weight.sum)} kg`;
|
|
}
|
|
|
|
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 = '';
|
|
if (hash === '#/login') {
|
|
main.className = 'login';
|
|
main.innerHTML = `
|
|
<form onsubmit="return actionLogin(this);">
|
|
<h1>Anmelden</h1>
|
|
<input type="text" name="username" placeholder="Mitgliedsnummer" value="${getCredentialsUsername() ?? ''}"/>
|
|
<input type="password" name="password" placeholder="Kennwort"/>
|
|
<button type="submit">Anmelden</button>
|
|
</form>`;
|
|
} else if (hash === '#/') {
|
|
nav.children[0].className = 'active';
|
|
main.className = 'overview';
|
|
main.innerHTML = `
|
|
<h1>Übersicht</h1>`;
|
|
updateOverview().then();
|
|
} else if (hash === '#/mitglied') {
|
|
nav.children[1].className = 'active';
|
|
main.className = 'member';
|
|
main.innerHTML = `
|
|
<h1>Mitglied</h1>
|
|
<button onclick="actionLogout()">Abmelden</button>
|
|
<pre>${JSON.stringify(MEMBER, null, 2)}</pre>`;
|
|
} else if (hash === '#/lieferungen') {
|
|
nav.children[2].className = 'active';
|
|
main.className = 'deliveries';
|
|
main.innerHTML = `
|
|
<h1>Lieferungen</h1>
|
|
<form>
|
|
<div>
|
|
<label for="season">Saison:</label>
|
|
<input name="season" type="number" min="1900" max="9999"
|
|
value="${getCurrentLastSeason()}" onchange="updateDeliveries(this.value).then()"/>
|
|
</div>
|
|
<div id="delivery-stat"/>
|
|
</form>
|
|
<table style="width: 100%;">
|
|
<colgroup>
|
|
<col style="width: 50px;"/>
|
|
<col style="width: 70px;"/>
|
|
<col style="width: 100px;"/>
|
|
<col style="width: 60px;"/>
|
|
<col style="width: 50px;"/>
|
|
<col style="width: 70px;"/>
|
|
<col style="width: 50px;"/>
|
|
<col style="width: 120px;"/>
|
|
<col style="width: 50px;"/>
|
|
<col style="width: 50px;"/>
|
|
<col style="width: 60px;"/>
|
|
<col style="min-width: 80px;"/>
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th rowspan="2"></th>
|
|
<th rowspan="2" colspan="2">Sorte</th>
|
|
<th rowspan="2" colspan="2">Bewirt.</th>
|
|
<th rowspan="2" colspan="2">Attribut</th>
|
|
<th rowspan="2">Qualitätsstufe</th>
|
|
<th colspan="2" class="center">Gradation</th>
|
|
<th class="center">Gewicht</th>
|
|
<th rowspan="2">Zu-/Abschläge</th>
|
|
<th></th>
|
|
</tr>
|
|
<tr>
|
|
<th class="unit">[°Oe]</th>
|
|
<th class="unit">[°KMW]</th>
|
|
<th class="unit">[kg]</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="delivery-list"></tbody>
|
|
</table>`;
|
|
updateDeliveries(getCurrentLastSeason()).then();
|
|
} else if (hash === '#/anmeldungen') {
|
|
nav.children[3].className = 'active';
|
|
main.className = 'announcements';
|
|
main.innerHTML = '<h1>Anmeldungen</h1>';
|
|
} else if (hash.startsWith('#/anmelden/')) {
|
|
nav.children[3].className = 'active';
|
|
main.className = 'announce';
|
|
main.innerHTML = '<h1>Anmelden</h1>';
|
|
} else {
|
|
window.location.hash = `#/`;
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await init();
|
|
});
|
|
|
|
window.addEventListener('hashchange', () => {
|
|
if ((!getCredentialsUsername() || !getCredentialsPassword()) && window.location.hash !== '#/login') {
|
|
window.location.hash = '#/login';
|
|
return;
|
|
}
|
|
render();
|
|
});
|
|
|
|
function actionLogin(form) {
|
|
window.localStorage.setItem(`${CLIENT}/username`, form.username.value);
|
|
window.localStorage.setItem(`${CLIENT}/password`, form.password.value);
|
|
load().then(success => {
|
|
if (success) window.location.hash = '#/';
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function actionLogout() {
|
|
window.localStorage.removeItem(`${CLIENT}/username`);
|
|
window.localStorage.removeItem(`${CLIENT}/password`);
|
|
window.location.reload();
|
|
}
|