From b8880eedee3a8b07ed7737d73ed6e028971a4afd Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Tue, 20 May 2025 19:25:25 +0300 Subject: [PATCH] move getAvailableTimeSlots to server --- apps/web/actions/api/slots.ts | 8 +++ .../components/time-select.tsx | 49 ++++++++----------- apps/web/hooks/api/slots.ts | 18 ++++++- packages/graphql/api/slots.ts | 22 +++++++++ packages/graphql/operations/slot.graphql | 1 - .../graphql/types/operations.generated.ts | 4 +- 6 files changed, 69 insertions(+), 33 deletions(-) diff --git a/apps/web/actions/api/slots.ts b/apps/web/actions/api/slots.ts index eba96cd..d469909 100644 --- a/apps/web/actions/api/slots.ts +++ b/apps/web/actions/api/slots.ts @@ -17,6 +17,14 @@ export async function deleteSlot(...variables: Parameters +) { + const service = await getService(); + + return service.getAvailableTimeSlots(...variables); +} + export async function getSlot(...variables: Parameters) { const service = await getService(); diff --git a/apps/web/components/orders/components/datetime-select/components/time-select.tsx b/apps/web/components/orders/components/datetime-select/components/time-select.tsx index da25224..4798c9f 100644 --- a/apps/web/components/orders/components/datetime-select/components/time-select.tsx +++ b/apps/web/components/orders/components/datetime-select/components/time-select.tsx @@ -1,32 +1,14 @@ -/* eslint-disable canonical/id-match */ 'use client'; -import { useSlotsQuery } from '@/hooks/api/slots'; +import { useAvailableTimeSlotsQuery } from '@/hooks/api/slots'; import { useOrderStore } from '@/stores/order'; -import { Enum_Slot_State, type SlotFieldsFragment } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; -import dayjs, { type Dayjs } from 'dayjs'; -import { sift } from 'radash'; - -const generateTimeSlots = (slots: SlotFieldsFragment[]): Array<{ slotId: string; time: Dayjs }> => { - const times: Array<{ slotId: string; time: Dayjs }> = []; - for (const slot of slots) { - let currentTime = dayjs(`${slot.date} ${slot.time_start}`); - const endTime = dayjs(`${slot.date} ${slot.time_end}`); - - while (currentTime.isBefore(endTime) || currentTime.isSame(endTime)) { - times.push({ slotId: slot.documentId, time: currentTime }); - currentTime = currentTime.add(30, 'minute'); - } - } - - return times; -}; export function TimeSelect() { const masterId = useOrderStore((store) => store.masterId); const date = useOrderStore((store) => store.date); - const { data: { slots } = {} } = useSlotsQuery({ + + const { data: { times } = {}, isLoading } = useAvailableTimeSlotsQuery({ filters: { date: { eq: date, @@ -39,12 +21,14 @@ export function TimeSelect() { }, }); - const openedSlots = slots?.filter((slot) => slot?.state === Enum_Slot_State.Open); - const timeSlots = generateTimeSlots(openedSlots ? sift(openedSlots) : []); + if (isLoading || !times) return null; - const morning = timeSlots.filter((time) => time.time.hour() < 12); - const afternoon = timeSlots.filter((time) => time.time.hour() >= 12 && time.time.hour() < 18); - const evening = timeSlots.filter((time) => time.time.hour() >= 18); + const morning = times.filter(({ time }) => getHour(time) < 12); + const afternoon = times?.filter(({ time }) => { + const hour = getHour(time); + return hour >= 12 && hour < 18; + }); + const evening = times?.filter(({ time }) => getHour(time) >= 18); return (
@@ -55,10 +39,17 @@ 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, -}: Readonly<{ times: Array<{ slotId: string; time: Dayjs }>; title: string }>) { +}: Readonly<{ times: Array<{ slotId: string; time: string }>; title: string }>) { const setTime = useOrderStore((store) => store.setTime); const setSlot = useOrderStore((store) => store.setSlotId); @@ -73,12 +64,12 @@ function TimeSlotsButtons({ className="mb-2" key={time.toString()} onClick={() => { - setTime(time.format('HH:mm')); + setTime(time); setSlot(slotId); }} variant="outline" > - {time.format('HH:mm')} + {time} ))}
diff --git a/apps/web/hooks/api/slots.ts b/apps/web/hooks/api/slots.ts index ec7cbd5..301a342 100644 --- a/apps/web/hooks/api/slots.ts +++ b/apps/web/hooks/api/slots.ts @@ -1,7 +1,14 @@ 'use client'; import { useCustomerQuery } from './customers'; -import { createSlot, deleteSlot, getSlot, getSlots, updateSlot } from '@/actions/api/slots'; +import { + createSlot, + deleteSlot, + getAvailableTimeSlots, + getSlot, + getSlots, + updateSlot, +} from '@/actions/api/slots'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; export const useSlotsQuery = (variables: Parameters[0]) => { @@ -25,6 +32,15 @@ export const useSlotQuery = (variables: Parameters[0]) => { }); }; +export const useAvailableTimeSlotsQuery = ( + variables: Parameters[0], +) => { + return useQuery({ + queryFn: () => getAvailableTimeSlots(variables), + queryKey: ['available-time-slots', variables], + }); +}; + export const useSlotMutation = ({ documentId, }: Pick[0], 'documentId'>) => { diff --git a/packages/graphql/api/slots.ts b/packages/graphql/api/slots.ts index 6b50c94..73c314a 100644 --- a/packages/graphql/api/slots.ts +++ b/packages/graphql/api/slots.ts @@ -4,6 +4,7 @@ import { formatDate, formatTime } from '../utils/datetime-format'; import { BaseService } from './base'; import { CustomersService } from './customers'; import { type VariablesOf } from '@graphql-typed-document-node/core'; +import dayjs from 'dayjs'; export class SlotsService extends BaseService { async createSlot(variables: VariablesOf) { @@ -44,6 +45,27 @@ export class SlotsService extends BaseService { return mutationResult.data; } + async getAvailableTimeSlots(variables: VariablesOf) { + const { slots } = await this.getSlots(variables); + + const openedSlots = slots.filter((x) => x?.state === GQL.Enum_Slot_State.Open); + + const times: Array<{ slotId: string; time: string }> = []; + + for (const slot of openedSlots) + if (slot?.time_start && slot?.time_end) { + let currentTime = dayjs(`${slot.date} ${slot.time_start}`); + const endTime = dayjs(`${slot.date} ${slot.time_end}`); + + while (currentTime.isBefore(endTime) || currentTime.isSame(endTime)) { + times.push({ slotId: slot.documentId, time: currentTime.format('HH:mm') }); + currentTime = currentTime.add(30, 'minute'); + } + } + + return { times }; + } + async getSlot(variables: VariablesOf) { const { query } = await getClientWithToken(); diff --git a/packages/graphql/operations/slot.graphql b/packages/graphql/operations/slot.graphql index 2113d49..6a5e76a 100644 --- a/packages/graphql/operations/slot.graphql +++ b/packages/graphql/operations/slot.graphql @@ -20,7 +20,6 @@ query GetSlots($filters: SlotFiltersInput) { query GetSlot($documentId: ID!) { slot(documentId: $documentId) { - state orders(sort: "time_start:asc") { documentId } diff --git a/packages/graphql/types/operations.generated.ts b/packages/graphql/types/operations.generated.ts index 15dbf92..3a112e3 100644 --- a/packages/graphql/types/operations.generated.ts +++ b/packages/graphql/types/operations.generated.ts @@ -748,7 +748,7 @@ export type GetSlotQueryVariables = Exact<{ }>; -export type GetSlotQuery = { __typename?: 'Query', slot?: { __typename?: 'Slot', state?: Enum_Slot_State | null | undefined, documentId: string, date?: any | null | undefined, time_start: any, time_end: any, orders: Array<{ __typename?: 'Order', documentId: string } | null | undefined>, master?: { __typename?: 'Customer', documentId: string } | null | undefined } | null | undefined }; +export type GetSlotQuery = { __typename?: 'Query', slot?: { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: any, time_end: any, state?: Enum_Slot_State | null | undefined, orders: Array<{ __typename?: 'Order', documentId: string } | null | undefined>, master?: { __typename?: 'Customer', documentId: string } | null | undefined } | null | undefined }; export type UpdateSlotMutationVariables = Exact<{ documentId: Scalars['ID']['input']; @@ -782,6 +782,6 @@ export const GetServicesDocument = {"kind":"Document","definitions":[{"kind":"Op export const GetServiceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetService"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"service"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServiceFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServiceFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Service"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}}]}}]} as unknown as DocumentNode; export const CreateSlotDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSlot"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SlotInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createSlot"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SlotFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SlotFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Slot"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]} as unknown as DocumentNode; export const GetSlotsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSlots"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"SlotFiltersInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"sort"},"value":{"kind":"StringValue","value":"time_start:asc","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SlotFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SlotFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Slot"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]} as unknown as DocumentNode; -export const GetSlotDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSlot"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slot"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"orders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sort"},"value":{"kind":"StringValue","value":"time_start:asc","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"master"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SlotFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SlotFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Slot"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]} as unknown as DocumentNode; +export const GetSlotDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSlot"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slot"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"sort"},"value":{"kind":"StringValue","value":"time_start:asc","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"master"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SlotFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SlotFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Slot"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]} as unknown as DocumentNode; export const UpdateSlotDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSlot"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SlotInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSlot"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SlotFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SlotFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Slot"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]} as unknown as DocumentNode; export const DeleteSlotDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteSlot"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteSlot"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file