feat: CRM-Tab Leads - Lead-Visualisierung aus Appwrite + Sucheinstellungen der 06:00-Routine
- Neue Seite /leads unter CRM: Lead-Karten (Score, Google-Bewertung, Website-Status, Kontakt, Maps/Website/Repo-Links), KPI-Zeile, Status-Workflow (neu/kontaktiert/interessiert/gewonnen/abgelehnt), Notizen, Portal-Zugangsdaten - Sucheinstellungen (Branche, Staedte oder ganz Deutschland, max. Leads pro Stadt, Mindestbewertung, E-Mail-Pflicht, Website-Kriterium, Zusatzhinweise) -> Appwrite leadSettings, gelesen von der Routine ueber webklar-hook /config - Ersetzt die Excel-Masterliste als Lead-Ansicht Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5786eed3ba
commit
1cec319fa8
5 files changed
+591
-1
No files matched your search
@@ -0,0 +1,141 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { COLLECTIONS, DATABASE_ID, Query, databases } from '../lib/appwrite'
|
||||
|
||||
const PAGE = 100
|
||||
|
||||
/** Recherche-Leads aus der taeglichen 06:00-Routine (Appwrite `leads`). */
|
||||
export function useLeads() {
|
||||
const [leads, setLeads] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const fetchLeads = useCallback(async () => {
|
||||
try {
|
||||
const out = []
|
||||
let offset = 0
|
||||
for (;;) {
|
||||
const res = await databases.listDocuments(DATABASE_ID, COLLECTIONS.LEADS, [
|
||||
Query.orderDesc('leadScore'),
|
||||
Query.limit(PAGE),
|
||||
Query.offset(offset),
|
||||
])
|
||||
out.push(...(res.documents || []))
|
||||
if ((res.documents || []).length < PAGE) break
|
||||
offset += PAGE
|
||||
}
|
||||
setLeads(out)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
if (err.code === 404 || err.message?.includes('not found')) {
|
||||
setLeads([])
|
||||
setError(null)
|
||||
} else {
|
||||
setError(err.message)
|
||||
setLeads([])
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchLeads()
|
||||
}, [fetchLeads])
|
||||
|
||||
const updateLead = async (id, data) => {
|
||||
try {
|
||||
const doc = await databases.updateDocument(DATABASE_ID, COLLECTIONS.LEADS, id, {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
setLeads((prev) => prev.map((l) => (l.$id === id ? doc : l)))
|
||||
return { success: true, data: doc }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const deleteLead = async (id) => {
|
||||
try {
|
||||
await databases.deleteDocument(DATABASE_ID, COLLECTIONS.LEADS, id)
|
||||
setLeads((prev) => prev.filter((l) => l.$id !== id))
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
return { leads, loading, error, refresh: fetchLeads, updateLead, deleteLead }
|
||||
}
|
||||
|
||||
export const DEFAULT_LEAD_SETTINGS = {
|
||||
branche: 'Autolackiererei',
|
||||
staedte: [],
|
||||
ganzDeutschland: false,
|
||||
maxLeadsProStadt: 5,
|
||||
minBewertung: 4.0,
|
||||
emailPflicht: true,
|
||||
nurSchlechteWebsite: true,
|
||||
zusatzHinweise: '',
|
||||
}
|
||||
|
||||
/** Sucheinstellungen der Routine (Appwrite `leadSettings`, Dokument `main`).
|
||||
* Die Cloud-Routine liest sie vor jedem Lauf ueber den Server-Hook (/config). */
|
||||
export function useLeadSettings() {
|
||||
const [settings, setSettings] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const fetchSettings = useCallback(async () => {
|
||||
try {
|
||||
const doc = await databases.getDocument(DATABASE_ID, COLLECTIONS.LEAD_SETTINGS, 'main')
|
||||
setSettings(doc)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
if (err.code === 404 || err.message?.includes('not found')) {
|
||||
setSettings({ ...DEFAULT_LEAD_SETTINGS })
|
||||
setError(null)
|
||||
} else {
|
||||
setError(err.message)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchSettings()
|
||||
}, [fetchSettings])
|
||||
|
||||
const saveSettings = async (data) => {
|
||||
const payload = {
|
||||
branche: data.branche || '',
|
||||
staedte: data.staedte || [],
|
||||
ganzDeutschland: Boolean(data.ganzDeutschland),
|
||||
maxLeadsProStadt: Number(data.maxLeadsProStadt) || 5,
|
||||
minBewertung: Number(data.minBewertung) || 0,
|
||||
emailPflicht: Boolean(data.emailPflicht),
|
||||
nurSchlechteWebsite: Boolean(data.nurSchlechteWebsite),
|
||||
zusatzHinweise: data.zusatzHinweise || '',
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
try {
|
||||
let doc
|
||||
try {
|
||||
doc = await databases.updateDocument(DATABASE_ID, COLLECTIONS.LEAD_SETTINGS, 'main', payload)
|
||||
} catch (err) {
|
||||
if (err.code === 404) {
|
||||
doc = await databases.createDocument(DATABASE_ID, COLLECTIONS.LEAD_SETTINGS, 'main', payload)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
setSettings(doc)
|
||||
return { success: true, data: doc }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
return { settings, loading, error, refresh: fetchSettings, saveSettings }
|
||||
}
|
||||
Reference in new issue
Block a user