feat: account edit request modal with auto-select and responsive improvements
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }>
|
||||
>();
|
||||
let userNavigatedCalendar = $state(false);
|
||||
let editRequestAutoSelectDone = $state(false);
|
||||
// ─── Date constants ─────────────────────────────────────
|
||||
const today = new Date();
|
||||
const minDate = new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
||||
@@ -187,12 +188,14 @@
|
||||
for (const slot of dayAH.slots) {
|
||||
const [sh, sm] = slot.startTime.split(':').map(Number);
|
||||
const [eh, em] = slot.endTime.split(':').map(Number);
|
||||
let startMin = sh * 60 + sm;
|
||||
// Round up to next 15-min boundary so generated times align with
|
||||
// the 15-min grid from working hours start used in generateGroupedTimeSlots
|
||||
let startMin = Math.ceil((sh * 60 + sm) / 15) * 15;
|
||||
const endMin = eh * 60 + em;
|
||||
|
||||
if (isToday) {
|
||||
const currentMin = now.getHours() * 60 + now.getMinutes();
|
||||
startMin = Math.max(startMin, currentMin + 120);
|
||||
startMin = Math.max(startMin, Math.ceil((currentMin + 60) / 15) * 15);
|
||||
}
|
||||
|
||||
for (let m = startMin; m < endMin; m += 15) {
|
||||
@@ -240,7 +243,7 @@
|
||||
const isToday = date.compare(todayCal) === 0;
|
||||
if (isToday) {
|
||||
const currentMin = now.getHours() * 60 + now.getMinutes();
|
||||
startMin = Math.max(startMin, currentMin + 120);
|
||||
startMin = Math.max(startMin, Math.ceil((currentMin + 60) / 15) * 15);
|
||||
}
|
||||
|
||||
const availableSlots = generateAvailableTimeSlots(duration, date);
|
||||
@@ -360,6 +363,7 @@
|
||||
notes = originalNotes;
|
||||
submitting = false;
|
||||
initializeHours();
|
||||
editRequestAutoSelectDone = false;
|
||||
placeholderDate = minDate;
|
||||
fetchHoursRange(minDate, 3);
|
||||
}
|
||||
@@ -384,6 +388,60 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Auto-select ────────────────────────────────────────
|
||||
$effect(() => {
|
||||
if (!workingHours || !availableHours || newDate || userNavigatedCalendar || editRequestAutoSelectDone) return;
|
||||
editRequestAutoSelectDone = true;
|
||||
|
||||
const currentDate = new SvelteDate();
|
||||
const maxDateJs = new SvelteDate(
|
||||
maxCalendarDate.year,
|
||||
maxCalendarDate.month - 1,
|
||||
maxCalendarDate.day
|
||||
);
|
||||
|
||||
const daysDifference = Math.floor(
|
||||
(maxDateJs.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
const daysToCheck = Math.min(daysDifference, 180);
|
||||
|
||||
for (let i = 0; i <= daysToCheck; i++) {
|
||||
const nextDate = new SvelteDate(currentDate);
|
||||
nextDate.setDate(currentDate.getDate() + i);
|
||||
const dateStr = nextDate.toISOString().split('T')[0];
|
||||
|
||||
if (workingHours[dateStr]?.isOpen) {
|
||||
const calDate = new CalendarDate(
|
||||
nextDate.getFullYear(),
|
||||
nextDate.getMonth() + 1,
|
||||
nextDate.getDate()
|
||||
);
|
||||
if (!isDateUnavailable(calDate)) {
|
||||
newDate = calDate;
|
||||
if (!userNavigatedCalendar) {
|
||||
placeholderDate = new CalendarDate(
|
||||
nextDate.getFullYear(),
|
||||
nextDate.getMonth() + 1,
|
||||
1
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tomorrow = new SvelteDate();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
newDate = new CalendarDate(
|
||||
tomorrow.getFullYear(),
|
||||
tomorrow.getMonth() + 1,
|
||||
tomorrow.getDate()
|
||||
);
|
||||
if (!userNavigatedCalendar) {
|
||||
placeholderDate = new CalendarDate(tomorrow.getFullYear(), tomorrow.getMonth() + 1, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── API calls ──────────────────────────────────────────
|
||||
async function fetchHoursRange(startDate: CalendarDate, months: number) {
|
||||
loadingHours = true;
|
||||
@@ -436,8 +494,9 @@
|
||||
availableHoursCache.set(key, ahMap);
|
||||
}
|
||||
|
||||
workingHours = whMap;
|
||||
availableHours = ahMap;
|
||||
// MERGE instead of replace — preserves data from previously loaded months
|
||||
workingHours = { ...(workingHours || {}), ...whMap };
|
||||
availableHours = { ...(availableHours || {}), ...ahMap };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch hours:', err);
|
||||
@@ -449,8 +508,11 @@
|
||||
async function fetchHoursForMonth(date: CalendarDate) {
|
||||
const monthKey = `${date.year}-${String(date.month).padStart(2, '0')}`;
|
||||
if (workingHoursCache.has(monthKey) && availableHoursCache.has(monthKey)) {
|
||||
workingHours = workingHoursCache.get(monthKey)!;
|
||||
availableHours = availableHoursCache.get(monthKey)!;
|
||||
const cachedWH = workingHoursCache.get(monthKey)!;
|
||||
const cachedAH = availableHoursCache.get(monthKey)!;
|
||||
// MERGE instead of replace — preserves data from other loaded months
|
||||
workingHours = { ...(workingHours || {}), ...cachedWH };
|
||||
availableHours = { ...(availableHours || {}), ...cachedAH };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -489,8 +551,9 @@
|
||||
|
||||
workingHoursCache.set(monthKey, whMap);
|
||||
availableHoursCache.set(monthKey, ahMap);
|
||||
workingHours = whMap;
|
||||
availableHours = ahMap;
|
||||
// MERGE instead of replace — preserves data from other loaded months
|
||||
workingHours = { ...(workingHours || {}), ...whMap };
|
||||
availableHours = { ...(availableHours || {}), ...ahMap };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch hours:', err);
|
||||
@@ -653,7 +716,7 @@
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-3xl">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
|
||||
<Modal.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<Modal.Title class="text-lg font-semibold">{modalTitle()}</Modal.Title>
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-3xl">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
|
||||
<Modal.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -584,7 +584,7 @@
|
||||
{/if}
|
||||
|
||||
<Modal.Root open={showCancelConfirm} onOpenChange={(v) => (showCancelConfirm = v)}>
|
||||
<Modal.Content class="max-w-sm">
|
||||
<Modal.Content class="max-w-[calc(100%-2rem)]">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Cancel Booking</Modal.Title>
|
||||
<Modal.Description>
|
||||
@@ -622,7 +622,7 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Modal.Content class="max-w-sm">
|
||||
<Modal.Content class="max-w-[calc(100%-2rem)]">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Leave a Tip</Modal.Title>
|
||||
<Modal.Description>Show your appreciation for great service</Modal.Description>
|
||||
|
||||
Reference in New Issue
Block a user