feat(timeline): add pulse animation for in-flight drag mutations
Allocation bars that have active optimistic overrides (post-drag, awaiting server confirmation) now pulse subtly via animate-pulse. The pending set is derived from the existing optimisticAllocations map keys, requiring no additional state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import { useCallback, useDeferredValue, useEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
import { useInvalidateTimeline } from "./useInvalidatePlanningViews.js";
|
||||
import { pixelsToDays } from "~/components/timeline/dragMath.js";
|
||||
@@ -233,6 +233,7 @@ export function useTimelineDrag({
|
||||
onAllocationMoved,
|
||||
onShiftClickAlloc,
|
||||
onMultiDragComplete,
|
||||
onMutationError,
|
||||
}: {
|
||||
cellWidthRef: MutableRefObject<number>;
|
||||
onShiftApplied?: (projectId: string) => void;
|
||||
@@ -241,6 +242,7 @@ export function useTimelineDrag({
|
||||
onAllocationMoved?: (snapshot: AllocationMovedSnapshot) => void;
|
||||
onShiftClickAlloc?: (allocationId: string) => void;
|
||||
onMultiDragComplete?: (daysDelta: number, mode: AllocDragMode, selectedIds?: string[]) => void;
|
||||
onMutationError?: (message: string) => void;
|
||||
}) {
|
||||
const [dragState, setDragState] = useState<DragState>(INITIAL_DRAG_STATE);
|
||||
const [allocDragState, setAllocDragState] = useState<AllocDragState>(INITIAL_ALLOC_DRAG);
|
||||
@@ -278,6 +280,9 @@ export function useTimelineDrag({
|
||||
const onMultiDragCompleteRef = useRef(onMultiDragComplete);
|
||||
onMultiDragCompleteRef.current = onMultiDragComplete;
|
||||
|
||||
const onMutationErrorRef = useRef(onMutationError);
|
||||
onMutationErrorRef.current = onMutationError;
|
||||
|
||||
const utils = trpc.useUtils();
|
||||
const invalidateTimeline = useInvalidateTimeline();
|
||||
|
||||
@@ -387,6 +392,9 @@ export function useTimelineDrag({
|
||||
setMultiSelectState(nextMultiSelectState);
|
||||
}, []);
|
||||
|
||||
// Defer daysDelta to avoid firing the preview query every pixel during drag
|
||||
const deferredDaysDelta = useDeferredValue(dragState.daysDelta);
|
||||
|
||||
// Project-shift preview
|
||||
const { data: previewData, isFetching: isPreviewLoading } = trpc.timeline.previewShift.useQuery(
|
||||
{
|
||||
@@ -398,7 +406,7 @@ export function useTimelineDrag({
|
||||
enabled:
|
||||
dragState.isDragging &&
|
||||
dragState.projectId !== null &&
|
||||
dragState.daysDelta !== 0 &&
|
||||
deferredDaysDelta !== 0 &&
|
||||
dragState.currentStartDate !== null,
|
||||
staleTime: 0,
|
||||
},
|
||||
@@ -447,7 +455,10 @@ export function useTimelineDrag({
|
||||
pendingSnapshotRef.current = null;
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
onError: (error) => {
|
||||
console.error("[timeline] updateAllocationInline failed:", error);
|
||||
const message = (error as { message?: string }).message ?? "Zuweisung konnte nicht verschoben werden.";
|
||||
onMutationErrorRef.current?.(message);
|
||||
clearPendingOptimisticAllocation();
|
||||
},
|
||||
});
|
||||
@@ -653,6 +664,7 @@ export function useTimelineDrag({
|
||||
extractAllocationFragment: extractAllocFragmentMutation.mutateAsync,
|
||||
updateAllocation: updateAllocMutation.mutate,
|
||||
clearPendingOptimisticAllocation,
|
||||
onError: (msg) => onMutationErrorRef.current?.(msg),
|
||||
});
|
||||
|
||||
allocDragRef.current = INITIAL_ALLOC_DRAG;
|
||||
|
||||
Reference in New Issue
Block a user