admin: fix drink-order reorder race under rapid clicks

BarDrinkEditor derived selected drink order straight from the bar prop,
which only refreshes once a fire-and-forget PATCH's reload() lands. A
second reorder/add/remove click before that round-trip completed
re-derived from the same stale array the first click started from, so
whichever PATCH the server applied last silently won and discarded the
rest.

Track our own in-flight edits in local pendingIds state so back-to-back
clicks chain off each other instead of the lagging prop; it resets to
null (defer to the prop) whenever a fresh bar.drink_ids comes back,
whether that's our own round-trip landing or an edit from elsewhere.
This commit is contained in:
iris 2026-07-31 01:12:20 +02:00
commit a589f88cd8

View file

@ -211,7 +211,23 @@ function BarDrinkEditor({
onChange: (ids: number[]) => void;
}) {
const byId = new Map(allDrinks.map(d => [d.id, d]));
const selected = bar.drink_ids.filter(id => {
// `onChange` fires a fire-and-forget PATCH (see Bars.patch) that only
// resolves into a fresh `bar` prop once its `reload()` completes. Deriving
// `selected` straight from `bar.drink_ids` meant a second click before that
// round-trip landed re-derived its reorder/add/remove from the same stale
// array the first click started from — whichever PATCH the server applied
// last won, silently discarding the rest. `pendingIds` tracks our own
// in-flight optimistic state so back-to-back clicks chain off each other
// instead of the lagging prop; it's cleared whenever the server sends back
// a fresh `bar.drink_ids` (our own round-trip landing, or an edit from
// elsewhere), deferring to that as the new source of truth.
const [pendingIds, setPendingIds] = useState<number[] | null>(null);
useEffect(() => {
setPendingIds(null);
}, [bar.drink_ids]);
const selected = (pendingIds ?? bar.drink_ids).filter(id => {
const d = byId.get(id);
return d && !d.archived;
});
@ -220,18 +236,22 @@ function BarDrinkEditor({
const dragFrom = useRef<number | null>(null);
const [dragOver, setDragOver] = useState<number | null>(null);
function commit(next: number[]) {
setPendingIds(next);
onChange(next);
}
function remove(idx: number) {
onChange(selected.filter((_, i) => i !== idx));
commit(selected.filter((_, i) => i !== idx));
}
function add(id: number) {
onChange([...selected, id]);
commit([...selected, id]);
}
function reorder(from: number, to: number) {
if (from === to || from < 0 || to < 0 || from >= selected.length || to >= selected.length) return;
const next = selected.slice();
const [moved] = next.splice(from, 1);
next.splice(to, 0, moved!);
onChange(next);
commit(next);
}
return (