Add a menu toggle button to show/hide the sidebar. Update locale files for `menuToggle` key. Refactor `protocol.tsx` to manage menu state, adjust layout for responsiveness, and apply square corners to the menu.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 470babcf-8feb-4bdb-8f18-66e74df799e6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZgwFc55
Replit-Helium-Checkpoint-Created: true
- protocol.tsx sidebar nav (~lines 1413-1543): the tabs menu now sits on a
dark navy panel (rounded-xl bg-[#0f1d3d] p-2) matching the user's reference
image, clearly separated from the light page background.
- Item styles adapted for the dark background:
- Active tab: white pill with navy text (bg-white text-[#0f1d3d] font-semibold).
- Inactive tabs: text-slate-300 with hover:bg-white/10 hover:text-white.
- Group headers (rooms/gifts): active = text-sky-300, inactive same as tabs.
- No logic changes (TABS, permissions, group collapse untouched); mobile
horizontal scroll mode keeps the same dark panel.
- tsc --noEmit clean. Style-only change, no API/i18n changes.
- New shared component artifacts/tx-os/src/components/attendee-table-editor.tsx:
AttendeeTableEditor (# | name | position | side select | remove), plus
syncAttendeeRows/emptyAttendeeRow helpers and MAX_ATTENDEE_ROWS=50 cap.
- Internal booking dialog (protocol.tsx): replaced the two side-by-side
BookingAttendeeColumn lists with the single numbered table; each row now
carries its own side. Edit mode prefills existing keyAttendees then pads
to attendeeCount. useEffect syncs row count with the attendeeCount field:
growing adds empty rows, shrinking removes only empty trailing rows (never
filled ones), min 1 row.
- Public form (protocol-request.tsx): same conversion (AttendeeColumn removed,
Arabic labels passed directly since the page is forced-RTL Arabic).
- Submission payload unchanged: only rows with non-empty name sent, same
keyAttendees {name, position, side} shape — no API/schema changes.
- i18n: added protocol.bookings.form.side ("الجهة"/"Side") in ar.json/en.json.
- Removed dead code (BookingAttendeeColumn, AttendeeColumn, AttendeeRow type,
unused Plus/X imports in protocol-request).
- Verified: tsc --noEmit clean; page serves 200; architect review PASS.
- Follow-up proposed: #719 UI test for count sync/prefill/submit.
- SlotsAdminSection (artifacts/tx-os/src/pages/protocol.tsx): replaced the
2-column card grid of booking slots with a compact shadcn Table sorted by
startTime: # | time range | duration | days | status | actions.
Rows are dense (py-1.5, small text, h-7 icon buttons), RTL/LTR safe.
- New edit flow: pencil button opens a Dialog to change start time, duration
(5–480, step 5) and days-of-week (same day-chip UI as the generator).
Saves via existing PATCH /api/protocol/booking-slots/:id (no backend
changes needed — route already accepted startTime/durationMinutes/daysOfWeek).
409 duplicate_slot shows a dedicated destructive toast; other errors fall
back to the shared onError. Invalidates slots-admin + parent slots queries.
- i18n: new keys under protocol.slots in ar.json/en.json (colTime, colStatus,
colActions, edit, editTitle, editSaved, duplicateSlot, durationValue with
full Arabic plural categories).
- tsc --noEmit passes for tx-os. Architect review: Pass; added the suggested
<=480 client-side duration guard.
- Follow-up #717 proposed (UI test for edit + 409 toast).
- 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); duplicate rule is day-scoped: same start time on a different day merges the days into the existing slot (existing duration kept), fully covered times skipped; returns {created,merged,skipped,slots}; manual POST create also merges disjoint days instead of 409; matchesActiveSlot and public availability filter by Riyadh weekday.
- UI (protocol.tsx): interval-generation form (from/to/duration + day toggle pills + workdays/all-days presets), toast shows created/merged/skipped, 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, day-scoped merge (same time different day is NOT a duplicate), duplicate skipping, validation, weekday filtering in availability + booking rejection, viewer 403 on generate.
- شريط "رابط الحجز العام (للمشاركة مع الضيوف)" في صفحة المراسم:
- أُزيل عرض نص الرابط الكامل (بقي العنوان والأيقونة فقط).
- أُضيف زر "فتح" (ExternalLink) بجانب زر "نسخ الرابط" يفتح
publicBookingUrl في تبويب جديد عبر window.open مع noopener,noreferrer.
- زر النسخ يعمل كما هو دون تغيير.
- مفاتيح ترجمة جديدة: protocol.bookings.openLink (ar: "فتح", en: "Open").
- لا تغييرات في الخادم. tsc نظيف. مراجعة architect: Pass.
- المشكلة: نطاق الوقت مثل "8:00 ص – 9:00 ص" كان داخل عناصر dir="ltr"،
و"ص/م" أحرف عربية RTL فيعيد المتصفح ترتيب الأجزاء فتظهر مقلوبة
("ص – 9:00 ص 8:00").
- الإصلاح:
- النموذج العام (protocol-request.tsx): زر الوقت أصبح dir="rtl"
(الصفحة عربية دائمًا).
- protocol.tsx: قسم إدارة الأوقات وقائمة اختيار الوقت في حوار الحجز
الداخلي أصبحا dir={isAr ? "rtl" : "ltr"} — العربية تُعرض RTL صحيحة
والإنجليزية (AM/PM) تبقى LTR.
- لا تغييرات في الخادم أو الصيغة نفسها. tsc نظيف.
- نموذج الطلب العام (/protocol/request) كان يعرض منتقي التاريخ/الوقت الحر
القديم قبل اختيار القاعة، لأن فحص وجود الأوقات (slot probe) مرتبط بالقاعة.
- الآن قبل اختيار القاعة تظهر رسالة "اختر القاعة أولًا لعرض المواعيد
المتاحة"، وأثناء تحميل الفحص تظهر حالة تحميل، وعند فشله رسالة خطأ؛
شبكة الأوقات أو المنتقي الحر لا يظهران إلا بعد نجاح الفحص.
- تعزيز: تغيير القاعة يصفّر التاريخ/الوقت/الوقت المحدد المختار لتجنّب
قيم قديمة عند التبديل السريع بين القاعات.
- حوار الحجز الداخلي لم يحتج تعديلًا (الأوقات تُجلب عالميًا عبر
/protocol/booking-slots/active فلا توجد المشكلة نفسها).
- لا تغييرات في الخادم. الصفحة العامة نصوصها عربية ثابتة (بدون i18n)
فلم تلزم مفاتيح ترجمة جديدة — انحراف بسيط عن خطوة الترجمة في الخطة.
- tsc نظيف؛ مراجعة الarchitect: PASS.
- 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 له، ومنع الإرسال في الواجهة قبل اختيار التاريخ والوقت).
- تعديل بعد الرفض الأول: قراءة قائمة الأوقات الكاملة والإعدادات تتطلب
protocol.rooms.manage (حسب المواصفة)؛ أُضيفت نقطة قراءة مصغّرة
/protocol/booking-slots/active (protocol.access) لحوار الحجز فقط.
- تعديل بعد الرفض الثاني: مهلة الحجز المسبقة (minLeadMinutes) تُطبَّق الآن
أيضًا على الحجوزات الداخلية (إنشاء وتعديل) — لا استثناء للموظفين.
- 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 له، ومنع الإرسال في الواجهة قبل اختيار التاريخ والوقت).
- تعديل بعد الرفض الأول: قراءة قائمة الأوقات الكاملة والإعدادات تتطلب
protocol.rooms.manage (حسب المواصفة)؛ أُضيفت نقطة قراءة مصغّرة
/protocol/booking-slots/active (protocol.access) لحوار الحجز فقط.
- 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.