Task #16: Per-user drag-and-drop reorder for Home apps
- New user_app_orders composite-PK table (user_id, app_id) -> sort_order - Added GET helper getVisibleAppsForUser that LEFT JOINs user order and sorts by COALESCE(user_sort, app.sort_order, name) - New PUT /api/me/app-order endpoint validates payload, filters to visible apps, dedupes, replaces row set in a transaction - Frontend: wrapped home apps grid in @dnd-kit DndContext + SortableContext with PointerSensor (distance:8) and TouchSensor (delay:250 / tolerance:5) so taps still navigate while a press-and-drag reorders - Optimistic local state with rollback on error; useEffect skips sync while a save mutation is in flight to avoid stomping on user changes - Bottom dock intentionally NOT sortable (per user choice) - DB schema pushed manually via SQL (drizzle-kit push prompted for rename ambiguity); regenerated api-zod / api-client-react - Verified end-to-end: PUT /api/me/app-order returns reordered list and subsequent GET /api/apps reflects the new per-user order
This commit is contained in:
@@ -98,8 +98,8 @@ export default function HomePage() {
|
||||
const [orderedApps, setOrderedApps] = useState<App[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (apps && !updateOrder.isPending) setOrderedApps(apps);
|
||||
}, [apps, updateOrder.isPending]);
|
||||
if (apps) setOrderedApps((prev) => prev ?? apps);
|
||||
}, [apps]);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
||||
@@ -112,12 +112,18 @@ export default function HomePage() {
|
||||
const oldIndex = orderedApps.findIndex((a) => a.id === active.id);
|
||||
const newIndex = orderedApps.findIndex((a) => a.id === over.id);
|
||||
if (oldIndex < 0 || newIndex < 0) return;
|
||||
const previous = orderedApps;
|
||||
const next = arrayMove(orderedApps, oldIndex, newIndex);
|
||||
setOrderedApps(next);
|
||||
updateOrder.mutate(
|
||||
{ data: { order: next.map((a) => a.id) } },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setOrderedApps(data);
|
||||
queryClient.setQueryData(getListAppsQueryKey(), data);
|
||||
},
|
||||
onError: () => {
|
||||
setOrderedApps(previous);
|
||||
queryClient.invalidateQueries({ queryKey: getListAppsQueryKey() });
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user