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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user