INSIGHT-MVP/packages/frontend/src/crm/settings/CrmSettingsPage.tsx
Thomas Reitz 4f05026bc8 feat(frontend): add Lexware Office Import/Export admin page
New admin page under /crm/lexware-sync with two tabs:
- Import: Search Lexware contacts and import as CRM company or contact
- Export: List CRM companies/contacts and push to Lexware Office
Accessible via CRM Settings page (admin-only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:00:42 +01:00

300 lines
8.8 KiB
TypeScript

import { Link, Navigate } from 'react-router-dom';
import { useAuth } from '../../auth/AuthContext';
import {
useCrmSettings,
type CrmModuleKey,
} from './CrmSettingsContext';
import styles from './CrmSettingsPage.module.css';
// ============================================================
// Module definitions (UI-Beschreibung je Modul)
// ============================================================
interface ModuleDef {
key: CrmModuleKey;
name: string;
description: string;
icon: React.ReactNode;
}
const iconProps = {
width: 18,
height: 18,
viewBox: '0 0 16 16',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 1.5,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
};
const MODULES: ModuleDef[] = [
{
key: 'contacts',
name: 'Kontakte',
description: 'Kontaktverwaltung (Personen & Organisationen)',
icon: (
<svg {...iconProps}>
<circle cx="8" cy="5" r="3" />
<path d="M2 14c0-2.5 2.5-4.5 6-4.5s6 2 6 4.5" />
</svg>
),
},
{
key: 'companies',
name: 'Unternehmen',
description: 'Unternehmensverwaltung mit Verknüpfung zu Kontakten & Vorgängen',
icon: (
<svg {...iconProps}>
<rect x="2" y="6" width="12" height="9" rx="1" />
<path d="M5 6V3a1 1 0 011-1h4a1 1 0 011 1v3" />
<path d="M5 9h2v2H5zM9 9h2v2H9z" />
</svg>
),
},
{
key: 'deals',
name: 'Vorgänge',
description: 'Vorgänge & Sales-Pipeline-Tracking',
icon: (
<svg {...iconProps}>
<path d="M2 2h3l2 9h6l2-6H6" />
<circle cx="7" cy="14" r="1" />
<circle cx="13" cy="14" r="1" />
</svg>
),
},
{
key: 'pipelines',
name: 'Pipelines',
description: 'Pipeline-Konfiguration & Stufen-Management',
icon: (
<svg {...iconProps}>
<rect x="1" y="2" width="14" height="3" rx="0.5" />
<rect x="1" y="7" width="10" height="3" rx="0.5" />
<rect x="1" y="12" width="6" height="3" rx="0.5" />
</svg>
),
},
{
key: 'lexware',
name: 'Lexware Office',
description: 'Lexware-Kontaktverknüpfung & Belegansicht auf Detail-Seiten',
icon: (
<svg {...iconProps}>
<rect x="1" y="3" width="14" height="10" rx="1" />
<path d="M4 7h8M4 10h5" />
<circle cx="12" cy="10" r="1" />
</svg>
),
},
];
// ============================================================
// Page Component
// ============================================================
export function CrmSettingsPage() {
const { user } = useAuth();
const { settings, toggleModule } = useCrmSettings();
// Zugriffskontrolle: nur Admins
if (
user?.role !== 'PLATFORM_ADMIN' &&
user?.role !== 'TENANT_ADMIN'
) {
return <Navigate to="/" replace />;
}
const anyDisabled = Object.values(settings.modules).some(
(m) => !m.enabled,
);
return (
<div>
{/* Zurück */}
<Link to="/" className={styles.backLink}>
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<path d="M9 2L4 7l5 5" />
</svg>
Zurück zum Dashboard
</Link>
{/* Header */}
<h1 style={{ fontSize: '1.5rem', fontWeight: 600, marginBottom: '1.5rem' }}>
CRM Einstellungen
</h1>
{/* Module-Card */}
<div className={styles.card}>
<h2 className={styles.cardTitle}>Module</h2>
<p className={styles.cardDesc}>
Aktiviere oder deaktiviere einzelne CRM-Module. Deaktivierte Module
werden aus dem Menü ausgeblendet.
</p>
<div className={styles.moduleList}>
{MODULES.map((mod) => {
const enabled = settings.modules[mod.key]?.enabled ?? true;
return (
<div key={mod.key} className={styles.moduleItem}>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
}}
>
<span
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 36,
height: 36,
borderRadius: 'var(--radius-sm)',
background: enabled
? 'var(--color-primary-bg, #eff6ff)'
: 'var(--color-bg)',
color: enabled
? 'var(--color-primary)'
: 'var(--color-text-muted)',
transition: 'background 0.2s, color 0.2s',
}}
>
{mod.icon}
</span>
<div className={styles.moduleInfo}>
<span
className={styles.moduleName}
style={{
opacity: enabled ? 1 : 0.5,
transition: 'opacity 0.2s',
}}
>
{mod.name}
</span>
<span className={styles.moduleDesc}>{mod.description}</span>
</div>
</div>
<label className={styles.toggle}>
<input
type="checkbox"
checked={enabled}
onChange={(e) =>
toggleModule(mod.key, e.target.checked)
}
/>
<span className={styles.toggleTrack} />
</label>
</div>
);
})}
</div>
{anyDisabled && (
<div className={styles.warning}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
style={{ flexShrink: 0, marginTop: 1 }}
>
<path d="M8 1L1 14h14L8 1z" />
<path d="M8 6v3M8 11.5v.5" />
</svg>
<span>
Deaktivierte Module werden aus dem Menü ausgeblendet.
Bestehende Daten bleiben erhalten und sind nach Reaktivierung
wieder verfügbar.
</span>
</div>
)}
</div>
{/* Lexware Synchronisation — nur wenn Modul aktiv */}
{settings.modules.lexware?.enabled && (
<div className={styles.card}>
<h2 className={styles.cardTitle}>
<span
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 24,
height: 24,
borderRadius: 4,
background: 'linear-gradient(135deg, #2563eb, #7c3aed)',
color: 'white',
fontSize: '0.5625rem',
fontWeight: 700,
letterSpacing: '0.5px',
marginRight: '0.5rem',
verticalAlign: 'middle',
}}
>
LX
</span>
Lexware Office Synchronisation
</h2>
<p className={styles.cardDesc}>
Kontakte aus Lexware Office importieren oder CRM-Daten nach Lexware
exportieren.
</p>
<Link
to="/crm/lexware-sync"
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.5rem 1rem',
background: 'var(--color-primary)',
color: 'white',
borderRadius: 'var(--radius-sm)',
textDecoration: 'none',
fontSize: '0.875rem',
fontWeight: 500,
transition: 'opacity 0.15s',
}}
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M1 8a7 7 0 0112.9-3.8M15 8a7 7 0 01-12.9 3.8" />
<path d="M14 1v3.2h-3.2M2 15v-3.2h3.2" />
</svg>
Import / Export öffnen
</Link>
</div>
)}
{/* Platzhalter für zukünftige Einstellungen */}
<div className={styles.card} style={{ opacity: 0.6 }}>
<h2 className={styles.cardTitle}>Weitere Einstellungen</h2>
<p className={styles.cardDesc}>
Zusätzliche Konfigurationsmöglichkeiten werden in zukünftigen
Versionen verfügbar sein.
</p>
</div>
</div>
);
}