feat(persons): redesign detail page sections to match Concept A spec
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- CoCorrespondentsList: white card wrapper with navy initials circles in chips - PersonDocumentList: flat row-divider pattern with variant-tinted icons (sent=navy, received=teal) - Add variant prop (sent/received) to PersonDocumentList and wire up in page - Add person_correspondents_hint i18n key to all three message files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,7 @@
|
||||
"person_role_sender": "Gesendet",
|
||||
"person_role_receiver": "Empfangen",
|
||||
"person_co_correspondents_heading": "Häufige Korrespondenten",
|
||||
"person_correspondents_hint": "klicken für Konversation",
|
||||
"person_show_more": "+ {count} weitere anzeigen",
|
||||
"conv_heading": "Konversationen",
|
||||
"conv_subtitle": "Verfolgen Sie den Schriftverkehr zwischen zwei Personen chronologisch.",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"person_role_sender": "Sent",
|
||||
"person_role_receiver": "Received",
|
||||
"person_co_correspondents_heading": "Frequent correspondents",
|
||||
"person_correspondents_hint": "click to view conversation",
|
||||
"person_show_more": "+ {count} more",
|
||||
"conv_heading": "Conversations",
|
||||
"conv_subtitle": "Follow the correspondence between two persons chronologically.",
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"person_role_sender": "Enviado",
|
||||
"person_role_receiver": "Recibido",
|
||||
"person_co_correspondents_heading": "Corresponsales frecuentes",
|
||||
"person_correspondents_hint": "clic para ver conversación",
|
||||
"person_show_more": "+ {count} más",
|
||||
"conv_heading": "Conversaciones",
|
||||
"conv_subtitle": "Siga la correspondencia entre dos personas cronológicamente.",
|
||||
|
||||
@@ -78,12 +78,14 @@ const coCorrespondents = $derived.by(() => {
|
||||
documents={sentDocuments}
|
||||
heading={m.person_docs_heading()}
|
||||
emptyMessage={m.person_no_docs()}
|
||||
variant="sent"
|
||||
/>
|
||||
|
||||
<PersonDocumentList
|
||||
documents={receivedDocuments}
|
||||
heading={m.person_received_docs_heading()}
|
||||
emptyMessage={m.person_no_received_docs()}
|
||||
variant="received"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,23 +8,43 @@ let {
|
||||
coCorrespondents: { id: string; name: string; count: number }[];
|
||||
personId: string;
|
||||
} = $props();
|
||||
|
||||
function initials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map((n) => n[0] ?? '')
|
||||
.join('')
|
||||
.slice(0, 2)
|
||||
.toUpperCase();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if coCorrespondents.length > 0}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-3 text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.person_co_correspondents_heading()}
|
||||
</h3>
|
||||
<div class="mb-4 rounded-sm border border-line bg-surface p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each coCorrespondents as c (c.id)}
|
||||
<a
|
||||
href="/conversations?senderId={personId}&receiverId={c.id}"
|
||||
title={m.doc_conversation_title()}
|
||||
class="inline-flex items-center gap-1.5 rounded-full border border-line px-3 py-1 font-serif text-sm text-ink transition-colors hover:border-primary"
|
||||
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 -->
|
||||
<span
|
||||
class="flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-primary font-serif text-[9px] font-bold text-primary-fg"
|
||||
>
|
||||
{initials(c.name)}
|
||||
</span>
|
||||
{c.name}
|
||||
<span class="text-[10px] font-normal text-ink-3">×{c.count}</span>
|
||||
<!-- Chat icon -->
|
||||
<svg
|
||||
class="h-3.5 w-3.5 flex-shrink-0 text-ink-3"
|
||||
class="h-3 w-3 flex-shrink-0 text-ink-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -37,8 +57,6 @@ let {
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
{c.name}
|
||||
<span class="font-sans text-xs text-ink-3">({c.count})</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { formatDate } from '$lib/utils/date';
|
||||
import { sortDocumentsByDate, type SortDir } from '$lib/utils/sort';
|
||||
import { formatDocumentStatus } from '$lib/utils/documentStatusLabel';
|
||||
import { sortDocumentsByDate, type SortDir } from '$lib/utils/sort';
|
||||
|
||||
const DOCS_PREVIEW_LIMIT = 5;
|
||||
|
||||
let {
|
||||
documents,
|
||||
heading,
|
||||
emptyMessage
|
||||
emptyMessage,
|
||||
variant = 'sent'
|
||||
}: {
|
||||
documents: {
|
||||
id: string;
|
||||
@@ -21,6 +22,7 @@ let {
|
||||
}[];
|
||||
heading: string;
|
||||
emptyMessage: string;
|
||||
variant?: 'sent' | 'received';
|
||||
} = $props();
|
||||
|
||||
const yearRange = $derived.by(() => {
|
||||
@@ -40,21 +42,29 @@ const sortedDocuments = $derived(sortDocumentsByDate(documents, sortDir));
|
||||
const visibleDocuments = $derived(
|
||||
showAll ? sortedDocuments : sortedDocuments.slice(0, DOCS_PREVIEW_LIMIT)
|
||||
);
|
||||
|
||||
// Spec: sent = navy-tint icon bg, received = teal-tint icon bg
|
||||
const iconClasses = $derived(
|
||||
variant === 'sent' ? 'bg-primary/10 text-primary' : 'bg-accent/20 text-accent-fg'
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="mb-10">
|
||||
<div class="mb-6 flex items-center gap-3 border-b border-ink/10 pb-2">
|
||||
<h2 class="font-serif text-xl text-ink">{heading}</h2>
|
||||
<span class="rounded-full bg-primary px-2 py-1 text-xs font-bold text-primary-fg">
|
||||
<div class="mb-4 rounded-sm border border-line bg-surface">
|
||||
<!-- Section header -->
|
||||
<div class="flex items-center gap-3 border-b border-line px-4 py-3">
|
||||
<h2 class="text-[10px] font-bold tracking-widest text-ink-3 uppercase">{heading}</h2>
|
||||
<span
|
||||
class="rounded-full bg-primary/10 px-2 py-0.5 font-sans text-[10px] font-bold text-primary"
|
||||
>
|
||||
{documents.length}
|
||||
</span>
|
||||
{#if yearRange}
|
||||
<span class="font-sans text-xs text-ink-3">{yearRange}</span>
|
||||
<span class="font-sans text-[10px] text-ink-3">{yearRange}</span>
|
||||
{/if}
|
||||
{#if documents.length > 1}
|
||||
<button
|
||||
onclick={() => (sortDir = sortDir === 'DESC' ? 'ASC' : 'DESC')}
|
||||
class="ml-auto text-xs font-bold tracking-widest text-ink-3 uppercase transition-colors hover:text-ink"
|
||||
class="ml-auto text-[10px] font-bold tracking-widest text-ink-3 uppercase transition-colors hover:text-ink"
|
||||
>
|
||||
{sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}
|
||||
</button>
|
||||
@@ -62,69 +72,68 @@ const visibleDocuments = $derived(
|
||||
</div>
|
||||
|
||||
{#if documents.length === 0}
|
||||
<div class="rounded-sm border border-dashed border-line bg-surface p-12 text-center">
|
||||
<p class="font-sans text-ink-2">{emptyMessage}</p>
|
||||
</div>
|
||||
<p class="px-4 py-6 text-center font-sans text-sm text-ink-3">{emptyMessage}</p>
|
||||
{:else}
|
||||
<ul class="space-y-3">
|
||||
<ul>
|
||||
{#each visibleDocuments as doc (doc.id)}
|
||||
<li class="group">
|
||||
<li>
|
||||
<a
|
||||
href="/documents/{doc.id}"
|
||||
class="block border border-line bg-surface p-4 transition-all duration-200 hover:border-primary hover:shadow-md"
|
||||
class="group flex items-center gap-3 border-b border-line px-4 py-3 transition-colors last:border-b-0 hover:bg-muted"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4 overflow-hidden">
|
||||
<div
|
||||
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded bg-muted text-ink transition-colors group-hover:bg-accent group-hover:text-ink"
|
||||
>
|
||||
<img
|
||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
class="truncate font-serif text-base font-medium text-ink group-hover:underline"
|
||||
>
|
||||
{doc.title || doc.originalFilename}
|
||||
</div>
|
||||
<div class="mt-0.5 flex items-center space-x-2 font-sans text-xs text-ink-2">
|
||||
<span>{doc.documentDate ? formatDate(doc.documentDate) : m.doc_no_date()}</span>
|
||||
{#if doc.location}
|
||||
<span class="text-accent">•</span>
|
||||
<span>{doc.location}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tinted doc icon -->
|
||||
<div
|
||||
class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded {iconClasses}"
|
||||
>
|
||||
<img
|
||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Title + meta -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate font-serif text-sm font-medium text-ink group-hover:underline">
|
||||
{doc.title || doc.originalFilename}
|
||||
</div>
|
||||
<div class="flex flex-shrink-0 items-center gap-2 pl-4">
|
||||
<span
|
||||
class="hidden items-center rounded-full border px-2 py-0.5 text-[10px] font-bold tracking-wide uppercase sm:inline-flex
|
||||
{doc.status === 'UPLOADED'
|
||||
? 'border-accent/50 bg-accent/20 text-ink'
|
||||
: 'border-yellow-200 bg-yellow-50 text-yellow-800'}"
|
||||
>
|
||||
{formatDocumentStatus(doc.status)}
|
||||
</span>
|
||||
<img
|
||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Right-MD.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="ml-2 h-5 w-5 opacity-40 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
<div class="mt-0.5 flex items-center gap-1.5 font-sans text-[11px] text-ink-3">
|
||||
<span>{doc.documentDate ? formatDate(doc.documentDate) : m.doc_no_date()}</span>
|
||||
{#if doc.location}
|
||||
<span>·</span>
|
||||
<span>{doc.location}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status chip -->
|
||||
<span
|
||||
class="hidden flex-shrink-0 rounded-full border px-2 py-0.5 font-sans text-[10px] font-bold sm:inline-block
|
||||
{doc.status === 'UPLOADED'
|
||||
? 'border-accent/50 bg-accent/20 text-ink'
|
||||
: doc.status === 'ARCHIVED'
|
||||
? 'border-green-200 bg-green-50 text-green-800'
|
||||
: 'border-yellow-200 bg-yellow-50 text-yellow-800'}"
|
||||
>
|
||||
{formatDocumentStatus(doc.status)}
|
||||
</span>
|
||||
|
||||
<img
|
||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Right-MD.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="h-4 w-4 flex-shrink-0 opacity-30 transition-opacity group-hover:opacity-80"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if documents.length > DOCS_PREVIEW_LIMIT && !showAll}
|
||||
<button
|
||||
onclick={() => (showAll = true)}
|
||||
class="mt-3 text-xs font-bold tracking-widest text-ink/50 uppercase transition-colors hover:text-ink"
|
||||
class="w-full border-t border-line py-2.5 text-center font-sans text-[11px] font-bold tracking-widest text-ink/50 uppercase transition-colors hover:text-ink"
|
||||
>
|
||||
{m.person_show_more({ count: documents.length - DOCS_PREVIEW_LIMIT })}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user