feat: Leads — Button zur erstellten Vorschau-Website

- Vorschau-Button (Auge, primary) je Lead -> previewUrl der deployten Website
- previewUrl live gejoint (Lead->Kunde per E-Mail->websiteProjects status deployed/ready); Preview haengt an anderem Repo als lead.repoUrl -> Kunden-Join
- attachWoids -> attachLeadLinks (haengt jetzt woid + previewUrl an)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-07-16 18:53:38 +00:00
parent fe3e39bd04
commit e59a066d3c
2 changed files with 35 additions and 8 deletions

View File

@@ -19,10 +19,12 @@ 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) {
/** Haengt an jeden Lead Verweise, die nicht am Lead selbst gespeichert sind,
* sondern ueber den Kunden verknuepft werden — via Lead -> Kunde (per E-Mail):
* - woid: WOID des Akquise-Tickets (workorders type=Akquise)
* - previewUrl: URL der erstellten, deployten Website (websiteProjects)
* Best effort; Fehler brechen die Lead-Liste nicht. */
async function attachLeadLinks(leads) {
const norm = (l) => (l.email || l.portalLogin || '').trim().toLowerCase()
const emails = [...new Set(leads.map(norm).filter(Boolean))]
if (!emails.length) return leads
@@ -57,10 +59,29 @@ async function attachWoids(leads) {
}
}
// 3) an die Leads haengen
// 3) deployte Preview je Kunde -> URL der erstellten Website.
// Die websiteProjects-Collection ist klein -> komplett laden + lokal zuordnen
// (die Preview haengt am selben Kunden, aber an einem anderen Repo als lead.repoUrl).
const customerToPreview = {}
const idSet = new Set(customerIds)
for (const p of await listAll(COLLECTIONS.WEBSITE_PROJECTS)) {
if (!idSet.has(p.customerId) || customerToPreview[p.customerId]) continue
const ready = p.status === 'deployed' || p.status === 'ready' || p.provisioningStatus === 'ready'
const url = p.previewUrl || (p.subdomain ? `https://${p.subdomain}.project.webklar.com` : '')
if (ready && url) customerToPreview[p.customerId] = url
}
// 4) an die Leads haengen
return leads.map((l) => {
const hit = customerToWoid[emailToCustomer[norm(l)]]
return hit ? { ...l, woid: hit.woid, ticketId: hit.ticketId } : l
const cid = emailToCustomer[norm(l)]
const t = customerToWoid[cid]
const previewUrl = customerToPreview[cid]
if (!t && !previewUrl) return l
return {
...l,
...(t ? { woid: t.woid, ticketId: t.ticketId } : {}),
...(previewUrl ? { previewUrl } : {}),
}
})
}
@@ -77,7 +98,7 @@ export function useLeads() {
try {
const out = await listAll(COLLECTIONS.LEADS, [Query.orderDesc('leadScore')])
let enriched = out
try { enriched = await attachWoids(out) } catch { /* WOID-Join best effort */ }
try { enriched = await attachLeadLinks(out) } catch { /* Live-Join best effort */ }
setLeads(enriched)
setError(null)
} catch (err) {

View File

@@ -17,6 +17,7 @@ import {
FaArrowsRotate,
FaCheck,
FaCopy,
FaEye,
FaHashtag,
FaTriangleExclamation,
} from 'react-icons/fa6'
@@ -428,6 +429,11 @@ function LeadCard({ lead, pipeline, onUpdate }) {
<FaGitAlt />
</Button>
)}
{lead.previewUrl && (
<Button size="sm" variant="primary" as="a" href={lead.previewUrl} target="_blank" rel="noreferrer" title="Erstellte Vorschau-Website öffnen (Login-geschützt)" aria-label="Vorschau öffnen">
<FaEye />
</Button>
)}
{lead.woid && (
<Button
size="sm"