diff --git a/apps/web/actions/api/slots.ts b/apps/web/actions/api/slots.ts new file mode 100644 index 0000000..2e0c3b5 --- /dev/null +++ b/apps/web/actions/api/slots.ts @@ -0,0 +1,35 @@ +'use server'; +import { useService } from './lib/service'; +import { SlotsService } from '@repo/graphql/api/slots'; + +const getService = useService(SlotsService); + +export async function createSlot(...variables: Parameters) { + const service = await getService(); + + return service.createSlot(...variables); +} + +export async function deleteSlot(...variables: Parameters) { + const service = await getService(); + + return service.deleteSlot(...variables); +} + +export async function getSlot(...variables: Parameters) { + const service = await getService(); + + return service.getSlot(...variables); +} + +export async function getSlots(...variables: Parameters) { + const service = await getService(); + + return service.getSlots(...variables); +} + +export async function updateSlot(...variables: Parameters) { + const service = await getService(); + + return service.updateSlot(...variables); +} diff --git a/apps/web/actions/orders.ts b/apps/web/actions/orders.ts index 70d0112..ac7dc78 100644 --- a/apps/web/actions/orders.ts +++ b/apps/web/actions/orders.ts @@ -4,9 +4,10 @@ import type * as ApolloTypes from '../../../packages/graphql/node_modules/@apollo/client/core'; import { getCustomer, getCustomerMasters } from './api/customers'; import { _temporaryGetCustomer } from './api/lib/service'; -import { formatDate, formatTime, sumTime } from '@/utils/date'; +import { getSlot } from './api/slots'; import * as api from '@repo/graphql/api'; import { Enum_Customer_Role, Enum_Slot_State } from '@repo/graphql/types'; +import { formatDate, formatTime, sumTime } from '@repo/graphql/utils/datetime-format'; // eslint-disable-next-line @typescript-eslint/no-unused-vars type FixTypescriptCringe = ApolloTypes.FetchResult; @@ -33,9 +34,9 @@ export async function createOrder(input: OrderInput) { throw new Error('Missing slot'); } - const { data } = await api.getSlot({ documentId: input.slotId }); + const { slot } = await getSlot({ documentId: input.slotId }); - if (data.slot?.state === Enum_Slot_State.Closed) { + if (slot?.state === Enum_Slot_State.Closed) { throw new Error('Slot is closed'); } @@ -44,7 +45,7 @@ export async function createOrder(input: OrderInput) { throw new Error('Invalid client'); } - const masterId = data.slot?.master?.documentId; + const masterId = slot?.master?.documentId; const masters = await getCustomerMasters(customer); @@ -55,7 +56,7 @@ export async function createOrder(input: OrderInput) { if ( customer.role === Enum_Customer_Role.Master && - data.slot?.master?.documentId !== customer.documentId + slot?.master?.documentId !== customer.documentId ) { throw new Error('Invalid master'); } diff --git a/apps/web/actions/slots.ts b/apps/web/actions/slots.ts deleted file mode 100644 index ca5bae0..0000000 --- a/apps/web/actions/slots.ts +++ /dev/null @@ -1,57 +0,0 @@ -'use server'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '../../../packages/graphql/node_modules/@apollo/client/core'; -import { getCustomer } from './api/customers'; -import { _temporaryGetCustomer } from './api/lib/service'; -import { formatDate, formatTime } from '@/utils/date'; -import * as api from '@repo/graphql/api'; -import type * as GQL from '@repo/graphql/types'; - -type AddSlotInput = Omit; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -export async function addSlot(input: AddSlotInput) { - const variables = await _temporaryGetCustomer(); - const { customer } = await getCustomer(variables); - - return api.createSlot({ - ...input, - date: formatDate(input.date).db(), - master: customer?.documentId, - time_end: formatTime(input.time_end).db(), - time_start: formatTime(input.time_start).db(), - }); -} - -export async function getSlots(input: GQL.GetSlotsQueryVariables) { - const variables = await _temporaryGetCustomer(); - const { customer } = await getCustomer(variables); - - return api.getSlots({ - filters: { - master: { - documentId: { - eq: customer?.documentId, - }, - }, - ...input.filters, - }, - }); -} - -export async function updateSlot(input: GQL.UpdateSlotMutationVariables) { - return api.updateSlot({ - ...input, - data: { - ...input.data, - date: input.data?.date ? formatDate(input.data.date).db() : undefined, - time_end: input.data?.time_end ? formatTime(input.data.time_end).db() : undefined, - time_start: input.data?.time_start ? formatTime(input.data.time_start).db() : undefined, - }, - }); -} - -export const getSlot = api.getSlot; -export const deleteSlot = api.deleteSlot; 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 4866e47..034b005 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,6 +1,6 @@ /* eslint-disable canonical/id-match */ 'use client'; -import { useSlots } from '@/hooks/slots'; +import { useSlots } 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'; @@ -25,9 +25,20 @@ const generateTimeSlots = (slots: SlotFieldsFragment[]): Array<{ slotId: string; export function TimeSelect() { const masterId = useOrderStore((store) => store.masterId); const date = useOrderStore((store) => store.date); - const { data } = useSlots({ date, masterId }); + const { data: { slots } = {} } = useSlots({ + filters: { + date: { + eq: date, + }, + master: { + documentId: { + eq: masterId, + }, + }, + }, + }); - const openedSlots = data?.data.slots.filter((slot) => slot?.state === Enum_Slot_State.Open); + const openedSlots = slots?.filter((slot) => slot?.state === Enum_Slot_State.Open); const timeSlots = generateTimeSlots(openedSlots ? sift(openedSlots) : []); const morning = timeSlots.filter((time) => time.time.hour() < 12); diff --git a/apps/web/components/schedule/components/slot-card.tsx b/apps/web/components/schedule/components/slot-card.tsx index 4d19768..8b1a2cc 100644 --- a/apps/web/components/schedule/components/slot-card.tsx +++ b/apps/web/components/schedule/components/slot-card.tsx @@ -1,7 +1,7 @@ /* eslint-disable canonical/id-match */ 'use client'; import { ReadonlyTimeRange } from './time-range'; -import { useSlotQuery } from '@/hooks/slots'; +import { useSlotQuery } from '@/hooks/api/slots'; import { Enum_Slot_State, type SlotFieldsFragment } from '@repo/graphql/types'; import { Badge } from '@repo/ui/components/ui/badge'; import { cn } from '@repo/ui/lib/utils'; @@ -18,8 +18,7 @@ export function SlotCard(props: Readonly) { const pathname = usePathname(); const { documentId } = props; - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); const ordersNumber = slot?.orders?.length; const hasOrders = Boolean(ordersNumber); diff --git a/apps/web/components/schedule/components/slot-date.tsx b/apps/web/components/schedule/components/slot-date.tsx index 8493aca..cdec86d 100644 --- a/apps/web/components/schedule/components/slot-date.tsx +++ b/apps/web/components/schedule/components/slot-date.tsx @@ -1,11 +1,10 @@ 'use client'; import { type SlotComponentProps } from '../types'; -import { useSlotQuery } from '@/hooks/slots'; -import { formatDate } from '@/utils/date'; +import { useSlotQuery } from '@/hooks/api/slots'; +import { formatDate } from '@repo/graphql/utils/datetime-format'; export function SlotDate({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/components/schedule/components/slot-time.tsx b/apps/web/components/schedule/components/slot-time.tsx index d0b0c62..3c85b34 100644 --- a/apps/web/components/schedule/components/slot-time.tsx +++ b/apps/web/components/schedule/components/slot-time.tsx @@ -3,7 +3,7 @@ import { ScheduleTimeContext } from '../context'; import { type SlotComponentProps } from '../types'; import { EditableTimeRangeForm, ReadonlyTimeRange } from './time-range'; -import { useSlotMutation, useSlotQuery } from '@/hooks/slots'; +import { useSlotMutation, useSlotQuery } from '@/hooks/api/slots'; import { Button } from '@repo/ui/components/ui/button'; import { PencilLine } from 'lucide-react'; import { use, useEffect } from 'react'; @@ -19,8 +19,7 @@ function SlotTimeEditForm({ documentId }: Readonly) { use(ScheduleTimeContext); const { isPending: isMutationPending, mutate: updateSlot } = useSlotMutation({ documentId }); - const { data, isPending: isQueryPending } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {}, isPending: isQueryPending } = useSlotQuery({ documentId }); const isPending = isMutationPending || isQueryPending; @@ -32,7 +31,7 @@ function SlotTimeEditForm({ documentId }: Readonly) { }, [editMode, setEndTime, setStartTime, slot]); function handleSubmit() { - updateSlot({ data: { time_end: endTime, time_start: startTime }, documentId }); + updateSlot({ data: { time_end: endTime, time_start: startTime } }); resetTime(); setEditMode(false); } @@ -46,11 +45,10 @@ function SlotTimeEditForm({ documentId }: Readonly) { ); } -function SlotTimeReadonly(props: Readonly) { +function SlotTimeReadonly({ documentId }: Readonly) { const { setEditMode } = use(ScheduleTimeContext); - const { data } = useSlotQuery(props); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/components/schedule/components/time-range.tsx b/apps/web/components/schedule/components/time-range.tsx index 6041ebd..d2f0edc 100644 --- a/apps/web/components/schedule/components/time-range.tsx +++ b/apps/web/components/schedule/components/time-range.tsx @@ -1,6 +1,6 @@ 'use client'; import { ScheduleTimeContext } from '../context'; -import { formatTime } from '@/utils/date'; +import { formatTime } from '@repo/graphql/utils/datetime-format'; import { Input } from '@repo/ui/components/ui/input'; import { cn } from '@repo/ui/lib/utils'; import { type FormEvent, type PropsWithChildren, use } from 'react'; diff --git a/apps/web/components/schedule/day-slot-add-form.tsx b/apps/web/components/schedule/day-slot-add-form.tsx index ccc593b..df5b64e 100644 --- a/apps/web/components/schedule/day-slot-add-form.tsx +++ b/apps/web/components/schedule/day-slot-add-form.tsx @@ -3,7 +3,7 @@ import { EditableTimeRangeForm } from './components/time-range'; import { ScheduleTimeContext, ScheduleTimeContextProvider } from './context'; import { ScheduleContext } from '@/context/schedule'; -import { useSlotAdd } from '@/hooks/slots'; +import { useSlotCreate } from '@/hooks/api/slots'; import { withContext } from '@/utils/context'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; @@ -15,16 +15,17 @@ export const DaySlotAddForm = withContext(ScheduleTimeContextProvider)(function const { selectedDate } = use(ScheduleContext); - const { isPending, mutate: addSlot } = useSlotAdd({ date: selectedDate }); + const { isPending, mutate: addSlot } = useSlotCreate({ date: selectedDate }); const handleSubmit = (event: FormEvent) => { event.preventDefault(); if (startTime && endTime) { addSlot({ - date: selectedDate, - state: Enum_Slot_State.Open, - time_end: endTime, - time_start: startTime, + input: { + state: Enum_Slot_State.Open, + time_end: endTime, + time_start: startTime, + }, }); resetTime(); diff --git a/apps/web/components/schedule/day-slots-list.tsx b/apps/web/components/schedule/day-slots-list.tsx index caf06b2..3f87ff7 100644 --- a/apps/web/components/schedule/day-slots-list.tsx +++ b/apps/web/components/schedule/day-slots-list.tsx @@ -3,13 +3,12 @@ import { SlotCard } from './components/slot-card'; import { DaySlotAddForm } from './day-slot-add-form'; import { LoadingSpinner } from '@/components/common/spinner'; import { ScheduleContext } from '@/context/schedule'; -import { useSlots } from '@/hooks/slots'; +import { useSlots } from '@/hooks/api/slots'; import { use } from 'react'; export function DaySlotsList() { const { selectedDate } = use(ScheduleContext); - const { data, isLoading } = useSlots({ date: selectedDate }); - const slots = data?.data.slots; + const { data: { slots } = {}, isLoading } = useSlots({ filters: { date: { eq: selectedDate } } }); if (isLoading) return ; diff --git a/apps/web/components/schedule/slot-buttons.tsx b/apps/web/components/schedule/slot-buttons.tsx index c9ce691..654f70e 100644 --- a/apps/web/components/schedule/slot-buttons.tsx +++ b/apps/web/components/schedule/slot-buttons.tsx @@ -2,36 +2,31 @@ /* eslint-disable canonical/id-match */ 'use client'; import { type SlotComponentProps } from './types'; -import { ScheduleContext } from '@/context/schedule'; -import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/slots'; +import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/api/slots'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; import { useRouter } from 'next/navigation'; -import { use } from 'react'; export function SlotButtons({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); + const { data: { slot } = {} } = useSlotQuery({ documentId }); const { mutate: updateSlot } = useSlotMutation({ documentId }); - const { selectedDate } = use(ScheduleContext); - const { mutate: deleteSlot } = useSlotDelete({ date: selectedDate, documentId }); + const { mutate: deleteSlot } = useSlotDelete({ documentId }); const router = useRouter(); - const slot = data?.data?.slot; - if (!slot) return null; const isOpened = slot?.state === Enum_Slot_State.Open; const isClosed = slot?.state === Enum_Slot_State.Closed; function handleOpenSlot() { - return updateSlot({ data: { state: Enum_Slot_State.Open }, documentId }); + return updateSlot({ data: { state: Enum_Slot_State.Open } }); } function handleCloseSlot() { - return updateSlot({ data: { state: Enum_Slot_State.Closed }, documentId }); + return updateSlot({ data: { state: Enum_Slot_State.Closed } }); } function handleDeleteSlot() { diff --git a/apps/web/components/schedule/slot-orders-list.tsx b/apps/web/components/schedule/slot-orders-list.tsx index f84226d..80ed9f1 100644 --- a/apps/web/components/schedule/slot-orders-list.tsx +++ b/apps/web/components/schedule/slot-orders-list.tsx @@ -1,11 +1,10 @@ 'use client'; import { OrderCard } from './components/order-card'; import { type SlotComponentProps } from './types'; -import { useSlotQuery } from '@/hooks/slots'; +import { useSlotQuery } from '@/hooks/api/slots'; export function SlotOrdersList({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/hooks/api/slots.ts b/apps/web/hooks/api/slots.ts new file mode 100644 index 0000000..78db87f --- /dev/null +++ b/apps/web/hooks/api/slots.ts @@ -0,0 +1,84 @@ +'use client'; +import { useCustomerQuery } from './customers'; +import { createSlot, deleteSlot, getSlot, getSlots, updateSlot } from '@/actions/api/slots'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +export const useSlots = (variables: Parameters[0]) => { + const { data: { customer } = {} } = useCustomerQuery(); + + const masterId = variables.filters?.master?.documentId?.eq || customer?.documentId; + const date = variables.filters?.date?.eq; + + return useQuery({ + queryFn: () => getSlots(variables), + queryKey: ['slots', 'master', masterId, 'list', date.toISOString()], + }); +}; + +export const useSlotQuery = (variables: Parameters[0]) => { + const { documentId } = variables; + + return useQuery({ + queryFn: () => getSlot(variables), + queryKey: ['slots', 'get', documentId], + }); +}; + +export const useSlotMutation = ({ + documentId, +}: Pick[0], 'documentId'>) => { + const queryClient = useQueryClient(); + function handleOnSuccess() { + queryClient.invalidateQueries({ + queryKey: ['slots', 'get', documentId], + }); + } + + return useMutation({ + mutationFn: ({ data }: Pick[0], 'data'>) => + updateSlot({ data, documentId }), + mutationKey: ['slots', 'update', documentId], + onSuccess: handleOnSuccess, + }); +}; + +export const useSlotCreate = ({ date }: { date: Date }) => { + const { data: { customer } = {} } = useCustomerQuery(); + const masterId = customer?.documentId; + + const queryClient = useQueryClient(); + + function handleOnSuccess() { + queryClient.invalidateQueries({ + queryKey: ['slots', 'master', masterId, 'list', date.toISOString()], + }); + } + + return useMutation({ + mutationFn: ({ input }: { input: Omit[0]['input'], 'date'> }) => + createSlot({ input: { ...input, date } }), + mutationKey: ['slots', 'create', 'date', date.toISOString(), 'master', masterId], + onSuccess: handleOnSuccess, + }); +}; + +export const useSlotDelete = ({ documentId }: Parameters[0]) => { + const { data: { slot } = {} } = useSlotQuery({ documentId }); + + const queryClient = useQueryClient(); + + function handleOnSuccess() { + const date = slot?.date; + const masterId = slot?.master; + + queryClient.invalidateQueries({ + queryKey: ['slots', 'master', masterId, 'list', date.toISOString()], + }); + } + + return useMutation({ + mutationFn: () => deleteSlot({ documentId }), + mutationKey: ['slots', 'delete', documentId], + onSuccess: handleOnSuccess, + }); +}; diff --git a/apps/web/hooks/slots/index.ts b/apps/web/hooks/slots/index.ts deleted file mode 100644 index eacc4c0..0000000 --- a/apps/web/hooks/slots/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use client'; -import { addSlot, deleteSlot, getSlot, getSlots, updateSlot } from '@/actions/slots'; -import { formatDate } from '@/utils/date'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '@repo/graphql/node_modules/@apollo/client/core'; -import { useMutation, useQuery } from '@tanstack/react-query'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -type SlotAddInput = { - date: Date; -}; - -type SlotMutationInput = { - documentId: string; -}; - -type SlotQueryInput = { - date: Date; - masterId?: null | string; -}; - -export const useSlots = ({ date, masterId }: SlotQueryInput) => { - return useQuery({ - queryFn: () => - getSlots({ - filters: { - date: { eq: formatDate(date).db() }, - master: masterId ? { documentId: { eq: masterId } } : undefined, - }, - }), - queryKey: ['slots', 'master', masterId, 'list', date], - }); -}; - -export const useSlotQuery = ({ documentId }: SlotMutationInput) => - useQuery({ - queryFn: () => getSlot({ documentId }), - queryKey: ['slots', 'get', documentId], - }); - -export const useSlotMutation = ({ documentId }: SlotMutationInput) => { - const { refetch } = useSlotQuery({ documentId }); - - return useMutation({ - mutationFn: updateSlot, - mutationKey: ['slots', 'update', documentId], - onSuccess: () => refetch(), - }); -}; - -export const useSlotAdd = ({ date }: SlotAddInput) => { - const { refetch } = useSlots({ date }); - - return useMutation({ - mutationFn: addSlot, - mutationKey: ['slots', 'add'], - onSuccess: () => refetch(), - }); -}; - -export const useSlotDelete = ({ date, documentId }: SlotAddInput & SlotMutationInput) => { - const { refetch } = useSlots({ date }); - - return useMutation({ - mutationFn: () => deleteSlot({ documentId }), - mutationKey: ['slots', 'delete', documentId], - onSuccess: () => refetch(), - }); -}; diff --git a/apps/web/package.json b/apps/web/package.json index c76fca5..4db2048 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,7 +17,7 @@ "@repo/ui": "workspace:*", "@tanstack/react-query": "^5.64.1", "@telegram-apps/sdk-react": "^2.0.19", - "dayjs": "^1.11.13", + "dayjs": "catalog:", "graphql": "catalog:", "lucide-react": "catalog:", "next": "15.3.0", diff --git a/packages/graphql/api/index.ts b/packages/graphql/api/index.ts index ae9c1ca..c37fc52 100644 --- a/packages/graphql/api/index.ts +++ b/packages/graphql/api/index.ts @@ -2,4 +2,4 @@ export * from './auth'; export * from './customers'; export * from './order'; export * from './service'; -export * from './slot'; +export * from './slots'; diff --git a/packages/graphql/api/slot.ts b/packages/graphql/api/slot.ts deleted file mode 100644 index 8e872ad..0000000 --- a/packages/graphql/api/slot.ts +++ /dev/null @@ -1,48 +0,0 @@ -'use server'; -import { getClientWithToken } from '../apollo/client'; -import * as GQL from '../types'; - -export async function createSlot(input: GQL.CreateSlotMutationVariables['input']) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.CreateSlotDocument, - variables: { input }, - }); -} - -export async function getSlots(input: GQL.GetSlotsQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetSlotsDocument, - variables: input, - }); -} - -export async function getSlot(input: GQL.GetSlotQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetSlotDocument, - variables: input, - }); -} - -export async function updateSlot(input: GQL.UpdateSlotMutationVariables) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.UpdateSlotDocument, - variables: input, - }); -} - -export async function deleteSlot(input: GQL.DeleteSlotMutationVariables) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.DeleteSlotDocument, - variables: input, - }); -} diff --git a/packages/graphql/api/slots.ts b/packages/graphql/api/slots.ts new file mode 100644 index 0000000..6b50c94 --- /dev/null +++ b/packages/graphql/api/slots.ts @@ -0,0 +1,98 @@ +import { getClientWithToken } from '../apollo/client'; +import * as GQL from '../types'; +import { formatDate, formatTime } from '../utils/datetime-format'; +import { BaseService } from './base'; +import { CustomersService } from './customers'; +import { type VariablesOf } from '@graphql-typed-document-node/core'; + +export class SlotsService extends BaseService { + async createSlot(variables: VariablesOf) { + const customerService = new CustomersService(this.customer); + + const { customer } = await customerService.getCustomer(this.customer); + + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.CreateSlotDocument, + variables: { + ...variables, + date: formatDate(variables.input.date).db(), + master: customer?.documentId, + time_end: formatTime(variables.input.time_end).db(), + time_start: formatTime(variables.input.time_start).db(), + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async deleteSlot(variables: VariablesOf) { + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.DeleteSlotDocument, + variables, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async getSlot(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetSlotDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async getSlots(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetSlotsDocument, + variables: { + filters: { + ...variables.filters, + date: { eq: formatDate(variables?.filters?.date?.eq).db() }, + }, + }, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async updateSlot(variables: VariablesOf) { + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.UpdateSlotDocument, + variables: { + ...variables, + date: variables.data?.date ? formatDate(variables.data.date).db() : undefined, + time_end: variables.data?.time_end ? formatTime(variables.data.time_end).db() : undefined, + time_start: variables.data?.time_start + ? formatTime(variables.data.time_start).db() + : undefined, + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } +} diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 66199b8..c44907b 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@apollo/client": "catalog:", + "dayjs": "catalog:", "jsonwebtoken": "catalog:", "zod": "catalog:" }, diff --git a/apps/web/utils/date/index.ts b/packages/graphql/utils/datetime-format.ts similarity index 100% rename from apps/web/utils/date/index.ts rename to packages/graphql/utils/datetime-format.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46f8ba2..afc67bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ catalogs: autoprefixer: specifier: ^10.4.20 version: 10.4.20 + dayjs: + specifier: ^1.11.3 + version: 1.11.13 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -268,6 +271,9 @@ importers: '@apollo/client': specifier: 'catalog:' version: 3.12.4(@types/react@19.1.2)(graphql-ws@5.16.0(graphql@16.9.0))(graphql@16.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + dayjs: + specifier: 'catalog:' + version: 1.11.13 jsonwebtoken: specifier: 'catalog:' version: 9.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6c1fe15..853b936 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ catalog: "@types/react-dom": ^19.1.2 "@vchikalkin/eslint-config-awesome": ^2.2.2 autoprefixer: ^10.4.20 + dayjs: ^1.11.3 dotenv-cli: ^7.4.4 eslint: ^9.17.0 graphql: ^16.9.0