From a809de1b7a2687cf5b08baa727f597bb8c246349 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 11:20:02 +0200 Subject: [PATCH] refactor(collab): keep the phone notes tab loading without a trip id guard The shared notes hook skipped loading and listening without a trip id, which only the desktop panel did. That guard is now a desktop option, so the phone tab loads and listens as before. --- client/src/components/Collab/CollabNotes.tsx | 8 +++++++- .../components/Collab/useCollabNotesData.test.ts | 14 ++++++++++---- client/src/components/Collab/useCollabNotesData.ts | 11 +++++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/client/src/components/Collab/CollabNotes.tsx b/client/src/components/Collab/CollabNotes.tsx index a4a563bbe..043249601 100644 --- a/client/src/components/Collab/CollabNotes.tsx +++ b/client/src/components/Collab/CollabNotes.tsx @@ -43,7 +43,13 @@ function useCollabNotes({ tripId, currentUser }: CollabNotesProps) { const can = useCanDo() const trip = useTripStore((s) => s.trip) const canEdit = can('collab_edit', trip) - const { notes, setNotes, loading, uploadNoteFiles } = useCollabNotesData({ tripId, t, toast, resetOnLoadError: true }) + const { notes, setNotes, loading, uploadNoteFiles } = useCollabNotesData({ + tripId, + t, + toast, + resetOnLoadError: true, + waitForTripId: true, + }) const [showNewModal, setShowNewModal] = useState(false) const [editingNote, setEditingNote] = useState(null) const [viewingNote, setViewingNote] = useState(null) diff --git a/client/src/components/Collab/useCollabNotesData.test.ts b/client/src/components/Collab/useCollabNotesData.test.ts index 04bfb8330..7c5c76852 100644 --- a/client/src/components/Collab/useCollabNotesData.test.ts +++ b/client/src/components/Collab/useCollabNotesData.test.ts @@ -1,5 +1,6 @@ // FE-COLLAB-NOTESDATA-001 to FE-COLLAB-NOTESDATA-008: the note list behind the desktop -// Collab panel (empties on a failed load) and the phone's notes tab (keeps the list), with +// Collab panel (empties on a failed load, waits for a trip id) and the phone's notes tab +// (keeps the list, always loads), with // the live note events and the attachment upload both run after saving a note. import { act, renderHook, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -79,11 +80,16 @@ describe('useCollabNotesData', () => { expect(empty.result.current.notes).toEqual([]); }); - it('FE-COLLAB-NOTESDATA-002: without a trip nothing loads or listens', () => { - const { result } = setup({ tripId: 0 }); + it('FE-COLLAB-NOTESDATA-002: without a trip the desktop list waits, the phone list still loads', async () => { + const desktop = setup({ tripId: 0, waitForTripId: true }); expect(collabApi.getNotes).not.toHaveBeenCalled(); expect(addListener).not.toHaveBeenCalled(); - expect(result.current.loading).toBe(true); + expect(desktop.result.current.loading).toBe(true); + + const phone = await loaded({ tripId: 0 }); + expect(collabApi.getNotes).toHaveBeenCalledWith(0); + expect(addListener).toHaveBeenCalledTimes(1); + expect(phone.result.current.notes.map((n) => n.id)).toEqual([1, 2]); }); it('FE-COLLAB-NOTESDATA-003: a failed reload empties the desktop list and keeps the phone list', async () => { diff --git a/client/src/components/Collab/useCollabNotesData.ts b/client/src/components/Collab/useCollabNotesData.ts index 19eddc006..7a07493b3 100644 --- a/client/src/components/Collab/useCollabNotesData.ts +++ b/client/src/components/Collab/useCollabNotesData.ts @@ -12,6 +12,8 @@ export interface CollabNotesDataOptions { toast: ReturnType; /** The desktop panel outlives a trip change, so a failed load there empties the list. */ resetOnLoadError?: boolean; + /** The desktop panel loads and listens only once it has a trip id; the phone tab always does. */ + waitForTripId?: boolean; } /** @@ -26,12 +28,13 @@ export function useCollabNotesData({ t, toast, resetOnLoadError = false, + waitForTripId = false, }: CollabNotesDataOptions) { const [notes, setNotes] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { - if (!tripId) return; + if (waitForTripId && !tripId) return; let cancelled = false; setLoading(true); collabApi @@ -48,10 +51,10 @@ export function useCollabNotesData({ return () => { cancelled = true; }; - }, [tripId, resetOnLoadError]); + }, [tripId, resetOnLoadError, waitForTripId]); useEffect(() => { - if (!tripId) return; + if (waitForTripId && !tripId) return; const handler = (msg: Record) => { // An event still in flight from a trip just left must not land in this list. if (String(msg?.tripId) !== String(tripId)) return; @@ -69,7 +72,7 @@ export function useCollabNotesData({ }; addListener(handler); return () => removeListener(handler); - }, [tripId]); + }, [tripId, waitForTripId]); /** * Uploads a saved note's attachments one by one; a file the server refuses is toasted