feat: zvýraznění dnů v historii obsahujících objednávky
CI / Generate TypeScript types (push) Successful in 12s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 12s
CI / Server unit tests (push) Successful in 20s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m17s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
This commit is contained in:
+91
-3
@@ -284,9 +284,97 @@ body {
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
|
||||
input[type="date"] {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
// react-datepicker obaluje input do wrapperu – necháme ho zabrat jen potřebnou šířku
|
||||
.react-datepicker-wrapper {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.order-date-input {
|
||||
width: 160px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Zvýraznění dnů, ve kterých existuje alespoň jedna objednávka – tečka pod číslem dne
|
||||
.react-datepicker__day.luncher-order-day {
|
||||
position: relative;
|
||||
font-weight: 700;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 2px;
|
||||
transform: translateX(-50%);
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--luncher-primary, #0d6efd);
|
||||
}
|
||||
|
||||
// U vybraného dne (tmavé pozadí) je tečka světlá, aby byla vidět
|
||||
&.react-datepicker__day--selected::after,
|
||||
&.react-datepicker__day--keyboard-selected::after {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// Vybraný den používá akcentovou barvu aplikace (v obou režimech), místo výchozí modré
|
||||
.react-datepicker__day--selected,
|
||||
.react-datepicker__day--keyboard-selected {
|
||||
background-color: var(--luncher-primary);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--luncher-primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
// Tmavý režim kalendáře (react-datepicker) – navázáno na CSS proměnné motivu
|
||||
[data-bs-theme="dark"] {
|
||||
.react-datepicker {
|
||||
background-color: var(--luncher-bg-card);
|
||||
border-color: var(--luncher-border);
|
||||
color: var(--luncher-text);
|
||||
}
|
||||
|
||||
.react-datepicker__header {
|
||||
background-color: var(--luncher-bg-hover);
|
||||
border-bottom-color: var(--luncher-border);
|
||||
}
|
||||
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker__day-name,
|
||||
.react-datepicker__day,
|
||||
.react-datepicker-year-header {
|
||||
color: var(--luncher-text);
|
||||
}
|
||||
|
||||
.react-datepicker__day:hover,
|
||||
.react-datepicker__month-text:hover {
|
||||
background-color: var(--luncher-bg-hover);
|
||||
}
|
||||
|
||||
.react-datepicker__day--today {
|
||||
color: var(--luncher-primary);
|
||||
}
|
||||
|
||||
.react-datepicker__day--disabled,
|
||||
.react-datepicker__day--outside-month {
|
||||
color: var(--luncher-text-muted);
|
||||
}
|
||||
|
||||
// Šipky pro přepínání měsíců
|
||||
.react-datepicker__navigation-icon::before {
|
||||
border-color: var(--luncher-text-secondary);
|
||||
}
|
||||
|
||||
// Špička popoveru (SVG) míří do hlavičky – sladíme barvy.
|
||||
// !important kvůli vyšší specificitě knihovního pravidla [data-placement].
|
||||
.react-datepicker__triangle {
|
||||
fill: var(--luncher-bg-hover) !important;
|
||||
color: var(--luncher-bg-hover) !important;
|
||||
stroke: var(--luncher-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ import { Alert, Badge, Button, Card, Form, Modal, OverlayTrigger, Table, Tooltip
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faBasketShopping, faChevronLeft, faChevronRight, faCircleCheck, faClockRotateLeft, faGear, faLock, faLockOpen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import { cs } from 'date-fns/locale';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import {
|
||||
ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember, PendingQr,
|
||||
getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes,
|
||||
getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, getOrderDates,
|
||||
} from '../../../types';
|
||||
import { computeFeeShare, computeMemberTotal, countActiveMembers } from '../utils/groupFees';
|
||||
import { EVENT_MESSAGE, EVENT_PENDING_QR, SocketContext } from '../context/socket';
|
||||
@@ -24,6 +27,9 @@ import PendingPayments from '../components/PendingPayments';
|
||||
const SLOT = MealSlot.EXTRA;
|
||||
const TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
|
||||
|
||||
// Český lokál pro date picker (názvy měsíců/dnů, pondělí jako první den)
|
||||
registerLocale('cs', cs);
|
||||
|
||||
/** 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`);
|
||||
@@ -31,6 +37,11 @@ function shiftIsoDate(iso: string, days: number): string {
|
||||
return formatDate(date);
|
||||
}
|
||||
|
||||
/** Převede ISO datum (YYYY-MM-DD) na lokální Date (půlnoc), nebo null. */
|
||||
function isoToDate(iso?: string): Date | null {
|
||||
return iso ? new Date(`${iso}T00:00:00`) : null;
|
||||
}
|
||||
|
||||
function stateBadge(state: GroupState) {
|
||||
const map: Record<GroupState, { bg: string; label: string }> = {
|
||||
[GroupState.OPEN]: { bg: 'success', label: 'Otevřeno' },
|
||||
@@ -53,6 +64,8 @@ export default function OrderGroupsPage() {
|
||||
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);
|
||||
// ISO data dnů, ve kterých existuje aspoň jedna objednávka (pro zvýraznění v date pickeru)
|
||||
const [orderDates, setOrderDates] = useState<string[]>([]);
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [adminModalOpen, setAdminModalOpen] = useState(false);
|
||||
@@ -78,6 +91,12 @@ export default function OrderGroupsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Načte dny s objednávkou pro zvýraznění v date pickeru
|
||||
const fetchOrderDates = async () => {
|
||||
const r = await getOrderDates();
|
||||
if (r.data?.dates) setOrderDates(r.data.dates);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
selectedDateRef.current = selectedDate;
|
||||
}, [selectedDate]);
|
||||
@@ -87,6 +106,11 @@ export default function OrderGroupsPage() {
|
||||
fetchData(selectedDate);
|
||||
}, [auth?.login, selectedDate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth?.login) return;
|
||||
fetchOrderDates();
|
||||
}, [auth?.login]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on(EVENT_MESSAGE, (newData: ClientData) => {
|
||||
// Živé aktualizace se týkají vždy dneška – při zobrazení historie je ignorujeme
|
||||
@@ -139,6 +163,8 @@ export default function OrderGroupsPage() {
|
||||
socket.emit?.('message', result.data as ClientData);
|
||||
}
|
||||
await fetchData();
|
||||
// Sada dnů s objednávkou se mohla změnit (vytvoření/smazání skupiny)
|
||||
fetchOrderDates();
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -265,6 +291,11 @@ export default function OrderGroupsPage() {
|
||||
setSelectedDate(todayIso != null && value >= todayIso ? undefined : value);
|
||||
};
|
||||
|
||||
// Dny s objednávkou jako Date objekty pro zvýraznění v kalendáři
|
||||
const highlightedOrderDates = orderDates
|
||||
.map(d => isoToDate(d))
|
||||
.filter((d): d is Date => d != null);
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
<Header choices={data.choices} />
|
||||
@@ -282,13 +313,16 @@ export default function OrderGroupsPage() {
|
||||
<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 }}
|
||||
<DatePicker
|
||||
selected={isoToDate(displayedIso)}
|
||||
onChange={(d: Date | null) => handleDatePick(d ? formatDate(d) : '')}
|
||||
maxDate={isoToDate(todayIso) ?? undefined}
|
||||
highlightDates={[{ 'luncher-order-day': highlightedOrderDates }]}
|
||||
locale="cs"
|
||||
dateFormat="d. M. yyyy"
|
||||
calendarStartDay={1}
|
||||
popperPlacement="bottom"
|
||||
className={`form-control text-center fw-semibold order-date-input ${isReadOnly ? 'text-muted' : ''}`}
|
||||
/>
|
||||
<span title="Následující den">
|
||||
<FontAwesomeIcon
|
||||
|
||||
Reference in New Issue
Block a user