bulk request
This commit is contained in:
88
src/components/useSelection.ts
Normal file
88
src/components/useSelection.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Which rows a person has ticked, and the header checkbox that follows.
|
||||
*
|
||||
* Shared by the three screens that grew a bulk action — importing from the
|
||||
* catalogue, requesting stock, and deciding requests — because the fiddly parts
|
||||
* are the same every time and getting them subtly different between screens is
|
||||
* how a merchant learns to distrust the tick boxes.
|
||||
*
|
||||
* ── The part that is easy to get wrong ──────────────────────────────────────
|
||||
*
|
||||
* A selection is kept against the ROWS CURRENTLY VISIBLE. Filter a list down,
|
||||
* tick everything, clear the filter, and press the button: a naive
|
||||
* implementation acts on rows the person could not see when they chose. So
|
||||
* "select all" only ever covers what is on screen, and `chosen` is intersected
|
||||
* with the visible ids before it is handed back.
|
||||
*
|
||||
* Ids that scroll out of view are NOT dropped from the set, because narrowing a
|
||||
* search and widening it again should not silently untick the work. They are
|
||||
* simply not acted on while they are hidden.
|
||||
*/
|
||||
export interface Selection {
|
||||
/** Visible ids that are ticked — what a bulk action should act on. */
|
||||
chosen: number[];
|
||||
count: number;
|
||||
has: (id: number) => boolean;
|
||||
toggle: (id: number) => void;
|
||||
/** Tick or untick everything currently visible. */
|
||||
toggleAll: () => void;
|
||||
clear: () => void;
|
||||
/** Every visible row is ticked. Drives the header checkbox. */
|
||||
allChosen: boolean;
|
||||
/** Some but not all — the indeterminate state. */
|
||||
someChosen: boolean;
|
||||
}
|
||||
|
||||
export function useSelection(visibleIds: readonly number[]): Selection {
|
||||
const [picked, setPicked] = useState<ReadonlySet<number>>(() => new Set());
|
||||
|
||||
const chosen = useMemo(
|
||||
() => visibleIds.filter((id) => picked.has(id)),
|
||||
[visibleIds, picked],
|
||||
);
|
||||
|
||||
const toggle = useCallback((id: number) => {
|
||||
setPicked((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const allChosen = visibleIds.length > 0 && chosen.length === visibleIds.length;
|
||||
|
||||
const toggleAll = useCallback(() => {
|
||||
setPicked((prev) => {
|
||||
const next = new Set(prev);
|
||||
const everyVisibleChosen =
|
||||
visibleIds.length > 0 && visibleIds.every((id) => next.has(id));
|
||||
for (const id of visibleIds) {
|
||||
if (everyVisibleChosen) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, [visibleIds]);
|
||||
|
||||
const clear = useCallback(() => setPicked(new Set()), []);
|
||||
|
||||
return {
|
||||
chosen,
|
||||
count: chosen.length,
|
||||
has: (id: number) => picked.has(id),
|
||||
toggle,
|
||||
toggleAll,
|
||||
clear,
|
||||
allChosen,
|
||||
someChosen: chosen.length > 0 && !allChosen,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user