create slot cards

This commit is contained in:
vchikalkin 2025-02-10 15:00:43 +03:00
parent 09627a5336
commit fbf93b3ee0
10 changed files with 103 additions and 106 deletions

View File

@ -1,65 +0,0 @@
'use client';
import { Context, ContextProvider } from '../context';
import { type Props } from '../types';
import { EditableTimePair, ReadonlyTimePair } from './time-pair';
import { ScheduleSlotsContext } from '@/context/schedule-slots';
import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/slots';
import { withContext } from '@/utils/context';
import { combineDateTime } from '@/utils/date';
import { Button } from '@repo/ui/components/ui/button';
import { CircleX, Pencil, Save } from 'lucide-react';
import { type FormEvent, use } from 'react';
export const EditSlotForm = withContext(ContextProvider)(function (props: Readonly<Props>) {
const { documentId } = props;
const { editMode, endTime, setEditMode, setEndTime, setStartTime, startTime } = use(Context);
const { selectedDate } = use(ScheduleSlotsContext);
const { isLoading: isLoadingSlotQuery } = useSlotQuery({ documentId });
const { isPending: isPendingSlotMutation, mutate: mutateSlot } = useSlotMutation({ documentId });
const { isPending: isLoadingSlotDelete, mutate: deleteSlot } = useSlotDelete({ documentId });
const isPending = isPendingSlotMutation || isLoadingSlotQuery || isLoadingSlotDelete;
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
if (startTime && endTime) {
mutateSlot({
data: {
dateend: combineDateTime(selectedDate, endTime).toISOString(),
datestart: combineDateTime(selectedDate, startTime).toISOString(),
},
documentId,
});
setStartTime('');
setEndTime('');
}
setEditMode(false);
};
const TimePair = editMode ? EditableTimePair : ReadonlyTimePair;
return (
<form
className="grid grid-cols-[auto_1fr_1fr_auto_auto] items-center gap-2"
onSubmit={handleSubmit}
>
<span className="text-base font-bold"></span>
<TimePair {...props} />
<Button
disabled={isPending}
onClick={editMode ? undefined : () => setEditMode(true)}
type={editMode ? 'submit' : 'button'}
>
{editMode ? <Save className="size-4" /> : <Pencil className="size-4" />}
</Button>
<Button disabled={isPending} onClick={() => deleteSlot()}>
<CircleX className="size-4" />
</Button>
</form>
);
});

View File

@ -0,0 +1,35 @@
'use client';
import { ContextProvider } from '../context';
import { type SlotProps } from '../types';
import { ReadonlyTimePair } from './time-pair';
import { useSlotQuery } from '@/hooks/slots';
import { withContext } from '@/utils/context';
import { Badge } from '@repo/ui/components/ui/badge';
import Link from 'next/link';
export const SlotCard = withContext(ContextProvider)(function (props: Readonly<SlotProps>) {
const { documentId } = props;
const { data: slotData } = useSlotQuery({ documentId });
if (!slotData?.slot) return null;
const ordersNumber = slotData.slot.orders?.length;
const hasOrders = Boolean(ordersNumber);
return (
<Link href={`/profile/schedule/slot/${documentId}`} rel="noopener noreferrer">
<div className="flex justify-between rounded-2xl bg-background p-4 px-6 dark:bg-primary/5">
<ReadonlyTimePair {...slotData.slot} />
<div>
{hasOrders ? (
<Badge variant="success">Есть записи</Badge>
) : (
<Badge className="bg-transparent text-xs font-normal text-muted-foreground">
Свободно
</Badge>
)}
</div>
</div>
</Link>
);
});

View File

@ -1,11 +1,10 @@
'use client';
import { Context, type ContextType } from '../context';
import { type Props } from '../types';
import { useSlotAdd, useSlotQuery } from '@/hooks/slots';
import { type Slot } from '../types';
import { useSlotAdd } from '@/hooks/slots';
import { getTimeString } from '@/utils/date';
import { Input } from '@repo/ui/components/ui/input';
import { Loader } from 'lucide-react';
import { use, useEffect } from 'react';
import { use } from 'react';
type TimePairProps = Pick<ContextType, 'endTime' | 'setEndTime' | 'setStartTime' | 'startTime'> & {
readonly disabled?: boolean;
@ -26,37 +25,13 @@ export function AddTimePair() {
);
}
export function EditableTimePair({ documentId }: Readonly<Props>) {
const { editMode, endTime, setEndTime, setStartTime, startTime } = use(Context);
const { data } = useSlotQuery({ documentId });
useEffect(() => {
if (editMode) {
setStartTime(getTimeString(data?.slot?.datestart));
setEndTime(getTimeString(data?.slot?.dateend));
}
}, [data?.slot?.dateend, data?.slot?.datestart, editMode, setEndTime, setStartTime]);
export function ReadonlyTimePair({ dateend, datestart }: Readonly<Slot>) {
return (
<TimeInputPair
endTime={endTime}
setEndTime={setEndTime}
setStartTime={setStartTime}
startTime={startTime}
/>
);
}
export function ReadonlyTimePair({ documentId }: Readonly<Props>) {
const { data } = useSlotQuery({ documentId });
if (!data) return <Loader className="animate-spin" />;
return (
<>
<span className="p-1 text-lg font-bold">{getTimeString(data?.slot?.datestart)}</span>
<span className="p-1 text-lg font-bold">{getTimeString(data?.slot?.dateend)}</span>
</>
<div className="flex flex-row items-center gap-2 text-lg font-bold">
<span>{getTimeString(datestart)}</span>
{' - '}
<span>{getTimeString(dateend)}</span>
</div>
);
}

View File

@ -1,6 +1,6 @@
'use client';
import { AddSlotForm } from './components/add-slot-form';
import { EditSlotForm } from './components/edit-slot-form';
import { SlotCard } from './components/slot-card';
import { useSlots } from '@/hooks/slots';
import { Loader } from 'lucide-react';
@ -15,11 +15,11 @@ export function TimeSlots() {
);
return (
<div className="space-y-2 p-4">
<div className="flex flex-col gap-4 p-4">
{slots?.map((slot) => {
if (!slot?.documentId) return null;
return <EditSlotForm key={slot.documentId} {...slot} />;
return <SlotCard key={slot.documentId} {...slot} />;
})}
<AddSlotForm />
</div>

View File

@ -1,4 +1,5 @@
'use client';
import { type SlotFieldsFragment } from '@repo/graphql/types';
import { type GetSlotQuery, type SlotFieldsFragment } from '@repo/graphql/types';
export type Props = Pick<SlotFieldsFragment, 'documentId'>;
export type Slot = NonNullable<GetSlotQuery['slot']>;
export type SlotProps = Pick<SlotFieldsFragment, 'documentId'>;

View File

@ -19,6 +19,9 @@ query GetSlots($filters: SlotFiltersInput) {
query GetSlot($documentId: ID!) {
slot(documentId: $documentId) {
orders {
documentId
}
...SlotFields
}
}

View File

@ -633,7 +633,7 @@ export type GetSlotQueryVariables = Exact<{
}>;
export type GetSlotQuery = { __typename?: 'Query', slot?: { __typename?: 'Slot', documentId: string, datestart: any, dateend: any, state?: Enum_Slot_State | null | undefined } | null | undefined };
export type GetSlotQuery = { __typename?: 'Query', slot?: { __typename?: 'Slot', documentId: string, datestart: any, dateend: any, state?: Enum_Slot_State | null | undefined, orders: Array<{ __typename?: 'Order', documentId: string } | null | undefined> } | null | undefined };
export type UpdateSlotMutationVariables = Exact<{
documentId: Scalars['ID']['input'];
@ -661,6 +661,6 @@ export const GetCustomerClientsDocument = {"kind":"Document","definitions":[{"ki
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: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 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"},"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":"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>;

View File

@ -0,0 +1,32 @@
import { cn } from '@repo/ui/lib/utils';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
defaultVariants: {
variant: 'default',
},
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
success: 'border-transparent bg-success text-success-foreground hover:bg-success/80',
warning: 'border-transparent bg-warning text-warning-foreground hover:bg-warning/80',
},
},
},
);
export type BadgeProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof badgeVariants>;
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };

View File

@ -30,6 +30,10 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--warning: 31 96% 53%;
--warning-foreground: 0 0% 98%;
--success: 100 77% 44%;
--success-foreground: 0 0% 98%;
}
.dark {
@ -58,6 +62,10 @@
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--warning: 31 96% 53%;
--warning-foreground: 0 0% 98%;
--success: 100 77% 33%;
--success-foreground: 0 0% 98%;
}
}

View File

@ -65,6 +65,14 @@ const config = {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
success: {
DEFAULT: 'hsl(var(--success))',
foreground: 'hsl(var(--success-foreground))',
},
warning: {
DEFAULT: 'hsl(var(--warning))',
foreground: 'hsl(var(--warning-foreground))',
},
},
keyframes: {
'accordion-down': {