fix: graf cena per zdroj nahrazen koláčem

feat: stránka doplnit údaje předělána na samostatnou stránku jídel + přidáno přejmenování
This commit is contained in:
Ondřej Anděl committed 2026-10-12 09:49:41 +02:00
1 parent b880b36edd
commit efed5e6e86
30 files changed
+514 -153

No files matched your search

@@ -0,0 +1,98 @@
import { useState } from "react";
import { Alert, Button, Form } from "react-bootstrap";
import { toast } from "react-toastify";
import { MealGroup, renameMealGroup } from "../../../types";
import { getMealKey } from "../Utils";
import SourceInput from "./SourceInput";
type Props = {
group: MealGroup,
/** Ostatní skupiny — podle nich se pozná, že se jídla po přejmenování s některou sloučí. */
groupList: MealGroup[],
onRenamed: () => void,
onCancel: () => void,
};
/** Popisek výsledku přejmenování se skloňovaným počtem jídel. */
const describeRenamed = (count: number): string => {
if (!count) return 'Název i zdroj už jídla mají, nic se nezměnilo.';
if (count === 1) return 'Změněno 1 jídlo.';
if (count < 5) return `Změněna ${count} jídla.`;
return `Změněno ${count} jídel.`;
};
/**
* Hromadná změna názvu a zdroje všech jídel jedné skupiny ve všech dnech.
*
* Na rozdíl od doplnění údajů přepisuje, proto je to samostatná akce. Když nový
* název a zdroj patří jiné skupině, jídla se do ní sloučí — formulář na to upozorní.
*/
const MealGroupRenameForm = ({ group, groupList, onRenamed, onCancel }: Readonly<Props>) => {
const [name, setName] = useState(group.name);
const [source, setSource] = useState(group.source ?? '');
const [saving, setSaving] = useState(false);
const targetKey = getMealKey({ name, source });
const mergeGroup = targetKey !== getMealKey(group)
? groupList.find(item => getMealKey(item) === targetKey)
: undefined;
const unchanged = name.trim() === group.name && source.trim() === (group.source ?? '');
const canSave = !!name.trim().length && !unchanged && !saving;
const save = async () => {
if (!canSave) return;
setSaving(true);
const response = await renameMealGroup({
body: {
name: group.name,
source: group.source,
newName: name.trim(),
...(source.trim().length ? { newSource: source.trim() } : {}),
},
});
setSaving(false);
if (response.data) {
toast.success(describeRenamed(response.data.count));
onRenamed();
}
};
return (
<Form className="meal-group-form" onSubmit={event => { event.preventDefault(); save(); }}>
<p className="text-body-secondary small">
{group.count === 1
? 'Změní se jen u tohoto jídla.'
: `Změní se u všech ${group.count} jídel ve všech dnech, kdy se jedla.`} Ostatní údaje zůstanou.
</p>
<div className="row g-3 mb-3">
<div className="col-sm-7">
<Form.Label>Název</Form.Label>
<Form.Control
autoFocus
value={name}
isInvalid={!name.trim().length}
onChange={event => setName(event.target.value)}
/>
</div>
<div className="col-sm-5">
<Form.Label>Zdroj</Form.Label>
<SourceInput value={source} placeholder="bez zdroje" onChange={value => setSource(value)} />
</div>
</div>
{mergeGroup && (
<Alert variant="warning" className="py-2 small">
Jídlo „{mergeGroup.name}“{mergeGroup.source ? ` (${mergeGroup.source})` : ' bez zdroje'} už
v seznamu je ({mergeGroup.count}×), jídla se s ním sloučí.
</Alert>
)}
<div className="d-flex justify-content-end gap-2">
<Button variant="outline-secondary" onClick={() => onCancel()}>Zrušit</Button>
<Button type="submit" disabled={!canSave}>
{saving ? 'Ukládám…' : 'Změnit u všech jídel'}
</Button>
</div>
</Form>
);
};
export default MealGroupRenameForm;