diff --git a/resources/js/guest/components/Header.tsx b/resources/js/guest/components/Header.tsx index d384c93..58b6390 100644 --- a/resources/js/guest/components/Header.tsx +++ b/resources/js/guest/components/Header.tsx @@ -13,6 +13,7 @@ import { Clock, MessageSquare, Sparkles, + LifeBuoy, UploadCloud, AlertCircle, Check, diff --git a/resources/js/guest/lib/__tests__/taskUtils.test.ts b/resources/js/guest/lib/__tests__/taskUtils.test.ts new file mode 100644 index 0000000..ea00e63 --- /dev/null +++ b/resources/js/guest/lib/__tests__/taskUtils.test.ts @@ -0,0 +1,22 @@ +import { dedupeTasksById } from '../taskUtils'; + +describe('dedupeTasksById', () => { + it('returns empty array for empty input', () => { + expect(dedupeTasksById([])).toEqual([]); + }); + + it('keeps the first occurrence and preserves order', () => { + const tasks = [ + { id: 1, title: 'A' }, + { id: 2, title: 'B' }, + { id: 1, title: 'A-dup' }, + { id: 3, title: 'C' }, + ]; + + expect(dedupeTasksById(tasks)).toEqual([ + { id: 1, title: 'A' }, + { id: 2, title: 'B' }, + { id: 3, title: 'C' }, + ]); + }); +}); diff --git a/resources/js/guest/lib/taskUtils.ts b/resources/js/guest/lib/taskUtils.ts new file mode 100644 index 0000000..9384e9d --- /dev/null +++ b/resources/js/guest/lib/taskUtils.ts @@ -0,0 +1,18 @@ +export type TaskIdentity = { + id: number; +}; + +export function dedupeTasksById(tasks: T[]): T[] { + const seen = new Set(); + const unique: T[] = []; + + tasks.forEach((task) => { + if (seen.has(task.id)) { + return; + } + seen.add(task.id); + unique.push(task); + }); + + return unique; +} diff --git a/resources/js/guest/pages/TaskPickerPage.tsx b/resources/js/guest/pages/TaskPickerPage.tsx index ffd72ce..3c93740 100644 --- a/resources/js/guest/pages/TaskPickerPage.tsx +++ b/resources/js/guest/pages/TaskPickerPage.tsx @@ -21,6 +21,7 @@ import { getDeviceId } from '../lib/device'; import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; import PullToRefresh from '../components/PullToRefresh'; import { triggerHaptic } from '../lib/haptics'; +import { dedupeTasksById } from '../lib/taskUtils'; interface Task { id: number; @@ -133,9 +134,10 @@ export default function TaskPickerPage() { ? payload.tasks : []; - const entry = { data: taskList, etag: response.headers.get('ETag') }; + const uniqueTasks = dedupeTasksById(taskList); + const entry = { data: uniqueTasks, etag: response.headers.get('ETag') }; tasksCacheRef.current.set(cacheKey, entry); - setTasks(taskList); + setTasks(uniqueTasks); } catch (err) { console.error('Failed to load tasks', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler');