Files
food-tracer/client/src/components/MealGroupRow.tsx
T
Ondřej Anděl e13f7ce8e5 fix: recepty přesnuty k jídlům
feat: přidána podpora fotek
2026-10-12 10:20:12 +02:00

150 lines
7.3 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 { Button } from "react-bootstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBookOpen, faCalendarDays, faCamera, faPen, faPenToSquare } from "@fortawesome/free-solid-svg-icons";
import { MealGroup } from "../../../types";
import { MealGroupAction } from "../enums";
import { formatDateString } from "../Utils";
import MealGroupForm from "./MealGroupForm";
import MealGroupRenameForm from "./MealGroupRenameForm";
import MealGroupDays from "./MealGroupDays";
import MealGroupRecipe from "./MealGroupRecipe";
import MealGroupPhotos from "./MealGroupPhotos";
type Props = {
group: MealGroup,
/** Všechny skupiny — přejmenování podle nich pozná sloučení s jinou. */
groupList: MealGroup[],
/** Rozbalená akce; nejvýš jeden řádek seznamu má nějakou otevřenou. */
action?: MealGroupAction,
onAction: (action?: MealGroupAction) => void,
/** Zavolá se po doplnění, přejmenování nebo změně receptu — seznam se načte znovu a akce zavře. */
onChanged: () => void,
/** Obnoví seznam a otevřenou akci nechá — po změně fotek, aby šlo přidávat dál. */
onRefresh: () => void,
};
/** Kolik jídel skupiny daný údaj nemá — "u všech", nebo "3 z 5". */
const describeMissing = (missing: number, count: number): string =>
missing === count ? 'u všech' : `${missing} z ${count}`;
/**
* Řádek skupiny jídel (stejný název a zdroj) s přehledem chybějících údajů.
* Nabízí recept a fotky jídla, doplnění údajů a změnu názvu a zdroje všem
* jídlům skupiny a dny, kdy se jídlo jedlo, jako odkazy na přehled dne.
*/
const MealGroupRow = ({ group, groupList, action, onAction, onChanged, onRefresh }: Readonly<Props>) => {
const [daysOpen, setDaysOpen] = useState(false);
const toggle = (value: MealGroupAction) => onAction(action === value ? undefined : value);
return (
<div className="card-panel meal-group">
<div className="meal-group-head">
{group.thumbnail && (
<button
type="button"
className="meal-group-thumb"
title="Fotky jídla"
onClick={() => toggle(MealGroupAction.FOTKY)}
>
<img src={group.thumbnail} alt={group.name} />
</button>
)}
<div className="meal-group-info">
<div className="meal-group-name">{group.name}</div>
<div className="meal-meta">
{group.source && <span className="tag">{group.source}</span>}
{group.lastDate
? <span className="tag">{group.count}× · naposledy {formatDateString(group.lastDate)}</span>
: <span className="tag">zatím nejedeno</span>}
{group.recipe && (
<span className="tag tag-recipe"><FontAwesomeIcon icon={faBookOpen} /> recept</span>
)}
{group.missingCaloriesCount > 0 && (
<span className="tag tag-missing">
bez kalorií {describeMissing(group.missingCaloriesCount, group.count)}
</span>
)}
{group.missingWeightCount > 0 && (
<span className="tag tag-missing">
bez gramáže {describeMissing(group.missingWeightCount, group.count)}
</span>
)}
{group.missingNutrientsCount > 0 && (
<span className="tag tag-missing">
bez živin {describeMissing(group.missingNutrientsCount, group.count)}
</span>
)}
</div>
</div>
<div className="meal-group-actions">
{!action && group.count > 0 && (
<Button
variant="outline-secondary"
size="sm"
active={daysOpen}
onClick={() => setDaysOpen(open => !open)}
>
<FontAwesomeIcon icon={faCalendarDays} /> Dny
</Button>
)}
<Button
variant="outline-secondary"
size="sm"
active={action === MealGroupAction.RECEPT}
title={group.recipe ? 'Zobrazit recept' : 'Zapsat k jídlu recept'}
onClick={() => toggle(MealGroupAction.RECEPT)}
>
<FontAwesomeIcon icon={faBookOpen} /> Recept
</Button>
<Button
variant="outline-secondary"
size="sm"
active={action === MealGroupAction.FOTKY}
title="Fotky jídla"
onClick={() => toggle(MealGroupAction.FOTKY)}
>
<FontAwesomeIcon icon={faCamera} /> {group.photoCount || ''}
</Button>
<Button
variant="outline-secondary"
size="sm"
active={action === MealGroupAction.PREJMENOVAT}
title="Změnit název a zdroj u všech jídel skupiny"
onClick={() => toggle(MealGroupAction.PREJMENOVAT)}
>
<FontAwesomeIcon icon={faPen} /> Přejmenovat
</Button>
{group.count > 0 && (
<Button
variant={action === MealGroupAction.DOPLNIT ? 'outline-secondary' : 'primary'}
size="sm"
onClick={() => toggle(MealGroupAction.DOPLNIT)}
>
<FontAwesomeIcon icon={faPenToSquare} />
{' '}{action === MealGroupAction.DOPLNIT ? 'Zavřít' : 'Doplnit'}
</Button>
)}
</div>
</div>
{(action === MealGroupAction.DOPLNIT || (!action && daysOpen)) && <MealGroupDays days={group.days} />}
{action === MealGroupAction.DOPLNIT && (
<MealGroupForm group={group} onFilled={onChanged} onCancel={() => onAction(undefined)} />
)}
{action === MealGroupAction.RECEPT && (
<MealGroupRecipe group={group} onChanged={onChanged} onClose={() => onAction(undefined)} />
)}
{action === MealGroupAction.FOTKY && <MealGroupPhotos group={group} onChanged={onRefresh} />}
{action === MealGroupAction.PREJMENOVAT && (
<MealGroupRenameForm
group={group}
groupList={groupList}
onRenamed={onChanged}
onCancel={() => onAction(undefined)}
/>
)}
</div>
);
};
export default MealGroupRow;