feat(persons): retarget frequent-correspondents card to document search
The "Häufige Korrespondenten" card linked into the standalone Briefwechsel view. Retarget each chip to the existing document search pre-filtered by sender and receiver (/documents?senderId=A&receiverId=B), naming both persons in a search-action title, swapping the chat-bubble icon for a magnifier, and clarifying that the ×N badge counts shared letters in both directions (not the unidirectional search result count). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -190,6 +190,9 @@
|
||||
"person_role_receiver": "Empfangen",
|
||||
"person_co_correspondents_heading": "Häufige Korrespondenten",
|
||||
"person_correspondents_hint": "klicken für Konversation",
|
||||
"person_correspondents_search_title": "Briefe von {A} an {B} durchsuchen",
|
||||
"person_correspondents_search_hint": "klicken, um Briefe zu durchsuchen",
|
||||
"person_correspondents_badge_title": "Gemeinsame Briefe in beide Richtungen",
|
||||
"person_show_more": "+ {count} weitere anzeigen",
|
||||
"conv_label_person_a": "Person A (Absender)",
|
||||
"conv_label_person_b": "Korrespondent",
|
||||
|
||||
@@ -190,6 +190,9 @@
|
||||
"person_role_receiver": "Received",
|
||||
"person_co_correspondents_heading": "Frequent correspondents",
|
||||
"person_correspondents_hint": "click to view conversation",
|
||||
"person_correspondents_search_title": "Search letters from {A} to {B}",
|
||||
"person_correspondents_search_hint": "click to search letters",
|
||||
"person_correspondents_badge_title": "Shared letters in both directions",
|
||||
"person_show_more": "+ {count} more",
|
||||
"conv_label_person_a": "Person A (Sender)",
|
||||
"conv_label_person_b": "Correspondent",
|
||||
|
||||
@@ -190,6 +190,9 @@
|
||||
"person_role_receiver": "Recibido",
|
||||
"person_co_correspondents_heading": "Corresponsales frecuentes",
|
||||
"person_correspondents_hint": "clic para ver conversación",
|
||||
"person_correspondents_search_title": "Buscar cartas de {A} a {B}",
|
||||
"person_correspondents_search_hint": "haz clic para buscar cartas",
|
||||
"person_correspondents_badge_title": "Cartas compartidas en ambas direcciones",
|
||||
"person_show_more": "+ {count} más",
|
||||
"conv_label_person_a": "Persona A (Remitente)",
|
||||
"conv_label_person_b": "Corresponsal",
|
||||
|
||||
@@ -68,7 +68,11 @@ const coCorrespondents = $derived.by(() => {
|
||||
|
||||
<!-- Right column: correspondents + relationships + documents -->
|
||||
<div>
|
||||
<CoCorrespondentsList coCorrespondents={coCorrespondents} personId={person.id} />
|
||||
<CoCorrespondentsList
|
||||
coCorrespondents={coCorrespondents}
|
||||
personId={person.id}
|
||||
personName={person.displayName}
|
||||
/>
|
||||
|
||||
<div class="mt-6">
|
||||
<PersonRelationshipsCard
|
||||
|
||||
@@ -3,10 +3,12 @@ import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
let {
|
||||
coCorrespondents,
|
||||
personId
|
||||
personId,
|
||||
personName
|
||||
}: {
|
||||
coCorrespondents: { id: string; name: string; count: number }[];
|
||||
personId: string;
|
||||
personName: string;
|
||||
} = $props();
|
||||
|
||||
function initials(name: string): string {
|
||||
@@ -25,13 +27,15 @@ function initials(name: string): string {
|
||||
<h3 class="text-[10px] font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.person_co_correspondents_heading()}
|
||||
</h3>
|
||||
<span class="font-sans text-[10px] text-ink-3 italic">{m.person_correspondents_hint()}</span>
|
||||
<span class="font-sans text-[10px] text-ink-3 italic"
|
||||
>{m.person_correspondents_search_hint()}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each coCorrespondents as c (c.id)}
|
||||
<a
|
||||
href="/briefwechsel?senderId={personId}&receiverId={c.id}"
|
||||
title={m.doc_conversation_title()}
|
||||
href="/documents?senderId={personId}&receiverId={c.id}"
|
||||
title={m.person_correspondents_search_title({ A: personName, B: c.name })}
|
||||
class="inline-flex items-center gap-1.5 rounded-full border border-line bg-muted px-3 py-1.5 font-sans text-xs font-bold text-ink transition-colors hover:border-primary hover:bg-surface"
|
||||
>
|
||||
<!-- Initials circle -->
|
||||
@@ -41,8 +45,11 @@ function initials(name: string): string {
|
||||
{initials(c.name)}
|
||||
</span>
|
||||
{c.name}
|
||||
<span class="text-[10px] font-normal text-ink-3">×{c.count}</span>
|
||||
<!-- Chat icon -->
|
||||
<span
|
||||
class="text-[10px] font-normal text-ink-3"
|
||||
title={m.person_correspondents_badge_title()}>×{c.count}</span
|
||||
>
|
||||
<!-- Search icon -->
|
||||
<svg
|
||||
class="h-3 w-3 flex-shrink-0 text-ink-3"
|
||||
fill="none"
|
||||
@@ -54,7 +61,7 @@ function initials(name: string): string {
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import CoCorrespondentsList from './CoCorrespondentsList.svelte';
|
||||
|
||||
afterEach(cleanup);
|
||||
@@ -8,7 +9,7 @@ afterEach(cleanup);
|
||||
describe('CoCorrespondentsList', () => {
|
||||
it('renders nothing when the coCorrespondents list is empty', async () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: { coCorrespondents: [], personId: 'p-1' }
|
||||
props: { coCorrespondents: [], personId: 'p-1', personName: 'Anna Schmidt' }
|
||||
});
|
||||
|
||||
await expect
|
||||
@@ -16,18 +17,19 @@ describe('CoCorrespondentsList', () => {
|
||||
.not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the heading and hint when there is at least one co-correspondent', async () => {
|
||||
it('renders the heading and search hint when there is at least one co-correspondent', async () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: 'Max Mustermann', count: 3 }],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
await expect
|
||||
.element(page.getByRole('heading', { name: /häufige korrespondenten/i }))
|
||||
.toBeVisible();
|
||||
await expect.element(page.getByText('klicken für Konversation')).toBeVisible();
|
||||
await expect.element(page.getByText(m.person_correspondents_search_hint())).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders one chip per co-correspondent with name and count', async () => {
|
||||
@@ -37,7 +39,8 @@ describe('CoCorrespondentsList', () => {
|
||||
{ id: 'c-1', name: 'Max Mustermann', count: 3 },
|
||||
{ id: 'c-2', name: 'Erika Beispiel', count: 1 }
|
||||
],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -47,24 +50,40 @@ describe('CoCorrespondentsList', () => {
|
||||
await expect.element(page.getByText('×1')).toBeVisible();
|
||||
});
|
||||
|
||||
it('points each chip to the bilateral conversation route with the correct ids', async () => {
|
||||
it('points each chip to the document search pre-filtered by sender and receiver', async () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: 'Max Mustermann', count: 3 }],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
await expect
|
||||
.element(page.getByRole('link', { name: /max mustermann/i }))
|
||||
.toHaveAttribute('href', '/briefwechsel?senderId=p-1&receiverId=c-1');
|
||||
.toHaveAttribute('href', '/documents?senderId=p-1&receiverId=c-1');
|
||||
});
|
||||
|
||||
it('labels the link as a search action naming both persons', async () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: 'Max Mustermann', count: 3 }],
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
const link = page.getByRole('link', { name: /max mustermann/i });
|
||||
await expect.element(link).toHaveAttribute('title', /Anna Schmidt/);
|
||||
await expect.element(link).toHaveAttribute('title', /Max Mustermann/);
|
||||
});
|
||||
|
||||
it('builds initials from up to two name parts', async () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: 'Max Mustermann Beispiel', count: 1 }],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -75,7 +94,8 @@ describe('CoCorrespondentsList', () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: 'Cher', count: 2 }],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -88,7 +108,8 @@ describe('CoCorrespondentsList', () => {
|
||||
render(CoCorrespondentsList, {
|
||||
props: {
|
||||
coCorrespondents: [{ id: 'c-1', name: ' Max', count: 1 }],
|
||||
personId: 'p-1'
|
||||
personId: 'p-1',
|
||||
personName: 'Anna Schmidt'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user