From b5626a0a582611e720c2682a47d14a5243f38bc3 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Fri, 18 Jul 2025 13:50:22 +0300 Subject: [PATCH] refactor(order-datetime): update date and time handling to use datetime_start and datetime_end for improved consistency --- apps/web/components/orders/order-datetime.tsx | 6 +- .../orders/order-form/datetime-select.tsx | 29 +++--- .../orders/order-form/submit-button.tsx | 3 +- .../orders/orders-list/date-select.tsx | 23 +++-- .../components/orders/orders-list/index.tsx | 14 ++- apps/web/components/schedule/calendar.tsx | 11 ++- .../day-slots-list/day-slot-add-form.tsx | 10 +- .../schedule/day-slots-list/index.tsx | 9 +- .../schedule/day-slots-list/slot-card.tsx | 5 +- .../schedule/slot-datetime/slot-date.tsx | 2 +- .../schedule/slot-datetime/slot-time.tsx | 32 +++--- apps/web/components/shared/order-card.tsx | 11 ++- .../components/shared/time-range/readonly.tsx | 18 +++- apps/web/hooks/api/slots.ts | 8 +- apps/web/utils/i18n/config.ts | 2 +- packages/graphql/api/notify.ts | 26 ++--- packages/graphql/api/orders.ts | 17 ++-- packages/graphql/api/slots.ts | 29 +++--- packages/graphql/graphql.config.cjs | 2 + packages/graphql/operations/orders.graphql | 10 +- packages/graphql/operations/slots.graphql | 13 ++- .../graphql/types/operations.generated.ts | 90 ++++++----------- packages/utils/src/datetime-format.ts | 98 ++++++++++--------- 23 files changed, 241 insertions(+), 227 deletions(-) diff --git a/apps/web/components/orders/order-datetime.tsx b/apps/web/components/orders/order-datetime.tsx index 3abfed0..0538219 100644 --- a/apps/web/components/orders/order-datetime.tsx +++ b/apps/web/components/orders/order-datetime.tsx @@ -13,12 +13,12 @@ export function OrderDateTime({ documentId }: Readonly) { return (
- {formatDate(order.slot?.date).user()} + {order.slot?.datetime_start ? formatDate(order.slot.datetime_start).user() : ''}
); diff --git a/apps/web/components/orders/order-form/datetime-select.tsx b/apps/web/components/orders/order-form/datetime-select.tsx index 7f0430c..0690e59 100644 --- a/apps/web/components/orders/order-form/datetime-select.tsx +++ b/apps/web/components/orders/order-form/datetime-select.tsx @@ -4,7 +4,7 @@ import { useAvailableTimeSlotsQuery } from '@/hooks/api/slots'; import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; import { Calendar } from '@repo/ui/components/ui/calendar'; -import { formatDate } from '@repo/utils/datetime-format'; +import { formatTime } from '@repo/utils/datetime-format'; import dayjs from 'dayjs'; import { useState } from 'react'; @@ -16,14 +16,14 @@ export function DateSelect() { const masterId = useOrderStore((store) => store.masterId); const serviceId = useOrderStore((store) => store.serviceId); - const [currentMonthDate, setCurrentMonthDate] = useState(new Date()); + const [selectedMonthDate, setSelectedMonthDate] = useState(new Date()); const { data: { slots } = {} } = useAvailableTimeSlotsQuery( { filters: { - date: { - gte: dayjs(currentMonthDate).startOf('month').format('YYYY-MM-DD'), - lte: dayjs(currentMonthDate).endOf('month').format('YYYY-MM-DD'), + datetime_start: { + gte: dayjs(selectedMonthDate).startOf('month').startOf('day').toISOString(), + lte: dayjs(selectedMonthDate).endOf('month').endOf('day').toISOString(), }, master: { documentId: { @@ -47,11 +47,11 @@ export function DateSelect() { disabled={(date) => { return ( dayjs().isAfter(dayjs(date), 'day') || - !slots?.some((slot) => dayjs(slot?.date).isSame(date, 'day')) + !slots?.some((slot) => dayjs(slot?.datetime_start).isSame(date, 'day')) ); }} mode="single" - onMonthChange={(date) => setCurrentMonthDate(date)} + onMonthChange={(date) => setSelectedMonthDate(date)} onSelect={(date) => { if (date) setDate(date); setTime(null); @@ -79,8 +79,9 @@ export function TimeSelect() { const { data: { times } = {}, isLoading } = useAvailableTimeSlotsQuery( { filters: { - date: { - eq: formatDate(date).db(), + datetime_start: { + gte: dayjs(date).startOf('day').toISOString(), + lte: dayjs(date).endOf('day').toISOString(), }, master: { documentId: { @@ -100,6 +101,7 @@ export function TimeSelect() { if (isLoading || !times) return null; + const getHour = (isoString: string) => dayjs(isoString).hour(); const morning = times.filter(({ time }) => getHour(time) < 12); const afternoon = times?.filter(({ time }) => { const hour = getHour(time); @@ -116,13 +118,6 @@ export function TimeSelect() { ); } -function getHour(time: string) { - const hour = time.split(':')[0]; - if (hour) return Number.parseInt(hour, 10); - - return -1; -} - function TimeSlotsButtons({ times, title, @@ -146,7 +141,7 @@ function TimeSlotsButtons({ }} variant="outline" > - {time} + {formatTime(time).user()} ))} diff --git a/apps/web/components/orders/order-form/submit-button.tsx b/apps/web/components/orders/order-form/submit-button.tsx index dbfb398..164a265 100644 --- a/apps/web/components/orders/order-form/submit-button.tsx +++ b/apps/web/components/orders/order-form/submit-button.tsx @@ -4,7 +4,6 @@ import { useOrderCreate } from '@/hooks/api/orders'; import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; -import { formatTime } from '@repo/utils/datetime-format'; import { useEffect } from 'react'; export function SubmitButton() { @@ -19,9 +18,9 @@ export function SubmitButton() { createOrder({ input: { client: clientId, + datetime_start: time, services: [serviceId], slot: slotId, - time_start: formatTime(time).db(), }, }); }; diff --git a/apps/web/components/orders/orders-list/date-select.tsx b/apps/web/components/orders/orders-list/date-select.tsx index 8fed90e..39abf0a 100644 --- a/apps/web/components/orders/orders-list/date-select.tsx +++ b/apps/web/components/orders/orders-list/date-select.tsx @@ -5,7 +5,8 @@ import { useOrdersQuery } from '@/hooks/api/orders'; import { useDateTimeStore } from '@/stores/datetime'; import { HorizontalCalendar } from '@repo/ui/components/ui/horizontal-calendar'; import dayjs from 'dayjs'; -import { useState } from 'react'; +import { sift } from 'radashi'; +import { useMemo, useState } from 'react'; export function DateSelect() { const { data: { customer } = {} } = useCustomerQuery(); @@ -14,7 +15,7 @@ export function DateSelect() { const [currentMonthDate, setCurrentMonthDate] = useState(new Date()); - const { data: { orders } = {} } = useOrdersQuery({ + const { data: { orders } = { orders: [] } } = useOrdersQuery({ filters: { client: { documentId: { @@ -22,9 +23,9 @@ export function DateSelect() { }, }, slot: { - date: { - gte: dayjs(currentMonthDate).startOf('month').format('YYYY-MM-DD'), - lte: dayjs(currentMonthDate).endOf('month').format('YYYY-MM-DD'), + datetime_start: { + gte: dayjs(currentMonthDate).startOf('month').toISOString(), + lte: dayjs(currentMonthDate).endOf('month').toISOString(), }, master: { documentId: { @@ -35,12 +36,22 @@ export function DateSelect() { }, }); + const daysWithOrders = useMemo(() => { + return sift( + orders.map((order) => { + const dateString = order?.slot?.datetime_start; + + return dateString ? new Date(dateString) : undefined; + }), + ); + }, [orders]); + const setSelectedDate = useDateTimeStore((store) => store.setDate); const selectedDate = useDateTimeStore((store) => store.date); return ( new Date(order?.slot?.date))} + daysWithOrders={daysWithOrders} onDateChange={setSelectedDate} onMonthChange={(date) => setCurrentMonthDate(date)} selectedDate={selectedDate} diff --git a/apps/web/components/orders/orders-list/index.tsx b/apps/web/components/orders/orders-list/index.tsx index 1285f6f..3838966 100644 --- a/apps/web/components/orders/orders-list/index.tsx +++ b/apps/web/components/orders/orders-list/index.tsx @@ -4,7 +4,7 @@ import { OrderCard } from '@/components/shared/order-card'; import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers'; import { useOrdersQuery } from '@/hooks/api/orders'; import { useDateTimeStore } from '@/stores/datetime'; -import { formatDate } from '@repo/utils/datetime-format'; +import { getDateUTCRange } from '@repo/utils/datetime-format'; export function ClientsOrdersList() { const { data: { customer } = {} } = useCustomerQuery(); @@ -12,13 +12,15 @@ export function ClientsOrdersList() { const isMaster = useIsMaster(); const selectedDate = useDateTimeStore((store) => store.date); + const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day(); const { data: { orders } = {}, isLoading } = useOrdersQuery( { filters: { slot: { - date: { - eq: formatDate(selectedDate).db(), + datetime_start: { + gte: startOfDay, + lt: endOfDay, }, master: { documentId: { @@ -45,6 +47,7 @@ export function OrdersList() { const { data: { customer } = {} } = useCustomerQuery(); const selectedDate = useDateTimeStore((store) => store.date); + const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day(); const { data: { orders } = {}, isLoading } = useOrdersQuery( { @@ -55,8 +58,9 @@ export function OrdersList() { }, }, slot: { - date: { - eq: formatDate(selectedDate).db(), + datetime_start: { + gte: startOfDay, + lt: endOfDay, }, }, }, diff --git a/apps/web/components/schedule/calendar.tsx b/apps/web/components/schedule/calendar.tsx index 33506be..76945f3 100644 --- a/apps/web/components/schedule/calendar.tsx +++ b/apps/web/components/schedule/calendar.tsx @@ -4,6 +4,7 @@ import { useCustomerQuery } from '@/hooks/api/customers'; import { useSlotsQuery } from '@/hooks/api/slots'; import { useDateTimeStore } from '@/stores/datetime'; import { Calendar } from '@repo/ui/components/ui/calendar'; +import { getDateUTCRange } from '@repo/utils/datetime-format'; import dayjs from 'dayjs'; import { useState } from 'react'; @@ -15,11 +16,13 @@ export function ScheduleCalendar() { const [currentMonthDate, setCurrentMonthDate] = useState(new Date()); + const { endOfMonth, startOfMonth } = getDateUTCRange(currentMonthDate).month(); + const { data: { slots } = {} } = useSlotsQuery({ filters: { - date: { - gte: dayjs(currentMonthDate).startOf('month').format('YYYY-MM-DD'), - lte: dayjs(currentMonthDate).endOf('month').format('YYYY-MM-DD'), + datetime_start: { + gte: startOfMonth, + lte: endOfMonth, }, master: { documentId: { @@ -38,7 +41,7 @@ export function ScheduleCalendar() { mode="single" modifiers={{ hasEvent: (date) => { - return slots?.some((slot) => dayjs(slot?.date).isSame(date, 'day')) || false; + return slots?.some((slot) => dayjs(slot?.datetime_start).isSame(date, 'day')) || false; }, }} modifiersClassNames={{ diff --git a/apps/web/components/schedule/day-slots-list/day-slot-add-form.tsx b/apps/web/components/schedule/day-slots-list/day-slot-add-form.tsx index 62c0161..d9c9a3e 100644 --- a/apps/web/components/schedule/day-slots-list/day-slot-add-form.tsx +++ b/apps/web/components/schedule/day-slots-list/day-slot-add-form.tsx @@ -8,7 +8,7 @@ import { ScheduleStoreProvider, useScheduleStore } from '@/stores/schedule'; import { withContext } from '@/utils/context'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; -import { formatDate, formatTime } from '@repo/utils/datetime-format'; +import { combineDateAndTimeToUTC } from '@repo/utils/datetime-format'; import { PlusSquare } from 'lucide-react'; import { type FormEvent } from 'react'; @@ -23,15 +23,15 @@ export const DaySlotAddForm = withContext(ScheduleStoreProvider)(function () { const handleSubmit = (event: FormEvent) => { event.preventDefault(); if (startTime && endTime) { + const datetimeStart = combineDateAndTimeToUTC(selectedDate, startTime); + const datetimeEnd = combineDateAndTimeToUTC(selectedDate, endTime); addSlot({ input: { - date: formatDate(selectedDate).db(), + datetime_end: datetimeEnd, + datetime_start: datetimeStart, state: Enum_Slot_State.Open, - time_end: formatTime(endTime).db(), - time_start: formatTime(startTime).db(), }, }); - resetTime(); } }; diff --git a/apps/web/components/schedule/day-slots-list/index.tsx b/apps/web/components/schedule/day-slots-list/index.tsx index 7cddfde..3f9b472 100644 --- a/apps/web/components/schedule/day-slots-list/index.tsx +++ b/apps/web/components/schedule/day-slots-list/index.tsx @@ -6,22 +6,21 @@ import { useCustomerQuery } from '@/hooks/api/customers'; import { useMasterSlotsQuery } from '@/hooks/api/slots'; import { useDateTimeStore } from '@/stores/datetime'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; -import { formatDate } from '@repo/utils/datetime-format'; +import { getDateUTCRange } from '@repo/utils/datetime-format'; export function DaySlotsList() { const { data: { customer } = {} } = useCustomerQuery(); - const selectedDate = useDateTimeStore((store) => store.date); + const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day(); + const { data: { slots } = {}, isLoading } = useMasterSlotsQuery({ filters: { - date: { eq: formatDate(selectedDate).db() }, + datetime_start: { gte: startOfDay, lt: endOfDay }, master: { documentId: { eq: customer?.documentId } }, }, }); - if (isLoading) return ; - return (

Слоты

diff --git a/apps/web/components/schedule/day-slots-list/slot-card.tsx b/apps/web/components/schedule/day-slots-list/slot-card.tsx index 147aaef..5dcc9cf 100644 --- a/apps/web/components/schedule/day-slots-list/slot-card.tsx +++ b/apps/web/components/schedule/day-slots-list/slot-card.tsx @@ -23,7 +23,10 @@ export function SlotCard(props: Readonly) {
- + ) { return ( - {formatDate(slot?.date).user()} + {formatDate(slot?.datetime_start).user()} ); } diff --git a/apps/web/components/schedule/slot-datetime/slot-time.tsx b/apps/web/components/schedule/slot-datetime/slot-time.tsx index e5e0dd4..8aab074 100644 --- a/apps/web/components/schedule/slot-datetime/slot-time.tsx +++ b/apps/web/components/schedule/slot-datetime/slot-time.tsx @@ -8,13 +8,13 @@ import { useSlotMutation, useSlotQuery } from '@/hooks/api/slots'; import { useScheduleStore } from '@/stores/schedule'; import { Enum_Order_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; -import { formatTime } from '@repo/utils/datetime-format'; +import { combineDateAndTimeToUTC, formatTime } from '@repo/utils/datetime-format'; +import dayjs from 'dayjs'; import { PencilLine } from 'lucide-react'; import { useEffect } from 'react'; export function SlotTime(props: Readonly) { const editMode = useScheduleStore((state) => state.editMode); - return editMode ? : ; } @@ -22,21 +22,25 @@ function SlotTimeEditForm({ documentId }: Readonly) { const { editMode, endTime, resetTime, setEditMode, setEndTime, setStartTime, startTime } = useScheduleStore((state) => state); const { isPending: isMutationPending, mutate: updateSlot } = useSlotMutation({ documentId }); - const { data: { slot } = {}, isPending: isQueryPending } = useSlotQuery({ documentId }); - const isPending = isMutationPending || isQueryPending; useEffect(() => { - if (editMode) { - if (slot?.time_start) setStartTime(slot.time_start); - if (slot?.time_end) setEndTime(slot.time_end); + if (editMode && slot?.datetime_start && slot?.datetime_end) { + setStartTime(formatTime(slot.datetime_start).user()); + setEndTime(formatTime(slot.datetime_end).user()); } }, [editMode, setEndTime, setStartTime, slot]); function handleSubmit() { + if (!slot?.datetime_start) return; + const date = slot.datetime_start; // ISO-строка, год-месяц-день и время + const dateOnly = dayjs(date).format('YYYY-MM-DD'); // Получаем только дату + + const newDatetimeStart = combineDateAndTimeToUTC(dateOnly, startTime); + const newDatetimeEnd = combineDateAndTimeToUTC(dateOnly, endTime); updateSlot({ - data: { time_end: formatTime(endTime).db(), time_start: formatTime(startTime).db() }, + data: { datetime_end: newDatetimeEnd, datetime_start: newDatetimeStart }, }); resetTime(); setEditMode(false); @@ -44,7 +48,7 @@ function SlotTimeEditForm({ documentId }: Readonly) { return ( - @@ -60,18 +64,18 @@ const FORBIDDEN_ORDER_STATES: Enum_Order_State[] = [ function SlotTimeReadonly({ documentId }: Readonly) { const setEditMode = useScheduleStore((state) => state.setEditMode); - const { data: { slot } = {} } = useSlotQuery({ documentId }); - if (!slot) return null; - const disabledEdit = slot?.orders.some( (order) => order?.state && FORBIDDEN_ORDER_STATES.includes(order?.state), ); - return (
- +