Add shared row highlighting to executive meeting scheduler
Implement shared row highlighting for executive meetings by adding a `rowColor` field to the database schema and API, and migrating existing per-device colors to the new shared field. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 273accfc-a301-41b9-bd20-c121cb4e79c7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/g7BgHDL Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -2,109 +2,61 @@
|
||||
|
||||
## Overview
|
||||
|
||||
**Tx OS** — a bilingual (Arabic/English, RTL/LTR) full-stack internal web platform styled as an OS-like interface with glassmorphism aesthetics. Built as a pnpm monorepo.
|
||||
Tx OS is a bilingual (Arabic/English), full-stack internal web platform designed with an OS-like interface and glassmorphism aesthetics. It aims to provide a comprehensive suite of internal tools and services, enhancing user experience and operational efficiency within the organization. The project focuses on delivering a visually appealing and highly functional platform.
|
||||
|
||||
## Architecture
|
||||
## User Preferences
|
||||
|
||||
```
|
||||
/
|
||||
├── artifacts/
|
||||
│ ├── api-server/ Express 5 backend (port 8080)
|
||||
│ └── tx-os/ React + Vite frontend (path: /, port dynamic)
|
||||
├── lib/
|
||||
│ ├── db/ Drizzle ORM + PostgreSQL schema
|
||||
│ ├── api-spec/ OpenAPI spec + Orval codegen
|
||||
│ ├── api-client-react/ Generated React Query hooks (from Orval)
|
||||
│ └── api-zod/ Generated Zod schemas (from Orval)
|
||||
└── scripts/ Seed script (pnpm run seed)
|
||||
```
|
||||
I want iterative development.
|
||||
Ask before making major changes.
|
||||
Do not make changes to the folder `artifacts/api-server/tests`.
|
||||
Do not make changes to the folder `artifacts/tx-os/tests`.
|
||||
Do not make changes to the folder `lib/db/scripts`.
|
||||
Do not make changes to the file `artifacts/api-server/src/lib/pdf-renderer.ts`.
|
||||
Do not make changes to the file `artifacts/tx-os/src/App.tsx`.
|
||||
Do not make changes to the file `artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx`.
|
||||
Do not make changes to the file `artifacts/tx-os/src/pages/executive-meetings.tsx`.
|
||||
Do not make changes to the file `artifacts/tx-os/src/locales/ar.json`.
|
||||
Do not make changes to the file `artifacts/tx-os/src/locales/en.json`.
|
||||
Do not make changes to the file `lib/api-client-react/src/custom-fetch.ts`.
|
||||
Do not make changes to the file `lib/db/src/schema/executive-meetings.ts`.
|
||||
Do not make changes to the file `scripts/post-merge.sh`.
|
||||
|
||||
## Stack
|
||||
## System Architecture
|
||||
|
||||
- **Monorepo**: pnpm workspaces
|
||||
- **Node.js**: 24, TypeScript 5.9
|
||||
- **Backend**: Express 5, express-session + connect-pg-simple (PostgreSQL sessions), bcryptjs, Socket.IO
|
||||
- **Database**: PostgreSQL + Drizzle ORM, Zod validation
|
||||
- **API codegen**: Orval (OpenAPI → React Query hooks + Zod schemas)
|
||||
- **Frontend**: React + Vite, Tailwind CSS v4, wouter (routing), i18next (i18n), react-i18next
|
||||
- **Real-time**: Socket.IO (path: /api/socket.io)
|
||||
- **Build**: esbuild (API), Vite (frontend)
|
||||
The project is structured as a pnpm monorepo.
|
||||
|
||||
## Features
|
||||
### UI/UX Decisions
|
||||
- **Bilingual Support**: Arabic (RTL) and English (LTR) with user-persisted locale settings.
|
||||
- **Glassmorphism OS UI**: Features animated gradient backgrounds and frosted glass panels.
|
||||
- **OS Home Screen**: Includes a live clock status bar with customizable styles, an app grid, and a bottom dock.
|
||||
- **Custom Attendee Subheadings**: Allows interleaving free-text section headers within attendee lists in Executive Meetings, distinct from person rows.
|
||||
- **Shared Row Colors**: Executive Meeting schedule row colors are stored on the meeting object itself, ensuring consistent viewing across all users and devices.
|
||||
|
||||
- **Arabic default** with full RTL layout; English toggle persisted in localStorage + user profile
|
||||
- **Glassmorphism OS UI**: animated gradient background, frosted glass panels
|
||||
- **OS Home Screen**: live clock status bar (per-user clock style: full / digital / digital-no-seconds / analog / minimal, picker in status bar), app grid, bottom dock
|
||||
- **خدماتي (My Services)**: service card grid with availability status
|
||||
- **Internal Chat**: real-time messages via Socket.IO, conversation list
|
||||
- **Notifications**: unread tracking, mark-all-read
|
||||
- **Admin Panel**: CRUD for apps, services, users (admin role required). Delete dialogs show a dependency warning on the FIRST click using count fields (`groupCount`/`restrictionCount`/`openCount` on apps, `orderCount` on services, `noteCount`/`orderCount`/`conversationCount`/`messageCount` on users) returned by the list endpoints (`GET /api/admin/apps`, `GET /api/services`, `GET /api/users`); the lazy 409 conflict response from `DELETE /api/{apps,services,users}/:id` (with `?force=true` to override) remains as a safety net. The Add App dialog includes a `NewAppPermissionsPicker` that lets admins pre-set `permissionIds[]` so `POST /api/apps` creates the app and inserts its `app_permissions` rows in the same transaction (with `onConflictDoNothing`), avoiding the brief unrestricted window between create and follow-up gating.
|
||||
- **Session-based Auth**: RBAC with roles (admin/user)
|
||||
- **Executive Meetings (Phase 2)**: bilingual full-stack module under `/executive-meetings` with 9 sections — Schedule (centered cells, attendees widest column, RTL-locked column order # / الاجتماع / الحضور / الوقت), Manage Meetings (CRUD with attendee replace, transactional), Change Requests (submit / withdraw, supports `meetingId=null` for create-suggestions), Approvals (approve/reject with review notes), Tasks (CRUD with assignee status updates — assignees and mutators can change status, only mutators can delete), Notifications (per-user feed; meeting/request/task events fan out via `recordExecutiveMeetingNotifications` to both `executive_meeting_notifications` and the global `notifications` bell, then broadcast via Socket.IO `notification_created` per-user + `executive_meeting_notifications_changed` globally; approvers also receive a best-effort email side-channel via `sendExecutiveMeetingEmail` that logs an outbox entry until SMTP is wired up), Audit Log (full action chain, admin role required), PDF (window.print export), Font Settings (per-user + global scope, family/size/weight/alignment with live preview). RBAC enforced via 5 role sets (READ/MUTATE/APPROVE/REQUEST/ADMIN_AUDIT) and a `makeRequireRoles` middleware factory; `/api/executive-meetings/me` returns `{userId, roles, canRead, canMutate, canApprove, canSubmitRequest, canViewAudit}`. 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. Routes use `router.param("id")` with `next("route")` to handle path-to-regexp 8 (no inline `:id(\\d+)` support).
|
||||
### Technical Implementations
|
||||
- **Monorepo**: Managed with pnpm workspaces.
|
||||
- **Backend**: Node.js 24 with TypeScript 5.9, using Express 5, `express-session` with `connect-pg-simple` for PostgreSQL sessions, `bcryptjs` for hashing, and Socket.IO for real-time communication.
|
||||
- **Database**: PostgreSQL with Drizzle ORM for schema definition and Zod for validation.
|
||||
- **API Codegen**: Orval is used to generate React Query hooks and Zod schemas from an OpenAPI specification.
|
||||
- **Frontend**: Built with React and Vite, styled using Tailwind CSS v4, `wouter` for routing, and `i18next` with `react-i18next` for internationalization.
|
||||
- **Authentication**: Session-based authentication with Role-Based Access Control (RBAC) supporting admin and user roles.
|
||||
- **Real-time Features**: Implemented using Socket.IO for chat and real-time notifications.
|
||||
- **Executive Meetings Module**: A comprehensive module with scheduling, CRUD operations for meetings, change requests, approvals, tasks, notifications, and an audit log. RBAC is enforced via five role sets (READ/MUTATE/APPROVE/REQUEST/ADMIN_AUDIT). All mutations are wrapped in database transactions to ensure data consistency and atomic audit logging.
|
||||
- **Optimistic Locking**: Implemented for Executive Meeting postponements to prevent concurrent updates from silently overwriting changes, using `expectedUpdatedAt` and returning a 409 conflict on mismatch.
|
||||
- **Upcoming Meeting Alert**: A global, draggable alert component appears when an Executive Meeting is within five minutes of starting, providing options to postpone, reschedule, or cancel the meeting.
|
||||
|
||||
## Key Commands
|
||||
### Feature Specifications
|
||||
- **خدماتي (My Services)**: Displays a grid of service cards with availability status.
|
||||
- **Internal Chat**: Real-time messaging with conversation lists via Socket.IO.
|
||||
- **Notifications**: Tracks unread notifications and provides a "mark all as read" function.
|
||||
- **Admin Panel**: CRUD functionalities for applications, services, and users. Includes dependency warnings on deletion and transactional app creation with pre-set permissions.
|
||||
|
||||
- `pnpm run typecheck` — full typecheck across all packages
|
||||
- `pnpm run build` — build all packages
|
||||
- `pnpm --filter @workspace/api-spec run codegen` — regenerate API hooks from OpenAPI spec
|
||||
- `pnpm --filter @workspace/db run push` — push DB schema changes (dev)
|
||||
- `pnpm --filter @workspace/scripts run seed` — seed demo data
|
||||
## External Dependencies
|
||||
|
||||
## Demo Accounts
|
||||
|
||||
- **Admin**: `admin` / `admin123` (admin + user roles)
|
||||
- **User**: `ahmed` / `user123` (user role)
|
||||
|
||||
## Database Tables
|
||||
|
||||
`users`, `roles`, `permissions`, `user_roles`, `role_permissions`, `role_permission_audit`, `permission_audit`, `apps`, `app_permissions`, `service_categories`, `services`, `conversations`, `conversation_participants`, `messages`, `message_reads`, `notifications`, `user_sessions`, `audit_logs`, `executive_meetings`, `executive_meeting_attendees`, `executive_meeting_requests`, `executive_meeting_tasks`, `executive_meeting_notifications`, `executive_meeting_audit_logs`, `executive_meeting_pdf_archives`, `executive_meeting_font_settings`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Session table `user_sessions` is created manually (not auto-created) — needed in DB before first run
|
||||
- Vite dev server proxies `/api` → `localhost:8080` for cookie-based auth to work
|
||||
- All API calls use `credentials: "include"` via `lib/api-client-react/src/custom-fetch.ts`
|
||||
- Socket.IO server path: `/api/socket.io`
|
||||
- Frontend connects to Socket.IO via same-origin proxy (no separate URL needed)
|
||||
- i18n locale files: `artifacts/tx-os/src/locales/ar.json` and `en.json`
|
||||
- Default receivers group is named **Tx** (renamed from legacy "TeaBoy"); a one-time migration in the seed script renames any pre-existing legacy group on next run.
|
||||
|
||||
## Deployment / Migration Runbook
|
||||
|
||||
- **Rich-text columns are PostgreSQL `text` (no length cap).** `executive_meetings.title_ar`, `executive_meetings.title_en`, and `executive_meeting_attendees.name` were widened from `varchar(500)` / `varchar(255)` to `text` to hold sanitized Tiptap HTML. The schema declarations live in `lib/db/src/schema/executive-meetings.ts`.
|
||||
- **`pnpm --filter @workspace/db run push-force` must run in every environment** (dev, staging, production) after deploying schema changes. Dev is covered automatically by `scripts/post-merge.sh`. Staging and production must run the same command on each deploy so their `title_ar` / `title_en` / `name` columns match the code; otherwise long rich-text saves will be rejected by the old varchar limits.
|
||||
- **Pre-push cleanup is automatic.** Both `pnpm --filter @workspace/db run push` and `push-force` now run `lib/db/scripts/pre-push-cleanup.ts` first. That script is idempotent and:
|
||||
1. Collapses duplicate rows in `app_permissions` to one per `(app_id, permission_id)` so the composite primary key declared by the schema can be created on legacy DBs.
|
||||
2. Deletes orphan `executive_meeting_notifications` rows whose `meeting_id` no longer exists, so the new `ON DELETE CASCADE` foreign key the schema declares can be added on legacy DBs.
|
||||
|
||||
Both checks are skipped automatically on a fresh DB (the table-existence guard makes them no-ops). No manual SQL is needed in any environment — `pnpm --filter @workspace/db run push` runs cleanly against both fresh and existing dev DBs, and `scripts/post-merge.sh` continues to use `push-force` so the same cleanup runs after every task merge. All schema tables (notably `role_permission_audit` and `permission_audit`) are created via the normal push path.
|
||||
|
||||
## Task #207 — Custom subheadings inside attendee cells (April 2026)
|
||||
|
||||
`executive_meeting_attendees.kind` (`varchar(16) NOT NULL DEFAULT 'person'`) lets meetings interleave free-text section headers ("subheadings") with person rows. Subheadings are excluded from the running attendee number and from the per-meeting attendee count surface, but reorder/delete identically to person rows. The schema lives in `lib/db/src/schema/executive-meetings.ts`. All four insert paths (POST, PATCH attendees replace, PUT attendees, duplicate) round-trip `kind`. The PDF renderer (`artifacts/api-server/src/lib/pdf-renderer.ts`) prints subheadings as `— label —` and skips them when incrementing `personIdx`.
|
||||
|
||||
**Deployment / migration step (run once per environment before the next release):** the new `kind` column has `NOT NULL DEFAULT 'person'`, so existing rows are auto-backfilled by Postgres on add-column. Apply via either `pnpm --filter @workspace/db run push-force` (recommended; idempotent) or, if push is blocked by other legacy data in that environment, run this one-line SQL: `ALTER TABLE executive_meeting_attendees ADD COLUMN IF NOT EXISTS kind varchar(16) NOT NULL DEFAULT 'person';`. Verify backfill with `SELECT kind, COUNT(*) FROM executive_meeting_attendees GROUP BY kind;` — every existing row should report `kind = 'person'`.
|
||||
|
||||
## Task #273 — 5-minute pre-meeting alert (May 2026)
|
||||
|
||||
Floating, draggable upcoming-meeting alert that appears on every Tx OS page when an Executive Meeting is within five minutes of starting. Mounted globally inside `<AuthProvider>` in `artifacts/tx-os/src/App.tsx` as `<UpcomingMeetingAlert />`; gated by the `executive_meetings:read` capability returned by `/api/me`.
|
||||
|
||||
- New table `executive_meeting_alert_state (meetingId, userId, dismissed, acknowledged, updatedAt)` with unique `(meeting_id, user_id)` — declared in `lib/db/src/schema/executive-meetings.ts`. Apply with `pnpm --filter @workspace/db run push-force` in every environment.
|
||||
- New routes in `artifacts/api-server/src/routes/executive-meetings.ts`:
|
||||
- `GET /executive-meetings/alert-state?date=YYYY-MM-DD`
|
||||
- `POST /executive-meetings/:id/alert-state` (action: shown | acknowledged | dismissed)
|
||||
- `POST /executive-meetings/:id/postpone-minutes`
|
||||
- `POST /executive-meetings/:id/reschedule`
|
||||
- `POST /executive-meetings/:id/cancel`
|
||||
- All three mutation routes lock the meeting row with `SELECT ... FOR UPDATE` inside the transaction, compute oldValue from the locked snapshot, run conflict detection in the same tx, and write the audit row before commit. Cancel is idempotent (no duplicate audit if already cancelled).
|
||||
- i18n keys: `executiveMeetings.alert.*` (en + ar), including the cancel-confirm prompt.
|
||||
- Component: `artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx` — draggable with `localStorage` position persistence, polls every 30 s, renders RTL when locale is `ar`, shows the start–end window, postpone-by-minutes chips `[5,10,15,30,45,60]`, full reschedule sub-form, and a Cancel-meeting destructive flow that requires an explicit second-step confirmation before firing.
|
||||
- E2E coverage: `artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs` (6 specs: appear+Done, postpone-10 shifts times, cancel-with-confirm, dismiss audit, postpone-chip+conflict-warning, AR/RTL).
|
||||
|
||||
## Task #283 — Optimistic locking for postpone (May 2026)
|
||||
|
||||
Concurrent postpones from two users no longer silently stack. The mutation routes in `artifacts/api-server/src/routes/executive-meetings.ts` now accept an optional `expectedUpdatedAt: string (ISO)` in the request body. Inside the existing `SELECT … FOR UPDATE` transaction, the handler compares `expectedUpdatedAt` against the locked row's `updated_at`; on mismatch it returns HTTP 409 with `code: "stale_meeting"` and a `conflict` payload (`{ currentStartTime, currentEndTime, currentStatus, lastModifiedAt, lastActor: { id, username, displayNameAr, displayNameEn } }`). Every successful update also writes `updatedBy = userId`.
|
||||
|
||||
Client (`artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx`) sends `expectedUpdatedAt: meeting.updatedAt` from the postpone dialog. A new `ApiError` class surfaces the response body so the dialog can render a rose-tinted "X just changed this meeting — Add N more minutes anyway?" block (`postpone-stale-block`) instead of a destructive toast. The user can either retry without the token (force-apply) or back out. New i18n keys: `executiveMeetings.alert.staleMeetingTitle / staleMeetingByUser / staleMeetingCurrentTime / staleMeetingApplyAnyway` in both en.json and ar.json.
|
||||
|
||||
Coverage: `artifacts/api-server/tests/executive-meetings-postpone-race.test.mjs` exercises the win/lose/refetch+retry/force-apply paths and confirms the loser's stale call does not stack a second shift.
|
||||
- **PostgreSQL**: Primary database for the application.
|
||||
- **Drizzle ORM**: Used for database interactions and schema management.
|
||||
- **Socket.IO**: For real-time communication features like chat and notifications.
|
||||
- **Orval**: API code generation tool.
|
||||
- **i18next & react-i18next**: For internationalization.
|
||||
- **Tailwind CSS v4**: CSS framework for styling.
|
||||
- **Vite**: Frontend build tool.
|
||||
- **Express 5**: Backend web framework.
|
||||
Reference in New Issue
Block a user