PageSourceSearch

https://www.smallbizagent.ai/assets/LeadsTab-CMxynlNC.js

js smallbizagent.ai collected 2026-10-05 15:07:01 UTC 16,608 bytes, 1 lines download raw bytes

1import{e as Re,r as n,u as g,d as k,j as e}from"./vendor-query-CnAhr2B8.js";import{a as De,H as f,p as A,q as F,r as E,aT as Z,$ as J,s as q,aC as W,O as L,Q as P,R as M,T as K,V as b,I as X,B as N,ak as V,S as $e,d as Q,P as Te,b6 as Ie,aN as ie,aA as ne}from"./index-CLUtDhnY.js";import{S as ke}from"./switch-wa839A7l.js";import{C as Ae}from"./checkbox-DwgIx1mG.js";import{T as le,a as re,b as z,c as l,d as ce,e as r}from"./table-2PZjRc6u.js";import{A as Fe,h as Ee,a as qe,b as Le,c as Pe,d as Me,e as Ke,f as Ve,g as Qe}from"./alert-dialog-CVgn4OwE.js";import{T as de}from"./target-B8Bp25HY.js";import{S as ze}from"./star-W3rPNB9i.js";import{E as _e}from"./external-link-Ck-FWxuF.js";import"./vendor-ui-CYA7SV0W.js";import"./vendor-charts-CPMWc2oJ.js";import"./vendor-forms-UY-il7MQ.js";const oe=[{value:"hvac",label:"HVAC"},{value:"plumbing",label:"Plumbing"},{value:"electrical",label:"Electrical"},{value:"salon",label:"Salon"},{value:"barbershop",label:"Barbershop"},{value:"spa",label:"Spa"}],Be=[{value:"maryland",label:"Maryland"},{value:"northern_va",label:"Northern Virginia"},{value:"delaware",label:"Delaware"},{value:"se_pa",label:"SE Pennsylvania"},{value:"custom",label:"Custom (paste ZIPs)"}],_={discovered:{label:"Discovered",color:"bg-blue-100 text-blue-800"},contacted:{label:"Contacted",color:"bg-amber-100 text-amber-800"},qualified:{label:"Qualified",color:"bg-emerald-100 text-emerald-800"},converted:{label:"Converted",color:"bg-green-200 text-green-900"},dismissed:{label:"Dismissed",color:"bg-gray-100 text-gray-700"}};function He(t){return t===null?"text-muted-foreground":t>=75?"text-emerald-600 font-semibold":t>=50?"text-amber-600 font-semibold":"text-red-600 font-semibold"}function is(){var ae,te;const{toast:t}=De(),c=Re(),[y,ue]=n.useState("maryland"),[Y,me]=n.useState(""),[B,xe]=n.useState(["hvac","plumbing"]),[S,he]=n.useState(!0),[C,je]=n.useState(null),[m,ee]=n.useState(null),[R,pe]=n.useState("all"),[D,ve]=n.useState("all"),[$,ge]=n.useState(0),[T,fe]=n.useState(""),[x,h]=n.useState(1),[H,be]=n.useState(!1),[I,Ne]=n.useState(!1),{data:j}=g({queryKey:["/api/admin/leads/spend"],refetchInterval:6e4}),{data:p,isLoading:ye}=g({queryKey:["/api/admin/leads",x,R,D,$,T],queryFn:async()=>{const s=new URLSearchParams({page:String(x),limit:"30"});return R!=="all"&&s.set("status",R),D!=="all"&&s.set("industry",D),$>0&&s.set("minScore",String($)),T.trim()&&s.set("search",T.trim()),(await f("GET",`/api/admin/leads?${s}`)).json()}}),{data:d}=g({queryKey:["/api/admin/leads/runs"],refetchInterval:m?5e3:6e4}),{data:v}=g({queryKey:["/api/admin/leads/rubric/active"]}),{data:w}=g({queryKey:["/api/admin/leads/rubric/history"],enabled:I}),{data:a}=g({queryKey:["/api/admin/leads/discover-run",m],queryFn:async()=>(await f("GET",`/api/admin/leads/discover-run/${m}`)).json(),enabled:m!==null,refetchInterval:3e3});n.useEffect(()=>{a&&(a.status==="completed"||a.status==="failed"||a.status==="aborted_budget")&&(c.invalidateQueries({queryKey:["/api/admin/leads"]}),c.invalidateQueries({queryKey:["/api/admin/leads/runs"]}),c.invalidateQueries({queryKey:["/api/admin/leads/spend"]}),t({title:a.status==="completed"?"Scan complete":`Scan ${a.status}`,description:a.status==="completed"?`${a.leadsDiscovered} new leads · $${Number(a.totalCost).toFixed(2)}`:a.errorMessage||"See run details below",variant:a.status==="failed"||a.status==="aborted_budget"?"destructive":void 0}),ee(null))},[a,c,t]);const se=k({mutationFn:async()=>{const s=y==="custom"?Y.split(/[,\s]+/).map(u=>u.trim()).filter(u=>/^\d{5}$/.test(u)):void 0;return(await f("POST","/api/admin/leads/discover-run",{industries:B,region:y==="custom"?void 0:y,zipCodes:s,dryRun:S})).json()},onSuccess:s=>{S?(je(s),t({title:"Dry run complete",description:`Estimated cost: $${Number(s.totalCost).toFixed(2)} (no spend incurred)`})):s.runId&&(ee(s.runId),t({title:"Scan started",description:"Polling for results…"}))},onError:s=>{t({title:"Scan failed to start",description:(s==null?void 0:s.message)||"Unknown error",variant:"destructive"})}}),Se=k({mutationFn:async s=>(await f("PATCH",`/api/admin/leads/${s.id}`,s)).json(),onSuccess:()=>{c.invalidateQueries({queryKey:["/api/admin/leads"]}),t({title:"Lead updated"})},onError:s=>{t({title:"Update failed",description:s==null?void 0:s.message,variant:"destructive"})}}),O=k({mutationFn:async s=>(await f("POST",`/api/admin/leads/${s}/rescore`)).json(),onSuccess:()=>{c.invalidateQueries({queryKey:["/api/admin/leads"]}),t({title:"Lead re-scored"})},onError:s=>{t({title:"Re-score failed",description:s==null?void 0:s.message,variant:"destructive"})}}),U=k({mutationFn:async()=>(await f("POST","/api/admin/leads/rubric/refine-now")).json(),onSuccess:s=>{c.invalidateQueries({queryKey:["/api/admin/leads/rubric/active"]}),c.invalidateQueries({queryKey:["/api/admin/leads/rubric/history"]});const i=s.status==="refined"?`Refined to v${s.newVersion} (${s.positiveSignalsCount} positive, ${s.negativeSignalsCount} negative signals)`:s.status==="skipped_insufficient_signal"?`Skipped: not enough feedback yet (${s.positiveSignalsCount} positive, ${s.negativeSignalsCount} negative). Need 5+ positive and 3+ negative.`:`Failed: ${s.errorMessage}`;t({title:s.status==="refined"?"Rubric refined":"Refinement skipped",de
1scription:i,variant:s.status==="failed"?"destructive":void 0})},onError:s=>{t({title:"Refinement failed",description:s==null?void 0:s.message,variant:"destructive"})}}),Ce=(s,i)=>{xe(o=>i?[...o,s]:o.filter(u=>u!==s))},G=m!==null||se.isPending,we=S?"Dry-Run Estimate":"Start Scan";return e.jsxs("div",{className:"space-y-6",children:[e.jsxs(A,{children:[e.jsx(F,{children:e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsxs("div",{children:[e.jsxs(E,{className:"flex items-center gap-2",children:[e.jsx(de,{className:"h-5 w-5"}),"Discover Leads"]}),e.jsx(Z,{children:"Scan Google Places for ICP-matching service businesses. Rule-based filters reject non-fits before any Claude spend."})]}),e.jsxs("div",{className:"text-right text-sm",children:[e.jsxs("div",{children:["Spend this month: ",e.jsxs("span",{className:"font-semibold",children:["$",((ae=j==null?void 0:j.currentMonthSpend)==null?void 0:ae.toFixed(2))??"0.00"]})," / $",(j==null?void 0:j.monthlyBudget)??20]}),v&&e.jsxs("div",{className:"text-xs text-muted-foreground mt-1 flex items-center gap-1 justify-end",children:[e.jsx(J,{className:"h-3 w-3"}),"Rubric v",v.version,v.refinedFromVersion&&` · refined from v${v.refinedFromVersion}`]})]})]})}),e.jsxs(q,{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx(W,{children:"Region"}),e.jsxs(L,{value:y,onValueChange:ue,children:[e.jsx(P,{className:"mt-1",children:e.jsx(M,{})}),e.jsx(K,{children:Be.map(s=>e.jsx(b,{value:s.value,children:s.label},s.value))})]}),y==="custom"&&e.jsx(X,{className:"mt-2",placeholder:"21201, 21401, 20814 …",value:Y,onChange:s=>me(s.target.value)})]}),e.jsxs("div",{children:[e.jsx(W,{children:"Industries"}),e.jsx("div",{className:"grid grid-cols-3 gap-2 mt-1",children:oe.map(s=>e.jsxs("label",{className:"flex items-center gap-2 text-sm cursor-pointer",children:[e.jsx(Ae,{checked:B.includes(s.value),onCheckedChange:i=>Ce(s.value,!!i)}),s.label]},s.value))})]}),e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-3",children:[e.jsxs("label",{className:"flex items-center gap-2 text-sm cursor-pointer",children:[e.jsx(ke,{checked:S,onCheckedChange:he}),"Dry Run (estimate cost only, no spend)"]}),e.jsxs(N,{onClick:()=>se.mutate(),disabled:G||B.length===0,"data-testid":"start-scan-button",children:[G?e.jsx(V,{className:"h-4 w-4 mr-2 animate-spin"}):e.jsx($e,{className:"h-4 w-4 mr-2"}),G?"Scanning…":we]})]}),S&&C&&e.jsxs("div",{className:"text-sm rounded-md border bg-muted/30 p-3",children:[e.jsx("div",{className:"font-medium mb-1",children:"Dry-run estimate"}),e.jsxs("div",{children:["~",C.placesSearchCount," text searches · ~",C.placesDetailsCount," details · ~",C.claudeScoringCount," Claude scores"]}),e.jsxs("div",{className:"mt-1",children:["Estimated cost: ",e.jsxs("span",{className:"font-semibold",children:["$",Number(C.totalCost).toFixed(2)]})]})]}),a&&m!==null&&e.jsx("div",{className:"text-sm rounded-md border border-blue-200 bg-blue-50 dark:bg-blue-950/20 p-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(V,{className:"h-4 w-4 animate-spin"}),"Run #",a.id," in progress · ",a.placesSearchCount," searches · ",a.placesDetailsCount," details · ",a.leadsDiscovered," leads so far"]})})]})]}),e.jsxs(A,{children:[e.jsxs(F,{children:[e.jsx(E,{children:"Leads"}),e.jsx(Z,{children:"Sorted by score (descending). Marking leads as Qualified, Converted, or Dismissed trains the rubric."}),e.jsxs("div",{className:"flex flex-wrap gap-2 mt-3",children:[e.jsx(X,{placeholder:"Search business name…",value:T,onChange:s=>{fe(s.target.value),h(1)},className:"max-w-[260px]"}),e.jsxs(L,{value:R,onValueChange:s=>{pe(s),h(1)},children:[e.jsx(P,{className:"w-[160px]",children:e.jsx(M,{placeholder:"Status"})}),e.jsxs(K,{children:[e.jsx(b,{value:"all",children:"All Statuses"}),Object.entries(_).map(([s,i])=>e.jsx(b,{value:s,children:i.label},s))]})]}),e.jsxs(L,{value:D,onValueChange:s=>{ve(s),h(1)},children:[e.jsx(P,{className:"w-[160px]",children:e.jsx(M,{placeholder:"Industry"})}),e.jsxs(K,{children:[e.jsx(b,{value:"all",children:"All Industries"}),oe.map(s=>e.jsx(b,{value:s.value,children:s.label},s.value))]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(W,{className:"text-xs whitespace-nowrap",children:"Min 
1score"}),e.jsx(X,{type:"number",min:0,max:100,value:$,onChange:s=>{ge(parseInt(s.target.value)||0),h(1)},className:"w-[80px]"})]})]})]}),e.jsx(q,{children:ye?e.jsx("div",{className:"flex justify-center py-12",children:e.jsx(V,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):!p||p.leads.length===0?e.jsxs("div",{className:"text-center py-12 text-muted-foreground",children:[e.jsx(de,{className:"h-10 w-10 mx-auto mb-3 opacity-30"}),e.jsx("div",{children:"No leads yet."}),e.jsx("div",{className:"text-sm mt-1",children:"Run a scan above to discover leads in your region."})]}):e.jsxs(e.Fragment,{children:[e.jsxs(le,{children:[e.jsx(re,{children:e.jsxs(z,{children:[e.jsx(l,{children:"Business"}),e.jsx(l,{children:"Industry"}),e.jsx(l,{children:"Location"}),e.jsx(l,{children:"Score"}),e.jsx(l,{children:"Rating"}),e.jsx(l,{children:"Phone"}),e.jsx(l,{children:"Status"}),e.jsx(l,{children:"Actions"})]})}),e.jsx(ce,{children:p.leads.map(s=>{const i=_[s.status]||_.discovered;return e.jsxs(z,{"data-testid":`lead-row-${s.id}`,children:[e.jsxs(r,{children:[e.jsx("div",{className:"font-medium",children:s.businessName}),s.painSummary&&e.jsx("div",{className:"text-xs text-muted-foreground line-clamp-1 max-w-[300px]",title:s.painSummary,children:s.painSummary})]}),e.jsx(r,{children:e.jsx(Q,{variant:"outline",className:"capitalize",children:s.industry})}),e.jsxs(r,{className:"text-sm text-muted-foreground",children:[s.city||"—",s.state?`, ${s.state}`:""," ",s.zipCode||""]}),e.jsx(r,{children:e.jsx("span",{className:He(s.leadScore),children:s.leadScore??"—"})}),e.jsx(r,{className:"text-sm",children:s.rating!==null?e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx(ze,{className:"h-3 w-3 fill-amber-400 text-amber-400"}),s.rating," ",e.jsxs("span",{className:"text-muted-foreground",children:["(",s.reviewCount??0,")"]})]}):"—"}),e.jsx(r,{className:"text-sm",children:s.phone?e.jsxs("a",{href:`tel:${s.phone}`,className:"inline-flex items-center gap-1 hover:underline",children:[e.jsx(Te,{className:"h-3 w-3"}),s.phone]}):"—"}),e.jsx(r,{children:e.jsx(Q,{className:`text-xs ${i.color}`,children:i.label})}),e.jsx(r,{className:"text-xs",children:e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs(L,{value:s.status,onValueChange:o=>Se.mutate({id:s.id,status:o}),children:[e.jsx(P,{className:"h-7 text-xs",children:e.jsx(M,{})}),e.jsx(K,{children:Object.entries(_).map(([o,u])=>e.jsx(b,{value:o,children:u.label},o))})]}),e.jsxs("div",{className:"flex gap-1",children:[s.website&&e.jsx("a",{href:s.website,target:"_blank",rel:"noreferrer",children:e.jsx(N,{variant:"outline",size:"sm",className:"h-7 px-2 text-xs",children:e.jsx(_e,{className:"h-3 w-3"})})}),e.jsx(N,{variant:"outline",size:"sm",className:"h-7 px-2 text-xs",onClick:()=>O.mutate(s.id),disabled:O.isPending,children:e.jsx(Ie,{className:`h-3 w-3 ${O.isPending?"animate-spin":""}`})})]})]})})]},s.id)})})]}),e.jsxs("div",{className:"flex justify-between items-center mt-4 text-sm text-muted-foreground",children:[e.jsxs("div",{children:["Showing ",(x-1)*30+1,"–",Math.min(x*30,p.total)," of ",p.total]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(N,{variant:"outline",size:"sm",disabled:x===1,onClick:()=>h(s=>s-1),children:"Previous"}),e.jsx(N,{variant:"outline",size:"sm",disabled:x*30>=p.total,onClick:()=>h(s=>s+1),children:"Next"})]})]})]})})]}),e.jsxs(A,{children:[e.jsx(F,{className:"cursor-pointer",onClick:()=>be(!H),children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs(E,{className:"text-base",children:["Recent Scans (",((te=d==null?void 0:d.runs)==null?void 0:te.length)??0,")"]}),H?e.jsx(ie,{className:"h-4 w-4"}):e.jsx(ne,{className:"h-4 w-4"})]})}),H&&e.jsx(q,{children:d!=null&&d.runs&&d.runs.length>0?e.jsxs(le,{children:[e.jsx(re,{children:e.jsxs(z,{children:[e.jsx(l,{children:"Run"}),e.jsx(l,{children:"Region"}),e.jsx(l,{children:"Industries"}),e.jsx(l,{children:"Status"}),e.jsx(l,{children:"Leads"}),e.jsx(l,{children:"Cost"})]})}),e.jsx(ce,{children:d.runs.slice(0,10).map(s=>e.jsxs(z,{children:[e.jsxs(r,{className:"text-sm",children:["#",s.id," · ",new Date(s.startedAt).toLocaleStr
1ing()]}),e.jsx(r,{className:"text-sm capitalize",children:s.region||"—"}),e.jsx(r,{className:"text-xs",children:s.industries.join(", ")}),e.jsx(r,{children:e.jsx(Q,{variant:"outline",className:"text-xs capitalize",children:s.status.replace(/_/g," ")})}),e.jsx(r,{className:"text-sm",children:s.leadsDiscovered}),e.jsxs(r,{className:"text-sm",children:["$",Number(s.totalCost).toFixed(2)]})]},s.id))})]}):e.jsx("div",{className:"text-sm text-muted-foreground",children:"No scans yet."})})]}),e.jsxs(A,{children:[e.jsx(F,{className:"cursor-pointer",onClick:()=>Ne(!I),children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsxs(E,{className:"text-base flex items-center gap-2",children:[e.jsx(J,{className:"h-4 w-4 text-purple-500"}),"Agent Learning · Rubric History"]}),v&&e.jsxs(Z,{className:"mt-1",children:["Currently scoring with v",v.version]})]}),I?e.jsx(ie,{className:"h-4 w-4"}):e.jsx(ne,{className:"h-4 w-4"})]})}),I&&e.jsxs(q,{className:"space-y-3",children:[e.jsx("div",{className:"flex justify-end",children:e.jsxs(Fe,{children:[e.jsx(Ee,{asChild:!0,children:e.jsxs(N,{variant:"outline",size:"sm",disabled:U.isPending,children:[U.isPending?e.jsx(V,{className:"h-4 w-4 mr-2 animate-spin"}):e.jsx(J,{className:"h-4 w-4 mr-2"}),"Force Refine Now"]})}),e.jsxs(qe,{children:[e.jsxs(Le,{children:[e.jsx(Pe,{children:"Refine the scoring rubric now?"}),e.jsx(Me,{children:"The agent will analyze your qualified/converted vs dismissed leads from the last 30 days and propose a new rubric. Requires at least 5 positive and 3 negative signals — otherwise it skips. Costs ~$0.02 in Claude tokens."})]}),e.jsxs(Ke,{children:[e.jsx(Ve,{children:"Cancel"}),e.jsx(Qe,{onClick:()=>U.mutate(),children:"Refine Rubric"})]})]})]})}),w!=null&&w.rubrics&&w.rubrics.length>0?e.jsx("div",{className:"space-y-2",children:w.rubrics.map(s=>e.jsxs("div",{className:"border rounded-md p-3",children:[e.jsxs("div",{className:"flex items-center justify-between mb-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"font-semibold",children:["v",s.version]}),s.isActive&&e.jsx(Q,{variant:"default",className:"text-xs",children:"Active"})]}),e.jsx("span",{className:"text-xs text-muted-foreground",children:new Date(s.createdAt).toLocaleDateString()})]}),s.refinedFromVersion!==null&&e.jsxs("div",{className:"text-xs text-muted-foreground mb-1",children:["Refined from v",s.refinedFromVersion," · ",s.positiveSignalsCount," positive, ",s.negativeSignalsCount," negative signals"]}),s.refinementSummary&&e.jsxs("div",{className:"text-sm text-muted-foreground",children:['"',s.refinementSummary,'"']})]},s.id))}):e.jsx("div",{className:"text-sm text-muted-foreground",children:'No refinements yet. Mark leads as qualified/converted/dismissed, then click "Force Refine Now".'})]})]})]})}export{is as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.