- DB: protocol_booking_slots (وقت "HH:mm" بتوقيت الرياض، مدة، تفعيل، ترتيب)
+ protocol_settings (booking.minLeadMinutes، الافتراضي 60 دقيقة).
- API: CRUD /protocol/booking-slots (الكتابة تتطلب protocol.rooms.manage،
تكرار الوقت → 409 duplicate_slot)، GET/PATCH /protocol/settings (upsert + سجل
تدقيق)، ونقطة عامة /protocol/public/availability (محدّد معدل خاص، تعيد
booked/tooSoon/available لكل وقت).
- الفرض في الخادم: الطلب العام يجب أن يطابق وقتاً مفعّلاً + يحترم المهلة
(400 invalid_slot / lead_time برسائل عربية)؛ الموظفون ملزمون بالأوقات لكن
معفيون من المهلة. بدون أوقات مفعّلة يعمل الوضع الحر القديم.
- الواجهة: نموذج الطلب العام يعرض شبكة أوقات حسب التوفر (مع fallback قديم)؛
تبويب القاعات يعرض قسم إدارة الأوقات + المهلة لمن يملك canManageRooms؛
حوار الحجز الداخلي يتحول لتاريخ + قائمة أوقات عند وجود أوقات مفعّلة.
- i18n: مفاتيح protocol.slots.* بالعربية والإنجليزية.
- اختبارات: tests/protocol-booking-slots.test.mjs — 10 اختبارات ناجحة
(CRUD/تكرار، الإعدادات، التوفر، invalid_slot، lead_time، تعارض، وقت معطّل،
إعفاء الموظفين، صلاحيات القراءة/الكتابة، الحذف).
- مراجعة المعماري: PASS؛ عولجت الملاحظات (توثيق قرار الصلاحيات + اختبار
regression له، ومنع الإرسال في الواجهة قبل اختيار التاريخ والوقت).
- DialogShell (artifacts/tx-os/src/pages/protocol.tsx) now caps the dialog at
max-h-[85vh] with a flex column layout: the title (DialogHeader) and the
save/cancel footer stay always visible, while the form fields scroll in an
inner overflow-y-auto area (overscroll-contain).
- Applies to all DialogShell users (booking, room, gift dialogs); short forms
are unaffected since the container only scrolls when content exceeds 85vh.
- RTL unaffected (dir attribute preserved).
- Verified: tsc clean for tx-os; architect review run.
- Architect flagged a potential grid-vs-flex display conflict in DialogContent,
but cn() uses tailwind-merge, so the later "flex" class deterministically
replaces the base "grid" — no change needed.
- Extended internal BookingDialog (artifacts/tx-os/src/pages/protocol.tsx) to match
the public booking form: requester name, phone, room, department, meeting title,
meeting type toggle (داخلي / مع جهة خارجية), conditional entity name (external only),
expected attendee count, purpose, from/to, key attendees in two columns
(من الهيئة / خارج الهيئة) with add/remove name+position rows, and notes.
- Edit mode prefills all new fields from the booking; click-to-book initialStart
behavior unchanged.
- api-server (artifacts/api-server/src/routes/protocol.ts): bookingCreateSchema and
bookingPatchSchema now accept requesterPhone, requesterOrg, department, meetingType,
purpose, keyAttendees; POST/PATCH /protocol/bookings persist them. requesterOrg is
nulled whenever effective meetingType is internal (create and patch).
- Added i18n keys protocol.bookings.form.{name,position,addRow,removeRow} (ar/en);
all other labels reuse existing protocol.bookings keys.
- Verified: tsc clean for changed files (push.ts / executive-meetings.ts errors are
pre-existing and unrelated); API workflow restarted and serving; architect review PASS.
- Note: entity name (اسم الجهة) is not enforced as required for external meetings on
the internal endpoint (unlike the strict public endpoint) — internal staff keep
flexibility; can be tightened later if requested.
- BookingDayGrid (artifacts/tx-os/src/pages/protocol.tsx) now accepts
onSlotClick?: (start: Date) => void. Clicking the empty grid background
computes the time from click Y (getBoundingClientRect + clientY), snaps
down to the nearest 30 minutes, clamps within grid hours (7–22, keeping
a full hour inside), and calls onSlotClick with the resulting Date on
the selected day. Cursor becomes pointer when handler is set.
- Booking blocks now stopPropagation on click so existing behavior
(opening the details dialog) is unchanged and never double-triggers.
- ProtocolPage passes onSlotClick only when capabilities.canRequest,
opening the existing BookingDialog (same form as the "حجز +" button)
with the new start prefilled.
- BookingDialog: new optional initialStart prop; startsAt = initialStart,
endsAt = initialStart + 1h (falls back to prior 9–10 defaults).
- Verified: tsc --noEmit clean in artifacts/tx-os; architect review Pass.
- Extracted shared AttendeesTable component in artifacts/tx-os/src/pages/protocol.tsx
(columns: name / position / side) with centered headers and cells, distinct
header background (bg-slate-100/80), comfortable row heights (py-2.5),
zebra striping (even:bg-slate-50/60), rounded border + shadow, side pill
colored by side (sky=internal, violet=external), overflow-x-auto for small
screens. Center alignment is RTL/LTR-safe.
- Booking details dialog now uses AttendeesTable (replaced inline table).
- Expanded row in bookings list now renders keyAttendees as the same table
(replaced dash-separated <ul> lines), capped at max-w-2xl.
- No i18n changes needed (reuses protocol.bookings.details.attendeeCols.*).
- Verified: tsc --noEmit clean in artifacts/tx-os; architect review passed.
- artifacts/tx-os/src/pages/protocol.tsx: replaced the dash-separated
<ul> list of keyAttendees in the booking details dialog with a shadcn
Table (already imported) with 3 columns: Name, Position/Title, Side.
Headers use text-start for correct RTL alignment; empty name/position
cells render an em dash; side rendered as the existing pill using
protocol.bookings.attendeeSides.*; table wrapped in rounded border +
overflow-x-auto so it stays responsive inside the dialog.
- i18n: added protocol.bookings.details.attendeeCols.{name,position,side}
to ar.json (الاسم/الصفة/الجهة) and en.json (Name/Title/Side).
- Scope limited to the details dialog; the expanded-row attendee display
elsewhere is unchanged (per task out-of-scope).
- Verified: both locale JSONs valid, tsc --noEmit clean, architect review
PASS.
- No follow-ups proposed: pending Task #695 already covers e2e testing of
the details dialog contents.
- BookingDayGrid (artifacts/tx-os/src/pages/protocol.tsx) now renders inside
its own scroll container (max-h-[60vh] overflow-y-auto overscroll-contain).
- New scrollRef + useEffect: when the selected day is today and the now-line
is within grid hours (showNow), sets scrollTop = nowTop - clientHeight/3
so the red line sits ~1/3 from the top; otherwise resets scroll to top.
Effect deps: [dayKey, showNow, scrollSignal] — not re-run on minute ticks,
so it never fights user scrolling.
- "اليوم" button bumps a todayScrollSignal counter passed as optional
scrollSignal prop, re-scrolling even when the selected day is already today.
- Removed the previous outer max-h-[70vh] wrapper around BookingDayGrid to
avoid nested scrollbars.
- No changes to booking block layout, statuses, or list view. RTL-safe
(logical properties untouched).
- tsc --noEmit clean for tx-os; architect review addressed (60vh per spec).
Replaces the card-based list view with a table component in `protocol.tsx`. Adds new translation keys for table headers and meeting types in `ar.json` and `en.json`. Formats booking references to display only numeric characters and dashes.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e1793d98-fbd5-4c99-99a0-e01d493482cc
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ychh3tH
Replit-Helium-Checkpoint-Created: true
Update executive meeting duplication to maintain `isExternal` and `rowColor` properties, and adjust switch component's RTL translation for correct alignment.
Original task: make the protocol booking-requests LIST cards collapsed by
default, showing a compact summary and expanding full details only when opened;
add numbering, show the booking reference, and highlight today's bookings green.
Changes (artifacts/tx-os/src/pages/protocol.tsx):
- renderBookingCard(b, index) now renders a collapsible card. Collapsed shows
only: sequence number (1-based over the current/filtered list), title,
room · date-time range, booking reference, status/public pills, and a chevron.
- Summary header is a real <button type="button"> with onClick toggle and
aria-expanded for keyboard accessibility.
- Expanded reveals the previously-shown detail fields (requester, department,
meeting type, entity, phone, attendee count, purpose, key attendees) and the
action buttons (approve/reject/cancel/edit/delete) — no functionality lost.
- Bookings starting today (sameYmd(startsAt, now)) get a green card
(emerald border/bg/ring) plus an "اليوم"/"Today" badge.
- Added expandedBookings Set state + toggleBookingExpanded near booking state.
- List map updated to pass 1-based index.
i18n: added protocol.bookings.today ("اليوم" / "Today") to ar.json and en.json.
Scope: list view only; calendar view, public request form, and other tabs
unchanged. Client-only "today" derivation, no backend changes.
Verification: tsc --noEmit passes; architect code review PASSED (no critical
issues). Screenshot skipped — protocol page requires auth.
Original task: make the protocol booking-requests LIST cards collapsed by
default, showing a compact summary and expanding full details only when opened;
add numbering, show the booking reference, and highlight today's bookings green.
Changes (artifacts/tx-os/src/pages/protocol.tsx):
- renderBookingCard(b, index) now renders a collapsible card. Collapsed shows
only: sequence number (1-based over the current/filtered list), title,
room · date-time range, booking reference, status/public pills, and a chevron.
- Summary header is a real <button type="button"> with onClick toggle and
aria-expanded for keyboard accessibility.
- Expanded reveals the previously-shown detail fields (requester, department,
meeting type, entity, phone, attendee count, purpose, key attendees) and the
action buttons (approve/reject/cancel/edit/delete) — no functionality lost.
- Bookings starting today (sameYmd(startsAt, now)) get a green card
(emerald border/bg/ring) plus an "اليوم"/"Today" badge.
- Added expandedBookings Set state + toggleBookingExpanded near booking state.
- List map updated to pass 1-based index.
i18n: added protocol.bookings.today ("اليوم" / "Today") to ar.json and en.json.
Scope: list view only; calendar view, public request form, and other tabs
unchanged. Client-only "today" derivation, no backend changes.
Verification: tsc --noEmit passes; architect code review PASSED (no critical
issues). Screenshot skipped — protocol page requires auth.
Problem: In the public room-booking form (protocol-request.tsx), the date
picker calendar showed English weekday headers and a scrambled day grid under
RTL. Root cause: the shared Calendar (react-day-picker v9) was rendered without
`locale` or `dir`, so weekdays stayed English and its flex week rows reversed
visually under CSS RTL.
Changes (artifacts/tx-os/src/components/ui/calendar.tsx):
- Pass date-fns Arabic locale (`ar`) + `dir="rtl"` when Arabic is active, else
`enUS` + `dir="ltr"`; placed before `{...props}` so callers can still override.
- Added `formatCaption` and `formatDay` formatters (alongside existing
`formatMonthDropdown`) using Intl `numberingSystem: "latn"` to keep Western
digits, matching the app-wide AR_LATN convention (Arabic locale would
otherwise emit Arabic-Indic numerals).
- `isArabic()` now resolves from `document.documentElement.lang`/`dir` first,
falling back to `i18n.language`.
Deviation from plan: after code review, changed language detection from
i18n.language-only to DOM-first. Reason: the booking page forces Arabic by
setting documentElement.lang/dir without changing i18n.language, so a guest with
saved English would otherwise get an English/LTR calendar on that page.
Out of scope (per user): no time-picker or من/إلى changes.
Verified: `tsc --noEmit` passes for @workspace/tx-os. English calendar
unaffected (default enUS/LTR).
Problem: In the public room-booking form (protocol-request.tsx), the date
picker calendar showed English weekday headers and a scrambled day grid under
RTL. Root cause: the shared Calendar (react-day-picker v9) was rendered without
`locale` or `dir`, so weekdays stayed English and its flex week rows reversed
visually under CSS RTL.
Changes (artifacts/tx-os/src/components/ui/calendar.tsx):
- Pass date-fns Arabic locale (`ar`) + `dir="rtl"` when Arabic is active, else
`enUS` + `dir="ltr"`; placed before `{...props}` so callers can still override.
- Added `formatCaption` and `formatDay` formatters (alongside existing
`formatMonthDropdown`) using Intl `numberingSystem: "latn"` to keep Western
digits, matching the app-wide AR_LATN convention (Arabic locale would
otherwise emit Arabic-Indic numerals).
- `isArabic()` now resolves from `document.documentElement.lang`/`dir` first,
falling back to `i18n.language`.
Deviation from plan: after code review, changed language detection from
i18n.language-only to DOM-first. Reason: the booking page forces Arabic by
setting documentElement.lang/dir without changing i18n.language, so a guest with
saved English would otherwise get an English/LTR calendar on that page.
Out of scope (per user): no time-picker or من/إلى changes.
Verified: `tsc --noEmit` passes for @workspace/tx-os. English calendar
unaffected (default enUS/LTR).
Renamed two Arabic sidebar tab labels in the protocol (room-booking)
module in artifacts/tx-os/src/locales/ar.json:
- protocol.tabs.bookings: "حجوزات القاعات" → "حجز القاعات"
- protocol.tabs.external: "اللقاءات الخارجية" → "اجتماعات خارجية"
English translations left unchanged. The chart label
"اللقاءات الخارجية حسب الحالة" was left as-is per the task's out-of-scope
note. Text-only change, no logic touched.
Renamed two Arabic sidebar tab labels in the protocol (room-booking)
module in artifacts/tx-os/src/locales/ar.json:
- protocol.tabs.bookings: "حجوزات القاعات" → "حجز القاعات"
- protocol.tabs.external: "اللقاءات الخارجية" → "اجتماعات خارجية"
English translations left unchanged. The chart label
"اللقاءات الخارجية حسب الحالة" was left as-is per the task's out-of-scope
note. Text-only change, no logic touched.
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Every booking (public + internal) now gets a unique, NOT NULL, searchable
reference (RB-{year}-{pad4(id)}) shown on the success screen. Staff can
search the admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (NOT NULL + unique) + requesterPhone index; new
enums/types. Applied via drizzle push; existing rows backfilled.
- api-server: extended public booking schema + refine (org required when
external); reserveBookingId (nextval on the serial sequence) so the
reference is written in a single INSERT and can never be null; GET
/protocol/bookings accepts a `q` param filtering by reference OR phone
(server-side, indexed, case-insensitive).
- tx-os public form rewritten; admin card shows new fields; admin search
box now debounced and wired to the API `q` param (server-side filtering)
with an empty state.
- ar/en i18n keys under protocol.bookings.
Deploy safety / backfill:
- lib/db pre-push-cleanup (runs before every drizzle push, dev + deploy)
now backfills booking_reference for any pre-existing rows
(RB-{year(created_at)}-{pad4(id)}) BEFORE the NOT NULL + unique
constraint is enforced, so pushing the constraint on a populated table
can never fail. Idempotent: only updates NULL rows.
Verification:
- Public booking POST returns a reference; external-without-org rejected
(400); DB has zero null references after push. Re-running push logs
"No protocol_room_bookings booking_reference to backfill" (idempotent).
- Pre-existing push.ts / executive-meetings.ts typecheck errors are
unrelated to this task (files untouched).
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Every booking (public + internal) now gets a unique, NOT NULL, searchable
reference (RB-{year}-{pad4(id)}) shown on the success screen. Staff can
search the admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (NOT NULL + unique) + requesterPhone index; new
enums/types. Applied via drizzle push; existing rows backfilled.
- api-server: extended public booking schema + refine (org required when
external); reserveBookingId (nextval on the serial sequence) so the
reference is written in a single INSERT and can never be null; GET
/protocol/bookings accepts a `q` param filtering by reference OR phone
(server-side, indexed, case-insensitive).
- tx-os public form rewritten; admin card shows new fields; admin search
box now debounced and wired to the API `q` param (server-side filtering)
with an empty state.
- ar/en i18n keys under protocol.bookings.
Deploy safety / backfill:
- lib/db pre-push-cleanup (runs before every drizzle push, dev + deploy)
now backfills booking_reference for any pre-existing rows
(RB-{year(created_at)}-{pad4(id)}) BEFORE the NOT NULL + unique
constraint is enforced, so pushing the constraint on a populated table
can never fail. Idempotent: only updates NULL rows.
Verification:
- Public booking POST returns a reference; external-without-org rejected
(400); DB has zero null references after push. Re-running push logs
"No protocol_room_bookings booking_reference to backfill" (idempotent).
- Pre-existing push.ts / executive-meetings.ts typecheck errors are
unrelated to this task (files untouched).
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Every booking (public + internal) now gets a unique, NOT NULL, searchable
reference (RB-{year}-{pad4(id)}) shown on the success screen. Staff can
search the admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (NOT NULL + unique) + requesterPhone index; new
enums/types. Applied via drizzle push; existing rows backfilled.
- api-server: extended public booking schema + refine (org required when
external); reserveBookingId (nextval on the serial sequence) so the
reference is written in a single INSERT and can never be null; GET
/protocol/bookings accepts a `q` param filtering by reference OR phone
(server-side, indexed, case-insensitive).
- tx-os public form rewritten; admin card shows new fields; admin search
box now debounced and wired to the API `q` param (server-side filtering)
with an empty state.
- ar/en i18n keys under protocol.bookings.
Verification:
- Public booking POST returns a reference; external-without-org rejected
(400); DB has zero null references after push.
- Pre-existing push.ts / executive-meetings.ts typecheck errors are
unrelated to this task (files untouched).
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Every booking (public + internal) now gets a unique, NOT NULL, searchable
reference (RB-{year}-{pad4(id)}) shown on the success screen. Staff can
search the admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (NOT NULL + unique) + requesterPhone index; new
enums/types. Applied via drizzle push; existing rows backfilled.
- api-server: extended public booking schema + refine (org required when
external); reserveBookingId (nextval on the serial sequence) so the
reference is written in a single INSERT and can never be null; GET
/protocol/bookings accepts a `q` param filtering by reference OR phone
(server-side, indexed, case-insensitive).
- tx-os public form rewritten; admin card shows new fields; admin search
box now debounced and wired to the API `q` param (server-side filtering)
with an empty state.
- ar/en i18n keys under protocol.bookings.
Verification:
- Public booking POST returns a reference; external-without-org rejected
(400); DB has zero null references after push.
- Pre-existing push.ts / executive-meetings.ts typecheck errors are
unrelated to this task (files untouched).
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Each booking now gets an auto-generated searchable reference
(RB-{year}-{pad4}) shown on the success screen; staff can search the
admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (unique) + requesterPhone index; new enums/types.
Applied via drizzle push; backfilled existing 4 rows.
- api-server: extended public booking schema + refine (org required when
external), makeBookingReference, public/internal POST generate+return
bookingReference.
- tx-os public form rewritten; admin card shows new fields + client-side
search with empty state.
- ar/en i18n keys under protocol.bookings.
Notes:
- Pre-existing push.ts typecheck errors are unrelated to this task.
- Architect review: PASS, no blocking issues.
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Each booking now gets an auto-generated searchable reference
(RB-{year}-{pad4}) shown on the success screen; staff can search the
admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (unique) + requesterPhone index; new enums/types.
Applied via drizzle push; backfilled existing 4 rows.
- api-server: extended public booking schema + refine (org required when
external), makeBookingReference, public/internal POST generate+return
bookingReference.
- tx-os public form rewritten; admin card shows new fields + client-side
search with empty state.
- ar/en i18n keys under protocol.bookings.
Notes:
- Pre-existing push.ts typecheck errors are unrelated to this task.
- Architect review: PASS, no blocking issues.