show slots

This commit is contained in:
vchikalkin 2025-02-05 00:45:07 +03:00
parent 9ac89066d3
commit 69427a1a8d
8 changed files with 199 additions and 38 deletions

View File

@ -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<GQL.CreateSlotMutationVariables['input'], 'master'>;
@ -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;

View File

@ -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 (
<form className="flex items-center space-x-2" onSubmit={handleSubmit}>
<div className="flex flex-1 space-x-2">
<div className="flex-1">
<Input
id="start-time"
onChange={(event) => setStartTime(event.target.value)}
required
type="time"
value={startTime}
/>
</div>
<div className="flex-1">
<Input
id="end-time"
onChange={(event) => setEndTime(event.target.value)}
required
type="time"
value={endTime}
/>
</div>
<form className="grid grid-cols-[1fr_1fr_auto] gap-2" onSubmit={handleSubmit}>
<div className="flex-1">
<Input
id="start-time"
onChange={(event) => setStartTime(event.target.value)}
required
type="time"
value={startTime}
/>
</div>
<Button type="submit">Добавить</Button>
<div className="flex-1">
<Input
id="end-time"
onChange={(event) => setEndTime(event.target.value)}
required
type="time"
value={endTime}
/>
</div>
<Button type="submit">
<PlusSquare className="size-4" />
</Button>
</form>
);
}

View File

@ -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<SlotFieldsFragment, 'documentId'>;
type TimeTextProps = { readonly date: Date };
export function EditSlotForm({ documentId }: Readonly<Props>) {
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 (
<form className="grid grid-cols-[auto_1fr_1fr_auto] items-center gap-2" onSubmit={handleSubmit}>
<span className="text-base font-bold"></span>
{editMode ? (
<>
<div className="flex-1">
<Input
id="start-time"
onChange={(event) => setStartTime(event.target.value)}
required
type="time"
value={startTime}
/>
</div>
<div className="flex-1">
<Input
id="end-time"
onChange={(event) => setEndTime(event.target.value)}
required
type="time"
value={endTime}
/>
</div>
</>
) : (
<>
<TimeText date={data?.slot?.datestart} />
<TimeText date={data?.slot?.dateend} />
</>
)}
{editMode ? (
<Button disabled={isLoadingSlot || isPendingSlotUpdate} type="submit">
<Save className="size-4" />
</Button>
) : null}
{editMode ? null : (
<Button disabled={isLoadingSlot || isPendingSlotUpdate} onClick={() => setEditMode(true)}>
<Pencil className="size-4" />
</Button>
)}
</form>
);
}
function TimeText({ date }: TimeTextProps) {
if (!date) return <Loader className="animate-spin" />;
return <span className="p-1 text-lg font-bold">{getTimeString(date)}</span>;
}

View File

@ -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 (
<div className="p-4">
<div className="space-y-2 p-4">
{slots?.map(
(slot) =>
slot?.documentId && <EditSlotForm documentId={slot.documentId} key={slot.documentId} />,
)}
<AddSlotForm />
</div>
);

View File

@ -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<ReturnType<typeof getSlots>>;
};
export const ScheduleSlotsContext = createContext<ContextType>({} 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 <ScheduleSlotsContext value={value}>{children}</ScheduleSlotsContext>;
}

View File

@ -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');
}

View File

@ -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
}
}

View File

@ -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<GetCustomerClientsQuery, GetCustomerClientsQueryVariables>;
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<UpdateCustomerProfileMutation, UpdateCustomerProfileMutationVariables>;
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<CreateSlotMutation, CreateSlotMutationVariables>;
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<GetSlotsQuery, GetSlotsQueryVariables>;
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<GetSlotsQuery, GetSlotsQueryVariables>;
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<GetSlotQuery, GetSlotQueryVariables>;
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<UpdateSlotMutation, UpdateSlotMutationVariables>;
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<DeleteSlotMutation, DeleteSlotMutationVariables>;