1import{g as U,j as e,r as a,t as u,B as b,L as F,bQ as H,h as M,bn as X,I as w,k as q,X as Q,aM as J,au as V}from"./index-CzAkNt2W.js";import{T as Y}from"./textarea-CMw1Ny2O.js";import{C as k,a as P,b as L,c as _}from"./card-DyZ6_8_C.js";import{D as I,a as D,b as E,c as $,d as T,e as A,f as z}from"./dialog-DFazxlVc.js";import{R as G}from"./RequireRole-DhUlFC_T.js";import{H as W}from"./HelpHint-D0u0IIus.js";import{e as R}from"./recent-auth-step-up-BSg7necW.js";import{L as C}from"./loader-circle-B0UeYsVT.js";import{P as ee}from"./plus-B3ftHR68.js";import{T as O}from"./trash-2-Bb14b2qT.js";import{S as B}from"./save-CP_iJoQe.js";import"./circle-question-mark-CDYWTUQU.js";const se=[["path",{d:"M9 14 4 9l5-5",key:"102s5s"}],["path",{d:"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11",key:"f3b9sd"}]],Z=U("undo-2",se);function te(){const[s,p]=a.useState([]),[f,d]=a.useState(!0),[l,x]=a.useState(null),[c,j]=a.useState([]),[g,y]=a.useState(!1),[h,t]=a.useState(null),o=a.useCallback(async()=>{d(!0);try{const{regions:r}=await N({action:"list"});p(r),r.length>0&&!l&&x(r[0].key)}catch(r){t(r instanceof Error?r.message:String(r))}finally{d(!1)}},[l]),m=a.useCallback(async r=>{y(!0);try{const{zips:i}=await N({action:"get",key:r});j(i)}catch(i){u.error(i instanceof Error?i.message:String(i))}finally{y(!1)}},[]);a.useEffect(()=>{o()},[o]),a.useEffect(()=>{l&&m(l)},[l,m]);const n=s.find(r=>r.key===l)??null;return e.jsxs("div",{className:"mx-auto max-w-6xl p-6 space-y-6",children:[e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{children:[e.jsx(b,{variant:"ghost",size:"sm",asChild:!0,className:"mb-2 -ml-3",children:e.jsxs(F,{to:"/admin/contractor-scrape",className:"gap-1.5",children:[e.jsx(H,{className:"h-3.5 w-3.5"}),"Back to scraper"]})}),e.jsxs("h1",{className:"font-display text-3xl font-bold tracking-tight inline-flex items-center gap-2",children:["Scrape regions",e.
1jsx(W,{title:"Scrape regions & ZIPs",description:"Define geographic regions (groups of ZIP codes) that the contractor scraper iterates through. Each region becomes a selectable target on the Scrape page.",tips:["Add ZIPs in priority order â the scraper runs top-to-bottom.","Edit a region's label to customize how it appears in dropdowns."]})]}),e.jsx("p",{className:"text-muted-foreground mt-1",children:"Manage the regions and ZIP code sets used by the contractor scraper."})]}),e.jsx(ie,{onCreated:o,existingKeys:s.map(r=>r.key)})]}),h&&e.jsx("div",{className:"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",children:h}),e.jsxs("div",{className:"grid md:grid-cols-[280px_1fr] gap-6",children:[e.jsxs(k,{className:"self-start",children:[e.jsx(P,{children:e.jsx(L,{className:"text-base",children:"Regions"})}),e.jsx(_,{className:"p-2",children:f?e.jsxs("div",{className:"p-4 text-sm text-muted-foreground flex items-center gap-2",children:[e.jsx(C,{className:"h-3.5 w-3.5 animate-spin"})," Loadingâ¦"]}):s.length===0?e.jsx("div",{className:"p-4 text-sm text-muted-foreground",children:"No regions yet. Create one above."}):e.jsx("ul",{className:"space-y-1",children:s.map(r=>e.jsx("li",{children:e.jsxs("button",{onClick:()=>x(r.key),className:`w-full text-left rounded-md px-3 py-2 transition-colors ${l===r.key?"bg-accent text-accent-foreground":"hover:bg-muted"}`,children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"font-medium truncate",children:r.label}),e.jsx(M,{variant:"secondary",className:"shrink-0",children:r.zip_count})]}),e.jsxs("div",{className:"text-xs text-muted-foreground mt-0.5",children:[e.jsx("code",{children:r.key})," · ",r.state]})]})},r.key))})})]}),n?e.jsx(ae,{region:n,zips:c,zipsLoading:g,onChanged:async()=>{await o(),l&&await m(l)},onDeleted:async()=>{x(null),j([]),await o()}},n.key):e.jsx(k,{children:e.jsx(_,{className:"p-10 text-center text-muted-foreground",children:"Select a region to manage its ZIPs."})})]})]})}function ae({region:s,zips:p,zipsLoading:f,onChanged:d,onDeleted:l}){const[x,c]=a.useState(s.label),[j,g]=a.useState(s.default_label),[y,h]=a.useState(s.state),[t,o]=a.useState(!1);a.useEffect(()=>{c(s.label),g(s.default_label),h(s.state)},[s]);const m=x!==s.label||j!==s.default_label||y!==s.state,n=async()=>{o(!0);try{await N({action:"update_region",key:s.key,label:x,default_label:j,state:y.toUpperCase()}),u.success("Region updated"),await d()}catch(i){u.error(i instanceof Error?i.message:String(i))}finally{o(!1)}},r=async()=>{if(confirm(`Delete region "${s.label}" and all ${s.zip_count} zips? This cannot be undone.`))try{await R("Confirm your password before deleting this scrape region."),await N({action:"delete_region",key:s.key}),u.success("Region deleted"),await l()}catch(i){u.error(i instanceof Error?i.message:String(i))}};return e.jsxs("div",{className:"space-y-6",children:[e.jsxs(k,{children:[e.jsx(P,{children:e.jsx(L,{className:"text-base",children:"Region settings"})}),e.jsxs(_,{className:"space-y-4",children:[e.jsxs("div",{className:"grid sm:grid-cols-[1fr_1fr_100px] gap-3",children:[e.jsx(v,{label:"Display label",children:e.jsx(w,{value:x,onChange:i=>c(i.target.value)})}),e.jsx(v,{label:"Run label (default)",children:e.jsx(w,{value:j,onChange:i=>g(i.target.value)})}),e.jsx(v,{label:"State",children:e.jsx(w,{value:y,onChange:i=>h(i.target.value.toUpperCase()),maxLength:2})})]}),e.jsx(v,{label:"Key (immutable)",children:e.jsx(w,{value:s.key,disabled:!0})}),e.jsxs("div",{className:"flex items-center justify-between gap-3 pt-2",children:[e.jsxs(b,{variant:"destructive",size:"sm",onClick:r,className:"gap-1.5",children:[e.jsx(O,{className:"h-3.5 w-3.5"}),"Delete region"]}),e.jsxs(b,{onClick:n,disabled:!m||t,className:"gap-1.5",children:[t?e.jsx(C,{className:"h-3.5 w-3.5 animate-spin"}):e.jsx(B,{className:"h-3.5 w-3.5"}),"Save changes"]})]})]})]}),e.jsxs(k,{children:[e.jsxs(P,{className:"flex flex-row items-center justify-between gap-2 space-y-0",children:[e.jsxs(L,{className:"text-base",children:["ZIP c
1odes (",p.length,")"]}),e.jsx(le,{regionKey:s.key,onAdded:d})]}),e.jsx(_,{children:f?e.jsxs("div",{className:"p-4 text-sm text-muted-foreground flex items-center gap-2",children:[e.jsx(C,{className:"h-3.5 w-3.5 animate-spin"})," Loadingâ¦"]}):p.length===0?e.jsx("div",{className:"p-6 text-center text-sm text-muted-foreground",children:'No ZIPs yet. Click "Add ZIPs" to bulk-paste a list.'}):e.jsx("ul",{className:"divide-y divide-border",children:p.map(i=>e.jsx(re,{zip:i,onChanged:d},i.id))})})]}),e.jsx(ne,{regionKey:s.key,onChanged:d})]})}function ne({regionKey:s,onChanged:p}){const[f,d]=a.useState(!1),[l,x]=a.useState(!1),[c,j]=a.useState([]),g=a.useCallback(async()=>{x(!0);try{const{zips:t}=await N({action:"list_deleted",key:s});j(t)}catch(t){u.error(t instanceof Error?t.message:String(t))}finally{x(!1)}},[s]);a.useEffect(()=>{f&&g()},[f,g]);const y=async(t,o)=>{try{await R("Confirm your password before restoring this ZIP assignment."),await N({action:"restore_zip",id:t}),u.success(`Restored ${o}`),await g(),await p()}catch(m){u.error(m instanceof Error?m.message:String(m))}},h=async()=>{if(c.length!==0)try{await R("Confirm your password before restoring these ZIP assignments."),await N({action:"restore_zips",ids:c.map(t=>t.id)}),u.success(`Restored ${c.length} ZIPs`),await g(),await p()}catch(t){u.error(t instanceof Error?t.message:String(t))}};return e.jsxs(k,{children:[e.jsxs(P,{className:"flex flex-row items-center justify-between gap-2 space-y-0",children:[e.jsxs("button",{onClick:()=>d(t=>!t),className:"flex items-center gap-2 text-left hover:opacity-80",children:[f?e.jsx(J,{className:"h-4 w-4"}):e.jsx(V,{className:"h-4 w-4"}),e.jsxs(L,{className:"text-base",children:["Deleted ZIPs",f&&c.length>0?` (${c.length})`:""]})]}),f&&c.length>0&&e.jsxs(b,{size:"sm",variant:"outline",onClick:h,className:"gap-1.5",children:[e.jsx(Z,{className:"h-3.5 w-3.5"}),"Restore all"]})]}),f&&e.jsx(_,{children:l?e.jsxs("div",{className:"p-4 text-sm text-muted-foreground flex items-center gap-2",children:[e.jsx(C,{className:"h-3.5 w-3.5 animate-spin"})," Loadingâ¦"]}):c.length===0?e.jsx("div",{className:"p-4 text-center text-sm text-muted-foreground",children:"No deleted ZIPs."}):e.jsx("ul",{className:"divide-y divide-border",children:c.map(t=>e.jsxs("li",{className:"py-2 grid grid-cols-[80px_1fr_180px_auto] gap-2 items-center text-sm",children:[e.jsx("span",{className:"font-mono font-semibold",children:t.zip}),e.jsx("span",{className:"truncate",children:t.city||e.jsx("span",{className:"text-muted-foreground italic",children:"no city"})}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["deleted ",new Date(t.deleted_at).toLocaleDateString()]}),e.jsxs(b,{size:"sm",variant:"ghost",onClick:()=>y(t.id,t.zip),className:"gap-1.5",children:[e.jsx(Z,{className:"h-3.5 w-3.5"}),"Restore"]})]},t.id))})})]})}function re({zip:s,onChanged:p}){const[f,d]=a.useState(!1),[l,x]=a.useState(s.city),[c,j]=a.useState(s.lat?.toString()??""),[g,y]=a.useState(s.lng?.toString()??""),[h,t]=a.useState(!1),o=async()=>{t(!0);try{await N({action:"update_zip",id:s.id,city:l,lat:c.trim()===""?null:Number(c),lng:g.trim()===""?null:Number(g)}),u.success(`Updated ${s.zip}`),d(!1),await p()}catch(n){u.error(n instanceof Error?n.message:String(n))}finally{t(!1)}},m=async()=>{if(confirm(`Remove ZIP ${s.zip}?`)){t(!0);try{await R("Confirm your password before removing this ZIP assignment."),await N({action:"delete_zip",id:s.id}),await p()}catch(n){u.error(n instanceof Error?n.message:String(n))}finally{t(!1)}}};return f?e.jsxs("li",{className:"py-2 grid grid-cols-[80px_1fr_110px_110px_auto] gap-2 items-center",children:[e.jsx("span",{className:"font-mono font-semibold",children:s.zip}),e.jsx(w,{value:l,onChange:n=>x(n.target.value),placeholder:"City",className:"h-8"}),e.jsx(w,{value:c,onChange:n=>j(n.target.value),placeholder:"Lat",className:"h-8 font-mono text-xs"}),e.jsx(w,{value:g,onChange:n=>y(n.target.value),placeholder:"Lng",className:"h-8 font-mono text-xs"}),e.jsxs("div",{className:"flex gap-1",children:[e.jsx(b,{size:"icon",variant:"ghost",className:"h-8 w-8",onClick:o,disabled:h,children:h?e.jsx(C,{className:"h-3.5 w-3.5 animate-spin"}):e.jsx(B,{className:"h-3.5 w-3.5"})}),e.jsx(b,{size:"icon",variant:"ghost",className:"h-8 w-8",onClick:()=>d(!1),disabled:h,children:e.jsx(Q,{className:"h-3.5 w-3.5"})})]})]}):e.jsxs("li",{className:"py-2 grid grid-cols-[80px_1fr_180px_auto] gap-2 items-center text-sm",children:[e.jsx("span",{className:"font-mono font-semibold",children:s.zip}),e.jsx("span",{className:"truncate",children:s.city||e.jsx("span",{className:"text-muted-foreground italic",children:"no city"})}),e.jsx("span",{className:"text-xs font-mono text-muted-foreground",children:s.lat!=null&&s.lng!=null?`${s.lat.toFixed(3)}, ${s.lng.toFixed(3)}`:"â"}),e.jsxs("div",{className:"flex gap-1 justify-end",children:[e.jsx(b,{size:"sm",variant:"ghost",onClick:()=>d(!0),disabled:h,children:"Edit"}),e.jsx(b,{size:"icon",variant:"ghost",className:"h-8 w-8 text-destructive",onClick:m,disabled:h,children:e.jsx(O,{className:"h-3.5 w-3.5"})})]})]})}function ie({onCreated:s,existingKeys:p}
1){const[f,d]=a.useState(!1),[l,x]=a.useState(""),[c,j]=a.useState(""),[g,y]=a.useState(""),[h,t]=a.useState(!1),o=()=>{x(""),j(""),y("")},m=async()=>{const n=l.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return u.error("Key must be lowercase letters, numbers, dash or underscore");if(p.includes(n))return u.error("That key already exists");if(!/^[A-Z]{2}$/.test(g.toUpperCase()))return u.error("State must be a 2-letter code (e.g. CA, FL, TX)");t(!0);try{await N({action:"create_region",key:n,label:c.trim(),default_label:c.trim(),state:g.toUpperCase()}),u.success("Region created"),o(),d(!1),await s()}catch(r){u.error(r instanceof Error?r.message:String(r))}finally{t(!1)}};return e.jsxs(I,{open:f,onOpenChange:d,children:[e.jsx(D,{asChild:!0,children:e.jsxs(b,{className:"gap-2",children:[e.jsx(ee,{className:"h-4 w-4"}),"New region"]})}),e.jsxs(E,{children:[e.jsxs($,{children:[e.jsx(T,{children:"Create scrape region"}),e.jsxs(A,{children:["A region is a named set of ZIPs the scraper targets together. Example: ",e.jsx("code",{children:"texas"})," / Texas / TX."]})]}),e.jsxs("div",{className:"space-y-4",children:[e.jsx(v,{label:"Key (URL-safe, no spaces)",children:e.jsx(w,{value:l,onChange:n=>x(n.target.value.toLowerCase()),placeholder:"texas"})}),e.jsx(v,{label:"Display label",children:e.jsx(w,{value:c,onChange:n=>j(n.target.value),placeholder:"Texas"})}),e.jsx(v,{label:"State (2-letter)",children:e.jsx(w,{value:g,onChange:n=>y(n.target.value.toUpperCase()),maxLength:2,placeholder:"TX"})})]}),e.jsxs(z,{children:[e.jsx(b,{variant:"ghost",onClick:()=>d(!1),children:"Cancel"}),e.jsxs(b,{onClick:m,disabled:h||!l||!c||!g,children:[h?e.jsx(C,{className:"h-4 w-4 animate-spin mr-2"}):null,"Create"]})]})]})]})}function le({regionKey:s,onAdded:p}){const[f,d]=a.useState(!1),[l,x]=a.useState(""),[c,j]=a.useState(!1),g=async()=>{const y=l.split(/\r?\n/).map(o=>o.trim()).filter(Boolean),h=[],t=[];for(const o of y){const m=o.split(/[,\t]/).map(K=>K.trim()),n=m[0];if(!/^\d{5}$/.test(n)){t.push(`"${o}" â invalid ZIP`);continue}const r=m[1]??"",i=m[2]&&m[2]!==""?Number(m[2]):null,S=m[3]&&m[3]!==""?Number(m[3]):null;if(i!==null&&(Number.isNaN(i)||i<-90||i>90)){t.push(`"${o}" â bad lat`);continue}if(S!==null&&(Number.isNaN(S)||S<-180||S>180)){t.push(`"${o}" â bad lng`);continue}h.push({zip:n,city:r,lat:i,lng:S})}if(t.length)return u.error(`Fix ${t.length} line(s): `+t.slice(0,3).join("; "));if(h.length===0)return u.error("No ZIPs to add");j(!0);try{const{added:o}=await N({action:"add_zips",key:s,zips:h});u.success(`Added/updated ${o??h.length} ZIPs`),x(""),d(!1),await p()}catch(o){u.error(o instanceof Error?o.message:String(o))}finally{j(!1)}};return e.jsxs(I,{open:f,onOpenChange:d,children:[e.jsx(D,{asChild:!0,children:e.jsxs(b,{size:"sm",className:"gap-1.5",children:[e.jsx(q,{className:"h-3.5 w-3.5"}),"Add ZIPs"]})}),e.jsxs(E,{className:"max-w-xl",children:[e.jsxs($,{children:[e.jsx(T,{children:"Add ZIPs"}),e.jsxs(A,{children:["One per line. Format: ",e.jsx("code",{children:"zip, city, lat, lng"})," (city/lat/lng optional). Duplicates are merged."]})]}),e.jsx(Y,{value:l,onChange:y=>x(y.target.value),rows:12,placeholder:`33156, Pinecrest, 25.668, -80.308 233143, South Miami 392660`,className:"font-mono text-sm"}),e.jsxs(z,{children:[e.jsx(b,{variant:"ghost",onClick:()=>d(!1),children:"Cancel"}),e.jsxs(b,{onClick:g,disabled:c||!l.trim(),children:[c?e.jsx(C,{className:"h-4 w-4 animate-spin mr-2"}):null,"Add ZIPs"]})]})]})]})}function v({label:s,children:p}){return e.jsxs("div",{children:[e.jsx("label",{className:"text-xs uppercase tracking-wider font-semibold text-muted-foreground mb-1.5 block",children:s}),p]})}async function N(s){const{supabase:p}=await X(async()=>{const{supabase:c}=await import("./index-CzAkNt2W.js").then(j=>j.ds);return{supabase:c}},[]),{data:{session:f}}=await p.auth.getSession(),d=f?.access_token;if(!d)throw new Error("You must be signed in as a SplashIQ superadmin.");const l=await fetch("/api/public/admin/scrape-regions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${d}`},body:JSON.stringify(s)}),x=await l.json();if(!l.ok)throw new Error(x.error||"Request failed");return x}const be=()=>e.jsx(G,{allowed:["superadmin"],children:e.jsx(te,{})});
3export{be as component};
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.