Skip to content

Commit 3c17a64

Browse files
committed
fix!: fix timezone problems.
1 parent 475605c commit 3c17a64

12 files changed

Lines changed: 148 additions & 84 deletions

File tree

src/app/admin/prepare/order/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import React, { useEffect, useMemo, useState } from 'react';
44
import { AlertTriangle, CheckCircle, Minus, Pizza, Plus, ScanIcon, XIcon } from 'lucide-react';
55
import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner";
66
import { ITEM_STATUSES, ItemStatus, ORDER_STATUSES, OrderDocument } from '@/model/order';
7-
import { timeslotToDate, timeslotToLocalTime } from '@/lib/time';
7+
import { timeslotToUTCDate, timeslotToLocalTime } from '@/lib/time';
88
import SearchInput from '@/app/components/SearchInput';
99
import Button from '@/app/components/Button';
1010
import { ItemDocument } from "@/model/item";
1111
import { ordersSortedByTimeslots } from "@/lib/order";
1212
import { Heading } from "@/app/components/layout/Heading";
1313
import ErrorMessage from "@/app/components/ErrorMessage";
14+
import { UTCDate } from "@date-fns/utc";
1415

1516
interface ItemType {
1617
id: string;
@@ -432,7 +433,7 @@ const OrderManagerDashboard = () => {
432433
const updatedOrder = {
433434
...order,
434435
status: ORDER_STATUSES.DELIVERED,
435-
finishedAt: new Date()
436+
finishedAt: new UTCDate()
436437
};
437438

438439
try {
@@ -486,8 +487,8 @@ const OrderManagerDashboard = () => {
486487

487488
// Sort by timeslot
488489
return filtered.sort((a, b) => {
489-
const timeA = timeslotToDate(a.timeslot).getTime();
490-
const timeB = timeslotToDate(b.timeslot).getTime();
490+
const timeA = timeslotToUTCDate(a.timeslot).getTime();
491+
const timeB = timeslotToUTCDate(b.timeslot).getTime();
491492
return timeA - timeB;
492493
});
493494
}, [orders, activeTab, searchFilter]);
@@ -497,7 +498,7 @@ const OrderManagerDashboard = () => {
497498
const now = Date.now()
498499
return filteredOrders.filter(order =>
499500
(order.status !== ORDER_STATUSES.DELIVERED && order.status !== ORDER_STATUSES.CANCELLED) &&
500-
timeslotToDate(order.timeslot).getTime() < now
501+
timeslotToUTCDate(order.timeslot).getTime() < now
501502
);
502503
}, [filteredOrders]);
503504

@@ -586,7 +587,7 @@ const OrderManagerDashboard = () => {
586587
{filteredOrders.map(order => {
587588
const now = Date.now()
588589
const isOverdue = (order.status !== ORDER_STATUSES.DELIVERED && order.status !== ORDER_STATUSES.CANCELLED)
589-
&& timeslotToDate(order.timeslot).getTime() < now
590+
&& timeslotToUTCDate(order.timeslot).getTime() < now
590591

591592
return (
592593
<OrderCard

src/app/api/manage/system/status/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import dbConnect from "@/lib/dbConnect";
22
import { getSystemStatus } from "@/lib/serverAuth";
3+
import { UTCDate } from "@date-fns/utc";
34

45
export const dynamic = "force-dynamic";
56
export const fetchCache = "force-no-store";
@@ -10,5 +11,5 @@ export const fetchCache = "force-no-store";
1011
*/
1112
export async function GET(request: Request) {
1213
await dbConnect();
13-
return Response.json({ status: await getSystemStatus(), timestamp: new Date() });
14+
return Response.json({ status: await getSystemStatus(), timestamp: new UTCDate(), timestampUTC: new UTCDate() });
1415
}

src/app/components/ErrorMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ interface ErrorMessageProps {
33
t?: any
44
}
55

6-
const ErrorMessage = ({ error, t }: ErrorMessageProps) => {
6+
const ErrorMessage = ({ error }: ErrorMessageProps) => {
77
return (
88
<div className="flex flex-col border-4 border-gray-200 rounded-2xl p-4">
99
<span className="text-2xl font-semibold text-red-500">{error}</span>

src/app/components/Timeline.tsx

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,95 @@ import useOrderStore from "@/app/zustand/order";
77
import { AggregatedSlotData } from "@/model/timeslot";
88
import { ORDER_AMOUNT_THRESHOLDS, TIME_SLOT_CONFIG } from "@/config";
99
import { timeslotToLocalTime } from "@/lib/time";
10+
import { useShallow } from "zustand/react/shallow";
1011

11-
const generateAllTimeslots = (startDate: Date, stopDate: Date): AggregatedSlotData[] => {
12-
const timeSlots: AggregatedSlotData[] = [];
13-
const currentTime = new Date(startDate);
14-
15-
while (currentTime <= stopDate) {
16-
timeSlots.push({
17-
time: currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
18-
height: 0,
19-
});
20-
currentTime.setMinutes(currentTime.getMinutes() + 5);
21-
}
22-
23-
return timeSlots;
24-
};
25-
26-
const Timeline = () => {
12+
const Timeline = ({
13+
noClick
14+
}: { noClick: boolean } = { noClick: false }) => {
2715
const [timeslots, setTimeslots] = useState<AggregatedSlotData[]>([]);
2816
const [isLoading, setIsLoading] = useState(true);
2917
const [error, setError] = useState<string | null>(null);
3018
const t = useTranslations();
3119

32-
// Timeline date range calculation (can be done outside component if static)
33-
const { startDate, stopDate } = useMemo(() => {
34-
const startDate = new Date()
35-
startDate.setHours(startDate.getHours() - 1);
36-
startDate.setMinutes(0, 0, 0);
20+
const { setTimeslot, order, totalItems } = useOrderStore(
21+
useShallow((state) => ({
22+
setTimeslot: state.setTimeslot,
23+
order: state.currentOrder,
24+
totalItems: state.getTotalItemCount()
25+
}))
26+
);
3727

38-
const stopDate = new Date();
39-
stopDate.setHours(stopDate.getHours() + 1);
40-
stopDate.setMinutes(59, 59, 999);
41-
return { startDate, stopDate };
28+
// Generate fallback timeslots
29+
const generateFallbackSlots = useCallback(() => {
30+
const slots: AggregatedSlotData[] = [];
31+
const start = new Date();
32+
start.setHours(start.getHours() - 1, 0, 0, 0);
33+
34+
const end = new Date();
35+
end.setHours(end.getHours() + 1, 59, 59, 999);
36+
37+
const current = new Date(start);
38+
while (current <= end) {
39+
slots.push({
40+
time: current.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
41+
height: 0,
42+
});
43+
current.setMinutes(current.getMinutes() + 5);
44+
}
45+
return slots;
4246
}, []);
4347

44-
const { setTimeslot } = useOrderStore();
4548
const fetchTimeline = useCallback(async () => {
4649
setError(null);
47-
let apiSlots: AggregatedSlotData[] = [];
48-
const response = await fetch('/api/timeline');
49-
if (!response.ok) {
50-
setError('Failed to fetch timeline data');
51-
apiSlots = generateAllTimeslots(startDate, stopDate);
52-
} else {
53-
apiSlots = await response.json()
54-
apiSlots = apiSlots.map((item: AggregatedSlotData) => ({
55-
...item,
56-
time: timeslotToLocalTime(item.time),
57-
}))
50+
try {
51+
const response = await fetch('/api/timeline');
52+
let apiSlots: AggregatedSlotData[];
53+
54+
if (!response.ok) {
55+
setError('Failed to fetch timeline data');
56+
apiSlots = generateFallbackSlots();
57+
} else {
58+
apiSlots = await response.json();
59+
apiSlots = apiSlots.map(item => ({
60+
...item,
61+
time: timeslotToLocalTime(item.time),
62+
})) as AggregatedSlotData[];
63+
}
64+
65+
setTimeslots(apiSlots);
66+
} catch (err) {
67+
setError('Network error');
68+
setTimeslots(generateFallbackSlots());
69+
} finally {
70+
setIsLoading(false);
5871
}
72+
}, [generateFallbackSlots]);
73+
74+
// Enhanced timeslots with selected state highlighting
75+
const enhancedTimeslots = useMemo(() => {
76+
const selectedTime = timeslotToLocalTime(order.timeslot);
77+
return timeslots.map(slot => ({
78+
...slot,
79+
...(slot.time === selectedTime && {
80+
border: TIME_SLOT_CONFIG.BORDER_STYLES.CURRENT_SLOT_COLOR,
81+
borderwidth: TIME_SLOT_CONFIG.BORDER_STYLES.CURRENT_SLOT_WIDTH,
82+
color: TIME_SLOT_CONFIG.STATUS_COLORS.SELECT,
83+
ordersAmount: totalItems > 0 ? totalItems : 1
84+
})
85+
}));
86+
}, [timeslots, order.timeslot, totalItems]);
5987

60-
setTimeslots(apiSlots);
61-
setIsLoading(false);
62-
}, [startDate, stopDate, TIME_SLOT_CONFIG.UPDATE_EVERY_SECONDS]);
88+
const handleBarClick = useCallback((event: any) => {
89+
if (!noClick && event?.activeLabel) {
90+
setTimeslot(event.activeLabel);
91+
}
92+
}, [noClick, setTimeslot]);
6393

6494
useEffect(() => {
6595
fetchTimeline();
6696
const interval = setInterval(fetchTimeline, TIME_SLOT_CONFIG.UPDATE_EVERY_SECONDS * 1000);
6797
return () => clearInterval(interval);
68-
}, [fetchTimeline, TIME_SLOT_CONFIG.UPDATE_EVERY_SECONDS]);
69-
70-
const handleBarClick = useCallback((event: any) => {
71-
if (event?.activeLabel) {
72-
setTimeslot(event.activeLabel);
73-
}
74-
}, []);
98+
}, [fetchTimeline]);
7599

76100
if (isLoading) {
77101
return (
@@ -85,22 +109,21 @@ const Timeline = () => {
85109
<div className="w-full">
86110
{error && (
87111
<div className="mb-2 p-2 bg-yellow-100 border border-yellow-400 text-yellow-700 rounded">
88-
{t('components.timeline.warning', { error: error })}
112+
{t('components.timeline.warning', { error })}
89113
</div>
90114
)}
91115

92116
<ResponsiveContainer height={300}>
93117
<BarChart
94-
data={timeslots}
118+
data={enhancedTimeslots}
95119
onClick={handleBarClick}
96120
margin={{ top: 5, right: 30, left: -30, bottom: 5 }}
97121
>
98122
<XAxis dataKey="time"/>
99123
<YAxis domain={[0, ORDER_AMOUNT_THRESHOLDS.MAX]}/>
100124
<Tooltip/>
101-
102125
<Bar dataKey="ordersAmount" name="Orders" fill="#007bff">
103-
{timeslots.map((entry, index) => (
126+
{enhancedTimeslots.map((entry, index) => (
104127
<Cell
105128
key={`cell-${index}-${entry.time}`}
106129
fill={entry.color ?? "#007bff"}

src/app/components/order/OrderSummary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ const OrderSummary: React.FC<OrderSummaryPageProps> = ({
145145
<p className="mb-4 text-base font-light leading-relaxed text-gray-700">
146146
{t('cart.timeslot.subtitle')}
147147
</p>
148-
<Timeline/>
148+
<Timeline noClick={false}/>
149149
</div>
150150
</div>
151151

src/app/order/[orderNumber]/clientOrderPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useEffect, useState } from "react";
4-
import { timeslotToDate, timeslotToLocalTime } from "@/lib/time";
4+
import { timeslotToUTCDate, timeslotToLocalTime } from "@/lib/time";
55
import { Order, OrderStatus } from "@/model/order";
66
import OrderQR from "@/app/components/order/OrderQR";
77
import { Loading } from "@/app/components/Loading";
@@ -125,10 +125,10 @@ export default function ClientOrderPage({ orderNumber }: { orderNumber: string }
125125
<div className="bg-blue-50 rounded-2xl p-6 mb-6 max-w-sm mx-auto">
126126
<p className="text-sm text-gray-600 mb-1">{t('order_status.status.ready_by')}</p>
127127
<p className="text-3xl font-light text-blue-600">
128-
{timeslotToLocalTime(formatDate(timeslotToDate(order.timeslot), 'HH:mm'))}
128+
{timeslotToLocalTime(formatDate(timeslotToUTCDate(order.timeslot), 'HH:mm'))}
129129
</p>
130130
<p className="text-sm text-gray-600">
131-
{formatDate(timeslotToDate(order.timeslot), 'dd.MM.yyyy')}
131+
{formatDate(timeslotToUTCDate(order.timeslot), 'dd.MM.yyyy')}
132132
</p>
133133
</div>
134134
)}

src/app/order/list/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ const OrdersSection = React.memo(({ t, ordersCount, ordersList }: any) => (
149149
</div>
150150
));
151151

152-
const OrderItem = React.memo(({ t, order }: { order: any }) => (
152+
const OrderItem = React.memo(({ t, order }: { t: any, order: any }) => (
153153
<a
154154
href={`/order/${order.id}`}
155155
className={`block p-6 bg-${order.statusColor}-50 text-${order.statusColor}-800 hover:bg-${order.statusColor}-500 transition-colors group`}
@@ -215,7 +215,7 @@ const TimelineSection = React.memo(({ t }: any) => (
215215
</p>
216216
</div>
217217
<div className="p-6">
218-
<Timeline/>
218+
<Timeline noClick={true}/>
219219
</div>
220220
</div>
221221
));

src/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const TIME_SLOT_CONFIG = {
1515
OK: '#0ce504',
1616
WARN: '#e59604',
1717
CRIT: '#B33',
18+
SELECT: '#7b7be1',
1819
},
1920

2021
BORDER_STYLES: {

src/lib/order.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { OrderDocument } from "@/model/order";
22
import { ItemDocument } from "@/model/item";
3-
import { timeslotToDate } from "@/lib/time";
3+
import { timeslotToUTCDate } from "@/lib/time";
44

55
export function ordersSortedByTimeslots(orders: OrderDocument[]): OrderDocument[] {
66
const currentTime = new Date().getTime()
@@ -10,7 +10,7 @@ export function ordersSortedByTimeslots(orders: OrderDocument[]): OrderDocument[
1010
const filteredOrders = orders.filter(order => order.status !== "cancelled" && order.status !== "delivered");
1111

1212
for (const order of filteredOrders) {
13-
const tsDate = timeslotToDate(order.timeslot).getTime()
13+
const tsDate = timeslotToUTCDate(order.timeslot).getTime()
1414
const diff = tsDate - currentTime;
1515
priorityByOrderId.set(order, diff)
1616
}

src/lib/system.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)