From ec7bbb229d281e52f6b35aa8e2e63b91b3cff9dc Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Mon, 4 May 2026 07:17:51 +0000 Subject: [PATCH] Task #357: Show attendee groups correctly in the upcoming meeting alert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming meeting alert's details panel was grouping attendees by the `attendanceType` field, but in practice every attendee is stored as type=internal regardless of their actual group. Users define groups via subheading rows in the schedule editor (e.g. "الحضور الخارجي", "الحضور الداخلي"), but the alert stripped subheadings and lumped everyone under "داخلي". Fix: Rewrote the grouping logic in DetailsPanel to walk through the attendees array in order, using subheading rows as natural group separators. Each subheading becomes a bold, clearly visible group header (text-xs font-bold) instead of the old tiny 10px/60% opacity labels. Person rows are listed under their preceding subheading. Edge cases handled: - Persons before any subheading: shown without a group header - No subheadings at all: flat list without misleading "داخلي" label - Empty subheading groups (heading with no persons): skipped - Person count still counts only persons, not subheadings Changed file: - artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx (DetailsPanel component, lines ~1744-1885) --- .../upcoming-meeting-alert.tsx | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) 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: