diff --git a/apps/web/app/(main)/orders/page.tsx b/apps/web/app/(main)/orders/page.tsx index 97426cb..a47d2fa 100644 --- a/apps/web/app/(main)/orders/page.tsx +++ b/apps/web/app/(main)/orders/page.tsx @@ -1,17 +1,18 @@ +'use client'; import { Container } from '@/components/layout'; import { ClientsOrdersList, OrdersList } from '@/components/orders'; -import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; - -export default async function ProfilePage() { - const queryClient = new QueryClient(); +import { DateSelect } from '@/components/orders/orders-list/date-select'; +import { DateTimeStoreProvider } from '@/stores/datetime'; +export default function ProfilePage() { return ( - - + +
+ - - + + ); } diff --git a/apps/web/components/orders/orders-list/date-select.tsx b/apps/web/components/orders/orders-list/date-select.tsx new file mode 100644 index 0000000..04c93df --- /dev/null +++ b/apps/web/components/orders/orders-list/date-select.tsx @@ -0,0 +1,10 @@ +'use client'; +import { useDateTimeStore } from '@/stores/datetime'; +import { HorizontalCalendar } from '@repo/ui/components/ui/horizontal-calendar'; + +export function DateSelect() { + const setSelectedDate = useDateTimeStore((store) => store.setDate); + const selectedDate = useDateTimeStore((store) => store.date); + + return ; +} diff --git a/apps/web/components/orders/orders-list.tsx b/apps/web/components/orders/orders-list/index.tsx similarity index 58% rename from apps/web/components/orders/orders-list.tsx rename to apps/web/components/orders/orders-list/index.tsx index 3906867..27c66e6 100644 --- a/apps/web/components/orders/orders-list.tsx +++ b/apps/web/components/orders/orders-list/index.tsx @@ -4,6 +4,7 @@ import { OrderCard } from '@/components/shared/order-card'; import { useCustomerQuery } from '@/hooks/api/customers'; import { useOrdersQuery } from '@/hooks/api/orders'; +import { useDateTimeStore } from '@/stores/datetime'; import { Enum_Customer_Role } from '@repo/graphql/types'; export function ClientsOrdersList() { @@ -11,17 +12,25 @@ export function ClientsOrdersList() { const isMaster = customer?.role === Enum_Customer_Role.Master; - const { data: { orders } = {}, isLoading } = useOrdersQuery({ - filters: { - slot: { - master: { - documentId: { - eq: isMaster ? customer?.documentId : undefined, + const selectedDate = useDateTimeStore((store) => store.date); + + const { data: { orders } = {}, isLoading } = useOrdersQuery( + { + filters: { + slot: { + date: { + eq: selectedDate, + }, + master: { + documentId: { + eq: isMaster ? customer?.documentId : undefined, + }, }, }, }, }, - }); + Boolean(customer?.documentId) && Boolean(selectedDate), + ); if (!orders?.length || isLoading) return null; @@ -36,15 +45,25 @@ export function ClientsOrdersList() { export function OrdersList() { const { data: { customer } = {} } = useCustomerQuery(); - const { data: { orders } = {}, isLoading } = useOrdersQuery({ - filters: { - client: { - documentId: { - eq: customer?.documentId, + const selectedDate = useDateTimeStore((store) => store.date); + + const { data: { orders } = {}, isLoading } = useOrdersQuery( + { + filters: { + client: { + documentId: { + eq: customer?.documentId, + }, + }, + slot: { + date: { + eq: selectedDate, + }, }, }, }, - }); + Boolean(customer?.documentId) && Boolean(selectedDate), + ); if (!orders?.length || isLoading) return null; diff --git a/apps/web/hooks/api/orders.ts b/apps/web/hooks/api/orders.ts index 61996b0..48178af 100644 --- a/apps/web/hooks/api/orders.ts +++ b/apps/web/hooks/api/orders.ts @@ -21,8 +21,9 @@ export const useOrderCreate = () => { }); }; -export const useOrdersQuery = (variables: Parameters[0]) => +export const useOrdersQuery = (variables: Parameters[0], enabled?: boolean) => useQuery({ + enabled, queryFn: () => getOrders(variables), queryKey: ['orders', variables], staleTime: 60 * 1_000, diff --git a/apps/web/stores/datetime/context.ts b/apps/web/stores/datetime/context.ts new file mode 100644 index 0000000..833d141 --- /dev/null +++ b/apps/web/stores/datetime/context.ts @@ -0,0 +1,6 @@ +import { createDateTimeStore } from './store'; +import { createZustandStore } from '@/utils/zustand/context'; + +const { Provider, useZustandStore } = createZustandStore(createDateTimeStore); + +export { Provider as DateTimeStoreProvider, useZustandStore as useDateTimeStore }; diff --git a/apps/web/stores/datetime/index.tsx b/apps/web/stores/datetime/index.tsx new file mode 100644 index 0000000..c38e8e8 --- /dev/null +++ b/apps/web/stores/datetime/index.tsx @@ -0,0 +1 @@ +export * from './context'; diff --git a/apps/web/stores/datetime/store.ts b/apps/web/stores/datetime/store.ts new file mode 100644 index 0000000..477c7cd --- /dev/null +++ b/apps/web/stores/datetime/store.ts @@ -0,0 +1,9 @@ +import { createDateTimeSlice } from '../lib/slices'; +import { type DateTimeStore } from './types'; +import { createStore } from 'zustand'; + +export function createDateTimeStore() { + return createStore((...args) => ({ + ...createDateTimeSlice(...args), + })); +} diff --git a/apps/web/stores/datetime/types.ts b/apps/web/stores/datetime/types.ts new file mode 100644 index 0000000..1567350 --- /dev/null +++ b/apps/web/stores/datetime/types.ts @@ -0,0 +1,3 @@ +import { type DateTimeSlice } from '../lib/slices'; + +export type DateTimeStore = DateTimeSlice; diff --git a/packages/graphql/api/orders.ts b/packages/graphql/api/orders.ts index 27e2423..83036bb 100644 --- a/packages/graphql/api/orders.ts +++ b/packages/graphql/api/orders.ts @@ -107,7 +107,16 @@ export class OrdersService extends BaseService { const result = await query({ query: GQL.GetOrdersDocument, - variables, + variables: { + filters: { + ...variables.filters, + date: { eq: formatDate(variables?.filters?.date?.eq).db() }, + slot: { + ...variables.filters?.slot, + date: { eq: formatDate(variables?.filters?.slot?.date?.eq).db() }, + }, + }, + }, }); if (result.error) throw new Error(result.error.message); diff --git a/packages/graphql/utils/datetime-format.ts b/packages/graphql/utils/datetime-format.ts index 790b161..9ad4744 100644 --- a/packages/graphql/utils/datetime-format.ts +++ b/packages/graphql/utils/datetime-format.ts @@ -15,6 +15,8 @@ export function combineDateTime(date: Date, time: string) { } export function formatDate(date: Date | string) { + if (!date) return { db: () => undefined, user: () => undefined }; + return { db: () => dayjs(date).format('YYYY-MM-DD'), user: () => { diff --git a/packages/ui/src/components/ui/horizontal-calendar.tsx b/packages/ui/src/components/ui/horizontal-calendar.tsx new file mode 100644 index 0000000..8bc2796 --- /dev/null +++ b/packages/ui/src/components/ui/horizontal-calendar.tsx @@ -0,0 +1,138 @@ +'use client'; + +import { cn } from '../../lib/utils'; +import { Button } from '../ui/button'; +import { addDays, format, isSameDay, subDays } from 'date-fns'; +import { ru } from 'date-fns/locale'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { useEffect, useMemo, useRef, useState } from 'react'; + +type HorizontalCalendarProps = { + readonly className?: string; + readonly numberOfDays?: number; + readonly onDateChange: (date: Date) => void; + readonly selectedDate: Date; +}; + +export function HorizontalCalendar({ + className, + numberOfDays = 14, + onDateChange, + selectedDate, +}: HorizontalCalendarProps) { + const scrollRef = useRef(null); + const [baseDate, setBaseDate] = useState(new Date()); + const [currentMonthDate, setCurrentMonthDate] = useState(new Date()); + + const dates = useMemo(() => { + return Array.from({ length: numberOfDays }, (_, index) => { + return addDays(baseDate, index); + }); + }, [baseDate, numberOfDays]); + + const updateCurrentMonth = (newBaseDate: Date) => { + setBaseDate(newBaseDate); + setCurrentMonthDate(newBaseDate); + setTimeout(() => { + if (scrollRef.current) { + scrollRef.current.scrollLeft = 0; + } + }, 0); + }; + + const scrollPrevious = () => { + const newDate = subDays(baseDate, numberOfDays); + updateCurrentMonth(newDate); + }; + + const scrollNext = () => { + const newDate = addDays(baseDate, numberOfDays); + updateCurrentMonth(newDate); + }; + + // Обработчик scroll — вычисляет дату ближайшую к левому краю + const handleScroll = () => { + const container = scrollRef.current; + if (!container) return; + + const children = Array.from(container.children) as HTMLElement[]; + + let closestChild: HTMLElement | null = null; + let minOffset = Infinity; + + for (const child of children) { + const offset = Math.abs(child.offsetLeft - container.scrollLeft); + if (offset < minOffset) { + minOffset = offset; + closestChild = child; + } + } + + if (closestChild) { + const dateString = closestChild.dataset.date; + if (dateString) { + const date = new Date(dateString); + setCurrentMonthDate(date); + } + } + }; + + useEffect(() => { + const scrollElement = scrollRef.current; + if (scrollElement) { + scrollElement.addEventListener('scroll', handleScroll, { passive: true }); + } + + return () => { + if (scrollElement) { + scrollElement.removeEventListener('scroll', handleScroll); + } + }; + }, []); + + return ( +
+
+

+ {format(currentMonthDate, 'LLLL yyyy', { locale: ru })} +

+
+ + +
+
+
+
+ {dates.map((date) => ( +
+ +
+ ))} +
+
+
+ ); +} diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css index f7c0d53..7282006 100644 --- a/packages/ui/src/globals.css +++ b/packages/ui/src/globals.css @@ -69,3 +69,14 @@ @apply bg-background text-foreground; } } + +/* Hide scrollbar for Chrome, Safari and Opera */ +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* Hide scrollbar for IE, Edge and Firefox */ +.scrollbar-hide { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +}