Task #739: تحويل تبويب القاعات من بطاقات إلى جدول

- protocol.tsx (tab === "rooms"): replaced the 2-column card grid with a
  Table styled like the bookings table (bg-slate-100/80 header, centered
  headers/cells) with columns: # | القاعات | الحالة | الإجراء.
  - # = sequential index; name via nameOf with capacity line beneath.
  - Status badge keeps emerald-100 / slate-200 colors.
  - Actions column (edit/delete, same setRoomDialog/setDeleteTarget
    handlers) rendered only when canManageRooms.
  - EmptyState (rooms.empty), "قاعة جديدة" button, and dialogs unchanged.
- i18n: added protocol.rooms.status ("الحالة"/"Status") and
  protocol.rooms.actions ("الإجراء"/"Action") to ar.json and en.json.
- Verified: pnpm --filter @workspace/tx-os exec tsc --noEmit clean;
  architect review passed with no blocking issues.
This commit is contained in:
Replit Agent
2026-07-09 09:58:09 +00:00
parent e2afc0bb28
commit 409b2c6fd5
3 changed files with 84 additions and 53 deletions
+2
View File
@@ -1893,6 +1893,8 @@
"location": "الموقع",
"active": "مفعّلة",
"isActive": "مفعّلة",
"status": "الحالة",
"actions": "الإجراء",
"empty": "لا توجد قاعات."
},
"issues": {
+2
View File
@@ -1738,6 +1738,8 @@
"location": "Location",
"active": "Active",
"isActive": "Active",
"status": "Status",
"actions": "Action",
"empty": "No rooms."
},
"issues": {
+48 -21
View File
@@ -1780,26 +1780,47 @@ export default function ProtocolPage() {
</Button>
)}
</div>
<div className="grid gap-2 sm:grid-cols-2">
{(rooms.data ?? []).map((r) => (
<div
key={r.id}
className="bg-white rounded-xl border border-slate-200 p-3"
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
{rooms.data?.length === 0 ? (
<EmptyState text={t("protocol.rooms.empty")} />
) : (
<div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-slate-100/80 hover:bg-slate-100/80">
<TableHead className="w-10 text-center">#</TableHead>
<TableHead className="text-center whitespace-nowrap">
{t("protocol.rooms.title")}
</TableHead>
<TableHead className="text-center whitespace-nowrap">
{t("protocol.rooms.status")}
</TableHead>
{c?.canManageRooms && (
<TableHead className="text-center whitespace-nowrap">
{t("protocol.rooms.actions")}
</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{(rooms.data ?? []).map((r, i) => (
<TableRow key={r.id}>
<TableCell className="text-center text-slate-500">
{i + 1}
</TableCell>
<TableCell className="text-center">
<div className="font-medium text-slate-800">
{nameOf(r.nameAr, r.nameEn)}
</div>
{r.capacity ? (
<div className="text-xs text-slate-500">
{r.capacity
? `${t("protocol.rooms.capacity")}: ${r.capacity}`
: ""}
</div>
{`${t("protocol.rooms.capacity")}: ${r.capacity}`}
</div>
) : null}
</TableCell>
<TableCell className="text-center">
<span
className={cn(
"text-xs px-2 py-0.5 rounded-full shrink-0",
"text-xs px-2 py-0.5 rounded-full",
r.isActive
? "bg-emerald-100 text-emerald-700"
: "bg-slate-200 text-slate-500",
@@ -1809,31 +1830,37 @@ export default function ProtocolPage() {
? t("protocol.rooms.active")
: t("protocol.rooms.inactive")}
</span>
</div>
</TableCell>
{c?.canManageRooms && (
<div className="flex gap-1.5 mt-2">
<TableCell className="text-center">
<div className="flex justify-center gap-1.5">
<Button
size="sm"
variant="ghost"
onClick={() => setRoomDialog({ open: true, edit: r })}
onClick={() =>
setRoomDialog({ open: true, edit: r })
}
>
<Pencil size={14} />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => setDeleteTarget({ kind: "room", id: r.id })}
onClick={() =>
setDeleteTarget({ kind: "room", id: r.id })
}
>
<Trash2 size={14} className="text-rose-500" />
</Button>
</div>
</TableCell>
)}
</div>
</TableRow>
))}
{rooms.data?.length === 0 && (
<EmptyState text={t("protocol.rooms.empty")} />
)}
</TableBody>
</Table>
</div>
)}
</section>
)}