c0cf2115dd
Goal: make the admin delete dialog show its dependency warning on
the FIRST click (matching the existing Groups UX), instead of
needing a 409 round-trip from the DELETE endpoint to populate the
warning.
Changes:
- lib/api-spec/openapi.yaml: added optional count fields to App
(groupCount, restrictionCount, openCount), Service (orderCount),
and UserProfile (noteCount, orderCount, conversationCount,
messageCount).
- artifacts/api-server/src/routes/apps.ts: GET /admin/apps now
batch-loads groupCount/restrictionCount/openCount via grouped
COUNT queries on group_apps, app_permissions, and app_opens.
- artifacts/api-server/src/routes/services.ts: GET /services now
batch-loads orderCount from service_orders.
- artifacts/api-server/src/routes/users.ts: GET /users now batch-
loads noteCount/orderCount/conversationCount/messageCount from
notes, service_orders, conversations.created_by, and
messages.sender_id.
- artifacts/tx-os/src/pages/admin.tsx: Apps, Services, and Users
delete buttons pre-populate their conflict-state from the row's
count fields so the DeletionWarningDialog shows the warning on
the first click. The lazy 409 fallback still works as a safety
net for any caller without counts.
- replit.md: documented the new admin-panel delete UX.
Tests:
- Added artifacts/api-server/tests/list-dependency-counts.test.mjs
with 6 tests verifying each list endpoint exposes the new count
fields with the expected non-zero values when dependents exist
AND zero values when they do not (apps, services, users).
- Existing artifacts/api-server/tests/delete-force-warnings.test.mjs
(9 tests) still passes — DELETE behavior unchanged.
- Verified end-to-end with a Playwright browser test: clicking the
service delete icon ONCE opens the confirmation modal with the
dependency warning ("orders", count >= 1) immediately.
Notes / drift:
- Count fields were added to the shared App/Service/UserProfile
schemas (not list-only sub-schemas) so the same shape is returned
from list and detail endpoints. Code review flagged this as
acceptable but broader than strictly necessary; left as is to
keep the API consistent.
Out of scope (already pre-existing on main, tracked as follow-ups):
- artifacts/api-server/tests/apps-open.test.mjs has a syntax error.
- artifacts/api-server/tests/app-permissions-unique.test.mjs has
two failing assertions about the composite primary key.
- artifacts/api-server/src/routes/executive-meetings.ts has 3 pre-
existing typecheck errors around the font-settings scope column.
- The `test` workflow exits with ECONNREFUSED when the api-server
isn't already up — needs a readiness check.
Replit-Task-Id: bd14fe73-9961-431b-ab5a-ab70f116e8c7
5.2 KiB
5.2 KiB
Tx OS
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.
Architecture
/
├── 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)
Stack
- 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)
Features
- 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/openCounton apps,orderCounton services,noteCount/orderCount/conversationCount/messageCounton users) returned by the list endpoints (GET /api/admin/apps,GET /api/services,GET /api/users); the lazy 409 conflict response fromDELETE /api/{apps,services,users}/:id(with?force=trueto override) remains as a safety net. - Session-based Auth: RBAC with roles (admin/user)
- Executive Meetings (Phase 2): bilingual full-stack module under
/executive-meetingswith 9 sections — Schedule (centered cells, attendees widest column, RTL-locked column order # / الاجتماع / الحضور / الوقت), Manage Meetings (CRUD with attendee replace, transactional), Change Requests (submit / withdraw, supportsmeetingId=nullfor 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), 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 amakeRequireRolesmiddleware factory;/api/executive-meetings/mereturns{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 samedb.transaction(...)so audit entries cannot drift from state. Routes userouter.param("id")withnext("route")to handle path-to-regexp 8 (no inline:id(\\d+)support).
Key Commands
pnpm run typecheck— full typecheck across all packagespnpm run build— build all packagespnpm --filter @workspace/api-spec run codegen— regenerate API hooks from OpenAPI specpnpm --filter @workspace/db run push— push DB schema changes (dev)pnpm --filter @workspace/scripts run seed— seed demo data
Demo Accounts
- Admin:
admin/admin123(admin + user roles) - User:
ahmed/user123(user role)
Database Tables
users, roles, permissions, user_roles, role_permissions, 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_sessionsis created manually (not auto-created) — needed in DB before first run - Vite dev server proxies
/api→localhost:8080for cookie-based auth to work - All API calls use
credentials: "include"vialib/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.jsonanden.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.