"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(); }