admin: surface silent failures, guard against double-submit on add
- Drinks.add()/patch() now check res.ok and alert() the error text, matching the pattern every other mutator in this file already used (Drinks.del(), everything in Bars) — previously a rejected name/price change just re-fetched the (unchanged) list with no indication anything went wrong. - Stats.reload(), Drinks.reload(), Bars.reload() now .catch() a failed fetch/non-ok response instead of leaving an unhandled promise rejection — a network hiccup used to leave the section stuck on 'Lade…' forever with no visible error. - Drinks 'Hinzufügen' and Bars 'Bar hinzufügen' are now disabled while their POST is in flight, mirroring the submitting guard Sale.tsx already uses — a fast double-tap could otherwise fire two requests before the first reload() resolved.
This commit is contained in:
parent
2a3097217e
commit
59f6041a16
1 changed files with 43 additions and 25 deletions
|
|
@ -93,7 +93,9 @@ function fmtDay(day: string): string {
|
|||
|
||||
function Stats() {
|
||||
const [data, setData] = useState<{ totals: Totals[]; per_drink: PerDrink[]; by_day: ByDay[] } | null>(null);
|
||||
function reload() { fetch('/admin/api/stats').then(j).then(setData); }
|
||||
function reload() {
|
||||
fetch('/admin/api/stats').then(j).then(setData).catch(e => alert(`Fehler beim Laden: ${e}`));
|
||||
}
|
||||
useEffect(reload, []);
|
||||
|
||||
async function reset() {
|
||||
|
|
@ -171,27 +173,37 @@ function Drinks() {
|
|||
const [list, setList] = useState<Drink[]>([]);
|
||||
const [name, setName] = useState('');
|
||||
const [price, setPrice] = useState('');
|
||||
const [adding, setAdding] = useState(false);
|
||||
|
||||
function reload() { fetch('/admin/api/drinks').then(j).then(setList); }
|
||||
function reload() {
|
||||
fetch('/admin/api/drinks').then(j).then(setList).catch(e => alert(`Fehler beim Laden: ${e}`));
|
||||
}
|
||||
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();
|
||||
if (!name || !Number.isFinite(cents) || adding) return;
|
||||
setAdding(true);
|
||||
try {
|
||||
const res = await fetch('/admin/api/drinks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, price_cents: cents }),
|
||||
});
|
||||
if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
|
||||
setName(''); setPrice(''); reload();
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function patch(id: number, body: Partial<Drink>) {
|
||||
await fetch(`/admin/api/drinks/${id}`, {
|
||||
const res = await fetch(`/admin/api/drinks/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
|
||||
reload();
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +249,7 @@ function Drinks() {
|
|||
<div class="row">
|
||||
<input placeholder="Name" value={name} onInput={(e: any) => setName(e.currentTarget.value)} />
|
||||
<input placeholder="Preis €" type="number" step="0.01" value={price} onInput={(e: any) => setPrice(e.currentTarget.value)} />
|
||||
<button onClick={add}>Hinzufügen</button>
|
||||
<button onClick={add} disabled={adding}>Hinzufügen</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
@ -333,10 +345,11 @@ function Bars() {
|
|||
const [bars, setBars] = useState<BarRow[]>([]);
|
||||
const [drinks, setDrinks] = useState<Drink[]>([]);
|
||||
const [newName, setNewName] = useState('');
|
||||
const [addingBar, setAddingBar] = useState(false);
|
||||
|
||||
function reload() {
|
||||
fetch('/admin/api/bars').then(j).then(setBars);
|
||||
fetch('/admin/api/drinks').then(j).then(setDrinks);
|
||||
fetch('/admin/api/bars').then(j).then(setBars).catch(e => alert(`Fehler beim Laden: ${e}`));
|
||||
fetch('/admin/api/drinks').then(j).then(setDrinks).catch(e => alert(`Fehler beim Laden: ${e}`));
|
||||
}
|
||||
useEffect(reload, []);
|
||||
|
||||
|
|
@ -359,18 +372,23 @@ function Bars() {
|
|||
|
||||
async function addBar() {
|
||||
const name = newName.trim();
|
||||
if (!name) return;
|
||||
const res = await fetch('/admin/api/bars', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, pfand_cents: 200 }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
alert(`Fehler: ${await errText(res)}`);
|
||||
return;
|
||||
if (!name || addingBar) return;
|
||||
setAddingBar(true);
|
||||
try {
|
||||
const res = await fetch('/admin/api/bars', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, pfand_cents: 200 }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
alert(`Fehler: ${await errText(res)}`);
|
||||
return;
|
||||
}
|
||||
setNewName('');
|
||||
reload();
|
||||
} finally {
|
||||
setAddingBar(false);
|
||||
}
|
||||
setNewName('');
|
||||
reload();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -412,7 +430,7 @@ function Bars() {
|
|||
value={newName}
|
||||
onInput={(e: any) => setNewName(e.currentTarget.value)}
|
||||
/>
|
||||
<button onClick={addBar}>Bar hinzufügen</button>
|
||||
<button onClick={addBar} disabled={addingBar}>Bar hinzufügen</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue