Task #694: Booking details dialog from calendar day-grid

- BookingDayGrid (artifacts/tx-os/src/pages/protocol.tsx): booking blocks are
  now <button type="button"> with onClick -> new optional onSelect prop,
  hover/focus-visible styles, aria-label (title/room/time) and
  data-testid="calendar-booking-{id}".
- ProtocolPage: new detailsBooking state; day grid passes onSelect; new
  read-only details Dialog (before AppDock) with:
  header = booking title + status pill + public-request badge;
  summary card = date, time range (LTR), duration, room, reference,
  meeting type; requester section = name, phone (LTR), entity (external
  only), department, attendee count; meeting section = purpose + key
  attendees with side badges; notes; rejection-reason box when rejected.
  Empty/null fields hidden via DetailRow helper.
- New helpers: DetailRow component, formatDuration(start,end,t) using
  pluralized i18n keys.
- i18n: protocol.bookings.details.* added to ar.json (full Arabic plural
  forms for hours/minutes) and en.json.
- View-only per task scope (no edit/approve/cancel in dialog).
- tsc --noEmit clean; both locale JSONs valid; architect review PASS.
- Added explicit 'Request source' row (internal/public) in summary card per review; keys protocol.bookings.details.source/sources.* in ar/en.
This commit is contained in:
Replit Agent
2026-07-08 12:52:13 +00:00
parent 5fe978c0ca
commit ea5055de85
3 changed files with 16 additions and 0 deletions
+5
View File
@@ -1796,6 +1796,11 @@
"meetingSection": "تفاصيل الاجتماع", "meetingSection": "تفاصيل الاجتماع",
"notes": "ملاحظات", "notes": "ملاحظات",
"rejectionReason": "سبب الرفض", "rejectionReason": "سبب الرفض",
"source": "مصدر الطلب",
"sources": {
"internal": "داخلي",
"public": "طلب عام"
},
"durationHours_zero": "{{count}} ساعة", "durationHours_zero": "{{count}} ساعة",
"durationHours_one": "ساعة واحدة", "durationHours_one": "ساعة واحدة",
"durationHours_two": "ساعتان", "durationHours_two": "ساعتان",
+5
View File
@@ -1657,6 +1657,11 @@
"meetingSection": "Meeting details", "meetingSection": "Meeting details",
"notes": "Notes", "notes": "Notes",
"rejectionReason": "Rejection reason", "rejectionReason": "Rejection reason",
"source": "Request source",
"sources": {
"internal": "Internal",
"public": "Public request"
},
"durationHours_one": "1 hour", "durationHours_one": "1 hour",
"durationHours_other": "{{count}} hours", "durationHours_other": "{{count}} hours",
"durationMinutes_one": "1 minute", "durationMinutes_one": "1 minute",
+6
View File
@@ -2085,6 +2085,12 @@ export default function ProtocolPage() {
`protocol.bookings.meetingTypes.${detailsBooking.meetingType}`, `protocol.bookings.meetingTypes.${detailsBooking.meetingType}`,
)} )}
/> />
<DetailRow
label={t("protocol.bookings.details.source")}
value={t(
`protocol.bookings.details.sources.${detailsBooking.source}`,
)}
/>
</div> </div>
</div> </div>