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.
This commit is contained in:
Maurice
2026-10-09 11:20:02 +02:00
parent 1c88a42890
commit a809de1b7a
3 changed files with 24 additions and 9 deletions
+7 -1
View File
@@ -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<CollabNote>({ tripId, t, toast, resetOnLoadError: true })
const { notes, setNotes, loading, uploadNoteFiles } = useCollabNotesData<CollabNote>({
tripId,
t,
toast,
resetOnLoadError: true,
waitForTripId: true,
})
const [showNewModal, setShowNewModal] = useState(false)
const [editingNote, setEditingNote] = useState(null)
const [viewingNote, setViewingNote] = useState<CollabNote | null>(null)
@@ -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 () => {
@@ -12,6 +12,8 @@ export interface CollabNotesDataOptions {
toast: ReturnType<typeof useToast>;
/** 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<N extends { id: number } = CollabNoteData>({
t,
toast,
resetOnLoadError = false,
waitForTripId = false,
}: CollabNotesDataOptions) {
const [notes, setNotes] = useState<N[]>([]);
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<N extends { id: number } = CollabNoteData>({
return () => {
cancelled = true;
};
}, [tripId, resetOnLoadError]);
}, [tripId, resetOnLoadError, waitForTripId]);
useEffect(() => {
if (!tripId) return;
if (waitForTripId && !tripId) return;
const handler = (msg: Record<string, unknown>) => {
// 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<N extends { id: number } = CollabNoteData>({
};
addListener(handler);
return () => removeListener(handler);
}, [tripId]);
}, [tripId, waitForTripId]);
/**
* Uploads a saved note's attachments one by one; a file the server refuses is toasted