Kunden an einer Stelle pflegen (Paket E)
- Kundenliste startet bei "Feste Kunden" (105 von 110 Einträgen sind
Leads der Lead-Automatik); Reihenfolge Feste Kunden, Leads, Alle, Archiv.
- Der zweite Kunden-Editor im Admin ist weg. Admins legen feste Kunden
mit Portal-Zugang jetzt unter Kunden an ("Neuer Kunde") und löschen sie
auf der Kundenseite ("Kunde löschen", mit Rückfrage); beides wie bisher
über die Admin-API des Kundenportals. Die Kundenseite hat das Feld
Standort, das es bisher nur im Admin gab.
- Die Kundenliste liest direkt aus Appwrite: die Admin-API lieferte
Admins gekürzte Daten, Stern und InvoiceNinja-Abzeichen fehlten dort.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
31144148d1
commit
5a066eb3c4
5 files changed
+111
-193
No files matched your search
@@ -1,13 +1,14 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare, FaBoxArchive, FaBoxOpen } from 'react-icons/fa6'
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom'
|
||||
import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare, 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 InvoicePanel from '../components/InvoicePanel'
|
||||
import AssignedProjectCard from '../components/AssignedProjectCard'
|
||||
import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential'
|
||||
import { updateCustomerWithPortalAccess } from '../lib/customerAdminApi'
|
||||
import { updateCustomerWithPortalAccess, deleteCustomerWithPortalAccess } from '../lib/customerAdminApi'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
|
||||
import { customerStage, isArchived, isInvoiceReady, setCustomerArchived, upgradeToCustomer, syncCustomerToNinja } from '../lib/leads'
|
||||
|
||||
@@ -26,6 +27,8 @@ export default function CustomerDetailPage() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [tab, setTab] = useState('overview')
|
||||
const { isAdmin } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true)
|
||||
@@ -42,7 +45,7 @@ export default function CustomerDetailPage() {
|
||||
useEffect(() => { load() }, [load])
|
||||
|
||||
if (loading) return <div className="page"><div className="empty"><FaSpinner className="spinner" /></div></div>
|
||||
if (error || !customer) return <div className="page"><EmptyState title="Kunde nicht gefunden" hint={error} action={<Button as={Link} to="/customers"><FaArrowLeft /> Zurueck</Button>} /></div>
|
||||
if (error || !customer) return <div className="page"><EmptyState title="Kunde nicht gefunden" hint={error} action={<Button as={Link} to="/customers"><FaArrowLeft /> Zurück</Button>} /></div>
|
||||
|
||||
const stage = customerStage(customer)
|
||||
const archived = isArchived(customer)
|
||||
@@ -53,6 +56,17 @@ export default function CustomerDetailPage() {
|
||||
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 (
|
||||
<div className="page">
|
||||
<PageHeader
|
||||
@@ -72,6 +86,7 @@ export default function CustomerDetailPage() {
|
||||
<Button variant="ghost" onClick={toggleArchived}>
|
||||
{archived ? <><FaBoxOpen /> Wiederherstellen</> : <><FaBoxArchive /> Archivieren</>}
|
||||
</Button>
|
||||
{isAdmin && <Button variant="danger" onClick={removeCustomer}><FaTrash /> Kunde löschen</Button>}
|
||||
<Button variant="ghost" as={Link} to="/customers"><FaArrowLeft /> Alle Kunden</Button>
|
||||
</>
|
||||
}
|
||||
@@ -154,6 +169,7 @@ function OverviewTab({ customer, stage, onSaved }) {
|
||||
<Field label="E-Mail"><input className="form-control" type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} /></Field>
|
||||
<Field label="Telefon"><input className="form-control" value={form.phone} onChange={(e) => setForm({ ...form, phone: e.target.value })} /></Field>
|
||||
</div>
|
||||
<Field label="Standort"><input className="form-control" value={form.location} onChange={(e) => setForm({ ...form, location: e.target.value })} /></Field>
|
||||
<Field label="Ansprechpartner"><input className="form-control" value={form.contactName} onChange={(e) => setForm({ ...form, contactName: e.target.value })} /></Field>
|
||||
<Field label="Notizen"><textarea className="form-control" rows={3} value={form.notes} onChange={(e) => setForm({ ...form, notes: e.target.value })} /></Field>
|
||||
<label className="flex items-center gap-2" style={{ marginBottom: 12, cursor: 'pointer' }}>
|
||||
|
||||
Reference in new issue
Block a user