Skip to content

Commit 019201b

Browse files
authored
fix: default-day-bug (#393)
* feat: ✨ added sorting for heatmap * fix: πŸ› sorting by capacity + delete old rooms being returned * fix: πŸ› cubic bot fixes * fix: πŸ› default day behaviour changed with fallback * fix: πŸ› no rerender of default search time * feat: ✨ added return loop for user calls * fix: πŸ› cubix fixes * fix: πŸ› more cubic fixes * fix: πŸ› time change and fixed nit * fix: πŸ› cubic changes
1 parent 1874957 commit 019201b

2 files changed

Lines changed: 171 additions & 65 deletions

File tree

β€Žsrc/app/studyrooms/page.tsxβ€Ž

Lines changed: 133 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker";
1414
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
1515
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
1616
import { format } from "date-fns";
17-
import { useEffect, useState } from "react";
17+
import { useCallback, useEffect, useState } from "react";
1818

1919
import { RoomsHeatmap } from "@/components/studyrooms/heatmap/rooms-heatmap";
2020
import { fetchStudyRooms } from "@/lib/rooms/get-rooms";
21+
import { getDefaultWindow, toLocalStr } from "@/lib/rooms/utils";
2122
import type { StudyRooms } from "@/lib/types/studyrooms";
2223

24+
const MAX_FALLBACK_DAYS = 7;
25+
2326
const LOCATION_OPTIONS = [
2427
"Plaza Verde",
2528
"Langson Library",
@@ -29,29 +32,28 @@ const LOCATION_OPTIONS = [
2932
"Ayala Science Library",
3033
];
3134

32-
const toLocalStr = (d: Date) => {
33-
const h = d.getHours();
34-
const m = d.getMinutes();
35-
return `${h % 12 || 12}:${m.toString().padStart(2, "0")}${h >= 12 ? "pm" : "am"}`;
36-
};
37-
3835
export default function Page() {
39-
const defaultStart = new Date();
40-
defaultStart.setHours(11, 0, 0, 0);
41-
const defaultEnd = new Date();
42-
defaultEnd.setHours(17, 0, 0, 0);
43-
44-
const tomorrow = new Date();
45-
tomorrow.setDate(tomorrow.getDate() + 1);
46-
47-
const [date, setDate] = useState<Date | null>(tomorrow);
36+
const [{ defaultDate, defaultStart, defaultEnd }] = useState(() => {
37+
const { start, end } = getDefaultWindow();
38+
return {
39+
defaultStart: start,
40+
defaultEnd: end,
41+
defaultDate: new Date(
42+
start.getFullYear(),
43+
start.getMonth(),
44+
start.getDate(),
45+
),
46+
};
47+
});
48+
const [date, setDate] = useState<Date | null>(defaultDate);
4849
const [startTime, setStartTime] = useState<Date | null>(defaultStart);
4950
const [endTime, setEndTime] = useState<Date | null>(defaultEnd);
5051
const [committedStart, setCommittedStart] = useState<Date | null>(
5152
defaultStart,
5253
);
5354
const [committedEnd, setCommittedEnd] = useState<Date | null>(defaultEnd);
54-
const [committedDate, setCommittedDate] = useState<Date | null>(tomorrow);
55+
const [committedDate, setCommittedDate] = useState<Date | null>(defaultDate);
56+
const [fallbackNotice, setFallbackNotice] = useState<string | null>(null);
5557
const [location, setLocation] = useState<string | null>(null);
5658
const [capacityMin, setCapacityMin] = useState("");
5759
const [capacityMax, setCapacityMax] = useState("");
@@ -88,10 +90,95 @@ export default function Page() {
8890
if (key === "techEnhanced") setIsTechEnhanced(false);
8991
};
9092

91-
async function handleSubmit(e: React.FormEvent) {
93+
const searchWithFallback = useCallback(
94+
async ({
95+
baseDate,
96+
startTime: slotStart,
97+
endTime: slotEnd,
98+
fallbackStart = slotStart,
99+
fallbackEnd = slotEnd,
100+
filters = {},
101+
updateFormState = false,
102+
}: {
103+
baseDate: Date;
104+
startTime: Date;
105+
endTime: Date;
106+
fallbackStart?: Date;
107+
fallbackEnd?: Date;
108+
filters?: {
109+
location?: string | null;
110+
capacityMin?: string;
111+
capacityMax?: string;
112+
isTechEnhanced?: boolean;
113+
};
114+
updateFormState?: boolean;
115+
}) => {
116+
for (let offset = 0; offset < MAX_FALLBACK_DAYS; offset++) {
117+
const tryDate = new Date(baseDate);
118+
tryDate.setDate(tryDate.getDate() + offset);
119+
const isFallback = offset > 0;
120+
const start = isFallback ? fallbackStart : slotStart;
121+
const end = isFallback ? fallbackEnd : slotEnd;
122+
123+
try {
124+
const { data } = await fetchStudyRooms({
125+
date: format(tryDate, "yyyy-MM-dd"),
126+
timeRange: `${toLocalStr(start)}-${toLocalStr(end)}`,
127+
location: filters.location || undefined,
128+
capacityMin: filters.capacityMin
129+
? Number(filters.capacityMin)
130+
: undefined,
131+
capacityMax: filters.capacityMax
132+
? Number(filters.capacityMax)
133+
: undefined,
134+
isTechEnhanced: filters.isTechEnhanced || undefined,
135+
});
136+
137+
if (data.length > 0) {
138+
const committedStartForDay = new Date(tryDate);
139+
committedStartForDay.setHours(
140+
start.getHours(),
141+
start.getMinutes(),
142+
0,
143+
0,
144+
);
145+
const committedEndForDay = new Date(tryDate);
146+
committedEndForDay.setHours(end.getHours(), end.getMinutes(), 0, 0);
147+
setRooms(data);
148+
setCommittedDate(tryDate);
149+
setCommittedStart(committedStartForDay);
150+
setCommittedEnd(committedEndForDay);
151+
if (updateFormState) {
152+
setDate(tryDate);
153+
setStartTime(committedStartForDay);
154+
setEndTime(committedEndForDay);
155+
}
156+
if (isFallback) {
157+
setFallbackNotice(
158+
`No rooms available for ${format(baseDate, "EEEE, MMM d")}. Showing results for ${format(tryDate, "EEEE, MMM d")} instead.`,
159+
);
160+
}
161+
return;
162+
}
163+
} catch (err) {
164+
setError(err instanceof Error ? err.message : "API call failed");
165+
return;
166+
}
167+
}
168+
setRooms([]);
169+
setCommittedDate(null);
170+
setCommittedStart(null);
171+
setCommittedEnd(null);
172+
setError(`No rooms available in the next ${MAX_FALLBACK_DAYS} days.`);
173+
},
174+
[],
175+
);
176+
177+
const handleSubmit = async (e: React.FormEvent) => {
92178
e.preventDefault();
93179
setError(null);
94180
setRooms(null);
181+
setFallbackNotice(null);
95182

96183
if (!date || !startTime || !endTime) {
97184
setError("Please select a date and time range.");
@@ -119,47 +206,30 @@ export default function Page() {
119206
return;
120207
}
121208

122-
const tr = `${toLocalStr(startTime)}-${toLocalStr(endTime)}`;
123-
124-
try {
125-
const { data } = await fetchStudyRooms({
126-
date: format(date, "yyyy-MM-dd"),
127-
timeRange: tr,
128-
location: location || undefined,
129-
capacityMin: capacityMin ? Number(capacityMin) : undefined,
130-
capacityMax: capacityMax ? Number(capacityMax) : undefined,
131-
isTechEnhanced: isTechEnhanced || undefined,
132-
});
133-
134-
setRooms(data);
135-
setCommittedDate(date);
136-
setCommittedStart(startTime);
137-
setCommittedEnd(endTime);
138-
} catch (err) {
139-
setError(err instanceof Error ? err.message : "API call failed");
140-
}
141-
}
209+
const baseDate = new Date(date);
210+
baseDate.setHours(0, 0, 0, 0);
211+
await searchWithFallback({
212+
baseDate,
213+
startTime,
214+
endTime,
215+
filters: { location, capacityMin, capacityMax, isTechEnhanced },
216+
});
217+
};
142218

143219
useEffect(() => {
144-
const tmrw = new Date();
145-
tmrw.setDate(tmrw.getDate() + 1);
146-
const today = format(tmrw, "yyyy-MM-dd");
147-
const defaultTr = "11:00am-5:00pm";
148-
const initialCommittedStart = new Date();
149-
initialCommittedStart.setHours(11, 0, 0, 0);
150-
const initialCommittedEnd = new Date();
151-
initialCommittedEnd.setHours(17, 0, 0, 0);
152-
fetchStudyRooms({ date: today, timeRange: defaultTr })
153-
.then(({ data }) => {
154-
setRooms(data);
155-
setCommittedDate(tmrw);
156-
setCommittedStart(initialCommittedStart);
157-
setCommittedEnd(initialCommittedEnd);
158-
})
159-
.catch((err) =>
160-
setError(err instanceof Error ? err.message : "API call failed"),
161-
);
162-
}, []);
220+
const fallbackStart = new Date(defaultDate);
221+
fallbackStart.setHours(11, 0, 0, 0);
222+
const fallbackEnd = new Date(defaultDate);
223+
fallbackEnd.setHours(17, 0, 0, 0);
224+
searchWithFallback({
225+
baseDate: defaultDate,
226+
startTime: defaultStart,
227+
endTime: defaultEnd,
228+
fallbackStart,
229+
fallbackEnd,
230+
updateFormState: true,
231+
});
232+
}, [searchWithFallback, defaultDate, defaultStart, defaultEnd]);
163233

164234
return (
165235
<LocalizationProvider dateAdapter={AdapterDateFns}>
@@ -263,6 +333,12 @@ export default function Page() {
263333
</Button>
264334
</Box>
265335

336+
{fallbackNotice && (
337+
<Typography variant="body2" color="info.main" sx={{ mt: 2 }}>
338+
{fallbackNotice}
339+
</Typography>
340+
)}
341+
266342
{rooms && committedDate && committedStart && committedEnd && (
267343
<RoomsHeatmap
268344
rooms={rooms}
@@ -271,14 +347,6 @@ export default function Page() {
271347
endTime={committedEnd}
272348
/>
273349
)}
274-
275-
{/*rooms && committedStart && committedEnd && (
276-
<RoomResults
277-
rooms={rooms}
278-
startTime={committedStart}
279-
endTime={committedEnd}
280-
/>
281-
)*/}
282350
</LocalizationProvider>
283351
);
284352
}

β€Žsrc/lib/rooms/utils.tsβ€Ž

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ export const formatISOToLocalTime = (isoString: string): string => {
1313

1414
const HALF_HOUR_MS = 30 * 60 * 1000;
1515

16+
/** Round `from` up to the next 30-min boundary. Rolls to next day if needed. */
17+
export function getNextHalfHour(from: Date = new Date()): Date {
18+
const d = new Date(from);
19+
const remainder = d.getMinutes() % 30;
20+
if (remainder === 0 && d.getSeconds() === 0 && d.getMilliseconds() === 0)
21+
return d;
22+
d.setMinutes(d.getMinutes() + (remainder === 0 ? 30 : 30 - remainder), 0, 0);
23+
return d;
24+
}
25+
1626
/** Calendar day + clock from `time` (same semantics as date pickers + time pickers). */
1727
export function mergeDateAndTime(date: Date, time: Date): Date {
1828
const d = new Date(date);
@@ -214,3 +224,31 @@ export function getBestTimeRanges(availabilityDates: any[]) {
214224

215225
return results;
216226
}
227+
228+
export const toLocalStr = (d: Date) => {
229+
const h = d.getHours();
230+
const m = d.getMinutes();
231+
return `${h % 12 || 12}:${m.toString().padStart(2, "0")}${h >= 12 ? "pm" : "am"}`;
232+
};
233+
234+
const WINDOW_MS = 6 * 60 * 60 * 1000;
235+
236+
export function getDefaultWindow() {
237+
const now = new Date();
238+
const start = getNextHalfHour(now);
239+
240+
const elevenPm = new Date(now);
241+
elevenPm.setHours(23, 0, 0, 0);
242+
243+
if (start >= elevenPm) {
244+
const nextDay = new Date(now);
245+
nextDay.setDate(nextDay.getDate() + 1);
246+
nextDay.setHours(11, 0, 0, 0);
247+
const end = new Date(nextDay.getTime() + WINDOW_MS);
248+
return { start: nextDay, end };
249+
}
250+
251+
const rawEnd = new Date(start.getTime() + WINDOW_MS);
252+
const end = rawEnd > elevenPm ? elevenPm : rawEnd;
253+
return { start, end };
254+
}

0 commit comments

Comments
Β (0)