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" / "معطّل")

Replit-Task-Id: 8d6fade8-e76c-4d3c-864b-59007688c12c
This commit is contained in:
riyadhafraa
2026-04-27 09:06:23 +00:00
parent cd0a8313e0
commit 5051b1b905
8 changed files with 92 additions and 31 deletions
+10 -3
View File
@@ -637,15 +637,22 @@ export default function AdminPage() {
</Button>
</div>
{apps?.map((app) => (
<div key={app.id} className="glass-panel rounded-2xl p-4 flex items-center justify-between gap-3">
<div key={app.id} className={cn("glass-panel rounded-2xl p-4 flex items-center justify-between gap-3", !app.isActive && "opacity-60")}>
<div className="flex items-center gap-3 min-w-0">
<div
className="w-8 h-8 rounded-lg shrink-0"
style={{ backgroundColor: `${app.color}40` }}
/>
<div className="min-w-0">
<div className="font-medium text-sm text-foreground truncate">
{lang === "ar" ? app.nameAr : app.nameEn}
<div className="flex items-center gap-2">
<div className="font-medium text-sm text-foreground truncate">
{lang === "ar" ? app.nameAr : app.nameEn}
</div>
{!app.isActive && (
<span className="text-[10px] font-medium bg-slate-200 text-slate-500 rounded px-1.5 py-0.5 shrink-0">
{t("admin.appDisabled")}
</span>
)}
</div>
<div className="text-xs text-muted-foreground">{app.route}</div>
</div>