diff --git a/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx b/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx index db17aa68..7e052c45 100644 --- a/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx +++ b/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx @@ -1741,27 +1741,32 @@ function DetailsPanel({ dividerColor, sectionBg, }: DetailsPanelProps) { - // Group attendees by attendanceType (internal/external/virtual) and - // skip subheading rows — those are user-defined section labels in the - // schedule editor, not actual people, so they shouldn't appear in a - // "who is attending" details panel. - const persons = (meeting.attendees ?? []).filter( - (a) => a.kind !== "subheading" && a.name && a.name.trim().length > 0, - ); - const groups: Record = { - internal: [], - external: [], - virtual: [], - }; - for (const p of persons) { - const key = - p.attendanceType === "internal" || - p.attendanceType === "external" || - p.attendanceType === "virtual" - ? p.attendanceType - : "internal"; - groups[key].push(p); + // Walk through attendees in order. Subheading rows act as group + // headers (e.g. "الحضور الخارجي", "الحضور الداخلي"); person rows + // following a subheading belong to that group. If persons appear + // before any subheading they land in a header-less group. + type AttendeeGroup = { heading: string | null; members: Attendee[] }; + const attendeeGroups: AttendeeGroup[] = []; + let currentGroup: AttendeeGroup = { heading: null, members: [] }; + for (const a of meeting.attendees ?? []) { + const cleanName = (a.name ?? "").replace(/<[^>]+>/g, "").trim(); + if (!cleanName) continue; + if (a.kind === "subheading") { + if (currentGroup.heading !== null || currentGroup.members.length > 0) { + attendeeGroups.push(currentGroup); + } + currentGroup = { heading: cleanName, members: [] }; + } else { + currentGroup.members.push(a); + } } + if (currentGroup.heading !== null || currentGroup.members.length > 0) { + attendeeGroups.push(currentGroup); + } + const personCount = attendeeGroups.reduce( + (sum, g) => sum + g.members.length, + 0, + ); const safeUrl = (() => { const raw = (meeting.meetingUrl ?? "").trim(); @@ -1831,16 +1836,14 @@ function DetailsPanel({ }); } - // Attendees row is always shown (even when empty) so the user can - // distinguish "no one yet" from "field not loaded". rows.push({ key: "attendees", icon: