import { asError } from '@/utils/errorTypes'; import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { SettingsFirmendatenPage } from './SettingsFirmendaten'; import { SettingsCurrenciesPage } from './SettingsCurrencies'; import { SettingsTaxesPage } from './SettingsTaxes'; import { SettingsSequencesPage } from './SettingsSequences'; import { Card } from '@/components/ui/Card'; import { Input } from '@/components/ui/Input'; import { Button } from '@/components/ui/Button'; import { Select } from '@/components/ui/Select'; import { Table, TableColumn } from '@/components/ui/Table'; import { Plus, Trash2, Pencil } from 'lucide-react'; import { apiGet, apiPost, apiPatch, apiDelete } from '@/api/client'; import { useAuthStore } from '@/store/authStore'; import { useToast } from '@/components/ui/Toast'; // --- Address type mapping (frontend German -> backend English) --- const addressTypeToBackend: Record = { standort: 'headquarters', rechnung: 'billing', lieferung: 'shipping', eigene: 'other', }; const addressTypeToFrontend: Record = { headquarters: 'standort', billing: 'rechnung', shipping: 'lieferung', branch: 'standort', private: 'eigene', other: 'eigene', }; const addressTypeOptions = [ { value: 'standort', label: 'Standort' }, { value: 'rechnung', label: 'Rechnungsadresse' }, { value: 'lieferung', label: 'Lieferadresse' }, { value: 'eigene', label: 'Eigene Bezeichnung' }, ]; // --- Adressen Tab --- interface Address { id: string; type: string; street: string; zip: string; city: string; country: string; label: string; } interface ApiAddress { id: string; address_type: string; street: string; zip: string; city: string; country: string; label: string; is_default: boolean; } function AdressenTab() { const { t } = useTranslation(); const toast = useToast(); const currentTenant = useAuthStore((state) => state.currentTenant); const [addresses, setAddresses] = useState([]); const [editing, setEditing] = useState
(null); const [showForm, setShowForm] = useState(false); const [loading, setLoading] = useState(true); const fetchAddresses = useCallback(async () => { if (!currentTenant?.id) return; try { setLoading(true); const data = await apiGet<{ items: ApiAddress[]; total: number }>( `/addresses?entity_type=contact&entity_id=${currentTenant.id}` ); setAddresses( data.items.map((a) => ({ id: a.id, type: addressTypeToFrontend[a.address_type] || 'eigene', street: a.street || '', zip: a.zip || '', city: a.city || '', country: a.country || 'DE', label: a.label || '', })) ); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Laden der Adressen'); } finally { setLoading(false); } }, [currentTenant?.id, toast]); useEffect(() => { fetchAddresses(); }, [fetchAddresses]); const handleAdd = () => { setEditing({ id: '', type: 'standort', street: '', zip: '', city: '', country: 'DE', label: '' }); setShowForm(true); }; const handleEdit = (addr: Address) => { setEditing({ ...addr }); setShowForm(true); }; const handleDelete = async (id: string) => { try { await apiDelete(`/addresses/${id}`); setAddresses(addresses.filter(a => a.id !== id)); toast.success('Adresse gelöscht'); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Löschen'); } }; const handleSave = async () => { if (!editing || !currentTenant?.id) return; try { const backendType = addressTypeToBackend[editing.type] || 'other'; const payload = { entity_type: 'contact', entity_id: currentTenant.id, label: editing.type === 'eigene' ? editing.label : addressTypeOptions.find(o => o.value === editing.type)?.label || editing.label, address_type: backendType, street: editing.street, zip: editing.zip, city: editing.city, country: editing.country, is_default: false, }; if (editing.id) { await apiPatch(`/addresses/${editing.id}`, { label: payload.label, address_type: payload.address_type, street: payload.street, zip: payload.zip, city: payload.city, country: payload.country, }); toast.success('Adresse aktualisiert'); } else { await apiPost('/addresses', payload); toast.success('Adresse angelegt'); } setShowForm(false); setEditing(null); await fetchAddresses(); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Speichern'); } }; const columns: TableColumn
[] = [ { key: 'type', header: 'Art', render: (row) => addressTypeOptions.find(o => o.value === row.type)?.label || row.type }, { key: 'street', header: t('address.street', 'Straße') }, { key: 'zip', header: t('address.zip', 'PLZ') }, { key: 'city', header: t('address.city', 'Stadt') }, { key: 'country', header: t('address.country', 'Land') }, { key: 'label', header: 'Bezeichnung', render: (row) => row.label || '—' }, { key: 'actions', header: '', render: (row) => (
), }, ]; return (
{t('common.add', 'Hinzufügen')} }> {loading ? (
Lade Adressen...
) : ( row.id} emptyMessage="Noch keine Adressen angelegt" /> )} {showForm && editing && (
setEditing({ ...editing, label: e.target.value })} /> )} setEditing({ ...editing, street: e.target.value })} /> setEditing({ ...editing, zip: e.target.value })} /> setEditing({ ...editing, city: e.target.value })} /> setEditing({ ...editing, country: e.target.value })} />
)} ); } // --- Konten Tab --- interface BankAccount { id: string; bankName: string; iban: string; bic: string; accountHolder: string; defaultTax: string; } interface ApiBankAccount { id: string; bank_name: string; iban: string; bic: string | null; account_holder: string | null; default_tax: string | null; is_default: boolean; } function KontenTab() { const { t } = useTranslation(); const toast = useToast(); const [accounts, setAccounts] = useState([]); const [editing, setEditing] = useState(null); const [showForm, setShowForm] = useState(false); const [loading, setLoading] = useState(true); const fetchAccounts = useCallback(async () => { try { setLoading(true); const data = await apiGet<{ items: ApiBankAccount[]; total: number }>('/bank-accounts'); setAccounts( data.items.map((a) => ({ id: a.id, bankName: a.bank_name, iban: a.iban, bic: a.bic || '', accountHolder: a.account_holder || '', defaultTax: a.default_tax || '', })) ); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Laden der Konten'); } finally { setLoading(false); } }, [toast]); useEffect(() => { fetchAccounts(); }, [fetchAccounts]); const handleAdd = () => { setEditing({ id: '', bankName: '', iban: '', bic: '', accountHolder: '', defaultTax: '' }); setShowForm(true); }; const handleEdit = (acc: BankAccount) => { setEditing({ ...acc }); setShowForm(true); }; const handleDelete = async (id: string) => { try { await apiDelete(`/bank-accounts/${id}`); setAccounts(accounts.filter(a => a.id !== id)); toast.success('Konto gelöscht'); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Löschen'); } }; const handleSave = async () => { if (!editing) return; try { const payload = { bank_name: editing.bankName, iban: editing.iban, bic: editing.bic || null, account_holder: editing.accountHolder || null, default_tax: editing.defaultTax || null, is_default: false, }; if (editing.id) { await apiPatch(`/bank-accounts/${editing.id}`, payload); toast.success('Konto aktualisiert'); } else { await apiPost('/bank-accounts', payload); toast.success('Konto angelegt'); } setShowForm(false); setEditing(null); await fetchAccounts(); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj.message || 'Fehler beim Speichern'); } }; const columns: TableColumn[] = [ { key: 'bankName', header: 'Bankname' }, { key: 'iban', header: 'IBAN' }, { key: 'bic', header: 'BIC' }, { key: 'accountHolder', header: 'Kontoinhaber' }, { key: 'defaultTax', header: 'Standard-Steuer', render: (row) => row.defaultTax || '—' }, { key: 'actions', header: '', render: (row) => (
), }, ]; return (
{t('common.add', 'Hinzufügen')} }> {loading ? (
Lade Konten...
) : (
row.id} emptyMessage="Noch keine Konten angelegt" /> )} {showForm && editing && (
setEditing({ ...editing, bankName: e.target.value })} /> setEditing({ ...editing, iban: e.target.value })} /> setEditing({ ...editing, bic: e.target.value })} /> setEditing({ ...editing, accountHolder: e.target.value })} /> setEditing({ ...editing, defaultTax: e.target.value })} />
)} ); } // --- Sonstige Daten Tab --- function SonstigeDatenTab() { return (
); } // --- Main Stammdaten Page --- export function SettingsStammdatenPage() { const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('firmendaten'); const tabs = [ { key: 'firmendaten', label: 'Firmendaten' }, { key: 'adressen', label: 'Adressen' }, { key: 'konten', label: 'Konten' }, { key: 'sonstige', label: 'Sonstige Daten' }, ]; return (

Stammdaten

{activeTab === 'firmendaten' && } {activeTab === 'adressen' && } {activeTab === 'konten' && } {activeTab === 'sonstige' && }
); }