Task #489 fix: decouple row drag from edit mode + fix optimistic order
Previous review rejected because row drag was gated on
`effectiveCanMutate = canMutate && editMode`, forcing users to
enter Edit mode to drag. Spec requires drag to work in view mode
for any user with mutate permission.
Second rejection: optimistic UI patched per-meeting (start/end/
dailyNumber) but did not reorder the rendered rows, so post-drop
slots could display rotated times against the wrong content
(e.g. 11:00, 09:00, 10:00) until refetch.
Changes:
- MeetingRow: new `dragEnabled` prop separate from `canMutate`.
Drives useSortable.disabled, safeRowDragListeners gate, attributes
spread, and cursor-grab class.
- Parent passes `dragEnabled={canMutate}` (raw, not gated by
editMode); `canMutate={effectiveCanMutate}` retained for inline
cell editing.
- handleRowClick: scope interactive `closest()` to descendants
(`interactive !== e.currentTarget`). Required because the row now
carries dnd-kit's role="button" in view mode and an unscoped
closest() matched the row itself, swallowing every popover click.
- rotateContent: also call `setOptimisticOrder(newOrder)` so slot[i]
visually shows newOrder[i] in the same React commit; rollback
restores the prior optimistic order on rotate failure.
- e2e: removed edit-toggle click from row-drag spec — drag now
passes in view mode.
Tests: row-drag spec (view mode), popover spec (4/4), schedule-
features drag tests (290, 834), backend rotate-content (6/6) all
pass. 3 unrelated schedule-features failures reference a missing
em-nav-settings testid that does not exist in source — pre-existing
broken tests, not caused by this task.
This commit is contained in:
@@ -2146,6 +2146,7 @@ function ScheduleSection({
|
||||
// never in `orderedMeetings`, so they keep their existing tuple.
|
||||
const queryKey = ["/api/executive-meetings", date] as const;
|
||||
const previous = qc.getQueryData<DayResponse>(queryKey);
|
||||
const previousOptimisticOrder = optimisticOrder;
|
||||
const slotById = new Map<number, { startTime: string | null; endTime: string | null; dailyNumber: number }>();
|
||||
newOrder.forEach((id, i) => {
|
||||
const slotMeeting = orderedMeetings[i];
|
||||
@@ -2173,6 +2174,12 @@ function ScheduleSection({
|
||||
}
|
||||
: prev,
|
||||
);
|
||||
// #489: also drive the rendered row order so slot[i] visually
|
||||
// shows newOrder[i]. Without this, the array order in cache
|
||||
// remains the pre-drag order and times appear rotated against
|
||||
// the wrong row content (e.g. 11:00, 09:00, 10:00). Cleared on
|
||||
// refetch by the useEffect on `meetings`.
|
||||
setOptimisticOrder(newOrder);
|
||||
const expectedUpdatedAt: Record<string, string> = {};
|
||||
for (const m of orderedMeetings) {
|
||||
expectedUpdatedAt[String(m.id)] = m.updatedAt;
|
||||
@@ -2193,6 +2200,9 @@ function ScheduleSection({
|
||||
if (previous !== undefined) {
|
||||
qc.setQueryData<DayResponse>(queryKey, previous);
|
||||
}
|
||||
// #489: restore the prior optimistic order on rollback so a
|
||||
// failed rotate doesn't leave the UI in the post-drop order.
|
||||
setOptimisticOrder(previousOptimisticOrder);
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
toast({
|
||||
title: t("common.error"),
|
||||
@@ -2204,7 +2214,7 @@ function ScheduleSection({
|
||||
setReordering(false);
|
||||
}
|
||||
},
|
||||
[orderedMeetings, date, qc, refreshDay, toast, t],
|
||||
[orderedMeetings, optimisticOrder, date, qc, refreshDay, toast, t],
|
||||
);
|
||||
|
||||
// #486 → #489: Move up / Move down were retired in favour of
|
||||
|
||||
Reference in New Issue
Block a user