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 {
display: flex;
align-items: center;
gap: 0.625rem;
gap: 0.75rem;
flex-wrap: wrap;
}
.name {
@ -48,22 +49,78 @@
color: var(--color-text);
}
.nameCompany {
font-size: 1.125rem;
font-weight: 400;
color: var(--color-text-muted);
}
.subtitle {
font-size: 0.9375rem;
color: var(--color-text-muted);
margin: 0.2rem 0 0;
margin: 0.25rem 0 0;
}
/* ---- Main layout ---- */
.layout {
/* ---- Status badges ---- */
.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;
grid-template-columns: 1fr minmax(380px, 40%);
grid-template-columns: 1fr 260px;
gap: 1.5rem;
align-items: start;
margin-bottom: 1.5rem;
}
@media (max-width: 960px) {
.layout {
.topRow {
grid-template-columns: 1fr;
}
}

View file

@ -126,11 +126,6 @@ export function ContactDetailPage() {
const activities: Activity[] = contact.activities ?? [];
const deals = dealsData?.data ?? [];
// Subtitle: "Position @ Unternehmen"
const subtitle = [contact.position, contact.company?.name]
.filter(Boolean)
.join(' @ ');
return (
<div>
{/* Back link */}
@ -151,27 +146,26 @@ export function ContactDetailPage() {
{/* ── Header ── */}
<div className={styles.header}>
<div className={styles.headerLeft}>
<div>
<div className={styles.nameRow}>
<h1 className={styles.name}>{contactDisplayName(contact)}</h1>
<span
style={{
display: 'inline-block',
width: 8,
height: 8,
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>
)}
<div className={styles.nameRow}>
<h1 className={styles.name}>
{contactDisplayName(contact)}
{contact.company && (
<span className={styles.nameCompany}>
{' '}({contact.company.name})
</span>
)}
</h1>
<span
className={
contact.isActive ? styles.badgeActive : styles.badgeInactive
}
>
{contact.isActive ? 'Aktiv' : 'Inaktiv'}
</span>
</div>
{contact.position && (
<p className={styles.subtitle}>{contact.position}</p>
)}
</div>
<div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}>
<button
@ -205,9 +199,8 @@ export function ContactDetailPage() {
</div>
</div>
{/* ── Two-column layout ── */}
<div className={styles.layout}>
{/* ── Left column ── */}
{/* ── Top row: Kontaktdaten (wide) + Lexware (small) ── */}
<div className={styles.topRow}>
<div>
{/* Contact data card */}
<div className={styles.card}>
@ -525,82 +518,76 @@ export function ContactDetailPage() {
</div>
)}
{/* Lexware Office */}
<div style={{ marginTop: '1.5rem' }}>
<LexwareSection
entityType="contact"
entityId={contact.id}
lexwareContactId={contact.lexwareContactId ?? null}
lexwareSyncedAt={contact.lexwareSyncedAt ?? null}
/>
</div>
</div>
{/* ── Right column: Activities ── */}
<div className={styles.card}>
<div
{/* ── Right: Lexware (compact) ── */}
<div>
<LexwareSection
entityType="contact"
entityId={contact.id}
lexwareContactId={contact.lexwareContactId ?? null}
lexwareSyncedAt={contact.lexwareSyncedAt ?? null}
/>
</div>
</div>
{/* ── Aktivitäten (full width below) ── */}
<div className={styles.card} style={{ marginTop: '1.5rem' }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '1rem',
}}
>
<h2 className={styles.cardTitle} style={{ margin: 0 }}>
Aktivitäten
</h2>
<button
onClick={() => setActivityOpen(true)}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '1rem',
padding: '0.25rem 0.625rem',
fontSize: '0.8125rem',
background: 'var(--color-primary)',
color: 'white',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
fontWeight: 500,
}}
>
<h2 className={styles.cardTitle} style={{ margin: 0 }}>
Aktivitäten
</h2>
<button
onClick={() => setActivityOpen(true)}
style={{
padding: '0.25rem 0.625rem',
fontSize: '0.8125rem',
background: 'var(--color-primary)',
color: 'white',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
fontWeight: 500,
}}
>
+ Neu
</button>
</div>
{activities.length === 0 ? (
<p
style={{
color: 'var(--color-text-muted)',
fontSize: '0.875rem',
}}
>
Keine Aktivitäten vorhanden
</p>
) : (
<div className={styles.timeline}>
{activities.map((act) => (
<div key={act.id} className={styles.timelineItem}>
<div className={styles.timelineIcon}>
{activityIcon(act.type)}
</div>
<div className={styles.timelineContent}>
<div className={styles.timelineSubject}>
{act.subject}
</div>
<div className={styles.timelineMeta}>
{ACTIVITY_TYPE_LABELS[act.type]} &middot;{' '}
{formatDate(act.createdAt)}
</div>
{act.description && (
<div className={styles.timelineDesc}>
{act.description}
</div>
)}
</div>
</div>
))}
</div>
)}
+ Neu
</button>
</div>
{activities.length === 0 ? (
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem' }}>
Keine Aktivitäten vorhanden
</p>
) : (
<div className={styles.timeline}>
{activities.map((act) => (
<div key={act.id} className={styles.timelineItem}>
<div className={styles.timelineIcon}>
{activityIcon(act.type)}
</div>
<div className={styles.timelineContent}>
<div className={styles.timelineSubject}>{act.subject}</div>
<div className={styles.timelineMeta}>
{ACTIVITY_TYPE_LABELS[act.type]} &middot;{' '}
{formatDate(act.createdAt)}
</div>
{act.description && (
<div className={styles.timelineDesc}>
{act.description}
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
{/* ── Modals ── */}