feat: spin the wheel, objednavani scrape menu, historie cen
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
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
This commit is contained in:
35 files changed
+3979
-15
No files matched your search
@@ -8,8 +8,11 @@ import { cs } from 'date-fns/locale';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import {
|
||||
ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember, PendingQr, TrackingProvider,
|
||||
getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, setTracking, getOrderDates,
|
||||
StoreMenuDish, getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, setTracking, getOrderDates, getStoreMenu,
|
||||
} from '../../../types';
|
||||
import MenuHintCell from '../components/MenuHintCell';
|
||||
import EaterBadge from '../components/EaterBadge';
|
||||
import { countNoteItems } from '../utils/menuMatch';
|
||||
import { computeFeeShare, computeMemberTotal, countActiveMembers } from '../utils/groupFees';
|
||||
import { getStoreUrls, isHttpUrl, storeUrlLabel } from '../utils/storeUrls';
|
||||
import { EVENT_MESSAGE, EVENT_PENDING_QR, SocketContext } from '../context/socket';
|
||||
@@ -147,6 +150,8 @@ export default function OrderGroupsPage() {
|
||||
const [mockOrdersModalOpen, setMockOrdersModalOpen] = useState(false);
|
||||
const [confirmOrderGroup, setConfirmOrderGroup] = useState<OrderGroup | null>(null);
|
||||
const [pageError, setPageError] = useState<string | null>(null);
|
||||
// Stažené nabídky podniků (dle názvu obchodu) pro sloupec „Z nabídky"
|
||||
const [storeMenus, setStoreMenus] = useState<Record<string, StoreMenuDish[] | null>>({});
|
||||
|
||||
const fetchData = async (date?: string) => {
|
||||
try {
|
||||
@@ -308,6 +313,35 @@ export default function OrderGroupsPage() {
|
||||
if (ok) setEditAmounts(prev => { const next = { ...prev }; delete next[key]; return next; });
|
||||
};
|
||||
|
||||
// Nabídky podniků zobrazených skupin — jen pro obchody ze seznamu, každou jednou
|
||||
const groupStoreNames = (data?.groups ?? [])
|
||||
.map(g => (data?.stores ?? []).find(s => s.name.toLowerCase() === g.name.toLowerCase())?.name)
|
||||
.filter((n): n is string => !!n);
|
||||
const groupStoreKey = [...new Set(groupStoreNames)].sort().join('|');
|
||||
useEffect(() => {
|
||||
for (const name of groupStoreKey ? groupStoreKey.split('|') : []) {
|
||||
if (name in storeMenus) continue;
|
||||
setStoreMenus(prev => ({ ...prev, [name]: null }));
|
||||
getStoreMenu({ query: { name } })
|
||||
.then(r => setStoreMenus(prev => ({ ...prev, [name]: r.data?.dishes ?? [] })))
|
||||
.catch(() => setStoreMenus(prev => ({ ...prev, [name]: [] })));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [groupStoreKey]);
|
||||
|
||||
const getGroupMenu = (group: OrderGroup): StoreMenuDish[] | undefined =>
|
||||
Object.entries(storeMenus).find(([name]) => name.toLowerCase() === group.name.toLowerCase())?.[1] ?? undefined;
|
||||
|
||||
/** Výběr položky z nabídky — připojí ji k poznámce a její cenu přičte k částce. */
|
||||
const handlePickDish = (group: OrderGroup, login: string, member: OrderGroupMember, dish: StoreMenuDish) => {
|
||||
const note = member.note?.trim() ? `${member.note.trim()} + ${dish.name}` : dish.name;
|
||||
const amount = dish.price != null ? (member.amount ?? 0) + Math.round(dish.price * 100) : member.amount;
|
||||
return refresh(() => updateGroupMember({ body: { id: group.id, login, note, ...(amount != null && { amount }) } }));
|
||||
};
|
||||
|
||||
const handleUseMenuPrice = (group: OrderGroup, login: string, priceKc: number) =>
|
||||
refresh(() => updateGroupMember({ body: { id: group.id, login, amount: Math.round(priceKc * 100) } }));
|
||||
|
||||
const handleSaveNote = async (groupId: string, login: string) => {
|
||||
const key = `${groupId}:${login}`;
|
||||
const note = editNotes[key] ?? '';
|
||||
@@ -630,6 +664,7 @@ export default function OrderGroupsPage() {
|
||||
<th style={{ width: 180 }}>Částka (bez slev)</th>
|
||||
<th style={{ width: 220 }}>Příplatek</th>
|
||||
<th>Poznámka</th>
|
||||
{getGroupMenu(group)?.length ? <th style={{ width: 240 }}>Z nabídky</th> : null}
|
||||
<th style={{ width: 160 }}>Celkem (s poplatky)</th>
|
||||
<th style={{ width: 40 }}></th>
|
||||
</tr>
|
||||
@@ -646,6 +681,7 @@ export default function OrderGroupsPage() {
|
||||
<tr key={memberLogin}>
|
||||
<td>
|
||||
<span className="user-info">
|
||||
<EaterBadge items={countNoteItems(member.note)} />
|
||||
<strong>{memberLogin}</strong>
|
||||
{memberLogin === group.creatorLogin && (
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip>Zakladatel / objednávající</Tooltip>}>
|
||||
@@ -744,6 +780,18 @@ export default function OrderGroupsPage() {
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
{getGroupMenu(group)?.length ? (
|
||||
<td>
|
||||
<MenuHintCell
|
||||
menu={getGroupMenu(group)}
|
||||
note={member.note}
|
||||
amount={member.amount}
|
||||
canEdit={canEdit}
|
||||
onPick={dish => handlePickDish(group, memberLogin, member, dish)}
|
||||
onUsePrice={price => handleUseMenuPrice(group, memberLogin, price)}
|
||||
/>
|
||||
</td>
|
||||
) : null}
|
||||
<td className="text-end">
|
||||
<small className={memberTotal > 0 ? 'fw-bold' : 'text-muted'}>
|
||||
{memberTotal > 0 ? `${memberTotal / 100} Kč` : '—'}
|
||||
@@ -775,7 +823,7 @@ export default function OrderGroupsPage() {
|
||||
return groupTotal > 0 ? (
|
||||
<tfoot>
|
||||
<tr style={{ fontWeight: 700, borderTop: '2px solid var(--luncher-border)' }}>
|
||||
<td colSpan={4} className="text-end" style={{ fontSize: '0.9em' }}>Celkem za skupinu:</td>
|
||||
<td colSpan={getGroupMenu(group)?.length ? 5 : 4} className="text-end" style={{ fontSize: '0.9em' }}>Celkem za skupinu:</td>
|
||||
<td className="text-end">{groupTotal / 100} Kč</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
Reference in new issue
Block a user