fix(crm): ContactDetailPage Layout-Anpassungen

- Name (Unternehmen) in Klammern in der Überschrift
- Grüner Punkt → "● Aktiv"/"● Inaktiv" Badge
- Position als Subtitle unterhalb des Namens
- Lexware-Card rechts neben Kontaktdaten (260px)
- Aktivitäten als volle Breite unterhalb der Kontaktdaten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Reitz 2026-03-12 20:39:18 +01:00
parent ec9f3ea364
commit bff4419c27
2 changed files with 148 additions and 104 deletions

View file

@ -38,7 +38,8 @@
.nameRow { .nameRow {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.625rem; gap: 0.75rem;
flex-wrap: wrap;
} }
.name { .name {
@ -48,22 +49,78 @@
color: var(--color-text); color: var(--color-text);
} }
.nameCompany {
font-size: 1.125rem;
font-weight: 400;
color: var(--color-text-muted);
}
.subtitle { .subtitle {
font-size: 0.9375rem; font-size: 0.9375rem;
color: var(--color-text-muted); color: var(--color-text-muted);
margin: 0.2rem 0 0; margin: 0.25rem 0 0;
} }
/* ---- Main layout ---- */ /* ---- Status badges ---- */
.layout { .badgeActive {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.1875rem 0.5rem;
background: #dcfce7;
color: #166534;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.badgeActive::before {
content: '';
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #16a34a;
flex-shrink: 0;
}
.badgeInactive {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.1875rem 0.5rem;
background: #fee2e2;
color: #991b1b;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.badgeInactive::before {
content: '';
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #dc2626;
flex-shrink: 0;
}
/* ---- Top row: Kontaktdaten + Lexware ---- */
.topRow {
display: grid; display: grid;
grid-template-columns: 1fr minmax(380px, 40%); grid-template-columns: 1fr 260px;
gap: 1.5rem; gap: 1.5rem;
align-items: start; align-items: start;
margin-bottom: 1.5rem;
} }
@media (max-width: 960px) { @media (max-width: 960px) {
.layout { .topRow {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }

View file

@ -126,11 +126,6 @@ export function ContactDetailPage() {
const activities: Activity[] = contact.activities ?? []; const activities: Activity[] = contact.activities ?? [];
const deals = dealsData?.data ?? []; const deals = dealsData?.data ?? [];
// Subtitle: "Position @ Unternehmen"
const subtitle = [contact.position, contact.company?.name]
.filter(Boolean)
.join(' @ ');
return ( return (
<div> <div>
{/* Back link */} {/* Back link */}
@ -151,27 +146,26 @@ export function ContactDetailPage() {
{/* ── Header ── */} {/* ── Header ── */}
<div className={styles.header}> <div className={styles.header}>
<div className={styles.headerLeft}> <div className={styles.headerLeft}>
<div>
<div className={styles.nameRow}> <div className={styles.nameRow}>
<h1 className={styles.name}>{contactDisplayName(contact)}</h1> <h1 className={styles.name}>
<span {contactDisplayName(contact)}
style={{ {contact.company && (
display: 'inline-block', <span className={styles.nameCompany}>
width: 8, {' '}({contact.company.name})
height: 8, </span>
borderRadius: '50%',
background: contact.isActive
? 'var(--color-success)'
: 'var(--color-error)',
flexShrink: 0,
}}
title={contact.isActive ? 'Aktiv' : 'Inaktiv'}
/>
</div>
{subtitle && (
<p className={styles.subtitle}>{subtitle}</p>
)} )}
</h1>
<span
className={
contact.isActive ? styles.badgeActive : styles.badgeInactive
}
>
{contact.isActive ? 'Aktiv' : 'Inaktiv'}
</span>
</div> </div>
{contact.position && (
<p className={styles.subtitle}>{contact.position}</p>
)}
</div> </div>
<div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}> <div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}>
<button <button
@ -205,9 +199,8 @@ export function ContactDetailPage() {
</div> </div>
</div> </div>
{/* ── Two-column layout ── */} {/* ── Top row: Kontaktdaten (wide) + Lexware (small) ── */}
<div className={styles.layout}> <div className={styles.topRow}>
{/* ── Left column ── */}
<div> <div>
{/* Contact data card */} {/* Contact data card */}
<div className={styles.card}> <div className={styles.card}>
@ -525,8 +518,10 @@ export function ContactDetailPage() {
</div> </div>
)} )}
{/* Lexware Office */} </div>
<div style={{ marginTop: '1.5rem' }}>
{/* ── Right: Lexware (compact) ── */}
<div>
<LexwareSection <LexwareSection
entityType="contact" entityType="contact"
entityId={contact.id} entityId={contact.id}
@ -536,8 +531,8 @@ export function ContactDetailPage() {
</div> </div>
</div> </div>
{/* ── Right column: Activities ── */} {/* ── Aktivitäten (full width below) ── */}
<div className={styles.card}> <div className={styles.card} style={{ marginTop: '1.5rem' }}>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -567,12 +562,7 @@ export function ContactDetailPage() {
</div> </div>
{activities.length === 0 ? ( {activities.length === 0 ? (
<p <p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem' }}>
style={{
color: 'var(--color-text-muted)',
fontSize: '0.875rem',
}}
>
Keine Aktivitäten vorhanden Keine Aktivitäten vorhanden
</p> </p>
) : ( ) : (
@ -583,9 +573,7 @@ export function ContactDetailPage() {
{activityIcon(act.type)} {activityIcon(act.type)}
</div> </div>
<div className={styles.timelineContent}> <div className={styles.timelineContent}>
<div className={styles.timelineSubject}> <div className={styles.timelineSubject}>{act.subject}</div>
{act.subject}
</div>
<div className={styles.timelineMeta}> <div className={styles.timelineMeta}>
{ACTIVITY_TYPE_LABELS[act.type]} &middot;{' '} {ACTIVITY_TYPE_LABELS[act.type]} &middot;{' '}
{formatDate(act.createdAt)} {formatDate(act.createdAt)}
@ -601,7 +589,6 @@ export function ContactDetailPage() {
</div> </div>
)} )}
</div> </div>
</div>
{/* ── Modals ── */} {/* ── Modals ── */}
<ContactFormModal <ContactFormModal