feat(person): sort control on document list #24

Closed
opened 2026-03-19 21:14:36 +01:00 by marcel · 0 comments
Owner

User Journey

Erik opens the person page for his great-uncle Georg, who has 40+ documents spanning 50 years. He wants to read the documents in chronological order to follow Georg's life story — but the list is in whatever order the API returned. He has to open documents at random to find the earliest one.

With this feature, Erik sees a "Neueste zuerst / Älteste zuerst" toggle above the document list — the same pattern used on the conversations page. He clicks "Älteste zuerst" and the list reorders instantly.


High-Level Plan

Add a sort direction state variable to the person page and sort the document array before rendering. Same pattern as the conversations page sort toggle. Pure frontend — no backend changes or additional API calls.


Detailed Plan

Frontend only

  1. Sort state in persons/[id]/+page.svelte:

    let sortDir = $state<'ASC' | 'DESC'>('DESC');
    
    const sortedSent = $derived(
        [...data.sentDocuments].sort((a, b) => {
            const da = a.documentDate ?? '';
            const db = b.documentDate ?? '';
            return sortDir === 'DESC' ? db.localeCompare(da) : da.localeCompare(db);
        })
    );
    
    const sortedReceived = $derived(
        [...data.receivedDocuments].sort((a, b) => {
            const da = a.documentDate ?? '';
            const db = b.documentDate ?? '';
            return sortDir === 'DESC' ? db.localeCompare(da) : da.localeCompare(db);
        })
    );
    

    Documents without a date sort to the end in both directions.

  2. Sort toggle button — place in the section header row (same row as the "Dokumente" heading and count badge):

    <button
        onclick={() => sortDir = sortDir === 'DESC' ? 'ASC' : 'DESC'}
        class="flex items-center gap-1 text-xs font-bold uppercase tracking-wide text-brand-navy/60 hover:text-brand-navy border border-brand-sand px-2 py-1 transition-colors"
    >
        {sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}
        <!-- chevron icon, rotated 180° when ASC -->
    </button>
    
  3. Reuse existing i18n keys conv_sort_newest / conv_sort_oldest — no new keys needed (same wording applies here).

  4. Render sortedSent and sortedReceived instead of data.sentDocuments / data.receivedDocuments.

  5. Single sort control — one toggle governs both sections simultaneously (they share the same sortDir state).

  6. Depends on: issue #1 (split sent/received). If implemented before issue #1, apply to the current single documents array.

Acceptance Criteria

  • Default sort is newest first (DESC)
  • Toggle switches between "Neueste zuerst" and "Älteste zuerst"
  • Both sent and received sections sort together
  • Documents without a date always appear last, regardless of direction
  • No URL change on sort (pure in-memory, no navigation)
  • No backend changes
## User Journey Erik opens the person page for his great-uncle Georg, who has 40+ documents spanning 50 years. He wants to read the documents in chronological order to follow Georg's life story — but the list is in whatever order the API returned. He has to open documents at random to find the earliest one. With this feature, Erik sees a **"Neueste zuerst / Älteste zuerst"** toggle above the document list — the same pattern used on the conversations page. He clicks "Älteste zuerst" and the list reorders instantly. --- ## High-Level Plan Add a sort direction state variable to the person page and sort the document array before rendering. Same pattern as the conversations page sort toggle. Pure frontend — no backend changes or additional API calls. --- ## Detailed Plan ### Frontend only 1. **Sort state** in `persons/[id]/+page.svelte`: ```typescript let sortDir = $state<'ASC' | 'DESC'>('DESC'); const sortedSent = $derived( [...data.sentDocuments].sort((a, b) => { const da = a.documentDate ?? ''; const db = b.documentDate ?? ''; return sortDir === 'DESC' ? db.localeCompare(da) : da.localeCompare(db); }) ); const sortedReceived = $derived( [...data.receivedDocuments].sort((a, b) => { const da = a.documentDate ?? ''; const db = b.documentDate ?? ''; return sortDir === 'DESC' ? db.localeCompare(da) : da.localeCompare(db); }) ); ``` Documents without a date sort to the end in both directions. 2. **Sort toggle button** — place in the section header row (same row as the "Dokumente" heading and count badge): ```svelte <button onclick={() => sortDir = sortDir === 'DESC' ? 'ASC' : 'DESC'} class="flex items-center gap-1 text-xs font-bold uppercase tracking-wide text-brand-navy/60 hover:text-brand-navy border border-brand-sand px-2 py-1 transition-colors" > {sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()} <!-- chevron icon, rotated 180° when ASC --> </button> ``` 3. **Reuse existing i18n keys** `conv_sort_newest` / `conv_sort_oldest` — no new keys needed (same wording applies here). 4. Render `sortedSent` and `sortedReceived` instead of `data.sentDocuments` / `data.receivedDocuments`. 5. **Single sort control** — one toggle governs both sections simultaneously (they share the same `sortDir` state). 6. **Depends on:** issue #1 (split sent/received). If implemented before issue #1, apply to the current single `documents` array. ### Acceptance Criteria - [ ] Default sort is newest first (DESC) - [ ] Toggle switches between "Neueste zuerst" and "Älteste zuerst" - [ ] Both sent and received sections sort together - [ ] Documents without a date always appear last, regardless of direction - [ ] No URL change on sort (pure in-memory, no navigation) - [ ] No backend changes
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#24