Executive Meetings Phase 2: full module + atomic audit

Original task (#108): polish the daily-schedule UI and ship the
remaining 8 sections of the Executive Meetings module (Manage,
Requests, Approvals, Tasks, Notifications, Audit, PDF, Font Settings)
with bilingual i18n and fine-grained RBAC.

What changed:
- Schedule UI: centered cells, '#' header (not 'م'), attendees widest,
  RTL column order locked, navy/white/gray + red highlighting only.
- Schema: made executive_meeting_requests.meetingId nullable so
  'create-meeting' suggestions don't need a target row.
- Backend rewrite of artifacts/api-server/src/routes/executive-meetings.ts:
  GET /me (now returns userId), full CRUD for meetings (transactional
  attendee replace), requests CRUD + approve/reject/withdraw, tasks
  CRUD with assignee status updates, audit-logs GET (admin), notifications
  GET, font-settings GET/PATCH (per-user + global). RBAC via 5 role
  sets and a makeRequireRoles middleware factory.
- Audit-in-transaction: every mutation (meeting/request/task/font CRUD)
  wraps the DB write AND the audit-log insert in the same db.transaction
  so audit entries cannot drift from state if either insert fails. This
  was the main finding from the architect review and is now fixed.
- Path-to-regexp 8 fix: replaced inline ':id(\\d+)' with router.param('id')
  + next('route') guard, plus NaN guards in handlers.
- Frontend rewrite of artifacts/tx-os/src/pages/executive-meetings.tsx
  (~2200 lines): 8 new section components, RBAC-aware UI via /me,
  per-task assignee check now shown for status buttons.
- i18n: full executiveMeetings.* key set added in ar.json + en.json.

Verified: full e2e (admin login -> all 9 tabs -> create meeting ->
submit request -> approve -> audit shows full chain -> font save)
plus a smoke regression after the audit-in-tx refactor (atomic
audit row created in lockstep with mutation).

Out of scope (proposed as follow-ups #110-#112): real notification
delivery, real PDF generation (currently window.print), and
automated integration tests for the new endpoints. The pre-existing
'apps-open.test.mjs' syntax error in the test workflow is unrelated
to this task.
This commit is contained in:
riyadhafraa
2026-04-28 06:18:08 +00:00
parent 539238028b
commit b4d47e1012
7 changed files with 3471 additions and 89 deletions
+225
View File
@@ -634,6 +634,231 @@
"audit": "Audit Log",
"pdf": "PDF Export",
"fontSettings": "Font Settings"
},
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"add": "Add",
"submit": "Submit",
"close": "Close",
"loading": "Loading...",
"saved": "Saved",
"saveFailed": "Save failed",
"actions": "Actions",
"noPermission": "You do not have permission for this action",
"yes": "Yes",
"no": "No",
"all": "All",
"filter": "Filter"
},
"manage": {
"heading": "Manage Meetings",
"addMeeting": "Add meeting",
"editMeeting": "Edit meeting",
"deleteMeeting": "Delete meeting",
"deleteConfirm": "Permanently delete this meeting?",
"noMeetings": "No meetings on this day. Click \"Add meeting\" to create one.",
"field": {
"titleAr": "Title (Arabic)",
"titleEn": "Title (English)",
"meetingDate": "Date",
"dailyNumber": "Daily number",
"dailyNumberHint": "Leave blank for auto-numbering",
"startTime": "Start time",
"endTime": "End time",
"location": "Location",
"meetingUrl": "Meeting URL",
"platform": "Platform",
"status": "Status",
"isHighlighted": "Highlight (red)",
"notes": "Notes"
},
"attendees": {
"label": "Attendees",
"name": "Name",
"type": "Attendance type",
"add": "Add attendee",
"remove": "Remove"
},
"platform": {
"none": "None",
"webex": "Webex",
"teams": "Teams",
"zoom": "Zoom",
"other": "Other"
},
"attendanceType": {
"internal": "Internal",
"virtual": "Virtual",
"external": "External"
},
"status": {
"scheduled": "Scheduled",
"cancelled": "Cancelled",
"completed": "Completed",
"postponed": "Postponed"
},
"errors": {
"titleRequired": "Arabic title is required",
"dateRequired": "Date is required",
"duplicateNumber": "That daily number already exists for this date",
"saveFailed": "Could not save the meeting"
}
},
"requests": {
"heading": "Change Requests",
"newRequest": "New request",
"myRequests": "My requests",
"allRequests": "All requests",
"empty": "No requests yet.",
"filterStatus": "Filter by status",
"submitFor": "Suggest a change to:",
"noTargetMeeting": "Propose a brand-new meeting (no link)",
"details": "Details",
"detailsPlaceholder": "Explain your suggestion in detail...",
"submitted": "Request submitted",
"submitFailed": "Could not submit the request",
"requestedBy": "Submitted by",
"requestedAt": "Submitted at",
"type": {
"create": "Create meeting",
"edit": "Edit details",
"delete": "Delete",
"reschedule": "Reschedule",
"highlight": "Highlight",
"unhighlight": "Remove highlight",
"other": "Other"
},
"status": {
"new": "New",
"approved": "Approved",
"rejected": "Rejected",
"withdrawn": "Withdrawn"
}
},
"approvals": {
"heading": "Office Manager Approvals",
"empty": "No requests pending review.",
"approve": "Approve",
"reject": "Reject",
"reviewNotes": "Review notes",
"reviewNotesPlaceholder": "Optional notes for the requester",
"approved": "Approved",
"rejected": "Rejected",
"actionFailed": "Could not complete the review"
},
"tasks": {
"heading": "Coordination Tasks",
"addTask": "Add task",
"empty": "No tasks yet.",
"field": {
"taskType": "Task type",
"assignee": "Assignee",
"dueDate": "Due date",
"notes": "Notes",
"meeting": "Meeting",
"status": "Status"
},
"status": {
"pending": "Pending",
"in_progress": "In progress",
"completed": "Completed",
"cancelled": "Cancelled"
},
"markCompleted": "Mark completed",
"markInProgress": "Start",
"deleteConfirm": "Delete this task?",
"saved": "Task saved",
"deleted": "Task deleted"
},
"audit": {
"heading": "Audit Log",
"empty": "No audit entries.",
"filterDate": "Filter by date",
"filterEntity": "Entity",
"all": "All",
"col": {
"when": "When",
"actor": "Actor",
"action": "Action",
"entity": "Entity",
"entityId": "ID",
"diff": "Details"
},
"entity": {
"meeting": "Meeting",
"attendee": "Attendee",
"request": "Request",
"task": "Task",
"font_settings": "Font settings"
},
"action": {
"create": "Create",
"update": "Update",
"delete": "Delete",
"approve": "Approve",
"reject": "Reject",
"submit": "Submit"
}
},
"pdf": {
"heading": "Print / Export",
"intro": "This view uses the browser print dialog to produce a PDF of the day's schedule. Pick a date and click Print.",
"print": "Print schedule",
"openSchedule": "Open schedule",
"selectDate": "Schedule date"
},
"fontSettingsPage": {
"heading": "Font Settings",
"intro": "These settings apply to the meetings schedule. You can save your personal preference; admins can also save the global default.",
"scope": "Scope",
"scopeUser": "My preference",
"scopeGlobal": "Global (everyone)",
"fontFamily": "Font family",
"fontSize": "Font size",
"fontWeight": "Weight",
"alignment": "Alignment",
"weight": {
"regular": "Regular",
"medium": "Medium",
"semibold": "Semibold",
"bold": "Bold"
},
"align": {
"start": "Start",
"center": "Center",
"end": "End"
},
"preview": "Preview",
"previewText": "Sample text 123 نص تجريبي ١٢٣",
"save": "Save settings",
"saved": "Saved",
"reset": "Reset to default"
},
"notificationsPage": {
"heading": "Notifications",
"intro": "Scheduled reminders and change-notice records for executive meetings. No actual delivery is wired up yet (placeholder).",
"empty": "No scheduled notifications.",
"col": {
"meeting": "Meeting",
"user": "User",
"type": "Type",
"scheduledAt": "Scheduled",
"status": "Status"
},
"type": {
"reminder": "Reminder",
"change": "Change notice",
"cancel": "Cancellation notice"
},
"status": {
"pending": "Pending",
"sent": "Sent",
"skipped": "Skipped",
"failed": "Failed"
}
}
}
}