diff --git a/apps/portal/pages/admin/scheduling.vue b/apps/portal/pages/admin/scheduling.vue index 7215ea8..9896df1 100644 --- a/apps/portal/pages/admin/scheduling.vue +++ b/apps/portal/pages/admin/scheduling.vue @@ -30,7 +30,8 @@ interface EventType { } interface MinuteInterval { startMinute: number; endMinute: number } interface WeeklyRule { dayOfWeek: number; intervals: MinuteInterval[] } -interface Availability { _id: string; name: string; timezone: string; weeklyRules: WeeklyRule[] } +interface DateOverride { date: string; isUnavailable: boolean; intervals: MinuteInterval[] } +interface Availability { _id: string; name: string; timezone: string; weeklyRules: WeeklyRule[]; dateOverrides?: DateOverride[] } interface Booking { _id: string status: string @@ -169,10 +170,14 @@ async function submitHost() { const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] const availOpen = ref(false) const availBusy = ref(false) +// Date-override editor rows mirror the backend shape but hold times as HH:MM +// for the controls; we convert to minutes on submit. +interface OverrideRow { date: string; isUnavailable: boolean; start: string; end: string } const availForm = reactive({ name: 'Working hours', timezone: 'Europe/Copenhagen', days: DAYS.map((_, i) => ({ enabled: i >= 1 && i <= 5, start: '09:00', end: '17:00' })), + overrides: [] as OverrideRow[], }) const availEditingId = ref(null) function openAvail(a?: Availability) { @@ -186,14 +191,30 @@ function openAvail(a?: Availability) { ? { enabled: true, start: minToTime(intv.startMinute), end: minToTime(intv.endMinute) } : { enabled: false, start: '09:00', end: '17:00' } }) + availForm.overrides = (a.dateOverrides ?? []).map((o) => { + const intv = o.intervals?.[0] + return { + date: o.date, + isUnavailable: o.isUnavailable, + start: intv ? minToTime(intv.startMinute) : '09:00', + end: intv ? minToTime(intv.endMinute) : '17:00', + } + }) } else { availEditingId.value = null availForm.name = 'Working hours' availForm.timezone = selectedHost.value?.timezone ?? 'Europe/Copenhagen' availForm.days = DAYS.map((_, i) => ({ enabled: i >= 1 && i <= 5, start: '09:00', end: '17:00' })) + availForm.overrides = [] } availOpen.value = true } +function addOverride() { + availForm.overrides.push({ date: '', isUnavailable: false, start: '09:00', end: '17:00' }) +} +function removeOverride(idx: number) { + availForm.overrides.splice(idx, 1) +} const timeToMin = (t: string) => { const [h, m] = t.split(':').map(Number); return h * 60 + m } async function submitAvail() { if (!selectedHostId.value) return @@ -203,7 +224,15 @@ async function submitAvail() { .map((d, i) => ({ dayOfWeek: i, enabled: d.enabled, start: d.start, end: d.end })) .filter((d) => d.enabled && timeToMin(d.end) > timeToMin(d.start)) .map((d) => ({ dayOfWeek: d.dayOfWeek, intervals: [{ startMinute: timeToMin(d.start), endMinute: timeToMin(d.end) }] })) - const body = { name: availForm.name, timezone: availForm.timezone, weeklyRules } + const dateOverrides: DateOverride[] = availForm.overrides + .filter((o) => /^\d{4}-\d{2}-\d{2}$/.test(o.date)) + .filter((o) => o.isUnavailable || timeToMin(o.end) > timeToMin(o.start)) + .map((o) => ({ + date: o.date, + isUnavailable: o.isUnavailable, + intervals: o.isUnavailable ? [] : [{ startMinute: timeToMin(o.start), endMinute: timeToMin(o.end) }], + })) + const body = { name: availForm.name, timezone: availForm.timezone, weeklyRules, dateOverrides } if (availEditingId.value) { await request(`${base.value}/availability/${availEditingId.value}`, { method: 'PATCH', body }) toast.ok('Availability updated') @@ -568,6 +597,21 @@ const detailTabs = computed(() => [ + + + Date overrides + Add override + + Block specific dates or set custom hours that replace the weekly rules. + + + Unavailable + + – + + + + Cancel @@ -719,4 +763,11 @@ const detailTabs = computed(() => [ .daytoggle { display: flex; align-items: center; gap: 6px; font-size: 13px; } .time { width: 100%; } .dash { text-align: center; color: var(--text-mute); } +.overrides { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border); padding-top: 12px; } +.overrides-head { display: flex; align-items: center; justify-content: space-between; } +.small { font-size: 12px; } +.overrow { display: grid; grid-template-columns: 150px auto 1fr auto 1fr auto; align-items: center; gap: 8px; } +.date { width: 100%; } +.removeov { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--text-mute); cursor: pointer; } +.removeov:hover { background: rgba(226, 48, 48, 0.08); color: var(--bad); }
Block specific dates or set custom hours that replace the weekly rules.