Commit Graph

412 Commits

Author SHA1 Message Date
Riyadh e21af0216d Treat 404 in incoming-order actions as "no longer available"
Task #87: Confirm-receipt should also handle the case where the order was
deleted.

When an admin deletes an order while a receiver is viewing it, the API
returns 404 Order not found for confirm-receipt and the status PATCH.
Previously the UI showed the generic "Could not complete the action"
toast and left the stale card in place.

Updated all three onError branches in
`artifacts/tx-os/src/pages/orders-incoming.tsx` so 404 responses are
handled the same way as the existing `order_unavailable` 409:

- handleConfirmReceipt: 404 now shows the localized
  "incomingOrders.orderUnavailable" toast instead of "actionFailed".
  invalidate() was already called for every error in this branch, so
  the stale card is removed.
- handleSetStatus (preparing/completed): 404 now shows the
  "orderUnavailable" toast and calls invalidate() to drop the card.
- handleCancel: same treatment for 404 on the cancel PATCH.

The 409 already-claimed branch is preserved unchanged. No new
translation keys were needed; `incomingOrders.orderUnavailable` already
exists in both `src/locales/en.json` and `src/locales/ar.json`.

Pre-existing TypeScript errors in `src/pages/admin.tsx` are unrelated
to this change.
2026-04-27 12:14:42 +00:00
Riyadh 694239f7b8 Warn admins before deleting non-empty users/apps/services (Task #85)
Apply the existing groups delete-warning pattern to user, app, and
service deletions so admins are warned (and have to confirm twice)
before destroying records that still own dependent data.

Backend
- openapi.yaml: added `force` query param to DELETE /users/{id},
  /apps/{id}, /services/{id} plus three new conflict schemas
  (UserDeletionConflict, AppDeletionConflict, ServiceDeletionConflict).
- routes/users.ts, apps.ts, services.ts: rewrote DELETE handlers to
  count dependents (notes/orders/conversations/messages for users;
  group_apps/restrictions/open events for apps; service_orders for
  services), return 409 with counts when non-empty and force is not set,
  and on `?force=true` perform the cascade in a transaction and write
  an `*.force_delete` audit_logs row.
- Existing 204 success path preserved for empty deletes.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New `DeletionWarningDialog` helper, plus app/service/user delete state
  + lazy 409 detection (first click probes; on 409 the dialog upgrades
  to show counts + "Delete anyway"; second click sends ?force=true).
- Replaced the three plain `confirm(t("admin.deleteConfirm"))` callsites.

i18n
- Added admin.deleteApp/deleteService/deleteUser keys (title, warning,
  forceHint, emptyBody, count keys, confirm, anyway) in en.json + ar.json.

Tests
- artifacts/api-server/tests/delete-force-warnings.test.mjs covers all
  9 cases (clean delete, 409 with counts, force=true 204 + audit log)
  for users, apps, and services. Existing groups-crud and service-orders
  tests still pass.

Notes / drift
- Lazy detection (vs eager counts on the row like Groups already does)
  was chosen because list endpoints don't return counts yet — proposed
  follow-up #96 covers eager counts so the warning appears on first
  click everywhere.
- E2E test for the UI flow flaked twice; backend integration tests
  (9/9 pass), direct curl validation of force=true returning 204, and
  typecheck across the monorepo all confirm correctness.
2026-04-27 12:07:07 +00:00
Riyadh 1b96b6528d Add executive meeting management system with role-based access
Implement a new API endpoint and UI for managing executive meetings, including role-based access control and database schema changes for meeting data and attendees.
2026-04-27 12:05:40 +00:00
Riyadh 2827e092b3 Add admin Audit Log view (task-84)
Original task: Show admins a history of sensitive actions. Forced group
deletions already write to the new `audit_logs` table; admins now have
a UI to browse and filter those entries.

API
- New OpenAPI endpoint GET /admin/audit-logs (admin-only) with optional
  filters: action (exact), from / to (YYYY-MM-DD, inclusive), limit
  (1-200, default 50), offset (default 0).
- New schemas: AuditLogActor, AuditLogEntry, AuditLogList. List
  response includes paginated `entries`, `totalCount`, `nextOffset`,
  and a distinct `actions` list to power the filter dropdown.
- New `audit` tag added to the spec; ran orval codegen so
  api-client-react / api-zod expose useListAuditLogs et al.
- Implemented `artifacts/api-server/src/routes/audit.ts` and registered
  it in routes/index.ts. Validates date format / order, joins users for
  actor info, and orders newest-first.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New "Audit log" entry under the User Management nav group (icon:
  ScrollText). Section deep-link works via #section=audit-log.
- New AuditLogPanel + AuditLogRow components: action chip + target,
  formatted timestamp, actor avatar/name, expandable JSON metadata.
- Filters bar: action dropdown (populated from API's distinct
  actions), from/to date inputs, Apply / Reset, plus a "Load more"
  control that increases the page size (caps at 200, the API limit).
  Read-only by design.
- Added en/ar locale strings for nav.auditLog and the audit subtree.

Verification
- Typecheck: pnpm -w run typecheck (clean).
- API smoke tested via curl: 401 unauthenticated, validation errors
  for bad / inverted dates, returns the seeded `group.force_delete`
  entry once produced.
- End-to-end browser test (testing skill): logged in as admin,
  navigated to /admin#section=audit-log, verified the row, expanded
  metadata, exercised filters (empty state + reset).

No deviations from the task description.
2026-04-27 11:28:20 +00:00
Riyadh 0243f1026b Task #83: Refresh role and permission changes live across user sessions
## Socket.IO broadcasts (core task):
- POST /users/:id/roles: uses .returning() to emit apps_changed only when row inserted
- DELETE /users/:id/roles/:roleName: uses .returning() to emit only when row deleted
- Added emitAppsChangedToRoleHolders(roleId) helper to realtime.ts
- New role-permission endpoints in roles.ts, emit only on effective changes:
  - GET /roles/:id/permissions — list permissions for a role
  - POST /roles/:id/permissions — assign; emits to role holders on actual insert
  - DELETE /roles/:id/permissions/:permId — remove; emits only if row was deleted

## Bug fix: admin panel not showing disabled apps
- Added GET /api/admin/apps (requireAdmin) returning ALL apps from DB
- Updated admin.tsx: useQuery → /api/admin/apps with ADMIN_APPS_KEY constant
- All admin.tsx mutation handlers invalidate ADMIN_APPS_KEY

## UI fix: disabled app cards nearly invisible (opacity-60 on glass)
- Gray background + grayscale icon + muted text + stronger Disabled badge


## DB: removed 11 duplicate rows from app_permissions table
2026-04-27 11:11:43 +00:00
Riyadh 1dd4245945 Let admins create and edit roles from the dashboard (Task #82)
What changed:
- Added an `is_system` column to the `roles` table (default 0). The
  built-in roles (admin, user, order_receiver) are flagged as system roles
  in the seed and via a one-off UPDATE on the existing dev DB.
- Created a new `/api/roles` route module with admin-guarded endpoints:
    - POST /api/roles – create a role (validates name format, rejects duplicates).
    - PATCH /api/roles/:id – update bilingual descriptions; name is immutable.
    - DELETE /api/roles/:id – returns 400 for system roles (also defended
      by name allowlist), 409 with userCount/groupCount when in use, 204 on
      success. Cleans up role_permissions in a transaction.
  GET /api/roles was moved out of groups.ts into the new module.
- OpenAPI updated with createRole / updateRole / deleteRole operations,
  isSystem on the Role schema, and CreateRoleBody / UpdateRoleBody /
  RoleDeletionConflict schemas. Regenerated api-zod and api-client-react.
- Added a "Roles" item under the User Management nav group in the admin
  dashboard, plus a new RolesPanel with search, create dialog, edit dialog
  (description-only), and delete dialog with conflict messaging.
- Bilingual translations added in en.json and ar.json (admin.nav.roles +
  admin.roles.*).

Notes / deviations:
- Also marked `order_receiver` as a system role since it is seeded by the
  system and required by the orders feature, even though the task brief
  only called out admin and user.
- Verified end-to-end via the testing skill: list, create, edit, delete,
  duplicate-name validation, and protection of system roles all work.
2026-04-27 10:26:24 +00:00
Riyadh a1c5b081dd Show role descriptions in both languages on the Groups Roles tab
Task #81: The Groups editor's Roles tab previously displayed only the
description in the active UI language with the role's machine name as
a small subtitle. Bilingual admin teams asked to see both languages at
once.

Changes (artifacts/tx-os/src/pages/admin.tsx):
- Imported Tooltip, TooltipContent, TooltipTrigger from
  @/components/ui/tooltip (TooltipProvider was already mounted at the
  App root, so no provider wiring was needed).
- Refactored each role row in the Roles tab of GroupDetailEditor:
  * Primary line: description in the active language
    (descriptionAr when lang === "ar", otherwise descriptionEn). Falls
    back to the other-language description, then to the role's name
    when no descriptions exist.
  * Secondary line (smaller, muted): the OTHER language's description,
    rendered with the appropriate dir attribute. Only shown when both
    descriptions are present so single-description roles fall back
    cleanly.
  * Machine name remains visible as a small monospace subtitle on the
    trailing edge of the row.
  * The whole row is wrapped in a Tooltip whose content shows both
    descriptions (each with its proper dir) plus the machine name for
    full context on hover.
- Added data-testid="group-role-<id>-secondary" so the secondary line
  is easily targetable from tests.

Verification:
- Type-checked tx-os (only pre-existing errors elsewhere in admin.tsx
  remain; none introduced by this change).
- e2e test passed: logged in as admin, opened a group, switched to the
  Roles tab, confirmed primary + secondary description lines, hovered
  to see the bilingual tooltip, toggled the UI language and confirmed
  the lines swapped, and confirmed checkboxes still toggle through the
  TooltipTrigger asChild wrapper.

The single-description-only fallback could not be exercised against
seeded data (all seeded roles are bilingual), but the rendering logic
gracefully handles that case via the `heading` and `showSecondary`
guards.

Also reverts an unrelated stray modification to
artifacts/tx-os/public/opengraph.jpg that was swept into the previous
auto-commit, restoring it to the version from HEAD~1.

Follow-up proposed: #88 "Let admins edit role descriptions in both
languages".
2026-04-27 10:08:41 +00:00
Riyadh 57292caa07 fix: tell receivers when an order is no longer available + fix disabled apps disappearing from admin
## Task #80 — Tell receivers when an order is no longer available to claim

### Problem
When a receiver tried to act on an order that had already been cancelled, completed,
or claimed by someone else, the UI showed a generic "Could not complete the action"
toast. The stale card also stayed in the list, requiring a manual refresh.

### Changes

**artifacts/api-server/src/routes/service-orders.ts**
- PATCH /orders/:id/confirm-receipt: after a failed atomic claim, query the current
  order status; return 409 `order_unavailable` if it's cancelled/completed (vs the
  existing 409 `already_claimed` for when it was grabbed by someone else first)
- PATCH /orders/:id/status (preparing/completed branch): terminal-state check runs
  BEFORE permission gating — returns 409 `order_unavailable` when order is already
  cancelled/completed instead of ever hitting 403 Forbidden
- PATCH /orders/:id/status (cancel branch): same — terminal-state check moved to
  the top of the branch so non-admin receivers trying to cancel an already-terminal
  order get 409 `order_unavailable` (not 403 Forbidden which would never let the UI
  remove the stale card)

**artifacts/tx-os/src/pages/orders-incoming.tsx**
- handleConfirmReceipt onError: distinguish 409 `order_unavailable` from
  `already_claimed`, show specific toast, remove stale card via invalidate()
- handleSetStatus onError: same pattern — specific toast + invalidate() on
  `order_unavailable`
- handleCancel onError: same pattern

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `incomingOrders.orderUnavailable` ("This order is no longer available" /
  "هذا الطلب لم يعد متاحاً")

## Bonus fix — Disabled app disappears from admin panel (user-reported)

### Problem
When an admin disabled an app via the toggle, the app disappeared from the admin
panel too (isActive filter applied to everyone), making it impossible to re-enable
without direct DB access.

### Changes

**artifacts/api-server/src/routes/apps.ts**
- getVisibleAppsForUser: admins now receive ALL apps including inactive ones via
  conditional $dynamic() Drizzle query; non-admins still only see active apps

**artifacts/tx-os/src/pages/home.tsx**
- Filter orderedApps to only active apps before rendering so inactive apps don't
  appear on the home screen even for admins

**artifacts/tx-os/src/pages/admin.tsx**
- Inactive app cards show reduced opacity + a "Disabled/معطّل" badge

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `admin.appDisabled` ("Disabled" / "معطّل")
2026-04-27 09:06:23 +00:00
Riyadh 14ce113e70 Warn admins before deleting non-empty groups
Original task: Task #79 — Make DELETE /api/groups/:id refuse to wipe a non-empty
group unless explicitly forced, surface the impacted counts in the admin UI,
and log forced deletions to an audit trail.

Changes:
- API: DELETE /api/groups/:id now computes member/app/role counts. If any are
  non-zero and `?force=true` is not passed, it returns 409 with
  { error, memberCount, appCount, roleCount }. With `force=true` it deletes
  and writes an entry to the new `audit_logs` table
  (action='group.force_delete', target_type='group', target_id, metadata).
  System group guard and 404 behaviour preserved.
- DB: Added `audit_logs` table (lib/db/src/schema/audit-logs.ts) with actor,
  action, target type/id, jsonb metadata, timestamp. Pushed via drizzle-kit.
- OpenAPI: Added `force` query param, 409 response, and `GroupDeletionConflict`
  schema. Re-ran orval codegen for api-client-react and api-zod.
- api-client-react: Re-exported `ApiError` and `ErrorType` so the UI can
  inspect 409 responses.
- Admin UI (artifacts/teaboy-os/src/pages/admin.tsx GroupsPanel):
  Replaced the bare `confirm()` with a confirmation dialog that shows the
  group name and impacted member/app/role counts. Empty groups get a simple
  "Delete" confirmation; non-empty groups get a warning + "Delete anyway"
  button which sends `force=true`. If the server returns 409 (race), the
  dialog updates to show the server-reported counts.
- Locales: Added en/ar strings for the new dialog (deleteTitle, deleteWarning,
  deleteForceHint, deleteEmptyBody, deleteConfirm, deleteAnyway).

Verified end-to-end: 409 on first DELETE, dialog warning visible, force
deletion succeeds, single audit_logs row recorded with correct metadata.
2026-04-22 11:05:34 +00:00
Riyadh 361b6848a2 Remove service descriptions from display and administration
Removes Arabic and English description fields from the admin form, the services display component, and the seed data in `scripts/src/seed.ts`, and removes the corresponding columns from the database.
2026-04-22 10:55:46 +00:00
Riyadh eb5eb75f05 Design modern, minimal icons based on "TX" for the platform
Add new SVG icon assets for the platform's favicon and branding, including multiple variants and a preview HTML file.
2026-04-22 10:53:54 +00:00
Riyadh aa462f9f52 Rename project and remove unused services
Update project name from teaboy-os to tx-os and remove obsolete services.
2026-04-22 10:52:09 +00:00