Timeline
diff --git a/apps/web/src/components/layout/AppShell.tsx b/apps/web/src/components/layout/AppShell.tsx
index 4093936..01f07b4 100644
--- a/apps/web/src/components/layout/AppShell.tsx
+++ b/apps/web/src/components/layout/AppShell.tsx
@@ -295,15 +295,16 @@ function SidebarContent({
sidebarCollapsed,
onToggleCollapse,
onNavClick,
+ onPrefsOpen,
}: {
userRole: string;
onChatOpen: () => void;
sidebarCollapsed: boolean;
onToggleCollapse: () => void;
onNavClick?: () => void;
+ onPrefsOpen: () => void;
}) {
const pathname = usePathname();
- const [prefsOpen, setPrefsOpen] = useState(false);
const activeHrefSet = useMemo(() => {
const set = new Set();
@@ -594,7 +595,7 @@ function SidebarContent({
- {prefsOpen &&
setPrefsOpen(false)} />}
>
);
}
@@ -660,11 +660,13 @@ function DesktopSidebar({
onChatOpen,
sidebarCollapsed,
onToggleCollapse,
+ onPrefsOpen,
}: {
userRole: string;
onChatOpen: () => void;
sidebarCollapsed: boolean;
onToggleCollapse: () => void;
+ onPrefsOpen: () => void;
}) {
return (
);
@@ -693,11 +696,13 @@ function MobileSidebar({
onClose,
userRole,
onChatOpen,
+ onPrefsOpen,
}: {
open: boolean;
onClose: () => void;
userRole: string;
onChatOpen: () => void;
+ onPrefsOpen: () => void;
}) {
return (
@@ -738,6 +743,10 @@ function MobileSidebar({
sidebarCollapsed={false}
onToggleCollapse={() => {}}
onNavClick={onClose}
+ onPrefsOpen={() => {
+ onPrefsOpen();
+ onClose();
+ }}
/>
>
@@ -754,6 +763,7 @@ export function AppShell({ children, userRole = "USER" }: { children: React.Reac
const [chatOpen, setChatOpen] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
+ const [prefsOpen, setPrefsOpen] = useState(false);
const pathname = usePathname();
const contentRef = useRef(null);
@@ -802,6 +812,7 @@ export function AppShell({ children, userRole = "USER" }: { children: React.Reac
onChatOpen={() => setChatOpen(true)}
sidebarCollapsed={sidebarCollapsed}
onToggleCollapse={handleToggleCollapse}
+ onPrefsOpen={() => setPrefsOpen(true)}
/>
{/* Mobile sidebar overlay */}
@@ -810,6 +821,7 @@ export function AppShell({ children, userRole = "USER" }: { children: React.Reac
onClose={() => setMobileOpen(false)}
userRole={userRole}
onChatOpen={() => setChatOpen(true)}
+ onPrefsOpen={() => setPrefsOpen(true)}
/>
{/* Main content area */}
@@ -844,6 +856,7 @@ export function AppShell({ children, userRole = "USER" }: { children: React.Reac
)}
+ {prefsOpen && setPrefsOpen(false)} />}
);
}
diff --git a/apps/web/src/components/timeline/TimelineView.tsx b/apps/web/src/components/timeline/TimelineView.tsx
index f46417a..88881f3 100644
--- a/apps/web/src/components/timeline/TimelineView.tsx
+++ b/apps/web/src/components/timeline/TimelineView.tsx
@@ -135,8 +135,8 @@ export function TimelineView() {
return { ...prev, isSelecting: false, selectedAllocationIds: [...ids] };
});
},
- onMultiDragComplete: (daysDelta, mode) => {
- const ids = multiSelectState.selectedAllocationIds;
+ onMultiDragComplete: (daysDelta, mode, selectedIds) => {
+ const ids = selectedIds ?? multiSelectState.selectedAllocationIds;
if (ids.length > 0 && daysDelta !== 0) {
pushBatchHistoryRef.current(ids, daysDelta, mode);
batchShiftMutationOuter.mutate({ allocationIds: ids, daysDelta, mode });
@@ -547,7 +547,7 @@ function TimelineViewContent({
resourceHoverTimerRef.current = null;
}
};
- }, [resourceHover?.resourceId]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [resourceHover?.resourceId, isInitialLoading]); // eslint-disable-line react-hooks/exhaustive-deps
// ─── Lazy-extend date range on scroll ─────────────────────────────────────
function handleContainerScroll() {
diff --git a/apps/web/src/hooks/useTimelineDrag.ts b/apps/web/src/hooks/useTimelineDrag.ts
index 034f383..8fcfb3b 100644
--- a/apps/web/src/hooks/useTimelineDrag.ts
+++ b/apps/web/src/hooks/useTimelineDrag.ts
@@ -178,7 +178,7 @@ export function useTimelineDrag({
onRangeSelected?: (info: RangeSelectedInfo) => void;
onAllocationMoved?: (snapshot: AllocationMovedSnapshot) => void;
onShiftClickAlloc?: (allocationId: string) => void;
- onMultiDragComplete?: (daysDelta: number, mode: AllocDragMode) => void;
+ onMultiDragComplete?: (daysDelta: number, mode: AllocDragMode, selectedIds?: string[]) => void;
}) {
const [dragState, setDragState] = useState(INITIAL_DRAG_STATE);
const [allocDragState, setAllocDragState] = useState(INITIAL_ALLOC_DRAG);
@@ -394,7 +394,9 @@ export function useTimelineDrag({
multiSelectRef.current = { ...multiSelectRef.current, isMultiDragging: false, multiDragDaysDelta: 0 };
if (finalDelta !== 0) {
- onMultiDragCompleteRef.current?.(finalDelta, dragMode);
+ // Pass IDs from ref to avoid stale closure in the callback
+ const ids = multiSelectRef.current.selectedAllocationIds;
+ onMultiDragCompleteRef.current?.(finalDelta, dragMode, ids);
}
}