Task #207: custom subheadings inside executive-meeting attendee cells
Adds a new `kind` column (`varchar(16) NOT NULL DEFAULT 'person'`) on `executive_meeting_attendees` so users can interleave free-text section labels with person rows in a meeting's attendee list. Subheadings are excluded from the running attendee number and from the per-meeting attendee count surface, but reorder and delete identically to person rows. DB - New `kind` column in `lib/db/src/schema/executive-meetings.ts`, defaulting to `"person"`. Applied via direct SQL because `drizzle-kit push` trips on a pre-existing duplicate-row issue in `app_permissions` (already documented in replit.md). API (artifacts/api-server/src/routes/executive-meetings.ts) - `attendeeSchema` accepts `kind: z.enum(["person","subheading"])` with default `"person"`. - All 4 insert paths round-trip `kind`: POST create, PATCH meeting update (attendees replacement), PUT `/attendees`, and duplicate. - `pdf-renderer` mapper forwards `kind`. `PdfMeetingAttendee.kind` typed as `string | null` to match the DB column shape. Frontend (artifacts/tx-os/src/pages/executive-meetings.tsx) - `AttendeeFlow` renders subheadings on a separate full-width row (`basis-full`, semibold, centered) and increments the running person index only for `kind === "person"`. Pending ghost row branches on `pendingKind`. - Inline "+ subheading" chip via `onStartAdd(type, "subheading")`. - Manage dialog: addSubheading button, subheading rows hide the title field, show a kind badge, and reorder/delete identically. Manage list summary count filters to person rows only (architect fix). - Print page renders subheadings as `.em-print-subheading` and skips them in the running counter. - New locale keys under `executiveMeetings.schedule` and `executiveMeetings.manage.attendees` in both `ar.json` and `en.json`. PDF - Subheadings print as `— label —` and never advance `personIdx`. Tests - New spec `executive-meetings-attendee-subheadings.spec.mjs` seeds mixed person+subheading rows and asserts (a) the subheading row renders, (b) numbering stays `1-`, `2-`, `3-` with a subheading wedged between persons, (c) zero-subheading meetings keep legacy numbering. Runs in both AR and EN. All 4 cases pass. Code review - Architect found one regression (Manage list summary count included subheadings) — fixed.
This commit is contained in:
@@ -74,12 +74,20 @@ export const executiveMeetingAttendeesTable = pgTable(
|
||||
.references(() => executiveMeetingsTable.id, { onDelete: "cascade" }),
|
||||
// attendee name holds sanitized rich-text HTML so it needs unbounded
|
||||
// length. Plain-text rows still render correctly (no tags = no parse).
|
||||
// For `kind = "subheading"` rows, this column stores the user-written
|
||||
// section label (free text, single value rendered as-is in AR and EN).
|
||||
name: text("name").notNull(),
|
||||
title: varchar("title", { length: 200 }),
|
||||
attendanceType: varchar("attendance_type", { length: 32 })
|
||||
.notNull()
|
||||
.default("internal"),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
// "person" — a real attendee row (default; matches all pre-existing
|
||||
// data after backfill).
|
||||
// "subheading" — a user-defined section label inserted between attendees
|
||||
// inside the same attendance-type group. Numbering, count,
|
||||
// and virtual-platform extraction MUST ignore these rows.
|
||||
kind: varchar("kind", { length: 16 }).notNull().default("person"),
|
||||
},
|
||||
(t) => ({
|
||||
meetingIdx: index("executive_meeting_attendees_meeting_idx").on(t.meetingId),
|
||||
|
||||
Reference in New Issue
Block a user