#486 Executive Meetings: row-click quick actions popover (Move up / Move down / Postpone)
Clicking any meeting row on the schedule now opens a small popover with three
quick actions: Move up, Move down, and Postpone. Gated only on canMutate (NOT
editMode) per spec.
Backend
- New POST /executive-meetings/swap-times endpoint (artifacts/api-server/src/
routes/executive-meetings.ts). FOR UPDATE locks both rows by ascending id to
avoid deadlocks, optimistic-lock check via expectedUpdatedAt{A,B} (returns
409 stale_meeting + conflict.lastActor — same shape PostponeDialog
understands), guards different_dates and no_time_window, swaps only
(startTime, endTime), audits each row as `meeting_swap_times`, calls
renumberDayByStartTime so the # column matches the new chronological order,
and broadcasts emitExecutiveMeetingsDayChanged.
- New zod body schema ExecutiveMeetingsSwapTimesBody in lib/api-zod/src/manual.ts.
Frontend
- New shared lib/api-json.ts (ApiError + apiJson) extracted from upcoming-
meeting-alert.tsx so the page can reuse the same fetch/error contract.
- upcoming-meeting-alert.tsx exports PostponeDialog so the page can mount it
for quick-action postpone without duplicating UI.
- artifacts/tx-os/src/pages/executive-meetings.tsx: Schedule wires
swapTimes/quickMoveUp/quickMoveDown/postpone state, computes per-day
neighbours via meetingNumbersById, and mounts a single page-level
PostponeDialog. MeetingRow wraps `<tr>` in a Popover/PopoverAnchor; row
onClick opens the popover with skip rules for buttons/inputs/contenteditable
and ARIA roles (button/checkbox/switch/combobox/dialog) plus testid
prefixes (em-row-grip/-actions/-select, em-edit-*, em-merge-edit-*, em-time-*)
so the time cell's inline editor and other affordances don't collide.
- en/ar locales gain executiveMeetings.quickActions.{label,moveUp,moveDown,postpone}.
Tests
- artifacts/api-server/tests/executive-meetings-swap-times.test.mjs (new):
happy path, 409 stale_meeting (with conflict actor), 400 different_dates,
400 no_time_window. Each scenario uses a distinct far-future date to avoid
daily_number races with seeded data.
- artifacts/tx-os/tests/executive-meetings-row-quick-actions.spec.mjs (new):
drives the date input, verifies row click → popover, Move up swap reflected
in DB, and Postpone item opens the dialog.
Code review (architect) flagged an edit-mode conflict where the time cell
(role=button div) would also bubble to the row handler; fixed by adding ARIA
role and em-time-* skip rules. Architect also flagged keyboard-trigger gap
and edit-mode test gaps — proposed as follow-ups.
Other test failures in the repo (executive-meetings reorder, font-settings,
notes-share, service-orders) pre-date this change and are unrelated.
This commit is contained in:
@@ -8,3 +8,19 @@ export const ExecutiveMeetingsReorderBody = z.object({
|
||||
export type ExecutiveMeetingsReorderBodyT = z.infer<
|
||||
typeof ExecutiveMeetingsReorderBody
|
||||
>;
|
||||
|
||||
// #486: Body for POST /executive-meetings/swap-times. Used by the
|
||||
// schedule row quick-actions popover (Move up / Move down) to swap
|
||||
// just the (startTime, endTime) tuple between two meetings on the
|
||||
// same date without changing daily numbering, so the time column
|
||||
// stays visually anchored.
|
||||
export const ExecutiveMeetingsSwapTimesBody = z.object({
|
||||
aId: z.number().int().positive(),
|
||||
bId: z.number().int().positive(),
|
||||
expectedUpdatedAtA: z.string().min(1),
|
||||
expectedUpdatedAtB: z.string().min(1),
|
||||
});
|
||||
|
||||
export type ExecutiveMeetingsSwapTimesBodyT = z.infer<
|
||||
typeof ExecutiveMeetingsSwapTimesBody
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user