feat: Kunden archivieren + Ticket-Status-Checkbox-Filter
Kunden: - Neues Feld customers.archived; Archivieren/Wiederherstellen-Button in der Kundendetailseite, 'Archiviert'-Badge - CustomersPage: vierter Filter-Tab 'Archiviert'; archivierte Kunden aus Alle/Leads/Feste Kunden ausgeblendet, nur im Archiv-Tab sichtbar Tickets: - Status-Mehrfachauswahl per Checkbox im Filterbereich; Default alle Status ausser 'Closed' (= archiviert). 'Standard' / 'Alle inkl. Archiv' Shortcuts - useWorkorders: Statusfilter serverseitig via Query.equal(array) statt Client-Filter nach dem Limit (behebt Bug, bei dem archivierte Tickets die neuesten Slots belegten und aktive verdraengten) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff536b048f
commit
42f729abfc
5 files changed
+82
-37
No files matched your search
@@ -3,19 +3,23 @@ import { Link } from 'react-router-dom'
|
||||
import { FaSpinner, FaPlus, FaStar, FaFileInvoiceDollar, FaFileLines } from 'react-icons/fa6'
|
||||
import { useCustomers } from '../hooks/useCustomers'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { createLead, customerStage } from '../lib/leads'
|
||||
import { createLead, customerStage, isArchived } from '../lib/leads'
|
||||
import { PageHeader, Card, Button, Badge, EmptyState, Field } from '../components/ui'
|
||||
|
||||
export default function CustomersPage() {
|
||||
const { customers, loading, refresh } = useCustomers()
|
||||
const { user } = useAuth()
|
||||
const [search, setSearch] = useState('')
|
||||
const [stage, setStage] = useState('all') // all | lead | customer
|
||||
const [stage, setStage] = useState('all') // all | lead | customer | archived
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return (customers || [])
|
||||
.filter((c) => {
|
||||
const archived = isArchived(c)
|
||||
// Archivierte nur im Archiviert-Tab, sonst nie
|
||||
if (stage === 'archived') return archived
|
||||
if (archived) return false
|
||||
const st = customerStage(c)
|
||||
if (st === 'lost') return stage === 'all'
|
||||
if (stage === 'all') return true
|
||||
@@ -56,6 +60,7 @@ export default function CustomersPage() {
|
||||
<button className={stage === 'all' ? 'active' : ''} onClick={() => setStage('all')}>Alle</button>
|
||||
<button className={stage === 'lead' ? 'active' : ''} onClick={() => setStage('lead')}>Leads</button>
|
||||
<button className={stage === 'customer' ? 'active' : ''} onClick={() => setStage('customer')}>Feste Kunden</button>
|
||||
<button className={stage === 'archived' ? 'active' : ''} onClick={() => setStage('archived')}>Archiviert</button>
|
||||
</div>
|
||||
<input className="form-control search-input" placeholder="Kunde suchen..." value={search} onChange={(e) => setSearch(e.target.value)} style={{ maxWidth: 320 }} />
|
||||
</div>
|
||||
@@ -77,11 +82,12 @@ export default function CustomersPage() {
|
||||
<div style={{ fontWeight: 700, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
{c.importantCustomer && <FaStar style={{ color: 'var(--warn)' }} title="Wichtiger Kunde" />}
|
||||
{c.name}
|
||||
{c.code && <span className="faint" style={{ fontWeight: 400 }}>· {c.code}</span>}
|
||||
{c.code && <span className="faint" style={{ fontWeight: 400 }}>� {c.code}</span>}
|
||||
</div>
|
||||
<div className="muted">{[c.location, c.email].filter(Boolean).join(' · ') || '-'}</div>
|
||||
<div className="muted">{[c.location, c.email].filter(Boolean).join(' � ') || '-'}</div>
|
||||
</div>
|
||||
<div className="flex gap-2 wrap" style={{ justifyContent: 'flex-end' }}>
|
||||
{isArchived(c) && <Badge tone="muted">Archiviert</Badge>}
|
||||
{st === 'lead' && <Badge tone="warn" dot>Lead</Badge>}
|
||||
{st === 'customer' && <Badge tone="ok" dot>Fester Kunde</Badge>}
|
||||
{st === 'lost' && <Badge tone="muted">Abgesagt</Badge>}
|
||||
|
||||
Reference in new issue
Block a user