feat: možnost zobrazení objednávek z historie
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 23s
CI / Build client (push) Successful in 33s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 23s
CI / Build client (push) Successful in 33s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s
This commit is contained in:
@@ -279,6 +279,17 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Varianta navigace mezi dny na stránce objednávání – šipky kolem date pickeru
|
||||||
|
.order-day-navigator {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
input[type="date"] {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// FOOD TABLES - CARD STYLE
|
// FOOD TABLES - CARD STYLE
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Alert, Badge, Button, Card, Form, Modal, OverlayTrigger, Table, Tooltip } from 'react-bootstrap';
|
import { Alert, Badge, Button, Card, Form, Modal, OverlayTrigger, Table, Tooltip } from 'react-bootstrap';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { faBasketShopping, faCircleCheck, faGear, faLock, faLockOpen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
import { faBasketShopping, faChevronLeft, faChevronRight, faCircleCheck, faClockRotateLeft, faGear, faLock, faLockOpen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||||
import {
|
import {
|
||||||
ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember, PendingQr,
|
ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember, PendingQr,
|
||||||
getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes,
|
getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes,
|
||||||
@@ -11,6 +11,7 @@ import { computeFeeShare, computeMemberTotal, countActiveMembers } from '../util
|
|||||||
import { EVENT_MESSAGE, EVENT_PENDING_QR, SocketContext } from '../context/socket';
|
import { EVENT_MESSAGE, EVENT_PENDING_QR, SocketContext } from '../context/socket';
|
||||||
import { useAuth } from '../context/auth';
|
import { useAuth } from '../context/auth';
|
||||||
import { useSettings } from '../context/settings';
|
import { useSettings } from '../context/settings';
|
||||||
|
import { formatDate, formatDateString } from '../Utils';
|
||||||
import Login from '../Login';
|
import Login from '../Login';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import Footer from '../components/Footer';
|
import Footer from '../components/Footer';
|
||||||
@@ -23,6 +24,13 @@ import PendingPayments from '../components/PendingPayments';
|
|||||||
const SLOT = MealSlot.EXTRA;
|
const SLOT = MealSlot.EXTRA;
|
||||||
const TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
|
const TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
|
||||||
|
|
||||||
|
/** Vrátí ISO datum (YYYY-MM-DD) posunuté o předaný počet dní. */
|
||||||
|
function shiftIsoDate(iso: string, days: number): string {
|
||||||
|
const date = new Date(`${iso}T00:00:00`);
|
||||||
|
date.setDate(date.getDate() + days);
|
||||||
|
return formatDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
function stateBadge(state: GroupState) {
|
function stateBadge(state: GroupState) {
|
||||||
const map: Record<GroupState, { bg: string; label: string }> = {
|
const map: Record<GroupState, { bg: string; label: string }> = {
|
||||||
[GroupState.OPEN]: { bg: 'success', label: 'Otevřeno' },
|
[GroupState.OPEN]: { bg: 'success', label: 'Otevřeno' },
|
||||||
@@ -39,6 +47,12 @@ export default function OrderGroupsPage() {
|
|||||||
const socket = useContext(SocketContext);
|
const socket = useContext(SocketContext);
|
||||||
const [data, setData] = useState<ClientData | undefined>();
|
const [data, setData] = useState<ClientData | undefined>();
|
||||||
const [failure, setFailure] = useState(false);
|
const [failure, setFailure] = useState(false);
|
||||||
|
// Vybrané datum pro zobrazení historie (undefined = aktuální den)
|
||||||
|
const [selectedDate, setSelectedDate] = useState<string | undefined>();
|
||||||
|
// ISO datum dnešního dne dle serveru (horní hranice navigace), zjištěné při prvním načtení
|
||||||
|
const [todayIso, setTodayIso] = useState<string | undefined>();
|
||||||
|
// Ref pro socket handler – aby věděl, zda se zobrazuje historie (na ní se živé aktualizace neaplikují)
|
||||||
|
const selectedDateRef = useRef<string | undefined>(undefined);
|
||||||
const [newGroupName, setNewGroupName] = useState('');
|
const [newGroupName, setNewGroupName] = useState('');
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const [adminModalOpen, setAdminModalOpen] = useState(false);
|
const [adminModalOpen, setAdminModalOpen] = useState(false);
|
||||||
@@ -51,22 +65,32 @@ export default function OrderGroupsPage() {
|
|||||||
const [confirmOrderGroup, setConfirmOrderGroup] = useState<OrderGroup | null>(null);
|
const [confirmOrderGroup, setConfirmOrderGroup] = useState<OrderGroup | null>(null);
|
||||||
const [pageError, setPageError] = useState<string | null>(null);
|
const [pageError, setPageError] = useState<string | null>(null);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async (date?: string) => {
|
||||||
try {
|
try {
|
||||||
const r = await getData({ query: { slot: SLOT } });
|
const r = await getData({ query: { slot: SLOT, date } });
|
||||||
if (r.data) setData(r.data);
|
if (r.data) {
|
||||||
|
setData(r.data);
|
||||||
|
// Při zobrazení aktuálního dne si zapamatujeme dnešní ISO datum jako horní hranici navigace
|
||||||
|
if (!date && r.data.isoDate) setTodayIso(r.data.isoDate);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setFailure(true);
|
setFailure(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectedDateRef.current = selectedDate;
|
||||||
|
}, [selectedDate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!auth?.login) return;
|
if (!auth?.login) return;
|
||||||
fetchData();
|
fetchData(selectedDate);
|
||||||
}, [auth?.login]);
|
}, [auth?.login, selectedDate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.on(EVENT_MESSAGE, (newData: ClientData) => {
|
socket.on(EVENT_MESSAGE, (newData: ClientData) => {
|
||||||
|
// Živé aktualizace se týkají vždy dneška – při zobrazení historie je ignorujeme
|
||||||
|
if (selectedDateRef.current) return;
|
||||||
if (newData.slot === SLOT) setData(prev => ({
|
if (newData.slot === SLOT) setData(prev => ({
|
||||||
...newData,
|
...newData,
|
||||||
stores: newData.stores ?? prev?.stores,
|
stores: newData.stores ?? prev?.stores,
|
||||||
@@ -74,11 +98,34 @@ export default function OrderGroupsPage() {
|
|||||||
});
|
});
|
||||||
// Nová nevyřízená platba (QR kód) – připojíme do dat, aby se zobrazila i bez znovunačtení stránky
|
// Nová nevyřízená platba (QR kód) – připojíme do dat, aby se zobrazila i bez znovunačtení stránky
|
||||||
socket.on(EVENT_PENDING_QR, (pendingQr: PendingQr) => {
|
socket.on(EVENT_PENDING_QR, (pendingQr: PendingQr) => {
|
||||||
|
if (selectedDateRef.current) return;
|
||||||
setData(prev => prev ? { ...prev, pendingQrs: [...(prev.pendingQrs ?? []), pendingQr] } : prev);
|
setData(prev => prev ? { ...prev, pendingQrs: [...(prev.pendingQrs ?? []), pendingQr] } : prev);
|
||||||
});
|
});
|
||||||
return () => { socket.off(EVENT_MESSAGE); socket.off(EVENT_PENDING_QR); };
|
return () => { socket.off(EVENT_MESSAGE); socket.off(EVENT_PENDING_QR); };
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
|
// Navigace mezi dny pomocí klávesových šipek (←/→), obdobně jako na hlavní stránce
|
||||||
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
|
// Ignorujeme, pokud uživatel právě píše do formulářového pole
|
||||||
|
const tag = (e.target as HTMLElement)?.tagName;
|
||||||
|
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return;
|
||||||
|
const currentIso = data?.isoDate;
|
||||||
|
if (!currentIso) return;
|
||||||
|
if (e.keyCode === 37) {
|
||||||
|
// Předchozí den – do minulosti bez omezení
|
||||||
|
setSelectedDate(shiftIsoDate(currentIso, -1));
|
||||||
|
} else if (e.keyCode === 39 && todayIso != null && currentIso < todayIso) {
|
||||||
|
// Následující den – nejvýše po dnešek (na dnešek přes undefined kvůli živým aktualizacím)
|
||||||
|
const target = shiftIsoDate(currentIso, 1);
|
||||||
|
setSelectedDate(target >= todayIso ? undefined : target);
|
||||||
|
}
|
||||||
|
}, [data?.isoDate, todayIso]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
const refresh = async (fn: () => Promise<any>): Promise<boolean> => {
|
const refresh = async (fn: () => Promise<any>): Promise<boolean> => {
|
||||||
setPageError(null);
|
setPageError(null);
|
||||||
const result = await fn();
|
const result = await fn();
|
||||||
@@ -170,7 +217,10 @@ export default function OrderGroupsPage() {
|
|||||||
if (ok) setEditTimes(prev => { const next = { ...prev }; delete next[group.id]; return next; });
|
if (ok) setEditTimes(prev => { const next = { ...prev }; delete next[group.id]; return next; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pozn.: tyto funkce se volají až v renderu, kde je k dispozici `selectedDate`.
|
||||||
|
// Historie (jiný než aktuální den) je vždy read-only.
|
||||||
const canEditMember = (group: OrderGroup, targetLogin: string) => {
|
const canEditMember = (group: OrderGroup, targetLogin: string) => {
|
||||||
|
if (selectedDate) return false;
|
||||||
if (group.state === GroupState.ORDERED) return false;
|
if (group.state === GroupState.ORDERED) return false;
|
||||||
if (auth?.login === group.creatorLogin) return true;
|
if (auth?.login === group.creatorLogin) return true;
|
||||||
if (auth?.login === targetLogin && group.state === GroupState.OPEN) return true;
|
if (auth?.login === targetLogin && group.state === GroupState.OPEN) return true;
|
||||||
@@ -178,6 +228,7 @@ export default function OrderGroupsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const canManageMembers = (group: OrderGroup) => {
|
const canManageMembers = (group: OrderGroup) => {
|
||||||
|
if (selectedDate) return false;
|
||||||
if (group.state === GroupState.ORDERED) return false;
|
if (group.state === GroupState.ORDERED) return false;
|
||||||
if (auth?.login === group.creatorLogin) return true;
|
if (auth?.login === group.creatorLogin) return true;
|
||||||
return group.state === GroupState.OPEN;
|
return group.state === GroupState.OPEN;
|
||||||
@@ -196,6 +247,24 @@ export default function OrderGroupsPage() {
|
|||||||
const stores = data.stores ?? [];
|
const stores = data.stores ?? [];
|
||||||
const groups = data.groups ?? [];
|
const groups = data.groups ?? [];
|
||||||
|
|
||||||
|
// Zobrazené datum a režim historie (vše read-only, pokud nejde o aktuální den)
|
||||||
|
const displayedIso = data.isoDate;
|
||||||
|
const isToday = !selectedDate || (todayIso != null && displayedIso === todayIso);
|
||||||
|
const isReadOnly = !isToday;
|
||||||
|
const canGoNext = todayIso != null && displayedIso != null && displayedIso < todayIso;
|
||||||
|
|
||||||
|
const goToDay = (offset: number) => {
|
||||||
|
if (!displayedIso) return;
|
||||||
|
const target = shiftIsoDate(displayedIso, offset);
|
||||||
|
// Na dnešek (či dál) se vracíme přes undefined, aby se obnovily živé aktualizace
|
||||||
|
setSelectedDate(todayIso != null && target >= todayIso ? undefined : target);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDatePick = (value: string) => {
|
||||||
|
if (!value) return;
|
||||||
|
setSelectedDate(todayIso != null && value >= todayIso ? undefined : value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
<Header choices={data.choices} />
|
<Header choices={data.choices} />
|
||||||
@@ -208,6 +277,40 @@ export default function OrderGroupsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<p style={{ color: 'var(--luncher-text-muted)' }}>Skupinové objednávky z obchodů a restaurací</p>
|
<p style={{ color: 'var(--luncher-text-muted)' }}>Skupinové objednávky z obchodů a restaurací</p>
|
||||||
|
|
||||||
|
{/* Navigace mezi dny – šipky kolem výběru data (i klávesami ←/→) */}
|
||||||
|
<div className="day-navigator order-day-navigator">
|
||||||
|
<span title="Předchozí den">
|
||||||
|
<FontAwesomeIcon icon={faChevronLeft} onClick={() => goToDay(-1)} />
|
||||||
|
</span>
|
||||||
|
<Form.Control
|
||||||
|
type="date"
|
||||||
|
value={displayedIso ?? ''}
|
||||||
|
max={todayIso}
|
||||||
|
onChange={e => handleDatePick(e.target.value)}
|
||||||
|
className={isReadOnly ? 'text-muted' : ''}
|
||||||
|
style={{ maxWidth: 200 }}
|
||||||
|
/>
|
||||||
|
<span title="Následující den">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faChevronRight}
|
||||||
|
style={{ visibility: canGoNext ? 'visible' : 'hidden' }}
|
||||||
|
onClick={() => canGoNext && goToDay(1)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isReadOnly && (
|
||||||
|
<Alert variant="secondary" className="d-flex align-items-center gap-2 py-2">
|
||||||
|
<FontAwesomeIcon icon={faClockRotateLeft} />
|
||||||
|
<span>
|
||||||
|
Prohlížíte historii ze dne <strong>{displayedIso ? formatDateString(displayedIso) : data.date}</strong> – data jsou pouze pro čtení.
|
||||||
|
</span>
|
||||||
|
<Button variant="link" size="sm" className="p-0 ms-auto" onClick={() => setSelectedDate(undefined)}>
|
||||||
|
Zpět na dnešek
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
{pageError && (
|
{pageError && (
|
||||||
<Alert variant="danger" dismissible onClose={() => setPageError(null)} className="mt-2">
|
<Alert variant="danger" dismissible onClose={() => setPageError(null)} className="mt-2">
|
||||||
{pageError}
|
{pageError}
|
||||||
@@ -216,7 +319,8 @@ export default function OrderGroupsPage() {
|
|||||||
|
|
||||||
<div className="content-wrapper">
|
<div className="content-wrapper">
|
||||||
<div className="content" style={{ maxWidth: 1200 }}>
|
<div className="content" style={{ maxWidth: 1200 }}>
|
||||||
{/* Vytvoření nové skupiny */}
|
{/* Vytvoření nové skupiny – pouze pro aktuální den */}
|
||||||
|
{!isReadOnly && (
|
||||||
<div className="choice-section fade-in mb-4">
|
<div className="choice-section fade-in mb-4">
|
||||||
<h5>Vytvořit skupinu</h5>
|
<h5>Vytvořit skupinu</h5>
|
||||||
{stores.length === 0 ? (
|
{stores.length === 0 ? (
|
||||||
@@ -242,10 +346,13 @@ export default function OrderGroupsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Seznam skupin */}
|
{/* Seznam skupin */}
|
||||||
{groups.length === 0 && (
|
{groups.length === 0 && (
|
||||||
<p className="text-muted fade-in">Zatím žádné skupiny pro dnešní den.</p>
|
<p className="text-muted fade-in">
|
||||||
|
{isReadOnly ? 'Pro tento den nejsou žádné skupiny.' : 'Zatím žádné skupiny pro dnešní den.'}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{groups.map(group => {
|
{groups.map(group => {
|
||||||
@@ -274,7 +381,7 @@ export default function OrderGroupsPage() {
|
|||||||
<small className="text-muted">zakladatel: {group.creatorLogin}</small>
|
<small className="text-muted">zakladatel: {group.creatorLogin}</small>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
{isCreator && !isOrdered && (
|
{!isReadOnly && isCreator && !isOrdered && (
|
||||||
<>
|
<>
|
||||||
<Button variant="outline-info" size="sm" onClick={() => setFeesModal(group)} title="Upravit poplatky a slevu">
|
<Button variant="outline-info" size="sm" onClick={() => setFeesModal(group)} title="Upravit poplatky a slevu">
|
||||||
Poplatky
|
Poplatky
|
||||||
@@ -292,7 +399,7 @@ export default function OrderGroupsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isCreator && isOrdered && (
|
{!isReadOnly && isCreator && isOrdered && (
|
||||||
<>
|
<>
|
||||||
{settings?.bankAccount && settings?.holderName && !group.qrGenerated && (
|
{settings?.bankAccount && settings?.holderName && !group.qrGenerated && (
|
||||||
<Button variant="primary" size="sm" onClick={() => setPayModal(group)}>
|
<Button variant="primary" size="sm" onClick={() => setPayModal(group)}>
|
||||||
@@ -305,7 +412,7 @@ export default function OrderGroupsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isMember && !isOrdered && !isLocked && (
|
{!isReadOnly && !isMember && !isOrdered && !isLocked && (
|
||||||
<Button variant="outline-success" size="sm" onClick={() => handleJoin(group.id)}>
|
<Button variant="outline-success" size="sm" onClick={() => handleJoin(group.id)}>
|
||||||
<FontAwesomeIcon icon={faUserPlus} className="me-1" />
|
<FontAwesomeIcon icon={faUserPlus} className="me-1" />
|
||||||
Přidat se
|
Přidat se
|
||||||
@@ -493,7 +600,7 @@ export default function OrderGroupsPage() {
|
|||||||
{/* Časy objednání a doručení */}
|
{/* Časy objednání a doručení */}
|
||||||
{isOrdered && (
|
{isOrdered && (
|
||||||
<div className="px-3 py-2 border-top">
|
<div className="px-3 py-2 border-top">
|
||||||
{isCreator && editingTimes ? (
|
{!isReadOnly && isCreator && editingTimes ? (
|
||||||
<div className="d-flex align-items-center gap-3 flex-wrap">
|
<div className="d-flex align-items-center gap-3 flex-wrap">
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex align-items-center gap-1">
|
||||||
<small className="text-muted text-nowrap">Objednáno v:</small>
|
<small className="text-muted text-nowrap">Objednáno v:</small>
|
||||||
@@ -526,9 +633,9 @@ export default function OrderGroupsPage() {
|
|||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="d-flex align-items-center gap-3 flex-wrap"
|
className="d-flex align-items-center gap-3 flex-wrap"
|
||||||
style={{ cursor: isCreator ? 'pointer' : undefined }}
|
style={{ cursor: !isReadOnly && isCreator ? 'pointer' : undefined }}
|
||||||
onClick={() => isCreator && setEditTimes(prev => ({ ...prev, [group.id]: { orderedAt: group.orderedAt ?? '', deliveryAt: group.deliveryAt ?? '' } }))}
|
onClick={() => !isReadOnly && isCreator && setEditTimes(prev => ({ ...prev, [group.id]: { orderedAt: group.orderedAt ?? '', deliveryAt: group.deliveryAt ?? '' } }))}
|
||||||
title={isCreator ? 'Klikněte pro úpravu časů' : undefined}
|
title={!isReadOnly && isCreator ? 'Klikněte pro úpravu časů' : undefined}
|
||||||
>
|
>
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
Objednáno v: <strong>{group.orderedAt ?? '—'}</strong>
|
Objednáno v: <strong>{group.orderedAt ?? '—'}</strong>
|
||||||
@@ -545,12 +652,14 @@ export default function OrderGroupsPage() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Nevyřízené platby přihlášeného uživatele */}
|
{/* Nevyřízené platby přihlášeného uživatele – jen v režimu aktuálního dne */}
|
||||||
<PendingPayments
|
{!isReadOnly && (
|
||||||
pendingQrs={data.pendingQrs}
|
<PendingPayments
|
||||||
login={auth.login}
|
pendingQrs={data.pendingQrs}
|
||||||
onDismissed={fetchData}
|
login={auth.login}
|
||||||
/>
|
onDismissed={() => fetchData()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -584,7 +693,7 @@ export default function OrderGroupsPage() {
|
|||||||
<PayForGroupModal
|
<PayForGroupModal
|
||||||
isOpen={!!payModal}
|
isOpen={!!payModal}
|
||||||
onClose={() => setPayModal(null)}
|
onClose={() => setPayModal(null)}
|
||||||
onSuccess={fetchData}
|
onSuccess={() => fetchData()}
|
||||||
group={payModal}
|
group={payModal}
|
||||||
groupId={payModal.id}
|
groupId={payModal.id}
|
||||||
payerLogin={auth.login}
|
payerLogin={auth.login}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
"Možnost zobrazení objednávek z historie",
|
||||||
"Podpora neplatících osob u objednávání",
|
"Podpora neplatících osob u objednávání",
|
||||||
"Zobrazení neuhrazených plateb i na stránce objednávek",
|
"Zobrazení neuhrazených plateb i na stránce objednávek",
|
||||||
"Oprava duplicitního zobrazení QR kódu u Pizza day",
|
"Oprava duplicitního zobrazení QR kódu u Pizza day",
|
||||||
|
|||||||
+9
-1
@@ -161,7 +161,15 @@ app.use("/api/", (req, res, next) => {
|
|||||||
/** Vrátí data pro aktuální den. */
|
/** Vrátí data pro aktuální den. */
|
||||||
app.get("/api/data", async (req, res) => {
|
app.get("/api/data", async (req, res) => {
|
||||||
let date = undefined;
|
let date = undefined;
|
||||||
if (req.query.dayIndex != null && typeof req.query.dayIndex === 'string') {
|
if (req.query.date != null && typeof req.query.date === 'string') {
|
||||||
|
// Konkrétní datum (YYYY-MM-DD) – umožňuje načtení historie i mimo aktuální týden
|
||||||
|
const parsed = new Date(`${req.query.date}T00:00:00`);
|
||||||
|
if (isNaN(parsed.getTime())) {
|
||||||
|
return res.status(400).json({ error: 'Neplatné datum' });
|
||||||
|
}
|
||||||
|
// Budoucnost ořízneme na dnešek – do budoucna historii nedává smysl zobrazovat
|
||||||
|
date = parsed.getTime() > getToday().getTime() ? getToday() : parsed;
|
||||||
|
} else if (req.query.dayIndex != null && typeof req.query.dayIndex === 'string') {
|
||||||
const index = parseInt(req.query.dayIndex);
|
const index = parseInt(req.query.dayIndex);
|
||||||
if (!isNaN(index)) {
|
if (!isNaN(index)) {
|
||||||
date = getDateForWeekIndex(parseInt(req.query.dayIndex));
|
date = getDateForWeekIndex(parseInt(req.query.dayIndex));
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function getEmptyData(date?: Date): ClientData {
|
|||||||
return {
|
return {
|
||||||
todayDayIndex: getDayOfWeekIndex(getToday()),
|
todayDayIndex: getDayOfWeekIndex(getToday()),
|
||||||
date: getHumanDate(usedDate),
|
date: getHumanDate(usedDate),
|
||||||
|
isoDate: formatDate(usedDate),
|
||||||
isWeekend: getIsWeekend(usedDate),
|
isWeekend: getIsWeekend(usedDate),
|
||||||
dayIndex: getDayOfWeekIndex(usedDate),
|
dayIndex: getDayOfWeekIndex(usedDate),
|
||||||
choices: {},
|
choices: {},
|
||||||
@@ -583,6 +584,7 @@ export async function getClientData(date?: Date, slot?: MealSlot): Promise<Clien
|
|||||||
return {
|
return {
|
||||||
...clientData,
|
...clientData,
|
||||||
todayDayIndex: getDayOfWeekIndex(getToday()),
|
todayDayIndex: getDayOfWeekIndex(getToday()),
|
||||||
|
isoDate: formatDate(targetDate),
|
||||||
...(slot === MealSlot.EXTRA ? { slot: MealSlot.EXTRA } : {}),
|
...(slot === MealSlot.EXTRA ? { slot: MealSlot.EXTRA } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,15 @@ get:
|
|||||||
type: integer
|
type: integer
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 4
|
maximum: 4
|
||||||
|
- in: query
|
||||||
|
name: date
|
||||||
|
description: >-
|
||||||
|
Konkrétní datum (YYYY-MM-DD), pro které se mají vrátit data. Má přednost
|
||||||
|
před dayIndex a umožňuje načtení historických dat i mimo aktuální týden.
|
||||||
|
Datum v budoucnosti je oříznuto na dnešek.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
- in: query
|
- in: query
|
||||||
name: slot
|
name: slot
|
||||||
description: Slot jídla. Pokud není předán, je použit výchozí slot (oběd).
|
description: Slot jídla. Pokud není předán, je použit výchozí slot (oběd).
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ ClientData:
|
|||||||
date:
|
date:
|
||||||
description: Human-readable datum dne
|
description: Human-readable datum dne
|
||||||
type: string
|
type: string
|
||||||
|
isoDate:
|
||||||
|
description: Datum zobrazeného dne ve formátu YYYY-MM-DD (pro navigaci mezi dny)
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
isWeekend:
|
isWeekend:
|
||||||
description: Příznak, zda je tento den víkend
|
description: Příznak, zda je tento den víkend
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user