import { useEffect, useState } from 'preact/hooks'; import { formatCents } from '../api'; async function j(res: Response): Promise { if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); return res.json(); } interface Drink { id: number; name: string; price_cents: number; archived: number } interface BarRow { id: number; name: string; pfand_cents: number; drink_ids: number[] } interface Totals { bar_id: number; bar_name: string; tx_count: number; paid_cents: number; crew_count: number; pfand_returns: number } interface PerDrink { drink_id: number; drink_name: string; sold_qty: number } export function Admin() { const [authed, setAuthed] = useState(null); useEffect(() => { fetch('/admin/api/me').then(j).then(r => setAuthed(r.authed)).catch(() => setAuthed(false)); }, []); if (authed === null) return

Lade…

; if (!authed) return setAuthed(true)} />; return setAuthed(false)} />; } function Login({ onAuthed }: { onAuthed: () => void }) { const [pw, setPw] = useState(''); const [err, setErr] = useState(null); async function submit(e: Event) { e.preventDefault(); setErr(null); try { await fetch('/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }), }).then(j); onAuthed(); } catch (e: any) { setErr(String(e)); } } return (
); } function Dashboard({ onLogout }: { onLogout: () => void }) { return (

wutzcalc Backoffice

); } function Stats() { const [data, setData] = useState<{ totals: Totals[]; per_drink: PerDrink[] } | null>(null); useEffect(() => { fetch('/admin/api/stats').then(j).then(setData); }, []); if (!data) return

Lade Statistik…

; return ( <>

Umsatz pro Bar

{data.totals.map(t => ( ))}
BarTransaktionenBezahltCrew-TransaktionenPfand zurück
{t.bar_name} {t.tx_count} {formatCents(t.paid_cents)} {t.crew_count} {t.pfand_returns}

Getränke

{data.per_drink.map(d => ( ))}
GetränkVerkauft
{d.drink_name} {d.sold_qty ?? 0}
); } function Exports() { return ( <>

CSV-Export

); } function Drinks() { const [list, setList] = useState([]); const [name, setName] = useState(''); const [price, setPrice] = useState(''); function reload() { fetch('/admin/api/drinks').then(j).then(setList); } useEffect(reload, []); async function add() { const cents = Math.round(parseFloat(price) * 100); if (!name || !Number.isFinite(cents)) return; await fetch('/admin/api/drinks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, price_cents: cents }), }); setName(''); setPrice(''); reload(); } async function patch(id: number, body: Partial) { await fetch(`/admin/api/drinks/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); reload(); } return ( <>

Getränke

{list.map(d => ( ))}
NamePreisStatus
patch(d.id, { name: e.currentTarget.value })} /> patch(d.id, { price_cents: Math.round(parseFloat(e.currentTarget.value) * 100) })} /> {d.archived ? 'archiviert' : 'aktiv'}
setName(e.currentTarget.value)} /> setPrice(e.currentTarget.value)} />
); } function Bars() { const [bars, setBars] = useState([]); const [drinks, setDrinks] = useState([]); function reload() { fetch('/admin/api/bars').then(j).then(setBars); fetch('/admin/api/drinks').then(j).then(setDrinks); } useEffect(reload, []); async function patch(id: number, body: Partial) { await fetch(`/admin/api/bars/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); reload(); } return ( <>

Bars

{bars.map(b => (
{b.name}
{drinks.filter(d => !d.archived).map(d => { const checked = b.drink_ids.includes(d.id); return ( ); })}
))} ); }