Task #725: red "external" badge + structured expanded booking details table
- protocol.tsx bookings table: external meeting-type badge changed from violet to rose (bg-rose-100 text-rose-700); internal unchanged. - Expanded row rebuilt as a professional card-table: header row with meeting title (label above value, full width), grid of labeled cells (requester, department, phone, entity for external only, meeting type, attendee count, booking reference, purpose) with light dividers, keyAttendees AttendeesTable section below, rejection reason as a red banner row. - Added i18n key protocol.bookings.table.title (ar: "اسم الاجتماع", en: "Meeting name"). - UI-only; tsc --noEmit clean; HMR applied; architect review PASS.
This commit is contained in:
@@ -1811,6 +1811,7 @@
|
||||
"linkCopied": "تم نسخ الرابط",
|
||||
"linkCopyFailed": "تعذّر نسخ الرابط",
|
||||
"table": {
|
||||
"title": "اسم الاجتماع",
|
||||
"number": "رقم الحجز",
|
||||
"requester": "مقدم الطلب",
|
||||
"date": "تاريخ الحجز",
|
||||
|
||||
@@ -1664,6 +1664,7 @@
|
||||
"linkCopied": "Link copied",
|
||||
"linkCopyFailed": "Could not copy the link",
|
||||
"table": {
|
||||
"title": "Meeting name",
|
||||
"number": "Booking No.",
|
||||
"requester": "Requester",
|
||||
"date": "Booking date",
|
||||
|
||||
@@ -1104,7 +1104,7 @@ export default function ProtocolPage() {
|
||||
className={cn(
|
||||
"text-xs px-2 py-0.5 rounded-full",
|
||||
b.meetingType === "external"
|
||||
? "bg-violet-100 text-violet-700"
|
||||
? "bg-rose-100 text-rose-700"
|
||||
: "bg-slate-100 text-slate-600",
|
||||
)}
|
||||
>
|
||||
@@ -1219,19 +1219,26 @@ export default function ProtocolPage() {
|
||||
</TableRow>
|
||||
{expanded && (
|
||||
<TableRow key={`${b.id}-details`} className="bg-slate-50/80">
|
||||
<TableCell colSpan={detailColSpan} className="py-3">
|
||||
<div className="space-y-1">
|
||||
<div className="text-sm text-slate-700 font-semibold">
|
||||
{b.title}
|
||||
<TableCell colSpan={detailColSpan} className="p-3">
|
||||
<div className="overflow-hidden rounded-lg border border-slate-200 bg-white">
|
||||
<div className="border-b border-slate-200 bg-slate-50 px-4 py-2.5">
|
||||
<span className="text-[11px] text-slate-400">
|
||||
{t("protocol.bookings.table.title")}
|
||||
</span>
|
||||
<div className="text-sm font-bold text-slate-800">
|
||||
{b.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-x-6 gap-y-1 sm:grid-cols-2 lg:grid-cols-3 text-xs">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 divide-x divide-x-reverse divide-slate-100 border-b border-slate-200">
|
||||
{(
|
||||
[
|
||||
[t("protocol.bookings.requester"), b.requesterName],
|
||||
[t("protocol.bookings.department"), b.department],
|
||||
[
|
||||
t("protocol.bookings.meetingType"),
|
||||
t(`protocol.bookings.meetingTypes.${b.meetingType}`),
|
||||
t("protocol.bookings.phone"),
|
||||
b.requesterPhone ? (
|
||||
<span dir="ltr">{b.requesterPhone}</span>
|
||||
) : null,
|
||||
],
|
||||
...(b.meetingType === "external"
|
||||
? [
|
||||
@@ -1242,49 +1249,55 @@ export default function ProtocolPage() {
|
||||
]
|
||||
: []),
|
||||
[
|
||||
t("protocol.bookings.phone"),
|
||||
b.requesterPhone ? (
|
||||
<span dir="ltr">{b.requesterPhone}</span>
|
||||
) : null,
|
||||
t("protocol.bookings.meetingType"),
|
||||
t(`protocol.bookings.meetingTypes.${b.meetingType}`),
|
||||
],
|
||||
[
|
||||
t("protocol.bookings.attendeeCount"),
|
||||
b.attendeeCount != null ? b.attendeeCount : null,
|
||||
],
|
||||
[t("protocol.bookings.purpose"), b.purpose],
|
||||
[
|
||||
t("protocol.bookings.reference"),
|
||||
b.bookingReference ? (
|
||||
<span dir="ltr">{b.bookingReference}</span>
|
||||
) : null,
|
||||
],
|
||||
[t("protocol.bookings.purpose"), b.purpose],
|
||||
] as Array<[string, React.ReactNode]>
|
||||
).map(([label, value], i) => (
|
||||
<div key={i} className="flex gap-1">
|
||||
<span className="text-slate-400 shrink-0">
|
||||
{label}:
|
||||
</span>
|
||||
<span className="text-slate-600 min-w-0 break-words">
|
||||
<div
|
||||
key={i}
|
||||
className="border-b border-slate-100 px-4 py-2"
|
||||
>
|
||||
<div className="text-[11px] text-slate-400">
|
||||
{label}
|
||||
</div>
|
||||
<div className="text-xs font-medium text-slate-700 break-words">
|
||||
{value != null && value !== "" ? value : "—"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
<span className="text-slate-400 font-medium">
|
||||
{t("protocol.bookings.keyAttendees")}:
|
||||
</span>
|
||||
<div className="px-4 py-2.5">
|
||||
<div className="mb-1.5 text-[11px] font-semibold text-slate-400">
|
||||
{t("protocol.bookings.keyAttendees")}
|
||||
</div>
|
||||
{b.keyAttendees && b.keyAttendees.length > 0 ? (
|
||||
<div className="mt-1.5 max-w-2xl">
|
||||
<div className="max-w-2xl">
|
||||
<AttendeesTable attendees={b.keyAttendees} t={t} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="ms-1 text-slate-600">—</span>
|
||||
<span className="text-xs text-slate-600">—</span>
|
||||
)}
|
||||
</div>
|
||||
{b.status === "rejected" && b.rejectionReason && (
|
||||
<div className="text-xs text-rose-500">
|
||||
{b.rejectionReason}
|
||||
<div className="border-t border-rose-200 bg-rose-50 px-4 py-2">
|
||||
<div className="text-[11px] font-semibold text-rose-600">
|
||||
{t("protocol.bookings.details.rejectionReason")}
|
||||
</div>
|
||||
<div className="text-xs text-rose-700 break-words">
|
||||
{b.rejectionReason}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user