import { useState, useEffect, useCallback } from 'react'
import { useParams, Link } from 'react-router-dom'
import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare } 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, PriorityPill } from '../components/ui/StatusPill'
import InvoicePanel from '../components/InvoicePanel'
import DocumentsPanel from '../components/DocumentsPanel'
import AssignedProjectCard from '../components/AssignedProjectCard'
import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential'
import { updateCustomerWithPortalAccess } from '../lib/customerAdminApi'
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
import { customerStage, isInvoiceReady, upgradeToCustomer, syncCustomerToNinja } from '../lib/leads'
const PORTAL_URL = 'https://project.webklar.com'
const TABS = [
{ id: 'overview', label: 'Uebersicht' },
{ id: 'tickets', label: 'Tickets' },
{ id: 'invoices', label: 'Rechnungen' },
{ id: 'documents', label: 'Dokumente' },
{ id: 'projects', label: 'Projekte' },
]
export default function CustomerDetailPage() {
const { id } = useParams()
const [customer, setCustomer] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [tab, setTab] = useState('overview')
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 Zurueck} />
const stage = customerStage(customer)
const syntheticTicket = { customerId: customer.$id, customerName: customer.name, woid: '' }
return (
{customer.importantCustomer && }
{customer.name}
{stage === 'lead' && Lead}
{stage === 'customer' && Fester Kunde}
{stage === 'lost' && Abgesagt}
}
subtitle={[customer.location, customer.email, customer.phone].filter(Boolean).join(' � ')}
actions={}
/>
{tab === 'overview' && }
{tab === 'tickets' && }
{tab === 'invoices' && (
stage === 'lead' ? (
setTab('overview')}>Daten erfassen} />
) : !isInvoiceReady(customer) ? (
setTab('overview')}>Daten vervollstaendigen} />
) : (
)
)}
{tab === 'documents' && }
{tab === 'projects' && }
)
}
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,
paperlessCorrespondent: customer.paperlessCorrespondent || '',
})
const [busy, setBusy] = useState(false)
const [msg, setMsg] = useState(null)
const ready = isInvoiceReady({ ...customer, ...form })
const save = async () => {
setBusy(true); setMsg(null)
try {
const updated = await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, form)
let note = 'Gespeichert'
// Wenn fester Kunde: Aenderungen nach InvoiceNinja syncen (sofern Daten reichen oder bereits verknuepft)
if (stage === 'customer' && (updated.invoiceNinjaClientId || isInvoiceReady(updated))) {
try { await syncCustomerToNinja(updated) } catch (e) { note = 'Gespeichert, InvoiceNinja-Sync fehlgeschlagen: ' + e.message }
}
setMsg(note)
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)
const r = await upgradeToCustomer(customer, form)
if (r.clientId) setMsg('Zum festen Kunden gemacht und mit InvoiceNinja verknuepft.')
else setMsg('Status gesetzt, aber InvoiceNinja-Verknuepfung fehlgeschlagen: ' + (r.syncError || 'unbekannt'))
setBusy(false)
onSaved()
}
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, contactName: e.target.value })} />
{stage === 'lead' && }
{msg && {msg}}
{!ready && Unvollstaendig - fuer Rechnungen erforderlich
}
setForm({ ...form, street: e.target.value })} />
setForm({ ...form, postalCode: e.target.value })} />
setForm({ ...form, city: e.target.value })} />
setForm({ ...form, country: e.target.value })} />
setForm({ ...form, vatNumber: e.target.value })} />
InvoiceNinja
{customer.invoiceNinjaClientId ? verknuepft : nicht verknuepft}
setForm({ ...form, paperlessCorrespondent: e.target.value })} placeholder={customer.name} />
)
}
function PortalAccessCard({ customer, onSaved }) {
const [password, setPassword] = useState('')
const [busy, setBusy] = useState(false)
const [msg, setMsg] = useState(null)
const hasAccess = Boolean(customer.portalAccessEnabled && customer.appwriteUserId)
const currentPassword = getCustomerPortalPassword(customer)
const savePassword = async () => {
if (!customer.email) {
setMsg('Bitte zuerst eine E-Mail-Adresse in den Stammdaten speichern.')
return
}
if (!password || password.length < 8) {
setMsg('Das Passwort muss mindestens 8 Zeichen haben.')
return
}
setBusy(true); setMsg(null)
try {
await updateCustomerWithPortalAccess(customer.$id, { password })
setPassword('')
setMsg(hasAccess ? 'Passwort aktualisiert.' : 'Portal-Zugang angelegt.')
onSaved()
} catch (err) {
setMsg('Fehler: ' + err.message)
} finally {
setBusy(false)
}
}
return (
Kundenportal
{hasAccess
? freigeschaltet
: kein Zugang}
setPassword(e.target.value)}
placeholder="min. 8 Zeichen"
autoComplete="off"
/>
{msg && {msg}}
Der Kunde meldet sich mit dieser E-Mail und dem Passwort unter {PORTAL_URL} an
und sieht dort seine Projekte/Previews.
)
}
function CustomerTickets({ customerId }) {
const [tickets, setTickets] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let active = true
;(async () => {
setLoading(true)
try {
const res = await databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [
Query.equal('customerId', customerId),
Query.orderDesc('$createdAt'),
Query.limit(100),
])
if (active) setTickets(res.documents)
} catch {
if (active) setTickets([])
} finally {
if (active) setLoading(false)
}
})()
return () => { active = false }
}, [customerId])
if (loading) return
if (tickets.length === 0) return
return (
{tickets.map((t) => (
{t.woid || t.$id.slice(-5)}
{t.topic || t.title || '-'}
{t.type} � {t.systemType || 'n/a'}
))}
)
}
function CustomerProjects({ customerId }) {
const { fetchAllProjects } = useWebsiteProjects()
const [projects, setProjects] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let active = true
;(async () => {
setLoading(true)
const all = await fetchAllProjects()
if (active) setProjects((all || []).filter((p) => p.customerId === customerId))
if (active) setLoading(false)
})()
return () => { active = false }
}, [customerId, fetchAllProjects])
if (loading) return
if (projects.length === 0) return
return (
{projects.map((p) => )}
)
}