From 69427a1a8d40264baff1a5ae02ea6299a0cca59f Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Wed, 5 Feb 2025 00:45:07 +0300 Subject: [PATCH] show slots --- apps/web/actions/slots.ts | 26 +++- .../schedule/time-slots/add-slot-form.tsx | 67 ++++++----- .../schedule/time-slots/edit-slot-form.tsx | 111 ++++++++++++++++++ .../components/schedule/time-slots/index.tsx | 12 +- apps/web/context/schedule-slots.tsx | 9 +- apps/web/utils/date/index.ts | 8 ++ packages/graphql/operations/slot.graphql | 2 +- .../graphql/types/operations.generated.ts | 2 +- 8 files changed, 199 insertions(+), 38 deletions(-) create mode 100644 apps/web/components/schedule/time-slots/edit-slot-form.tsx diff --git a/apps/web/actions/slots.ts b/apps/web/actions/slots.ts index 212b945..572c1e7 100644 --- a/apps/web/actions/slots.ts +++ b/apps/web/actions/slots.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line sonarjs/no-internal-api-use import type * as ApolloTypes from '../../../packages/graphql/node_modules/@apollo/client/core'; import { getProfile } from './profile'; -import { createSlot } from '@repo/graphql/api'; +import * as api from '@repo/graphql/api'; import type * as GQL from '@repo/graphql/types'; type AddSlotInput = Omit; @@ -13,7 +13,25 @@ type FixTypescriptCringe = ApolloTypes.FetchResult; export async function addSlot(input: AddSlotInput) { const customer = await getProfile(); - const master = customer?.documentId; - - return createSlot({ ...input, master }); + return api.createSlot({ ...input, master: customer?.documentId }); } + +export async function getSlots(input: GQL.GetSlotsQueryVariables) { + const customer = await getProfile(); + + if (!customer?.documentId) throw new Error('Customer not found'); + + return api.getSlots({ + filters: { + master: { + documentId: { + eq: customer.documentId, + }, + }, + }, + ...input.filters, + }); +} + +export const getSlot = api.getSlot; +export const updateSlot = api.updateSlot; diff --git a/apps/web/components/schedule/time-slots/add-slot-form.tsx b/apps/web/components/schedule/time-slots/add-slot-form.tsx index 530e6fc..2ead945 100644 --- a/apps/web/components/schedule/time-slots/add-slot-form.tsx +++ b/apps/web/components/schedule/time-slots/add-slot-form.tsx @@ -6,49 +6,56 @@ import { combineDateTime } from '@/utils/date'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; import { Input } from '@repo/ui/components/ui/input'; +import { useMutation } from '@tanstack/react-query'; +import { PlusSquare } from 'lucide-react'; import { type FormEvent, use, useState } from 'react'; export function AddSlotForm() { const [startTime, setStartTime] = useState(''); const [endTime, setEndTime] = useState(''); + const { selectedDate } = use(ScheduleSlotsContext); + const { mutate } = useMutation({ + mutationFn: addSlot, + mutationKey: ['schedule', 'slots', 'add'], + }); + const handleSubmit = (event: FormEvent) => { event.preventDefault(); - if (startTime && endTime) { - addSlot({ - dateend: combineDateTime(selectedDate, endTime).toISOString(), - datestart: combineDateTime(selectedDate, startTime).toISOString(), - state: Enum_Slot_State.Open, - }); - setStartTime(''); - setEndTime(''); - } + mutate({ + dateend: combineDateTime(selectedDate, endTime).toISOString(), + datestart: combineDateTime(selectedDate, startTime).toISOString(), + state: Enum_Slot_State.Open, + }); + + setStartTime(''); + setEndTime(''); }; return ( -
-
-
- setStartTime(event.target.value)} - required - type="time" - value={startTime} - /> -
-
- setEndTime(event.target.value)} - required - type="time" - value={endTime} - /> -
+ +
+ setStartTime(event.target.value)} + required + type="time" + value={startTime} + />
- +
+ setEndTime(event.target.value)} + required + type="time" + value={endTime} + /> +
+ ); } diff --git a/apps/web/components/schedule/time-slots/edit-slot-form.tsx b/apps/web/components/schedule/time-slots/edit-slot-form.tsx new file mode 100644 index 0000000..bb97545 --- /dev/null +++ b/apps/web/components/schedule/time-slots/edit-slot-form.tsx @@ -0,0 +1,111 @@ +'use client'; +import { getSlot, updateSlot } from '@/actions/slots'; +import { ScheduleSlotsContext } from '@/context/schedule-slots'; +import { combineDateTime, getTimeString } from '@/utils/date'; +import { type SlotFieldsFragment } from '@repo/graphql/types'; +import { Button } from '@repo/ui/components/ui/button'; +import { Input } from '@repo/ui/components/ui/input'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Loader, Pencil, Save } from 'lucide-react'; +import { type FormEvent, use, useEffect, useState } from 'react'; + +type Props = Pick; + +type TimeTextProps = { readonly date: Date }; + +export function EditSlotForm({ documentId }: Readonly) { + const [editMode, setEditMode] = useState(false); + + const [startTime, setStartTime] = useState(''); + const [endTime, setEndTime] = useState(''); + + const { selectedDate } = use(ScheduleSlotsContext); + + const { + data, + isLoading: isLoadingSlot, + refetch: refetchSlot, + } = useQuery({ + queryFn: () => getSlot({ documentId }), + queryKey: ['schedule', 'slots', documentId], + }); + + const { isPending: isPendingSlotUpdate, mutate } = useMutation({ + mutationFn: updateSlot, + mutationKey: ['schedule', 'slots', documentId], + onSuccess: () => refetchSlot(), + }); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + if (startTime && endTime) { + mutate({ + data: { + dateend: combineDateTime(selectedDate, endTime).toISOString(), + datestart: combineDateTime(selectedDate, startTime).toISOString(), + }, + documentId, + }); + setStartTime(''); + setEndTime(''); + } + + setEditMode(false); + }; + + useEffect(() => { + if (editMode) { + setStartTime(getTimeString(data?.slot?.datestart)); + setEndTime(getTimeString(data?.slot?.dateend)); + } + }, [data?.slot?.dateend, data?.slot?.datestart, editMode]); + + return ( +
+ + {editMode ? ( + <> +
+ setStartTime(event.target.value)} + required + type="time" + value={startTime} + /> +
+
+ setEndTime(event.target.value)} + required + type="time" + value={endTime} + /> +
+ + ) : ( + <> + + + + )} + {editMode ? ( + + ) : null} + {editMode ? null : ( + + )} + + ); +} + +function TimeText({ date }: TimeTextProps) { + if (!date) return ; + + return {getTimeString(date)}; +} diff --git a/apps/web/components/schedule/time-slots/index.tsx b/apps/web/components/schedule/time-slots/index.tsx index 5394a60..96c9aaa 100644 --- a/apps/web/components/schedule/time-slots/index.tsx +++ b/apps/web/components/schedule/time-slots/index.tsx @@ -1,8 +1,18 @@ +'use client'; import { AddSlotForm } from './add-slot-form'; +import { EditSlotForm } from './edit-slot-form'; +import { ScheduleSlotsContext } from '@/context/schedule-slots'; +import { use } from 'react'; export function TimeSlots() { + const { slots } = use(ScheduleSlotsContext); + return ( -
+
+ {slots?.map( + (slot) => + slot?.documentId && , + )}
); diff --git a/apps/web/context/schedule-slots.tsx b/apps/web/context/schedule-slots.tsx index 61cbd1b..098e060 100644 --- a/apps/web/context/schedule-slots.tsx +++ b/apps/web/context/schedule-slots.tsx @@ -1,17 +1,24 @@ 'use client'; +import { getSlots } from '@/actions/slots'; +import { useQuery } from '@tanstack/react-query'; import { createContext, useMemo, useState } from 'react'; type ContextType = { selectedDate: Date; setSelectedDate: (date: Date) => void; + slots: Awaited>; }; export const ScheduleSlotsContext = createContext({} as ContextType); export function ScheduleSlotsProvider({ children }: { readonly children: React.ReactNode }) { const [selectedDate, setSelectedDate] = useState(new Date()); + const { data: slots = [] } = useQuery({ + queryFn: () => getSlots({}), + queryKey: ['schedule', 'slots'], + }); - const value = useMemo(() => ({ selectedDate, setSelectedDate }), [selectedDate, setSelectedDate]); + const value = useMemo(() => ({ selectedDate, setSelectedDate, slots }), [selectedDate, slots]); return {children}; } diff --git a/apps/web/utils/date/index.ts b/apps/web/utils/date/index.ts index f51ceca..5f8ed01 100644 --- a/apps/web/utils/date/index.ts +++ b/apps/web/utils/date/index.ts @@ -1,3 +1,5 @@ +import dayjs from 'dayjs'; + export function combineDateTime(date: Date, time: string) { const [hours = '00', minutes = '00'] = time.split(':'); @@ -9,3 +11,9 @@ export function combineDateTime(date: Date, time: string) { Number.parseInt(minutes, 10), ); } + +export function getTimeString(date: Date) { + if (!date) return '-'; + + return dayjs(date).format('HH:mm'); +} diff --git a/packages/graphql/operations/slot.graphql b/packages/graphql/operations/slot.graphql index 338a68a..2c5faa8 100644 --- a/packages/graphql/operations/slot.graphql +++ b/packages/graphql/operations/slot.graphql @@ -12,7 +12,7 @@ mutation CreateSlot($input: SlotInput!) { } query GetSlots($filters: SlotFiltersInput) { - slots(filters: $filters, sort: "datestart:desc") { + slots(filters: $filters, sort: "datestart:asc") { documentId } } diff --git a/packages/graphql/types/operations.generated.ts b/packages/graphql/types/operations.generated.ts index c37165e..8c6209e 100644 --- a/packages/graphql/types/operations.generated.ts +++ b/packages/graphql/types/operations.generated.ts @@ -660,7 +660,7 @@ export const GetCustomerMastersDocument = {"kind":"Document","definitions":[{"ki export const GetCustomerClientsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCustomerClients"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}}]}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}}]}}]}]}},{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"BooleanValue","value":true}}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"clients"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode; export const UpdateCustomerProfileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomerProfile"},"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":"CustomerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCustomer"},"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":"CustomerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} 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":"datestart"}},{"kind":"Field","name":{"kind":"Name","value":"dateend"}},{"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":"datestart:desc","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}}]}}]} 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":"datestart:asc","block":false}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}}]}}]} 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":"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":"datestart"}},{"kind":"Field","name":{"kind":"Name","value":"dateend"}},{"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":"datestart"}},{"kind":"Field","name":{"kind":"Name","value":"dateend"}},{"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