Files
food-tracer/client/src/components/MealGroupRenameForm.tsx
T
Ondřej Anděl efed5e6e86 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í
2026-10-12 09:49:41 +02:00

99 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;