diff --git a/src/hooks/useLeads.js b/src/hooks/useLeads.js index ad58ca3..a1de8bb 100644 --- a/src/hooks/useLeads.js +++ b/src/hooks/useLeads.js @@ -19,6 +19,51 @@ async function listAll(collection, queries = []) { return out } +/** Haengt an jeden Lead die WOID seines Akquise-Tickets: + * Lead -> Kunde (per E-Mail) -> Akquise-Workorder -> woid. Best effort; + * am Lead selbst ist keine WOID gespeichert, daher live gejoint. */ +async function attachWoids(leads) { + const norm = (l) => (l.email || l.portalLogin || '').trim().toLowerCase() + const emails = [...new Set(leads.map(norm).filter(Boolean))] + if (!emails.length) return leads + + // 1) Kunden per E-Mail -> customerId + const emailToCustomer = {} + const customerIds = [] + for (let i = 0; i < emails.length; i += 90) { + const docs = await listAll(COLLECTIONS.CUSTOMERS, [Query.equal('email', emails.slice(i, i + 90))]) + for (const c of docs) { + const e = (c.email || '').trim().toLowerCase() + if (e && !emailToCustomer[e]) { + emailToCustomer[e] = c.$id + customerIds.push(c.$id) + } + } + } + if (!customerIds.length) return leads + + // 2) Akquise-Tickets per customerId -> kleinste (urspruengliche) WOID je Kunde + const customerToWoid = {} + for (let i = 0; i < customerIds.length; i += 90) { + const docs = await listAll(COLLECTIONS.WORKORDERS, [ + Query.equal('customerId', customerIds.slice(i, i + 90)), + Query.equal('type', ['Akquise']), + ]) + for (const w of docs) { + const n = parseInt(w.woid, 10) + if (!w.customerId || isNaN(n)) continue + const cur = customerToWoid[w.customerId] + if (!cur || n < cur.n) customerToWoid[w.customerId] = { n, woid: w.woid, ticketId: w.$id } + } + } + + // 3) an die Leads haengen + return leads.map((l) => { + const hit = customerToWoid[emailToCustomer[norm(l)]] + return hit ? { ...l, woid: hit.woid, ticketId: hit.ticketId } : l + }) +} + /** Recherche-Leads aus der taeglichen 06:00-Routine (Appwrite `leads`). * Pollt automatisch nach — schneller, solange die 5-Minuten-Pipeline * (processor.py auf dem Server) gerade Leads verarbeitet. */ @@ -31,7 +76,9 @@ export function useLeads() { const fetchLeads = useCallback(async () => { try { const out = await listAll(COLLECTIONS.LEADS, [Query.orderDesc('leadScore')]) - setLeads(out) + let enriched = out + try { enriched = await attachWoids(out) } catch { /* WOID-Join best effort */ } + setLeads(enriched) setError(null) } catch (err) { if (err.code === 404 || err.message?.includes('not found')) { diff --git a/src/hooks/useWorkorders.js b/src/hooks/useWorkorders.js index 0e835d7..756c58b 100644 --- a/src/hooks/useWorkorders.js +++ b/src/hooks/useWorkorders.js @@ -11,6 +11,7 @@ function buildFiltersKey(filters = {}) { status: filters.status ?? null, type: filters.type ?? null, priority: filters.priority ?? null, + woid: filters.woid ?? null, }) } @@ -87,6 +88,7 @@ export function useWorkorders(filters = {}) { filters.status, filters.type, filters.priority, + filters.woid, ]) const fetchWorkorders = useCallback(async () => { @@ -102,6 +104,9 @@ export function useWorkorders(filters = {}) { if (activeFilters.priority?.length > 0) { filtered = filtered.filter(wo => activeFilters.priority.includes(wo.priority)) } + if (activeFilters.woid) { + filtered = filtered.filter(wo => String(wo.woid) === String(activeFilters.woid)) + } if (activeFilters.limit) { filtered = filtered.slice(0, activeFilters.limit) } @@ -140,6 +145,11 @@ export function useWorkorders(filters = {}) { queries.push(Query.equal('assignedTo', activeFilters.assignedTo)) } + // WOID-Direktsuche (z. B. Deep-Link aus den Leads: /tickets?woid=123) + if (activeFilters.woid) { + queries.push(Query.equal('woid', [String(activeFilters.woid)])) + } + // Debug: Zeige Collection ID if (import.meta.env.DEV) { console.log('📋 Fetching workorders:') diff --git a/src/pages/LeadsPage.jsx b/src/pages/LeadsPage.jsx index 66b13e2..2907a1c 100644 --- a/src/pages/LeadsPage.jsx +++ b/src/pages/LeadsPage.jsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from 'react' +import { Link } from 'react-router-dom' import { FaSpinner, FaStar, @@ -16,6 +17,7 @@ import { FaArrowsRotate, FaCheck, FaCopy, + FaHashtag, FaTriangleExclamation, } from 'react-icons/fa6' import { useLeads, useLeadSettings, useLeadCycle } from '../hooks/useLeads' @@ -410,20 +412,32 @@ function LeadCard({ lead, pipeline, onUpdate }) { )} -
+
{lead.googleMapsLink && ( - )} {websiteUrl && ( - )} {lead.repoUrl && ( - + )} + {lead.woid && ( + )}