Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit 5d10258

Browse files
committed
fix: calculating series of 100+ taking too long
1 parent d06ab5c commit 5d10258

5 files changed

Lines changed: 71 additions & 38 deletions

File tree

apps/admin-ui/src/spa/my-units/recurring/RecurringReservationForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ export function RecurringReservationForm({ reservationUnits }: Props) {
222222
reservationUnitPk,
223223
begin: fromUIDate(getValues("startingDate")) ?? new Date(),
224224
end: fromUIDate(getValues("endingDate")) ?? new Date(),
225+
startTime,
226+
endTime,
225227
reservationType,
226228
});
227229

apps/admin-ui/src/spa/my-units/recurring/hooks/useFilteredReservationList.tsx

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,23 @@ import {
44
ReservationTypeChoice,
55
useReservationTimesInReservationUnitQuery,
66
} from "@gql/gql-types";
7-
import { toApiDate } from "common/src/common/util";
8-
import { addDays } from "date-fns";
7+
import { isValidDate, toApiDate } from "common/src/common/util";
8+
import { addDays, addMinutes, startOfDay } from "date-fns";
99
import {
1010
type CollisionInterval,
1111
doesIntervalCollide,
1212
reservationToInterval,
1313
} from "@/helpers";
1414
import { type NewReservationListItem } from "@/component/ReservationsList";
15-
import { convertToDate } from "../utils";
16-
import { base64encode, filterNonNullable } from "common/src/helpers";
15+
import {
16+
base64encode,
17+
filterNonNullable,
18+
timeToMinutes,
19+
} from "common/src/helpers";
1720
import { RELATED_RESERVATION_STATES } from "common/src/const";
1821
import { gql } from "@apollo/client";
1922
import { errorToast } from "common/src/common/toast";
2023

21-
// TODO this is only used for RecurringReservationForm, why? (the above query + hook also)
22-
23-
function listItemToInterval(
24-
item: NewReservationListItem,
25-
type: ReservationTypeChoice
26-
): CollisionInterval | null {
27-
const start = convertToDate(item.date, item.startTime);
28-
const end = convertToDate(item.date, item.endTime);
29-
if (start && end) {
30-
return {
31-
start,
32-
end,
33-
buffers: {
34-
before:
35-
type !== ReservationTypeChoice.Blocked
36-
? (item.buffers?.before ?? 0)
37-
: 0,
38-
after:
39-
type !== ReservationTypeChoice.Blocked
40-
? (item.buffers?.after ?? 0)
41-
: 0,
42-
},
43-
type,
44-
};
45-
}
46-
return null;
47-
}
48-
4924
// TODO there is multiples of these fragments (for each Calendar), should be unified
5025
const RESERVATIONS_IN_INTERVAL_FRAGMENT = gql`
5126
fragment ReservationsInInterval on ReservationNode {
@@ -148,17 +123,23 @@ function useReservationsInInterval({
148123
return { reservations, loading, refetch };
149124
}
150125

126+
// Could also be reworked to work the other way around, return list of collisions, not list of items
127+
// TODO this should be debounced because keypress message handler is taking 200ms+
151128
export function useFilteredReservationList({
152129
items,
153130
reservationUnitPk,
154131
begin,
155132
end,
133+
startTime,
134+
endTime,
156135
reservationType,
157136
}: {
158137
items: NewReservationListItem[];
159138
reservationUnitPk?: number;
160139
begin: Date;
161140
end: Date;
141+
startTime: string;
142+
endTime: string;
162143
reservationType: ReservationTypeChoice;
163144
}) {
164145
const { reservations, refetch } = useReservationsInInterval({
@@ -172,12 +153,24 @@ export function useFilteredReservationList({
172153
if (reservations.length === 0) {
173154
return items;
174155
}
156+
if (items.length === 0) {
157+
return [];
158+
}
159+
160+
const startMin = timeToMinutes(startTime);
161+
const endMin = timeToMinutes(endTime);
175162
const isReservationInsideRange = (
176163
reservationToMake: NewReservationListItem,
177164
interval: CollisionInterval
178165
) => {
179166
const type = interval.type ?? ReservationTypeChoice.Blocked;
180-
const interval2 = listItemToInterval(reservationToMake, type);
167+
const interval2 = listItemToInterval(
168+
reservationToMake.date,
169+
startMin,
170+
endMin,
171+
type,
172+
reservationToMake.buffers
173+
);
181174
if (interval2 && interval) {
182175
return doesIntervalCollide(interval2, interval);
183176
}
@@ -189,7 +182,35 @@ export function useFilteredReservationList({
189182
? { ...x, isOverlapping: true }
190183
: x
191184
);
192-
}, [items, reservations]);
185+
}, [items, reservations, startTime, endTime]);
193186

194187
return { reservations: data, refetch };
195188
}
189+
190+
// unsafe
191+
function listItemToInterval(
192+
date: Date,
193+
startTime: number,
194+
endTime: number,
195+
type: ReservationTypeChoice,
196+
buffers?: { before?: number; after?: number }
197+
): CollisionInterval {
198+
const start = addMinutes(startOfDay(date), startTime);
199+
const end = addMinutes(startOfDay(date), endTime);
200+
if (!isValidDate(start) && !isValidDate(end)) {
201+
throw new Error("Invalid date");
202+
}
203+
const before =
204+
type !== ReservationTypeChoice.Blocked ? (buffers?.before ?? 0) : 0;
205+
const after =
206+
type !== ReservationTypeChoice.Blocked ? (buffers?.after ?? 0) : 0;
207+
return {
208+
start,
209+
end,
210+
buffers: {
211+
before,
212+
after,
213+
},
214+
type,
215+
};
216+
}

apps/admin-ui/src/spa/my-units/recurring/hooks/useMultipleReservation.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ type ReservationUnitBufferType = {
88
bufferTimeAfter?: number;
99
};
1010

11+
// This is only used in recurring form so we can rework it
12+
// we want to remove the early generation of reservations
1113
export function useMultipleReservation({
1214
form,
1315
reservationUnit,

apps/admin-ui/src/spa/my-units/recurring/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ type DateRange = {
77

88
// Assumes that 08:00 - 09:00 and 09:00 - 10:00 do not overlap
99
// Assumes that begin <= end
10-
export const isOverlapping = (a: DateRange, b: DateRange) => {
10+
export function isOverlapping(a: DateRange, b: DateRange) {
1111
if (a.begin >= b.end || b.begin >= a.end) {
1212
return false;
1313
}
1414
return true;
15-
};
15+
}
1616

1717
// no exception wrapping because parse only throws on invalid format strings (not on invalid inputs)
18-
export const convertToDate = (d: Date, time: string) => {
18+
export function convertToDate(d: Date, time: string) {
1919
if (!d || Number.isNaN(d.getTime())) {
2020
return undefined;
2121
}
2222
const res = parse(time, "HH:mm", d);
2323
return !Number.isNaN(res.getTime()) ? res : undefined;
24-
};
24+
}

packages/common/src/helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,11 @@ export function dayMax(days: Array<Date | undefined>): Date | undefined {
163163
return pickMaybeDay(acc, day, isAfter);
164164
}, undefined);
165165
}
166+
167+
export function timeToMinutes(time: string) {
168+
const [hours, minutes] = time.split(":").map(Number).filter(Number.isFinite);
169+
if (hours != null && minutes != null) {
170+
return hours * 60 + minutes;
171+
}
172+
return 0;
173+
}

0 commit comments

Comments
 (0)