#486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only on canMutate, not editMode) opens a small popover with Move up / Move down / Postpone. Move up/down swap only the (startTime, endTime) tuple between the clicked meeting and its chronological neighbour on the same date — the Time column stays visually anchored to its row position. Backend - POST /executive-meetings/swap-times: transactional swap with FOR UPDATE row locking, optimistic-lock conflict shape (stale_meeting + conflict payload), date/time-window guards, audit logging, and renumberDayByStartTime + day-changed broadcast. - Zod schema in lib/api-zod/src/manual.ts. Frontend - Shared lib/api-json.ts JSON helper. - ScheduleSection.swapTimes does an optimistic (startTime, endTime) swap against the day query cache and rolls back on failure (mirrors the existing inline-edit UX). - Edge enablement (canQuickMoveUp/Down) now derived from the full orderedMeetings index, not the search-filtered displayedMeetings, so "Move up/down" disabled state always matches the true day boundary (and never silently swaps with a hidden filtered neighbour). - MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* / em-row-grip / em-row-actions data-testid prefixes do NOT open the popover. - PostponeDialog reused from upcoming-meeting-alert.tsx. Tests - Backend swap-times: happy path, stale_meeting (409), different_dates (400), no_time_window (400), unauth (401), viewer-no-mutate (403), malformed-timestamp (400) — all 7 pass. - Hardened expectedUpdatedAt zod schema to z.string().datetime() so malformed tokens fail at validation with a controlled 400 instead of bubbling up as a 500. - E2E: Move up swap, edge-disable states (solo / first / middle / last), Postpone 5-min chip end-to-end, click-exclusion on grip / time cell / row-actions — all 4 pass. Each test uses its own future date to avoid cross-test pollution. Code review approved on second pass. Pre-existing failures in other suites (executive-meetings reorder, font-settings, notes-share, service-orders) are unrelated to this task and predate it. Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit- mode test gaps).
This commit is contained in:
@@ -2931,8 +2931,22 @@ function ScheduleSection({
|
||||
bulkSelected={selectedMeetingIds.has(m.id)}
|
||||
onBulkSelectChange={(v) => toggleMeetingSelected(m.id, v)}
|
||||
quickActionsCanMutate={canMutate}
|
||||
canQuickMoveUp={idx > 0}
|
||||
canQuickMoveDown={idx < displayedMeetings.length - 1}
|
||||
// Edge enablement is computed against the FULL day order,
|
||||
// not the (possibly search-filtered) `displayedMeetings`.
|
||||
// quickMoveUp/Down resolve neighbours from
|
||||
// `orderedMeetings`, so deriving disabled state from the
|
||||
// filtered index would let a user click "Move up" on what
|
||||
// looks like the first visible row but is actually mid-day,
|
||||
// and silently swap with a hidden neighbour.
|
||||
canQuickMoveUp={
|
||||
orderedMeetings.findIndex((om) => om.id === m.id) > 0
|
||||
}
|
||||
canQuickMoveDown={(() => {
|
||||
const oIdx = orderedMeetings.findIndex(
|
||||
(om) => om.id === m.id,
|
||||
);
|
||||
return oIdx >= 0 && oIdx < orderedMeetings.length - 1;
|
||||
})()}
|
||||
onQuickMoveUp={() => quickMoveUp(m.id)}
|
||||
onQuickMoveDown={() => quickMoveDown(m.id)}
|
||||
onQuickPostpone={() => setPostponeMeetingId(m.id)}
|
||||
|
||||
Reference in New Issue
Block a user