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
94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
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>
|
||
);
|
||
}
|