take into service duration when computing times
This commit is contained in:
parent
0b867a9136
commit
52d68964f1
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ module.exports = {
|
||||
onlyOperationTypes: true,
|
||||
scalars: {
|
||||
Long: 'number',
|
||||
Time: 'string',
|
||||
},
|
||||
useTypeImports: true,
|
||||
},
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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(':');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user