take into service duration when computing times

This commit is contained in:
vchikalkin 2025-05-21 16:27:56 +03:00
parent 0b867a9136
commit 52d68964f1
6 changed files with 70 additions and 55 deletions

View File

@ -7,19 +7,29 @@ import { Button } from '@repo/ui/components/ui/button';
export function TimeSelect() {
const masterId = useOrderStore((store) => store.masterId);
const date = useOrderStore((store) => store.date);
const serviceId = useOrderStore((store) => store.serviceId);
const { data: { times } = {}, isLoading } = useAvailableTimeSlotsQuery({
filters: {
date: {
eq: date,
},
master: {
documentId: {
eq: masterId,
const { data: { times } = {}, isLoading } = useAvailableTimeSlotsQuery(
{
filters: {
date: {
eq: date,
},
master: {
documentId: {
eq: masterId,
},
},
},
},
});
{
service: {
documentId: {
eq: serviceId,
},
},
},
);
if (isLoading || !times) return null;

View File

@ -33,11 +33,12 @@ export const useSlotQuery = (variables: Parameters<typeof getSlot>[0]) => {
};
export const useAvailableTimeSlotsQuery = (
variables: Parameters<typeof getAvailableTimeSlots>[0],
...variables: Parameters<typeof getAvailableTimeSlots>
) => {
return useQuery({
queryFn: () => getAvailableTimeSlots(variables),
queryFn: () => getAvailableTimeSlots(...variables),
queryKey: ['available-time-slots', variables],
staleTime: 15 * 1_000,
});
};

View File

@ -1,8 +1,9 @@
import { getClientWithToken } from '../apollo/client';
import * as GQL from '../types';
import { formatDate, formatTime } from '../utils/datetime-format';
import { formatDate, formatTime, getMinutes } from '../utils/datetime-format';
import { BaseService } from './base';
import { CustomersService } from './customers';
import { ServicesService } from './services';
import { type VariablesOf } from '@graphql-typed-document-node/core';
import dayjs from 'dayjs';
@ -20,8 +21,8 @@ export class SlotsService extends BaseService {
...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(),
time_end: variables.input.time_end && formatTime(variables.input.time_end).db(),
time_start: variables.input.time_start && formatTime(variables.input.time_start).db(),
},
});
@ -45,13 +46,13 @@ export class SlotsService extends BaseService {
return mutationResult.data;
}
async getAvailableTimeSlots(variables: VariablesOf<typeof GQL.GetSlotsDocument>) {
async getAvailableTimeSlots(
variables: VariablesOf<typeof GQL.GetSlotsDocument>,
context: { service: GQL.ServiceFiltersInput },
) {
const { query } = await getClientWithToken();
const {
data: { slots: openedSlots },
error,
} = await query({
const getSlotsResult = await query({
query: GQL.GetSlotsOrdersDocument,
variables: {
filters: {
@ -62,36 +63,33 @@ export class SlotsService extends BaseService {
},
});
if (error) throw new Error(error.message);
if (getSlotsResult.error) throw new Error(getSlotsResult.error.message);
const servicesService = new ServicesService(this.customer);
if (!context?.service?.documentId?.eq) throw new Error('Missing service id');
const { service } = await servicesService.getService({
documentId: context.service.documentId.eq,
});
if (!service) throw new Error('Service not found');
const duration = getMinutes(service.duration);
const openedSlots = getSlotsResult.data.slots;
const times: Array<{ slotId: string; time: string }> = [];
for (const slot of openedSlots) {
if (!slot?.time_start || !slot?.time_end) continue;
const orders = slot.orders ?? [];
let startTime = dayjs(`${slot.date} ${slot.time_start}`);
const endTime = dayjs(`${slot.date} ${slot.time_end}`).subtract(duration, 'minutes');
let currentTime = dayjs(`${slot.date} ${slot.time_start}`);
const endTime = dayjs(`${slot.date} ${slot.time_end}`);
while (currentTime.isBefore(endTime)) {
const slotStart = currentTime;
const slotEnd = currentTime.add(30, 'minute');
const overlaps = orders.some((order) => {
if (!order?.time_start || !order?.time_end) return false;
const orderStart = dayjs(`${slot.date} ${order.time_start}`);
const orderEnd = dayjs(`${slot.date} ${order.time_end}`);
return slotStart.isBefore(orderEnd) && slotEnd.isAfter(orderStart);
});
if (!overlaps) {
times.push({ slotId: slot.documentId, time: slotStart.format('HH:mm') });
}
currentTime = slotEnd;
while (startTime.valueOf() <= endTime.valueOf()) {
times.push({ slotId: slot.documentId, time: startTime.format('HH:mm') });
startTime = startTime.add(15, 'minutes');
}
}

View File

@ -9,6 +9,7 @@ module.exports = {
onlyOperationTypes: true,
scalars: {
Long: 'number',
Time: 'string',
},
useTypeImports: true,
},

View File

@ -17,7 +17,7 @@ export type Scalars = {
DateTime: { input: any; output: any; }
JSON: { input: any; output: any; }
Long: { input: number; output: number; }
Time: { input: any; output: any; }
Time: { input: string; output: string; }
};
export type BlockFiltersInput = {
@ -695,67 +695,67 @@ export type UpdateCustomerMutationVariables = Exact<{
export type UpdateCustomerMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: number | null | undefined } | null | undefined };
export type OrderFieldsFragment = { __typename?: 'Order', documentId: string, time_start?: any | null | undefined, time_end?: any | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined };
export type OrderFieldsFragment = { __typename?: 'Order', documentId: string, time_start?: string | null | undefined, time_end?: string | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined };
export type GetOrderQueryVariables = Exact<{
documentId: Scalars['ID']['input'];
}>;
export type GetOrderQuery = { __typename?: 'Query', order?: { __typename?: 'Order', documentId: string, time_start?: any | null | undefined, time_end?: any | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined } | null | undefined };
export type GetOrderQuery = { __typename?: 'Query', order?: { __typename?: 'Order', documentId: string, time_start?: string | null | undefined, time_end?: string | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined } | null | undefined };
export type CreateOrderMutationVariables = Exact<{
input: OrderInput;
}>;
export type CreateOrderMutation = { __typename?: 'Mutation', createOrder?: { __typename?: 'Order', documentId: string, time_start?: any | null | undefined, time_end?: any | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined } | null | undefined };
export type CreateOrderMutation = { __typename?: 'Mutation', createOrder?: { __typename?: 'Order', documentId: string, time_start?: string | null | undefined, time_end?: string | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined } | null | undefined };
export type ServiceFieldsFragment = { __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: any };
export type ServiceFieldsFragment = { __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: string };
export type GetServicesQueryVariables = Exact<{
filters?: InputMaybe<ServiceFiltersInput>;
}>;
export type GetServicesQuery = { __typename?: 'Query', services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: any } | null | undefined> };
export type GetServicesQuery = { __typename?: 'Query', services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: string } | null | undefined> };
export type GetServiceQueryVariables = Exact<{
documentId: Scalars['ID']['input'];
}>;
export type GetServiceQuery = { __typename?: 'Query', service?: { __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: any } | null | undefined };
export type GetServiceQuery = { __typename?: 'Query', service?: { __typename?: 'Service', documentId: string, name?: string | null | undefined, duration: string } | null | undefined };
export type SlotFieldsFragment = { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: any, time_end: any, state?: Enum_Slot_State | null | undefined };
export type SlotFieldsFragment = { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: string, time_end: string, state?: Enum_Slot_State | null | undefined };
export type CreateSlotMutationVariables = Exact<{
input: SlotInput;
}>;
export type CreateSlotMutation = { __typename?: 'Mutation', createSlot?: { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: any, time_end: any, state?: Enum_Slot_State | null | undefined } | null | undefined };
export type CreateSlotMutation = { __typename?: 'Mutation', createSlot?: { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: string, time_end: string, state?: Enum_Slot_State | null | undefined } | null | undefined };
export type GetSlotsQueryVariables = Exact<{
filters?: InputMaybe<SlotFiltersInput>;
}>;
export type GetSlotsQuery = { __typename?: 'Query', slots: Array<{ __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: any, time_end: any, state?: Enum_Slot_State | null | undefined } | null | undefined> };
export type GetSlotsQuery = { __typename?: 'Query', slots: Array<{ __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: string, time_end: string, state?: Enum_Slot_State | null | undefined } | null | undefined> };
export type GetSlotsOrdersQueryVariables = Exact<{
filters?: InputMaybe<SlotFiltersInput>;
}>;
export type GetSlotsOrdersQuery = { __typename?: 'Query', slots: Array<{ __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, time_start?: any | null | undefined, time_end?: any | null | undefined } | null | undefined> } | null | undefined> };
export type GetSlotsOrdersQuery = { __typename?: 'Query', slots: Array<{ __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: string, time_end: string, state?: Enum_Slot_State | null | undefined, orders: Array<{ __typename?: 'Order', documentId: string, time_start?: string | null | undefined, time_end?: string | null | undefined } | null | undefined> } | null | undefined> };
export type GetSlotQueryVariables = Exact<{
documentId: Scalars['ID']['input'];
}>;
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, time_start?: any | null | undefined, time_end?: any | null | undefined } | 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: string, time_end: string, state?: Enum_Slot_State | null | undefined, orders: Array<{ __typename?: 'Order', documentId: string, time_start?: string | null | undefined, time_end?: string | null | undefined } | null | undefined>, master?: { __typename?: 'Customer', documentId: string } | null | undefined } | null | undefined };
export type UpdateSlotMutationVariables = Exact<{
documentId: Scalars['ID']['input'];
@ -763,7 +763,7 @@ export type UpdateSlotMutationVariables = Exact<{
}>;
export type UpdateSlotMutation = { __typename?: 'Mutation', updateSlot?: { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: any, time_end: any, state?: Enum_Slot_State | null | undefined } | null | undefined };
export type UpdateSlotMutation = { __typename?: 'Mutation', updateSlot?: { __typename?: 'Slot', documentId: string, date?: any | null | undefined, time_start: string, time_end: string, state?: Enum_Slot_State | null | undefined } | null | undefined };
export type DeleteSlotMutationVariables = Exact<{
documentId: Scalars['ID']['input'];

View File

@ -34,6 +34,11 @@ export function formatTime(time: string) {
};
}
export function getMinutes(time: string) {
const [hours = '00', minutes = '00'] = time.split(':');
return Number.parseInt(hours, 10) * 60 + Number.parseInt(minutes, 10);
}
export function sumTime(time1: string, time2: string) {
const [hours1 = '00', minutes1 = '00'] = time1.split(':');
const [hours2 = '00', minutes2 = '00'] = time2.split(':');