Files
Luncher/client/src/components/MenuHintCell.tsx
T
batmanisko e281615cc3
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 25s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 39s
CI / Playwright E2E tests (push) Successful in 1m52s
CI / Build and push Docker image (push) Successful in 51s
CI / Notify (push) Successful in 2s
feat: spin the wheel, objednavani scrape menu, historie cen
2026-09-23 15:41:15 +02:00

94 lines
3.9 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 { useMemo } from "react";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import SelectSearch, { SelectedOptionValue, SelectSearchOption } from "react-select-search";
import { StoreMenuDish } from "../../../types";
import { matchNoteToMenu } from "../utils/menuMatch";
import "./MenuHintCell.scss";
type Props = {
/** Celá nabídka podniku (stažená z Bolt Food/Wolt), undefined = načítá se / není */
menu?: StoreMenuDish[],
note?: string,
/** Zadaná částka člena v haléřích */
amount?: number,
canEdit: boolean,
onPick: (dish: StoreMenuDish) => void,
onUsePrice: (priceKc: number) => void,
};
const formatDish = (d: StoreMenuDish) => d.price != null ? `${d.name} (${d.price} Kč)` : d.name;
/**
* Buňka „Z nabídky" ve skupinové objednávce: výběr položky ze stažené nabídky podniku
* (doplní poznámku i částku) a kontrola ceny rozpoznané z poznámky proti zadané částce.
*/
export default function MenuHintCell({ menu, note, amount, canEdit, onPick, onUsePrice }: Readonly<Props>) {
const options = useMemo<SelectSearchOption[]>(() => {
if (!menu?.length) return [];
const groups = new Map<string, SelectSearchOption[]>();
menu.forEach((d, index) => {
const category = d.category || 'Ostatní';
if (!groups.has(category)) groups.set(category, []);
groups.get(category)!.push({ name: formatDish(d), value: String(index) });
});
return [...groups.entries()].map(([name, items]) => ({ name, type: 'group', items }));
}, [menu]);
const match = useMemo(() => menu ? matchNoteToMenu(note, menu) : undefined, [menu, note]);
if (!menu?.length) {
return <small className="text-muted">—</small>;
}
const menuPrice = match?.total;
const differs = menuPrice != null && amount != null && Math.round(menuPrice * 100) !== amount;
const unmatched = match?.parts.filter(p => !p.dish).map(p => p.text) ?? [];
return (
<div className="menu-hint-cell">
{canEdit && (
<SelectSearch
search
options={options}
placeholder="Přidat z nabídky…"
value=""
onBlur={() => { }}
onFocus={() => { }}
onChange={(value: SelectedOptionValue | SelectedOptionValue[]) => {
const dish = menu[Number(value)];
if (dish) onPick(dish);
}}
/>
)}
{match && (
<OverlayTrigger
placement="top"
overlay={
<Tooltip>
{match.parts.map((p, i) => (
<div key={i}>
{p.dish
? `${p.quantity > 1 ? `${p.quantity}× ` : ''}${formatDish(p.dish)}`
: `„${p.text}" — v nabídce nenalezeno`}
</div>
))}
</Tooltip>
}
>
<small className={`menu-hint-price ${differs ? 'text-warning' : 'text-muted'}`}>
{menuPrice != null
? <>Dle nabídky: <strong>{menuPrice} Kč</strong></>
: <>Nalezeno {match.parts.length - unmatched.length}/{match.parts.length} položek</>}
{differs && canEdit && (
<button type="button" className="menu-hint-use" onClick={() => onUsePrice(menuPrice!)}
title="Nastavit částku podle ceny v nabídce">
použít
</button>
)}
</small>
</OverlayTrigger>
)}
</div>
);
}