import { useState, useEffect, useCallback } from 'react' import { useParams, Link, useNavigate, useSearchParams } from 'react-router-dom' import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaBoxArchive, FaBoxOpen, FaTrash } from 'react-icons/fa6' import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite' import { PageHeader, Card, Button, Badge, Tabs, EmptyState, Field } from '../components/ui' import { StatusPill, UrgentPill } from '../components/ui/StatusPill' import CustomerInvoices from '../components/finance/CustomerInvoices' import AssignedProjectCard from '../components/AssignedProjectCard' import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential' import { updateCustomerWithPortalAccess, deleteCustomerWithPortalAccess } from '../lib/customerAdminApi' import { useAuth } from '../context/AuthContext' import { useWebsiteProjects } from '../hooks/useWebsiteProjects' import { customerTabs, resolveCustomerTab } from '../lib/customerTabs' import { customerStage, isArchived, isInvoiceReady, setCustomerArchived, upgradeToCustomer } from '../lib/leads' import CustomerViewLog from '../components/customer/CustomerViewLog' import ChatThread from '../components/chat/ChatThread' import AboStatusCard from '../components/customer/AboStatusCard' import CustomerFunktionen from '../components/customer/CustomerFunktionen' import CreditsView from '../components/credits/CreditsView' import OffersView from '../components/offers/OffersView' import KundenansichtButton from '../components/customer/KundenansichtButton' import { getCustomerPortalStatus } from '../lib/teamApi' import { formatLastLogin } from '../lib/lastLogin' const PORTAL_URL = 'https://project.webklar.com' export default function CustomerDetailPage() { const { id } = useParams() const [customer, setCustomer] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Reiter steht in der Adresse (/customers/?tab=protokoll), damit Sammellisten und Team-Mails direkt hineinspringen const [searchParams, setSearchParams] = useSearchParams() const tab = searchParams.get('tab') || 'overview' const setTab = useCallback( (next) => setSearchParams(next === 'overview' ? {} : { tab: next }, { replace: true }), [setSearchParams] ) const { isAdmin } = useAuth() const navigate = useNavigate() const load = useCallback(async () => { setLoading(true) try { setCustomer(await databases.getDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, id)) setError(null) } catch (err) { setError(err.message) } finally { setLoading(false) } }, [id]) useEffect(() => { load() }, [load]) if (loading) return
if (error || !customer) return
Zurück} />
const stage = customerStage(customer) // „Rechnungen“ nur für Admins (die Finanzen-API gibt sonst 403); ein nicht erlaubter Reiter fällt auf die Übersicht zurück const activeTab = resolveCustomerTab(tab, isAdmin) const archived = isArchived(customer) const toggleArchived = async () => { await setCustomerArchived(customer, !archived) await load() } // Loescht Kunde und Portal-Login ueber die Admin-API des Kundenportals (nur Admins) const removeCustomer = async () => { if (!window.confirm(`${customer.name || customer.email} und den Portal-Zugang wirklich löschen?`)) return try { await deleteCustomerWithPortalAccess(customer.$id) navigate('/customers') } catch (err) { window.alert(`Löschen fehlgeschlagen: ${err.message}`) } } return (
{customer.importantCustomer && } {customer.name} {stage === 'lead' && Lead} {stage === 'customer' && Fester Kunde} {stage === 'lost' && Abgesagt} {archived && Archiviert} } subtitle={[customer.location, customer.email, customer.phone].filter(Boolean).join(' · ')} actions={ <> {isAdmin && } } /> {activeTab === 'overview' && } {activeTab === 'tickets' && } {activeTab === 'invoices' && setTab('overview')} />} {activeTab === 'projects' && } {activeTab === 'chat' && } {activeTab === 'abo' && ( <>
)} {activeTab === 'gutschriften' && } {activeTab === 'funktionen' && } {activeTab === 'protokoll' && }
) } function OverviewTab({ customer, stage, onSaved }) { const [form, setForm] = useState({ name: customer.name || '', companyName: customer.companyName || customer.name || '', code: customer.code || '', email: customer.email || '', phone: customer.phone || '', contactName: customer.contactName || '', street: customer.street || '', postalCode: customer.postalCode || '', city: customer.city || '', country: customer.country || 'Deutschland', vatNumber: customer.vatNumber || '', location: customer.location || '', notes: customer.notes || '', importantCustomer: !!customer.importantCustomer, }) const [busy, setBusy] = useState(false) const [msg, setMsg] = useState(null) const ready = isInvoiceReady({ ...customer, ...form }) const save = async () => { setBusy(true); setMsg(null) try { await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, form) setMsg('Gespeichert') onSaved() } catch (err) { setMsg('Fehler: ' + err.message) } finally { setBusy(false) } } const upgrade = async () => { if (!ready) { setMsg('Bitte zuerst alle Pflichtfelder (Unternehmen, E-Mail, Strasse, PLZ, Stadt) ausfuellen.'); return } setBusy(true); setMsg(null) try { await upgradeToCustomer(customer, form) setMsg('Zum festen Kunden gemacht.') onSaved() } catch (err) { setMsg('Fehler: ' + err.message) } finally { setBusy(false) } } return (
setForm({ ...form, name: e.target.value, companyName: e.target.value })} /> setForm({ ...form, code: e.target.value })} />
setForm({ ...form, email: e.target.value })} /> setForm({ ...form, phone: e.target.value })} />
setForm({ ...form, location: e.target.value })} /> setForm({ ...form, contactName: e.target.value })} />