Task #697: جدول حضور احترافي متوسّط + جدول في الصف الموسّع

- Extracted shared AttendeesTable component in artifacts/tx-os/src/pages/protocol.tsx
  (columns: name / position / side) with centered headers and cells, distinct
  header background (bg-slate-100/80), comfortable row heights (py-2.5),
  zebra striping (even:bg-slate-50/60), rounded border + shadow, side pill
  colored by side (sky=internal, violet=external), overflow-x-auto for small
  screens. Center alignment is RTL/LTR-safe.
- Booking details dialog now uses AttendeesTable (replaced inline table).
- Expanded row in bookings list now renders keyAttendees as the same table
  (replaced dash-separated <ul> lines), capped at max-w-2xl.
- No i18n changes needed (reuses protocol.bookings.details.attendeeCols.*).
- Verified: tsc --noEmit clean in artifacts/tx-os; architect review passed.
This commit is contained in:
Replit Agent
2026-07-08 13:15:04 +00:00
parent a7acdb216f
commit 49f71389b3
+64 -52
View File
@@ -1115,19 +1115,13 @@ export default function ProtocolPage() {
<div className="text-xs">
<span className="text-slate-400 font-medium">
{t("protocol.bookings.keyAttendees")}:
</span>{" "}
</span>
{b.keyAttendees && b.keyAttendees.length > 0 ? (
<ul className="mt-0.5 space-y-0.5 text-slate-600">
{b.keyAttendees.map((a, i) => (
<li key={i}>
{a.name}
{a.position ? `${a.position}` : ""} (
{t(`protocol.bookings.attendeeSides.${a.side}`)})
</li>
))}
</ul>
<div className="mt-1.5 max-w-2xl">
<AttendeesTable attendees={b.keyAttendees} t={t} />
</div>
) : (
<span className="text-slate-600"></span>
<span className="ms-1 text-slate-600"></span>
)}
</div>
{b.status === "rejected" && b.rejectionReason && (
@@ -2150,47 +2144,11 @@ export default function ProtocolPage() {
<div className="text-xs text-slate-400">
{t("protocol.bookings.keyAttendees")}
</div>
<div className="mt-1 overflow-x-auto rounded-lg border border-slate-200">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead className="h-8 whitespace-nowrap px-3 text-start text-xs font-semibold text-slate-500">
{t(
"protocol.bookings.details.attendeeCols.name",
)}
</TableHead>
<TableHead className="h-8 whitespace-nowrap px-3 text-start text-xs font-semibold text-slate-500">
{t(
"protocol.bookings.details.attendeeCols.position",
)}
</TableHead>
<TableHead className="h-8 whitespace-nowrap px-3 text-start text-xs font-semibold text-slate-500">
{t(
"protocol.bookings.details.attendeeCols.side",
)}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{detailsBooking.keyAttendees!.map((a, i) => (
<TableRow key={i}>
<TableCell className="px-3 py-2 font-medium text-slate-700">
{a.name || "—"}
</TableCell>
<TableCell className="px-3 py-2 text-slate-600">
{a.position || "—"}
</TableCell>
<TableCell className="px-3 py-2">
<span className="whitespace-nowrap rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-500">
{t(
`protocol.bookings.attendeeSides.${a.side}`,
)}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className="mt-1.5">
<AttendeesTable
attendees={detailsBooking.keyAttendees!}
t={t}
/>
</div>
</div>
)}
@@ -2243,6 +2201,60 @@ export default function ProtocolPage() {
// ---------------------------------------------------------------------------
// Sub-views & dialogs
// ---------------------------------------------------------------------------
function AttendeesTable({
attendees,
t,
}: {
attendees: KeyAttendee[];
t: (key: string) => string;
}) {
return (
<div className="overflow-x-auto rounded-xl border border-slate-200 shadow-sm">
<Table>
<TableHeader>
<TableRow className="border-b border-slate-200 bg-slate-100/80 hover:bg-slate-100/80">
{(["name", "position", "side"] as const).map((col) => (
<TableHead
key={col}
className="h-10 whitespace-nowrap px-4 text-center text-xs font-bold uppercase tracking-wide text-slate-600"
>
{t(`protocol.bookings.details.attendeeCols.${col}`)}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{attendees.map((a, i) => (
<TableRow
key={i}
className="border-b border-slate-100 last:border-b-0 even:bg-slate-50/60"
>
<TableCell className="px-4 py-2.5 text-center font-medium text-slate-700">
{a.name || "—"}
</TableCell>
<TableCell className="px-4 py-2.5 text-center text-slate-600">
{a.position || "—"}
</TableCell>
<TableCell className="px-4 py-2.5 text-center">
<span
className={cn(
"inline-block whitespace-nowrap rounded-full px-2.5 py-0.5 text-[11px] font-medium",
a.side === "external"
? "bg-violet-100 text-violet-700"
: "bg-sky-100 text-sky-700",
)}
>
{t(`protocol.bookings.attendeeSides.${a.side}`)}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
function DetailRow({
label,
value,