From 923e6bc1271736f9b9190e41f8214c99c580e8ca Mon Sep 17 00:00:00 2001 From: Thomas Reitz Date: Thu, 12 Mar 2026 12:08:53 +0100 Subject: [PATCH] feat(frontend): redesign CompanyDetailPage with tabbed layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 3-column grid with 4-tab layout: - Tab 1 (Unternehmen): 2-column grid with master data + Lexware status (left), contacts + relationships (right) - Tab 2 (Aktivitäten): ActivityFeed component at full width - Tab 3 (Vorgänge): Combined CRM deals + Lexware vouchers table with source badges, filters, and color-coded Lexware rows - Tab 4 (Verträge): ContractsCard placeholder at full width Co-Authored-By: Claude Opus 4.6 --- .../companies/CompanyDetailPage.module.css | 400 ++++++-- .../src/crm/companies/CompanyDetailPage.tsx | 860 +++++++++++++----- 2 files changed, 957 insertions(+), 303 deletions(-) diff --git a/packages/frontend/src/crm/companies/CompanyDetailPage.module.css b/packages/frontend/src/crm/companies/CompanyDetailPage.module.css index 9cd0172..d03cd5d 100644 --- a/packages/frontend/src/crm/companies/CompanyDetailPage.module.css +++ b/packages/frontend/src/crm/companies/CompanyDetailPage.module.css @@ -1,5 +1,5 @@ /* ============================================================ - CompanyDetailPage – 3-Spalten Layout & Komponenten + CompanyDetailPage – Tabbed Layout ============================================================ */ .backLink { @@ -49,29 +49,63 @@ } /* ============================================================ - 3-Spalten Grid + Tab Bar ============================================================ */ -.layout { +.tabBar { + display: flex; + gap: 0; + border-bottom: 1px solid var(--color-border); + margin-bottom: 1.5rem; +} + +.tab { + padding: 0.625rem 1.25rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--color-text-muted); + background: none; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: color 0.15s, border-color 0.15s; + white-space: nowrap; +} + +.tab:hover { + color: var(--color-text); +} + +.tabActive { + color: var(--color-primary); + border-bottom-color: var(--color-primary); +} + +.tabContent { + min-height: 200px; +} + +/* ============================================================ + Tab 1: Unternehmensdaten – 2-Spalten Grid + ============================================================ */ + +.overviewGrid { display: grid; - grid-template-columns: 300px 1fr 360px; + grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start; } -@media (max-width: 1200px) { - .layout { - grid-template-columns: 1fr 360px; - } - .layout > :first-child { - grid-column: 1 / -1; +@media (max-width: 900px) { + .overviewGrid { + grid-template-columns: 1fr; } } -@media (max-width: 768px) { - .layout { - grid-template-columns: 1fr; - } +.rightStack { + display: flex; + flex-direction: column; + gap: 1.5rem; } /* ============================================================ @@ -105,12 +139,12 @@ } /* ============================================================ - Info Grid (linke Spalte) + Info Grid (Stammdaten) ============================================================ */ .infoGrid { display: grid; - grid-template-columns: 100px 1fr; + grid-template-columns: 120px 1fr; gap: 0.5rem 0.75rem; font-size: 0.8125rem; } @@ -158,7 +192,300 @@ } /* ============================================================ - Activity Feed (mittlere Spalte) + Lexware Verknuepfungs-Info (in Stammdaten-Card) + ============================================================ */ + +.lexwareInfo { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--color-border); +} + +.lexwareStatusRow { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.75rem; +} + +.lexwareBadge { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 500; +} + +.lexwareBadgeLinked { + background: #d1fae5; + color: #065f46; +} + +.lexwareBadgeNotLinked { + background: var(--color-bg); + color: var(--color-text-muted); + border: 1px solid var(--color-border); +} + +:global([data-theme='dark']) .lexwareBadgeLinked { + background: #064e3b; + color: #6ee7b7; +} + +.lexwareActions { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +.lexwareActionBtn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.lexwareActionBtn:hover { + background: var(--color-bg); + color: var(--color-text); +} + +.lexwareActionBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.lexwareActionBtnPrimary { + composes: lexwareActionBtn; + border-color: var(--color-primary); + color: var(--color-primary); +} + +.lexwareActionBtnPrimary:hover { + background: var(--color-primary); + color: white; +} + +.lexwareActionBtnDanger { + composes: lexwareActionBtn; + border-color: #fecaca; + color: var(--color-error); +} + +.lexwareActionBtnDanger:hover { + background: #fef2f2; + color: #dc2626; +} + +:global([data-theme='dark']) .lexwareActionBtnDanger:hover { + background: #450a0a; + color: #fca5a5; +} + +/* ============================================================ + Compact Table (Kontakte in Stammdaten-Tab) + ============================================================ */ + +.compactTable { + width: 100%; + border-collapse: collapse; + font-size: 0.8125rem; +} + +.compactTable th { + text-align: left; + padding: 0.375rem 0; + font-size: 0.6875rem; + text-transform: uppercase; + color: var(--color-text-muted); + border-bottom: 1px solid var(--color-border); + font-weight: 500; +} + +.compactTable td { + padding: 0.375rem 0; + border-bottom: 1px solid var(--color-border); +} + +.compactTable tr:last-child td { + border-bottom: none; +} + +.compactTable tr[data-clickable='true'] { + cursor: pointer; +} + +.compactTable tr[data-clickable='true']:hover td { + background: var(--color-bg); +} + +/* ============================================================ + Tab 3: Vorgaenge — Kombinierte Deals + Vouchers Tabelle + ============================================================ */ + +.vorgaengeHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + gap: 0.75rem; + flex-wrap: wrap; +} + +.filterRow { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + align-items: center; +} + +.filterSelect { + padding: 0.375rem 0.625rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + font-size: 0.8125rem; + background: var(--color-bg-card); + color: var(--color-text); + outline: none; + cursor: pointer; +} + +.filterSelect:focus { + border-color: var(--color-primary); +} + +.refreshBtn { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + font-size: 0.8125rem; + font-weight: 500; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.refreshBtn:hover { + background: var(--color-bg); + color: var(--color-text); +} + +.refreshBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.vorgaengeTable { + width: 100%; + border-collapse: collapse; + font-size: 0.8125rem; +} + +.vorgaengeTable th { + text-align: left; + padding: 0.625rem 0.75rem; + font-size: 0.6875rem; + text-transform: uppercase; + color: var(--color-text-muted); + border-bottom: 1px solid var(--color-border); + font-weight: 500; + background: var(--color-bg); +} + +.vorgaengeTable td { + padding: 0.625rem 0.75rem; + border-bottom: 1px solid var(--color-border); + color: var(--color-text-secondary); +} + +.vorgaengeTable tbody tr { + cursor: pointer; + transition: background 0.12s; +} + +.vorgaengeTable tbody tr:hover { + background: var(--color-bg); +} + +.vorgaengeTableCard { + background: var(--color-bg-card); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); + border: 1px solid var(--color-border); + overflow: hidden; +} + +/* Lexware-Zeilen farblich hervorheben */ +.lexwareRow { + background: rgba(99, 102, 241, 0.03); + border-left: 3px solid rgba(99, 102, 241, 0.4); +} + +.lexwareRow:hover { + background: rgba(99, 102, 241, 0.07) !important; +} + +:global([data-theme='dark']) .lexwareRow { + background: rgba(99, 102, 241, 0.06); + border-left-color: rgba(129, 140, 248, 0.4); +} + +:global([data-theme='dark']) .lexwareRow:hover { + background: rgba(99, 102, 241, 0.12) !important; +} + +/* Quell-Badges */ +.sourceBadge { + display: inline-block; + padding: 0.0625rem 0.375rem; + border-radius: 9999px; + font-size: 0.625rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.sourceBadgeCrm { + background: var(--color-bg); + color: var(--color-text-muted); + border: 1px solid var(--color-border); +} + +.sourceBadgeLexware { + background: #e0e7ff; + color: #4338ca; +} + +:global([data-theme='dark']) .sourceBadgeLexware { + background: #312e81; + color: #a5b4fc; +} + +/* Leerer Zustand in Tabelle */ +.emptyRow { + text-align: center; + padding: 2rem 1rem; + color: var(--color-text-muted); + font-size: 0.875rem; + font-style: italic; +} + +/* ============================================================ + Activity Feed — von ActivityFeed.tsx genutzt ============================================================ */ .feedContainer { @@ -344,7 +671,7 @@ } /* ============================================================ - Relationships Card (rechte Spalte) + Relationships Card ============================================================ */ .relationItem { @@ -449,40 +776,3 @@ background: var(--color-primary); color: white; } - -/* ============================================================ - Compact Table (rechte Spalte) - ============================================================ */ - -.compactTable { - width: 100%; - border-collapse: collapse; - font-size: 0.8125rem; -} - -.compactTable th { - text-align: left; - padding: 0.375rem 0; - font-size: 0.6875rem; - text-transform: uppercase; - color: var(--color-text-muted); - border-bottom: 1px solid var(--color-border); - font-weight: 500; -} - -.compactTable td { - padding: 0.375rem 0; - border-bottom: 1px solid var(--color-border); -} - -.compactTable tr:last-child td { - border-bottom: none; -} - -.compactTable tr[data-clickable='true'] { - cursor: pointer; -} - -.compactTable tr[data-clickable='true']:hover td { - background: var(--color-bg); -} diff --git a/packages/frontend/src/crm/companies/CompanyDetailPage.tsx b/packages/frontend/src/crm/companies/CompanyDetailPage.tsx index 17274c0..692c03d 100644 --- a/packages/frontend/src/crm/companies/CompanyDetailPage.tsx +++ b/packages/frontend/src/crm/companies/CompanyDetailPage.tsx @@ -1,19 +1,59 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { useParams, Link, useNavigate } from 'react-router-dom'; -import { useCompany, useDeleteCompany } from '../hooks'; +import { + useCompany, + useDeleteCompany, + useCompanyVouchers, + useRefreshCompanyVouchers, + useLinkLexwareCompany, + useUnlinkLexwareCompany, + useSyncFromLexware, + usePushToLexware, +} from '../hooks'; +import { useCrmSettings } from '../settings/CrmSettingsContext'; import { CompanyFormModal } from './CompanyFormModal'; import { ActivityFeed } from './ActivityFeed'; import { CompanyRelationshipsCard } from './CompanyRelationshipsCard'; import { ContractsCard } from './ContractsCard'; +import { LexwareSearchModal } from '../lexware/LexwareSearchModal'; import { Modal } from '../../components/Modal'; -import { LexwareSection } from '../lexware/LexwareSection'; -import type { DealStatus } from '../types'; +import type { DealStatus, LexwareVoucher, Deal } from '../types'; +import { VOUCHER_TYPE_LABELS } from '../types'; import styles from './CompanyDetailPage.module.css'; // ============================================================ -// Constants +// Types // ============================================================ +type DetailTab = 'company' | 'activities' | 'deals' | 'contracts'; +type SourceFilter = 'ALL' | 'CRM' | 'LEXWARE'; + +interface UnifiedItem { + id: string; + source: 'CRM' | 'LEXWARE'; + title: string; + type: string; + date: string | null; + status: string; + statusColor?: { bg: string; color: string }; + amount: number | null; + currency: string; + link: string; + openExternal?: boolean; + stageColor?: string; +} + +// ============================================================ +// Constants & Helpers +// ============================================================ + +const TAB_LABELS: Record = { + company: 'Unternehmen', + activities: 'Aktivitäten', + deals: 'Vorgänge', + contracts: 'Verträge', +}; + const STATUS_COLORS: Record = { OPEN: { bg: '#dbeafe', color: '#1e40af' }, WON: { bg: '#d1fae5', color: '#065f46' }, @@ -46,12 +86,38 @@ function formatDate(iso: string): string { export function CompanyDetailPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const { isModuleEnabled } = useCrmSettings(); + const lexwareEnabled = isModuleEnabled('lexware'); + + // ---- Core data ---- const { data, isLoading, error } = useCompany(id!); const deleteMutation = useDeleteCompany(); + // ---- UI State ---- + const [activeTab, setActiveTab] = useState('company'); const [isEditOpen, setEditOpen] = useState(false); const [isDeleteOpen, setDeleteOpen] = useState(false); + const [isLexwareSearchOpen, setLexwareSearchOpen] = useState(false); + const [sourceFilter, setSourceFilter] = useState('ALL'); + const [typeFilter, setTypeFilter] = useState(''); + // ---- Lexware Mutations (must be called before conditional returns) ---- + const linkCompany = useLinkLexwareCompany(); + const unlinkCompany = useUnlinkLexwareCompany(); + const syncFromLexware = useSyncFromLexware(); + const pushToLexware = usePushToLexware(); + const refreshVouchers = useRefreshCompanyVouchers(); + + // ---- Lexware Vouchers ---- + const companyData = data?.data; + const isLinked = !!companyData?.lexwareContactId; + + const vouchersQuery = useCompanyVouchers( + id!, + isLinked && lexwareEnabled ? { pageSize: 100 } : undefined, + ); + + // ---- Loading / Error ---- if (isLoading) return

Laden...

; if (error || !data) return ( @@ -63,11 +129,20 @@ export function CompanyDetailPage() { const company = data.data; const contacts = company.contacts ?? []; const deals = company.deals ?? []; + const vouchers: LexwareVoucher[] = vouchersQuery.data?.data ?? []; - // Industry badge color + // Industry badge const industryColor = company.industryRef?.color ?? '#6366f1'; const industryName = company.industryRef?.name ?? company.industry; + // Lexware mutation state + const isAnyLexwareMutating = + linkCompany.isPending || + unlinkCompany.isPending || + syncFromLexware.isPending || + pushToLexware.isPending || + refreshVouchers.isPending; + return (
{/* Zurück */} @@ -142,259 +217,276 @@ export function CompanyDetailPage() {
- {/* 3-Spalten Layout */} -
- {/* ======== Linke Spalte: Stammdaten ======== */} -
-
-

Unternehmensdaten

-
- {company.accountType && ( - <> - Kontotyp - {company.accountType.name} - - )} - {company.ownerName && ( - <> - Zuständig - {company.ownerName} - - )} - {company.email && ( - <> - E-Mail - - - {company.email} - - - - )} - {company.phone && ( - <> - Telefon - {company.phone} - - )} - {company.website && ( - <> - Website - - - {company.website} - - - - )} - {(company.street || company.zip || company.city) && ( - <> - Adresse - - {company.street && <>{company.street}
} - {company.zip} {company.city} - {company.country && company.country !== 'DE' && ( - <>, {company.country} - )} -
- - )} - Erstellt - - {formatDate(company.createdAt)} - -
+ {/* ======== Tab Bar ======== */} +
+ {(Object.keys(TAB_LABELS) as DetailTab[]).map((tab) => ( + + ))} +
- {/* Tags */} - {company.tags && company.tags.length > 0 && ( -
- - Tags - -
- {company.tags.map((tag) => ( - - {tag} - - ))} -
-
- )} - - {/* Notizen */} - {company.notes && ( -
- - Notizen - -

{company.notes}

-
- )} -
-
- - {/* ======== Mittlere Spalte: Activity Feed ======== */} -
- -
- - {/* ======== Rechte Spalte: Relations ======== */} -
- {/* Kontakte */} -
-
-

- Kontakte ({company._count?.contacts ?? contacts.length}) -

-
- {contacts.length === 0 ? ( -

- Keine Kontakte vorhanden -

- ) : ( - - - - - - - - - {contacts.map((c) => { - const name = [c.firstName, c.lastName] - .filter(Boolean) - .join(' ') || '—'; - return ( - navigate(`/crm/contacts/${c.id}`)} - > - - - - ); - })} - -
NamePosition
{name} - {c.position ?? '—'} -
- )} -
- - {/* Vorgänge */} -
-
-

- Vorgänge ({company._count?.deals ?? deals.length}) -

-
- {deals.length === 0 ? ( -

- Keine Vorgänge vorhanden -

- ) : ( - - - - - - - - - - {deals.map((deal) => ( - navigate(`/crm/deals/${deal.id}`)} - > - - - - - ))} - -
TitelStufeWert
-
- {deal.title} - - {STATUS_LABELS[deal.status]} - -
-
- {deal.stage && ( - - - {deal.stage.name} - + {/* ======== Tab Content ======== */} +
+ {/* ---- Tab 1: Unternehmensdaten ---- */} + {activeTab === 'company' && ( +
+ {/* Left: Stammdaten */} +
+
+

Unternehmensdaten

+
+ {company.accountType && ( + <> + Kontotyp + {company.accountType.name} + + )} + {company.ownerName && ( + <> + Zuständig + {company.ownerName} + + )} + {company.email && ( + <> + E-Mail + + + {company.email} + + + + )} + {company.phone && ( + <> + Telefon + {company.phone} + + )} + {company.website && ( + <> + Website + + + {company.website} + + + + )} + {(company.street || company.zip || company.city) && ( + <> + Adresse + + {company.street && <>{company.street}
} + {company.zip} {company.city} + {company.country && company.country !== 'DE' && ( + <>, {company.country} )} -
- {deal.value - ? currencyFormatter.format(parseFloat(deal.value)) - : '—'} -
- )} + + + )} + Erstellt + + {formatDate(company.createdAt)} + +
+ + {/* Tags */} + {company.tags && company.tags.length > 0 && ( +
+ + Tags + +
+ {company.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} + + {/* Notizen */} + {company.notes && ( +
+ + Notizen + +

{company.notes}

+
+ )} + + {/* Lexware Verknüpfung */} + {lexwareEnabled && ( +
+
+ + + LX {isLinked ? 'Verknüpft' : 'Nicht verknüpft'} + + {isLinked && company.lexwareSyncedAt && ( + + Sync: {formatDate(company.lexwareSyncedAt)} + + )} +
+
+ {!isLinked ? ( + + ) : ( + <> + + + + + )} +
+
+ )} +
+
+ + {/* Right: Kontakte + Beziehungen */} +
+ {/* Kontakte */} +
+
+

+ Kontakte ({company._count?.contacts ?? contacts.length}) +

+
+ {contacts.length === 0 ? ( +

+ Keine Kontakte vorhanden +

+ ) : ( + + + + + + + + + {contacts.map((c) => { + const cName = [c.firstName, c.lastName] + .filter(Boolean) + .join(' ') || '—'; + return ( + navigate(`/crm/contacts/${c.id}`)} + > + + + + ); + })} + +
NamePosition
{cName} + {c.position ?? '—'} +
+ )} +
+ + {/* Beziehungen */} + +
+ )} - {/* Beziehungen */} - + {/* ---- Tab 2: Aktivitäten ---- */} + {activeTab === 'activities' && ( +
+ +
+ )} - {/* Verträge (Platzhalter) */} + {/* ---- Tab 3: Vorgänge ---- */} + {activeTab === 'deals' && ( + + )} + + {/* ---- Tab 4: Verträge ---- */} + {activeTab === 'contracts' && ( - - {/* Lexware Office */} - - + )} - {/* Modals */} + {/* ======== Modals ======== */} setEditOpen(false)} @@ -402,6 +494,18 @@ export function CompanyDetailPage() { onSuccess={() => setEditOpen(false)} /> + setLexwareSearchOpen(false)} + onLink={(lexwareId) => { + linkCompany.mutate( + { lexwareContactId: lexwareId, companyId: company.id }, + { onSuccess: () => setLexwareSearchOpen(false) }, + ); + }} + isLinking={linkCompany.isPending} + /> + setDeleteOpen(false)} @@ -475,3 +579,263 @@ export function CompanyDetailPage() { ); } + +// ============================================================ +// VorgaengeTab — CRM Deals + Lexware Belege kombiniert +// ============================================================ + +function VorgaengeTab({ + deals, + vouchers, + isLoadingVouchers, + isLinked, + lexwareEnabled, + companyId, + refreshVouchers, + sourceFilter, + setSourceFilter, + typeFilter, + setTypeFilter, + navigate, +}: { + deals: Deal[]; + vouchers: LexwareVoucher[]; + isLoadingVouchers: boolean; + isLinked: boolean; + lexwareEnabled: boolean; + companyId: string; + refreshVouchers: ReturnType; + sourceFilter: SourceFilter; + setSourceFilter: (v: SourceFilter) => void; + typeFilter: string; + setTypeFilter: (v: string) => void; + navigate: ReturnType; +}) { + // Map CRM deals to unified format + const crmItems: UnifiedItem[] = useMemo( + () => + deals.map((deal) => ({ + id: `crm-${deal.id}`, + source: 'CRM' as const, + title: deal.title, + type: deal.stage?.name ?? deal.pipeline?.name ?? '—', + date: deal.createdAt, + status: STATUS_LABELS[deal.status] ?? deal.status, + statusColor: STATUS_COLORS[deal.status], + amount: deal.value ? parseFloat(deal.value) : null, + currency: deal.currency ?? 'EUR', + link: `/crm/deals/${deal.id}`, + stageColor: deal.stage?.color, + })), + [deals], + ); + + // Map Lexware vouchers to unified format + const lexwareItems: UnifiedItem[] = useMemo( + () => + vouchers.map((v) => ({ + id: `lx-${v.id}`, + source: 'LEXWARE' as const, + title: v.title || v.voucherNumber || '—', + type: VOUCHER_TYPE_LABELS[v.voucherType] ?? v.voucherType, + date: v.voucherDate, + status: v.voucherStatus ?? '—', + amount: v.totalGrossAmount ? parseFloat(v.totalGrossAmount) : null, + currency: v.currency ?? 'EUR', + link: v.lexwareDeepLink ?? '#', + openExternal: true, + })), + [vouchers], + ); + + // Merge + sort by date descending + const allItems = useMemo(() => { + let items = [...crmItems, ...lexwareItems]; + + // Source filter + if (sourceFilter === 'CRM') items = items.filter((i) => i.source === 'CRM'); + if (sourceFilter === 'LEXWARE') items = items.filter((i) => i.source === 'LEXWARE'); + + // Type filter + if (typeFilter) items = items.filter((i) => i.type === typeFilter); + + // Sort by date (newest first) + items.sort((a, b) => { + if (!a.date && !b.date) return 0; + if (!a.date) return 1; + if (!b.date) return -1; + return new Date(b.date).getTime() - new Date(a.date).getTime(); + }); + + return items; + }, [crmItems, lexwareItems, sourceFilter, typeFilter]); + + // Collect all unique types for filter dropdown + const allTypes = useMemo(() => { + const types = new Set(); + [...crmItems, ...lexwareItems].forEach((i) => { + if (i.type && i.type !== '—') types.add(i.type); + }); + return Array.from(types).sort(); + }, [crmItems, lexwareItems]); + + return ( +
+ {/* Filter + Actions */} +
+
+ + +
+ {isLinked && lexwareEnabled && ( + + )} +
+ + {/* Loading */} + {isLoadingVouchers && isLinked && ( +

+ Lexware Belege werden geladen... +

+ )} + + {/* Tabelle */} +
+ + + + + + + + + + + + + {allItems.length === 0 && ( + + + + )} + {allItems.map((item) => ( + { + if (item.openExternal) { + window.open(item.link, '_blank', 'noopener,noreferrer'); + } else { + navigate(item.link); + } + }} + > + {/* Quelle */} + + {/* Bezeichnung */} + + {/* Typ */} + + {/* Datum */} + + {/* Status */} + + {/* Betrag */} + + + ))} + +
QuelleBezeichnungTypDatumStatusBetrag
+ Keine Vorgänge oder Belege vorhanden +
+ + {item.source === 'LEXWARE' ? 'LX' : 'CRM'} + + + {item.title} + + + {item.stageColor && ( + + )} + {item.type} + + {item.date ? formatDate(item.date) : '—'} + {item.statusColor ? ( + + {item.status} + + ) : ( + {item.status} + )} + + {item.amount != null + ? currencyFormatter.format(item.amount) + : '—'} +
+
+
+ ); +}