Task #710: Weekly availability intervals (Calendly-style) for protocol booking slots

- DB: protocol_booking_slots.days_of_week integer[] NOT NULL default {0..6} (0=Sun..6=Sat, Riyadh); pushed via drizzle-kit, legacy rows keep all-days behaviour.
- API (routes/protocol.ts): slot create/patch accept daysOfWeek (validated, deduped, sorted); new POST /protocol/booking-slots/generate (requireManageRooms) generates stepped slots in [from,to) skipping existing start times (onConflictDoNothing), returns {created,skipped,slots}, 400 interval_too_short; matchesActiveSlot and public availability now filter by Riyadh weekday.
- UI (protocol.tsx): interval-generation form (from/to/duration + day toggle pills + workdays/all-days presets), slot list shows day names, booking dialog filters slots by chosen date weekday with stale-selection reset and empty-day hint. i18n keys added (ar/en).
- Tests: 13/13 pass incl. generation, duplicate skipping, validation, weekday filtering in availability + booking rejection, viewer 403 on generate.
- Architect review: Pass; added the suggested authz test.
This commit is contained in:
Replit Agent
2026-07-08 20:16:33 +00:00
parent de407b6a8d
commit 19e6693200
6 changed files with 504 additions and 15 deletions
+23 -1
View File
@@ -1911,7 +1911,29 @@
"leadSaved": "تم حفظ المهلة",
"dateLabel": "التاريخ",
"slotLabel": "الوقت",
"pickSlot": "اختر الوقت"
"pickSlot": "اختر الوقت",
"days": "الأيام",
"allDays": "كل الأيام",
"weekdaysPreset": "أيام العمل",
"allDaysPreset": "كل الأيام",
"dayNames": {
"0": "الأحد",
"1": "الاثنين",
"2": "الثلاثاء",
"3": "الأربعاء",
"4": "الخميس",
"5": "الجمعة",
"6": "السبت"
},
"intervalTitle": "إضافة فترة توفر",
"intervalHint": "حدد فترة (من – إلى) والأيام ومدة الوقت، وستتولّد الأوقات تلقائيًا ضمن الفترة. الأوقات المكررة تُتجاهل.",
"intervalFrom": "من",
"intervalTo": "إلى",
"generate": "توليد الأوقات",
"generated": "تم توليد {{created}} وقتًا (تم تجاهل {{skipped}} مكررًا)",
"generatedNone": "لم يتولد أي وقت — كل الأوقات موجودة مسبقًا.",
"pickDay": "اختر يومًا واحدًا على الأقل",
"noSlotsForDay": "لا توجد أوقات متاحة في هذا اليوم"
}
}
}