PageSourceSearch

https://splashiq.ai/assets/estimates._id-DWNxHPPy.js

js splashiq.ai collected 2026-10-05 12:44:37 UTC 31,100 bytes, 5 lines download raw bytes

1import{g as ne,r as x,j as e,B as H,c_ as xe,d as fe,L as J,bQ as _e,E as ye,as as ke,M as ve,W as je,I as E,t as w,s as N}from"./index-CzAkNt2W.js";import{L as D}from"./label-BwWVuV93.js";import{T as we}from"./textarea-CMw1Ny2O.js";import{S as Z,a as K,b as ee,c as te,d as ae}from"./select-CgG1LT6p.js";import{D as Ne,b as Ce,c as Se,d as He,e as Pe,f as Te}from"./dialog-DFazxlVc.js";import{C as Fe}from"./checkbox-G3X53UcF.js";import{E as De}from"./jspdf.es.min-DlZwYFgk.js";import{F as Ee,P as $e,d as Le}from"./PdfPreviewDialog-CYGxYJM5.js";import{R as ze}from"./RequireRole-DhUlFC_T.js";import{S as Re}from"./send-CH5TfEm8.js";import{P as Ae}from"./plus-B3ftHR68.js";import{T as Ie}from"./trash-2-Bb14b2qT.js";import{C as qe}from"./copy-Ba4NJFqU.js";import{C as Me}from"./circle-check-gM2QolJ6.js";import"./loader-circle-B0UeYsVT.js";import"./printer-C3BfX3tt.js";import"./download-C5tX2Uho.js";const Oe=[["path",{d:"M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21",key:"g5wo59"}],["path",{d:"m5.082 11.09 8.828 8.828",key:"1wx5vj"}]],Be=ne("eraser",Oe);const We=[["path",{d:"M13 21h8",key:"1jsn5i"}],["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}]],le=ne("pen-line",We),ie=[{key:"pump",label:"Pump",description:"Variable-speed and single-speed pumps, motors, seals, impellers.",items:[{key:"pump_vsf",label:"Variable-speed pump (install)",laborHours:3,kind:"part"},{key:"pump_single",label:"Single-speed pump (install)",laborHours:2,kind:"part"},{key:"pump_motor",label:"Pump motor replacement",laborHours:1.5,kind:"part"},{key:"pump_seal",label:"Pump seal kit",laborHours:1,kind:"part"},{key:"pump_impeller",label:"Impeller replacement",laborHours:1,kind:"part"},{key:"pump_basket",label:"Strainer basket replacement",laborHours:.25,kind:"part"},{key:"pump_diag",label:"Pump diagnostic",laborHours:.75,kind:"labor"}]},{key:"filter",label:"Filter",description:"Cartridge, DE and sand filters; media, elements, multiport valves.",items:[{key:"filter_cart",label:"Cartridge filter (install)",laborHours:2.5,kind:"part"},{key:"filter_de",label:"DE filter (install)",laborHours:3,kind:"part"},{key:"filter_sand",label:"Sand filter (install)",laborHours:3,kind:"part"},{key:"filter_cart_elem",label:"Cartridge element replacement",laborHours:.5,kind:"part"},{key:"filter_de_grids",label:"DE grids replacement",laborHours:1.5,kind:"part"},{key:"filter_sand_media",label:"Sand media change",laborHours:2.5,kind:"part"},{key:"filter_mpv",label:"Multiport valve replacement",laborHours:1.5,kind:"part"},{key:"filter_clean",label:"Filter cleaning",laborHours:1,kind:"labor"},{key:"filter_de_recharge",label:"DE recharge (powder included)",laborHours:.5,kind:"labor"}]},{key:"heater",label:"Heater",description:"Gas, heat pump, electric heaters and key heater components.",items:[{key:"heater_gas",label:"Gas heater (install)",laborHours:4,kind:"part"},{key:"heater_hp",label:"Heat pump (install)",laborHours:4,kind:"part"},{key:"heater_electric",label:"Electric heater (install)",laborHours:3.5,kind:"part"},{key:"heater_ignitor",label:"Heater ignitor replacement",laborHours:1,kind:"part"},{key:"heater_thermostat",label:"Thermostat / temp sensor",laborHours:.75,kind:"part"},{key:"heater_pswitch",label:"Pressure switch",laborHours:.75,kind:"part"},{key:"heater_hx",label:"Heat exchanger",laborHours:3,kind:"part"},{key:"heater_diag",label:"Heater diagnostic",laborHours:1,kind:"labor"}]},{key:"salt",label:"Salt system / Chlorinator",description:"Salt cells, control boards, inline chlorinators.",items:[{key:"salt_cell",label:"Salt cell replacement",laborHours:1.5,kind:"part"},{key:"salt_board",label:"Salt system control board",laborHours:1,kind:"part"},{key:"salt_flow",label:"Flow switch",laborHours:.5,kind:"part"},{key:"chlor_inline",label:"Inline chlorinator (install)",laborHours:1.5,kind:"part"},{key:"salt_add",label:"Add salt (per 40 lb bag)",laborHours:.25,kind:"part",unit:"bag"}]},{key:"automation",label:"Automation / Controls",description:"Automation panels, actuators, relays, remotes.",items:[{key:"auto_panel",label:"Automation panel (install)",laborHours:4,kind:"part"},{key:"auto_actuator",label:"Actuator valve",laborHours:1.5,kind:"part"},{key:"auto_relay",label:"Relay replacement",laborHours:1,kind:"part"},{key:"auto_remote",label:"Wireless remote pairing",laborHours:.5,kind:"part"}]},{key:"lighting",label:"Lighting",description:"LED pool lights, niche gaskets, transformers.",items:[{key:"light_led",label:"LED pool light (install)",laborHours:2,kind:"part"},{key:"light_gasket",label:"Niche gasket replacement",laborHours:1,kind:"part"},{key:"light_xfmr",label:"Transformer replacement",laborHours:1.5,kind:"part"},{key:"light_bulb",label:"Bulb replacement (incandescent)",laborHours:.5,kind:"part"}]},{key:"plumbing",label:"Plumbing / Valves",description:"Diverter valves, check valves, PVC repair.",items:[{key:"plumb_3way",label:"3-way diverter valve",laborHours:1.5,kind:"part"},{key:"plumb_check",label:"Check valve",laborHours:1,kind:"part"},{key:"plumb_union",label:"Union replacement",laborHours:.75,kind:"part"},{key:"plumb_pvc",label:"PVC repair (per joint)",laborHours:1,kind:"part"}]},{key:"cleaner",label:"Pool Cleaner",description:"Robotic, suction, and pressure-side cleaners.",items:[{key:"clean_robot",label:"Robotic cleaner setup",laborHours:.5,kind:"part"},{key:"clean_suction",label:"Suction cleaner install",laborHours:.5,kind:"part"},{key:"clean_pressure",label:"Pressure cleaner + booster pump",laborHours:2,kind:"part"}]}
1,{key:"service",label:"Service labor (no part)",description:"Standalone labor lines — cleanings, descaling, openings, closings.",items:[{key:"svc_tile",label:"Tile descaling (per linear ft)",laborHours:.1,kind:"labor",unit:"ft"},{key:"svc_acidwash",label:"Acid wash",laborHours:4,kind:"labor"},{key:"svc_drainrefill",label:"Drain & refill",laborHours:3,kind:"labor"},{key:"svc_leakdetect",label:"Leak detection",laborHours:2,kind:"labor"},{key:"svc_greenclean",label:"Green-to-clean treatment",laborHours:6,kind:"labor"},{key:"svc_inspect",label:"Equipment inspection",laborHours:.5,kind:"labor"},{key:"svc_winterize",label:"Winterization / closing",laborHours:2,kind:"labor"},{key:"svc_open",label:"Spring opening",laborHours:2,kind:"labor"},{key:"svc_general",label:"General labor (per hour)",laborHours:1,kind:"labor",unit:"hr"}]}];function Ue(n){for(const g of ie){const a=g.items.find(m=>m.key===n);if(a)return{category:g,item:a}}return null}function Ve(n){const g=[14,165,233];if(!n)return g;const a=n.replace("#","").match(/^([a-f0-9]{6})$/i);if(!a)return g;const m=parseInt(a[1],16);return[m>>16&255,m>>8&255,m&255]}const R=n=>`$${(n/100).toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`;async function Ge(n){try{const g=await fetch(n);if(!g.ok)return null;const a=await g.blob(),m=a.type.includes("png")?"PNG":"JPEG";return{data:await new Promise((i,d)=>{const o=new FileReader;o.onloadend=()=>i(o.result),o.onerror=d,o.readAsDataURL(a)}),format:m}}catch{return null}}async function Xe(n,g,a,m){const t=new De({unit:"pt",format:"letter"}),i=t.internal.pageSize.getWidth(),d=t.internal.pageSize.getHeight(),o=48,[C,F,S]=Ve(m.brand_color);let s=o;const u=m.logo_url?await Ge(m.logo_url):null;if(t.setFillColor(C,F,S),t.rect(0,0,i,6,"F"),u)try{t.addImage(u.data,u.format,o,s,64,64)}catch{}if(t.setFont("helvetica","bold"),t.setFontSize(20),t.setTextColor(20),t.text(m.company_name,u?o+76:o,s+22),t.setFont("helvetica","normal"),t.setFontSize(10),t.setTextColor(120),t.text("Estimate",u?o+76:o,s+38),t.setFont("helvetica","bold"),t.setFontSize(11),t.setTextColor(C,F,S),t.text("ESTIMATE",i-o,s+14,{align:"right"}),t.setFont("helvetica","normal"),t.setTextColor(60),t.setFontSize(10),t.text(`# ${n.number}`,i-o,s+30,{align:"right"}),t.text(`Issued ${new Date(n.created_at).toLocaleDateString()}`,i-o,s+44,{align:"right"}),n.valid_until&&t.text(`Valid until ${new Date(n.valid_until).toLocaleDateString()}`,i-o,s+58,{align:"right"}),s+=88,a){t.setFont("helvetica","bold"),t.setFontSize(10),t.setTextColor(120),t.text("PREPARED FOR",o,s),s+=14,t.setFont("helvetica","normal"),t.setFontSize(11),t.setTextColor(20),a.customer_name&&(t.text(a.customer_name,o,s),s+=14),a.address&&(t.text(a.address,o,s),s+=14);const p=[[a.city,a.state].filter(Boolean).join(", "),a.zip].filter(Boolean).join(" ");p&&(t.text(p,o,s),s+=14),s+=8}const h=o,b=i-o-240,f=i-o-160,k=i-o-90,I=i-o;t.setDrawColor(220),t.setFillColor(245,247,250),t.rect(o,s-4,i-o*2,22,"F"),t.setFont("helvetica","bold"),t.setFontSize(9),t.setTextColor(80),t.text("DESCRIPTION",h+6,s+11),t.text("QTY",b,s+11,{align:"right"}),t.text("PART",f,s+11,{align:"right"}),t.text("LABOR",k,s+11,{align:"right"}),t.text("AMOUNT",I-6,s+11,{align:"right"}),s+=24,t.setFont("helvetica","normal"),t.setFontSize(10),t.setTextColor(30);for(const p of g){s>d-o-200&&(t.addPage(),s=o);const W=t.splitTextToSize(`${p.description}${p.system_category?`  [${p.system_category}]`:""}`,b-h-16);t.text(W,h+6,s+4),t.text(String(p.qty),b,s+4,{align:"right"}),t.text(p.part_cents?R(p.part_cents):"—",f,s+4,{align:"right"}),t.text(p.labor_hours?`${p.labor_hours}h × ${R(p.labor_rate_cents)}`:"—",k,s+4,{align:"right"}),t.text(R(p.line_total_cents),I-6,s+4,{align:"right"});const q=Math.max(16,W.length*12+4);s+=q,t.setDrawColor(235),t.line(o,s,i-o,s),s+=6}s+=8;const $=i-o-220;if(t.setFont("helvetica","normal"),t.setFontSize(10),t.setTextColor(80),t.text("Subtotal",$,s),t.text(R(n.subtotal_cents),i-o,s,{align:"right"}),s+=14,n.tax_cents>0&&(t.text("Tax",$,s),t.text(R(n.tax_cents),i-o,s,{align:"right"}),s+=14),t.setDrawColor(C,F,S),t.setLineWidth(1.5),t.line($,s,i-o,s),t.setLineWidth(.5),s+=16,t.setFont("helvetica","bold"),t.setFontSize(13),t.setTextColor(20),t.text("Total",$,s),t.text(R(n.total_cents),i-o,s,{align:"right"}),s+=24,n.notes){t.setFont("helvetica","bold"),t.setFontSize(10),t.setTextColor(80),t.text("Notes",o,s),s+=14,t.setFont("helvetica","normal"),t.setTextColor(40);
1const p=t.splitTextToSize(n.notes,i-o*2);t.text(p,o,s),s+=p.length*12+12}s>d-o-140&&(t.addPage(),s=o),t.setDrawColor(220),t.rect(o,s,i-o*2,120),t.setFont("helvetica","bold"),t.setFontSize(10),t.setTextColor(60),t.text("ACCEPTANCE",o+10,s+16),t.setFont("helvetica","normal"),t.setFontSize(9),t.setTextColor(80);const B=t.splitTextToSize(`By signing below, the customer authorizes ${m.company_name} to perform the work described on this estimate at the stated price. Estimate prices are valid until ${n.valid_until?new Date(n.valid_until).toLocaleDateString():"30 days from issue"}. Additional work discovered during service will be quoted separately.`,i-o*2-20);t.text(B,o+10,s+32);const P=s+90;if(t.setDrawColor(120),t.line(o+10,P,o+260,P),t.setFontSize(8),t.setTextColor(120),t.text("Customer signature",o+10,P+10),t.line(i-o-200,P,i-o-10,P),t.text("Date",i-o-200,P+10),n.signature_data_url&&n.signature_name){try{t.addImage(n.signature_data_url,"PNG",o+10,P-28,180,28)}catch{}t.setFontSize(9),t.setTextColor(40),t.text(n.signature_name,o+10,P-32),n.signed_at&&t.text(new Date(n.signed_at).toLocaleString(),i-o-200,P-4)}const L=t.getNumberOfPages();for(let p=1;p<=L;p++)t.setPage(p),t.setFontSize(8),t.setTextColor(140),m.report_footer&&t.text(m.report_footer,o,d-18),t.text(`Page ${p} of ${L}`,i-o,d-18,{align:"right"});return t.output("blob")}function Qe({value:n,onChange:g,height:a=140}){const m=x.useRef(null),t=x.useRef(!1),[i,d]=x.useState(!1);x.useEffect(()=>{const u=m.current;if(!u)return;const h=u.getContext("2d");if(!h)return;const b=window.devicePixelRatio||1,f=u.getBoundingClientRect();if(u.width=f.width*b,u.height=f.height*b,h.scale(b,b),h.fillStyle="#ffffff",h.fillRect(0,0,f.width,f.height),h.lineCap="round",h.lineJoin="round",h.lineWidth=2,h.strokeStyle="#0f172a",n){const k=new Image;k.onload=()=>{h.drawImage(k,0,0,f.width,f.height),d(!0)},k.src=n}else d(!1)},[n]);function o(u){const b=m.current.getBoundingClientRect();return{x:u.clientX-b.left,y:u.clientY-b.top}}function C(u){const h=m.current;if(!h)return;t.current=!0,u.target.setPointerCapture?.(u.pointerId);const{x:b,y:f}=o(u),k=h.getContext("2d");k.beginPath(),k.moveTo(b,f)}function F(u){if(!t.current)return;const h=m.current;if(!h)return;const{x:b,y:f}=o(u),k=h.getContext("2d");k.lineTo(b,f),k.stroke(),d(!0)}function S(){if(!t.current)return;t.current=!1;const u=m.current;u&&g(u.toDataURL("image/png"))}function s(){const u=m.current;if(!u)return;const h=u.getContext("2d"),b=u.getBoundingClientRect();h.fillStyle="#ffffff",h.fillRect(0,0,b.width,b.height),d(!1),g(null)}return e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"rounded-lg border border-input bg-background",style:{height:a},children:e.jsx("canvas",{ref:m,className:"w-full h-full touch-none rounded-lg",onPointerDown:C,onPointerMove:F,onPointerUp:S,onPointerLeave:S,onPointerCancel:S})}),e.jsxs("div",{className:"flex items-center justify-between text-xs text-muted-foreground",children:[e.jsx("span",{children:i?"Signature captured":"Sign with your mouse or finger"}),e.jsxs(H,{type:"button",variant:"ghost",size:"sm",className:"h-7 gap-1",onClick:s,disabled:!i,children:[e.jsx(Be,{className:"h-3 w-3"})," Clear"]})]})]})}const A=n=>`$${(n/100).toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:2})}`;function X(n){const g=Math.round(n.labor_hours*n.labor_rate_cents);return Math.round(n.qty*(n.part_cents+g))}function Ye(){const{id:n}=xe.useParams(),g=fe(),[a,m]=x.useState(null),[t,i]=x.useState([]),[d,o]=x.useState(null),[C,F]=x.useState([]),[S,s]=x.useState([]),[u,h]=x.useState(!0),[b,f]=x.useState(!1),[k,I]=x.useState("0"),[$,B]=x.useState(!1),[P,L]=x.useState(!1),[p,W]=x.useState(null);async function q(){h(!0);const{data:r}=await N.auth.getUser();if(!r.user){w.error("Sign in to open saved estimates."),h(!1);return}const{data:l}=await N.from("profiles").select("company_id").eq("id",r.user.id).single(),c=l?.company_id;if(!c){h(!1);return}const[{data:_},{data:j},{data:y},{data:T},{data:Y}]=await Promise.all([N.from("companies").select("id, name, labor_rate_cents, logo_url, brand_color, report_footer, report_cover_enabled, report_include_photos, report_include_notes").eq("id",c).single(),N.from("estimates").select("*").eq("id",n).single(),N.from("estimate_lines").select("*").eq("estimate_id",n).order("sort_order"),N.from("customers").select("id, full_name, email").eq("company_id",c).order("full_name"),N.from("properties").select("id, address, customer_id").eq("company_id",c).order("address")]);if(_&&o(_),j&&(m(j),I((j.tax_bps/100).toString())),i((y??[]).map((O,be)=>({...O,sort_order:O.sort_order??be}))),F(T??[]),s(Y??[]),j?.property_id){const{data:O}=await N.from("properties").select("share_token").eq("id",j.property_id).single();O?.share_token&&W(`${window.location.origin}/portal/${O.share_token}#estimates`)}h(!1)}x.useEffect(()=>{q()},[n]);const re=x.useMemo(()=>a?S.filter(r=>
1r.customer_id===a.customer_id):[],[S,a]),M=x.useMemo(()=>t.reduce((r,l)=>r+X(l),0),[t]),Q=Math.max(0,Math.round((parseFloat(k)||0)*100)),U=Math.round(M*Q/1e4),z=M+U,v=a?.status==="approved"||a?.status==="declined";function ce(r){const l=Ue(r);if(!l||!d)return;const c=l.item.kind==="labor";i(_=>[..._,{kind:c?"labor":"system_part",system_category:l.category.label,catalog_key:r,description:l.item.label,qty:1,part_cents:l.item.defaultPartCents??0,labor_hours:l.item.laborHours,labor_rate_cents:d.labor_rate_cents,sort_order:_.length}])}function de(){d&&i(r=>[...r,{kind:"custom",system_category:null,catalog_key:null,description:"",qty:1,part_cents:0,labor_hours:0,labor_rate_cents:d.labor_rate_cents,sort_order:r.length}])}function V(r,l){i(c=>c.map((_,j)=>j===r?{..._,...l}:_))}async function G(r){if(!a)return!1;if(t.some(l=>!l.description.trim()))return w.error("Every line needs a description"),!1;f(!0);try{if(await N.from("estimate_lines").delete().eq("estimate_id",a.id),t.length){const _=d.id,j=t.map((T,Y)=>({estimate_id:a.id,company_id:_,kind:T.kind,system_category:T.system_category,catalog_key:T.catalog_key,description:T.description.trim(),qty:T.qty,part_cents:T.part_cents,labor_hours:T.labor_hours,labor_rate_cents:T.labor_rate_cents,line_total_cents:X(T),sort_order:Y})),{error:y}=await N.from("estimate_lines").insert(j);if(y)throw y}const l={subtotal_cents:M,tax_bps:Q,tax_cents:U,total_cents:z,notes:a.notes?.trim()||null,valid_until:a.valid_until,property_id:a.property_id,customer_id:a.customer_id};r?.status&&(l.status=r.status,r.status==="sent"&&(l.sent_at=new Date().toISOString()));const{error:c}=await N.from("estimates").update(l).eq("id",a.id);if(c)throw c;return w.success(r?.afterMsg??"Estimate saved"),await q(),!0}catch(l){return w.error(l instanceof Error?l.message:"Save failed"),!1}finally{f(!1)}}async function ue(){if(!a||!a.property_id){w.error("Pick a property first — the homeowner needs a portal link");return}if(t.length===0||z===0){w.error("Add at least one line");return}if(await G({status:"sent",afterMsg:"Estimate published to the homeowner portal"})&&p)try{await navigator.clipboard.writeText(p),w.success("Portal link copied to clipboard")}catch{}}function me(){if(!a)return;const r=C.find(j=>j.id===a.customer_id),l=r?.email??"",c=`Estimate ${a.number} from ${d?.name??"your pool service"}`,_=[`Hi ${r?.full_name?.split(" ")[0]??"there"},`,"",`Your estimate ${a.number} is ready. Total: ${A(z)}.`,p?`
2View and approve online:
3${p}
4`:"","Thanks,",d?.name??""].filter(Boolean).join(`
5`);window.location.href=`mailto:${encodeURIComponent(l)}?subject=${encodeURIComponent(c)}&body=${encodeURIComponent(_)}`}function pe(){return{company_name:d?.name??"Your Company",logo_url:d?.logo_url??null,brand_color:d?.brand_color??"#0ea5e9",report_footer:d?.report_footer??null,report_cover_enabled:d?.report_cover_enabled??!0,report_include_photos:d?.report_include_photos??!0,report_include_notes:d?.report_include_notes??!0}}async function se(){if(!a)throw new Error("No estimate");const r=a.property_id?S.find(y=>y.id===a.property_id):null,l=C.find(y=>y.id===a.customer_id),c=r?await N.from("properties").select("address, city, state, zip").eq("id",r.id).single():null,_={number:a.number,status:a.status,subtotal_cents:M,tax_cents:U,total_cents:z,notes:a.notes,valid_until:a.valid_until,signature_name:a.signature_name,signature_data_url:a.signature_data_url,signed_at:a.signed_at,created_at:a.created_at},j=t.map(y=>({description:y.description,qty:y.qty,part_cents:y.part_cents,labor_hours:y.labor_hours,labor_rate_cents:y.labor_rate_cents,line_total_cents:X(y),system_category:y.system_category,kind:y.kind}));return Xe(_,j,{address:c?.data?.address??null,city:c?.data?.city??null,state:c?.data?.state??null,zip:c?.data?.zip??null,customer_name:l?.full_name??null,customer_email:l?.email??null},pe())}async function he(){try{const r=await se();Le(r,`estimate-${a?.number??"draft"}.pdf`)}catch(r){w.error(r instanceof Error?r.message:"PDF failed")}}async function ge(){if(a){if(a.work_order_id){w.info("This estimate already has a work order"),g({to:"/work-orders"});return}if(t.length===0){w.error("Add at least one line first");return}f(!0);try{if(!await G())return;const{data:l,error:c}=await N.from("work_orders").insert({company_id:d.id,property_id:a.property_id,title:`Estimate ${a.number}`,description:`Created from estimate ${a.number}`,priority:"normal",status:"open",quote_amount_cents:z,customer_decision:"approved"}).select("id").single();if(c)throw c;const{error:_}=await N.from("estimates").update({work_order_id:l.id}).eq("id",a.id);if(_)throw _;w.success("Work order created — open in Work Orders"),g({to:"/work-orders"})}catch(r){w.error(r instanceof Error?r.message:"Could not create work order")}finally{f(!1)}}}return u||!a||!d?e.jsx("div",{className:"p-8 text-muted-foreground",children:"Loading…"}):e.jsxs("div",{className:"p-6 md:p-8 max-w-5xl",children:[e.jsx("div",{className:"mb-4",children:e.jsxs(J,{to:"/estimates",className:"text-sm text-muted-foreground inline-flex items-center gap-1 hover:text-foreground",children:[e.jsx(_e,{className:"h-3.5 w-3.5"})," Back to estimates"]})}),e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3 mb-6",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-muted-foreground",children:"Estimate"}),e.jsx("h1",{className:"font-display text-3xl font-bold",children:a.number}),e.jsxs("div",{className:"mt-1 inline-flex items-center gap-2",children:[e.jsx(Je,{status:a.status}),a.work_order_id&&e.jsxs(J,{to:"/work-orders",className:"text-xs text-primary inline-flex items-center gap-1 hover:underline",children:[e.jsx(ye,{className:"h-3 w-3"})," Open work order"]})]})]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsxs(H,{variant:"outline",className:"gap-1.5",onClick:()=>B(!0),children:[e.jsx(ke,{className:"h-4 w-4"})," Preview PDF"]}),e.jsxs(H,{variant:"outline",className:"gap-1.5",onClick:he,children:[e.jsx(Ee,{className:"h-4 w-4"})," Download"]}),e.jsxs(H,{variant:"outline",className:"gap-1.5",onClick:me,disabled:!a.customer_id,children:[e.jsx(ve,{className:"h-4 w-4"})," Email"]}),!v&&e.jsxs(H,{variant:"outline",className:"gap-1.5",onClick:()=>L(!0),children:[e.jsx(le,{className:"h-4 w-4"})," Accept in field"]}),!a.work_order_id&&e.jsxs(H,{variant:"outline",className:"gap-1.5",onClick:ge,disabled:b,children:[e.jsx(je,{className:"h-4 w-4"})," Create work order"]}
5),!v&&e.jsxs(H,{className:"gap-1.5",onClick:ue,disabled:b,children:[e.jsx(Re,{className:"h-4 w-4"})," ",a.status==="sent"?"Copy portal link again":"Publish & copy link"]})]})]}),e.jsxs("div",{className:"grid md:grid-cols-3 gap-6",children:[e.jsxs("div",{className:"md:col-span-2 space-y-4",children:[e.jsxs("section",{className:"rounded-xl border border-border bg-card p-4 grid sm:grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx(D,{children:"Customer"}),e.jsxs(Z,{value:a.customer_id,onValueChange:r=>m({...a,customer_id:r,property_id:null}),disabled:v,children:[e.jsx(K,{children:e.jsx(ee,{})}),e.jsx(te,{children:C.map(r=>e.jsx(ae,{value:r.id,children:r.full_name},r.id))})]})]}),e.jsxs("div",{children:[e.jsx(D,{children:"Property"}),e.jsxs(Z,{value:a.property_id??"",onValueChange:r=>m({...a,property_id:r||null}),disabled:v||re.length===0,children:[e.jsx(K,{children:e.jsx(ee,{placeholder:"Select property"})}),e.jsx(te,{children:re.map(r=>e.jsx(ae,{value:r.id,children:r.address},r.id))})]})]}),e.jsxs("div",{children:[e.jsx(D,{children:"Valid until"}),e.jsx(E,{type:"date",value:a.valid_until??"",onChange:r=>m({...a,valid_until:r.target.value||null}),disabled:v})]}),e.jsxs("div",{children:[e.jsx(D,{children:"Tax %"}),e.jsx(E,{type:"number",min:0,max:20,step:"0.01",value:k,onChange:r=>I(r.target.value),disabled:v})]})]}),e.jsxs("section",{className:"rounded-xl border border-border bg-card p-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2 mb-3",children:[e.jsx(D,{className:"mr-1",children:"Quick-add from catalog"}),e.jsxs(Z,{onValueChange:ce,disabled:v,children:[e.jsx(K,{className:"w-[260px]",children:e.jsx(ee,{placeholder:"Pick a system / service…"})}),e.jsx(te,{className:"max-h-80",children:ie.map(r=>e.jsxs("div",{children:[e.jsx("div",{className:"px-2 py-1.5 text-[10px] uppercase tracking-wide text-muted-foreground font-semibold",children:r.label}),r.items.map(l=>e.jsxs(ae,{value:l.key,children:[l.label,l.laborHours?` · ${l.laborHours}h`:""]},l.key))]},r.key))})]}),e.jsxs(H,{variant:"outline",size:"sm",onClick:de,disabled:v,className:"gap-1",children:[e.jsx(Ae,{className:"h-3.5 w-3.5"})," Custom line"]}),e.jsxs("div",{className:"ml-auto text-xs text-muted-foreground",children:["Labor rate: ",e.jsxs("span",{className:"font-semibold text-foreground",children:[A(d.labor_rate_cents),"/hr"]})," ",e.jsx(J,{to:"/branding",className:"underline",children:"edit"})]})]}),t.length===0?e.jsx("div",{className:"rounded-lg border border-dashed border-border p-8 text-center text-sm text-muted-foreground",children:"No line items yet. Pick a system above or add a custom line."}):e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"hidden md:grid grid-cols-12 gap-2 text-[10px] uppercase tracking-wide text-muted-foreground font-semibold px-1",children:[e.jsx("div",{className:"col-span-5",children:"Description"}),e.jsx("div",{className:"col-span-1 text-right",children:"Qty"}),e.jsx("div",{className:"col-span-2 text-right",children:"Part $"}),e.jsx("div",{className:"col-span-2 text-right",children:"Labor hrs"}),e.jsx("div",{className:"col-span-2 text-right",children:"Line total"})]}),t.map((r,l)=>e.jsxs("div",{className:"grid grid-cols-12 gap-2 items-start rounded-lg border border-border p-2",children:[e.jsxs("div",{className:"col-span-12 md:col-span-5 space-y-1",children:[e.jsx(E,{value:r.description,onChange:c=>V(l,{description:c.target.value}),disabled:v,placeholder:"Description"}),r.system_category&&e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-muted-foreground px-1",children:r.system_category})]}),e.jsx("div",{className:"col-span-3 md:col-span-1",children:e.jsx(E,{type:"number",min:1,step:"0.01",value:r.qty,onChange:c=>V(l,{qty:parseFloat(c.target.value)||1}),disabled:v,className:"text-right"})}),e.jsx("div",{className:"col-span-3 md:col-span-2",children:e.jsx(E,{type:"number",min:0,step:"0.01",value:r.part_cents?(r.part_cents/100).toString():"",onChange:c=>V(l,{part_cents:Math.round((parseFloat(c.target.value)||0)*100)}),disabled:v,className:"text-right",placeholder:"0.00"})}),e.jsx("div",{className:"col-span-3 md:col-span-2",children:e.jsx(E,{type:"number",min:0,step:"0.25",value:r.labor_hours||"",onChange:c=>V(l,{labor_hours:parseFloat(c.target.value)||0}),disabled:v,className:"text-right",placeholder:"0"})}),e.jsx("div",{className:"col-span-2 md:col-span-1 text-right tabular-nums font-semibold pt-2",children:A(X(r))}),e.jsx("div",{className:"col-span-1 md:col-span-1 flex justify-end",children:e.jsx(H,{size:"icon",variant:"ghost",onClick:()=>i(c=>c.filter((_,j)=>j!==l)),disabled:v,children:e.jsx(Ie,{className:"h-4 w-4"})})})]},l))]})]}),e.jsxs("section",{className:"rounded-xl border border-border bg-card p-4",children:[e.jsx(D,{children:"Notes for the homeowner"}),e.jsx(we,{rows:3,value:a.notes??"",onChange:r=>m({...a,notes:r.target.value}),disabled:v,placeholder:"Timing, warranty, what's included/excluded…"})]})]}),e.jsx("div",{className:"space-y-4",children:e.jsxs("section",{className:"rounded-xl border border-border bg-card p-4 sticky top-20",children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-muted-foreground",children:"Totals"}),e.jsxs("div",{className:"mt-2 space-y-1.5 text-sm",children:[e.jsx(oe,{label:"Subtotal",value:A(M)}),e.jsx(oe,{label:`Tax (${(Q/100).toFixed(2)}%)`,value:A(U)}),e.jsx("div",{className:"border-t border-border my-2"}),e.jsxs("div",{className:"flex items-baseline justify-between",children:[e.jsx("span",{className:"text-sm font-semibold",children:"Total"}),e.jsx("span",{className:"font-display text-2xl font-bold tabular-nums",children:A(z)})]})]}),!v&&e.jsx(H,{variant:"outline",className:"w-full mt-3",onClick:()=>G(),disabled:b,children:b?"Saving…":"Save draft"}),p&&e.jsxs("div",{className:"mt-3 rounded-md border border-dashed border-border p-2 text-xs text-muted-foreground",children:[e.jsx("div",{className:"font-semibold text-foreground mb-1",children:"Homeowner link"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate flex-1",children:p}),e.jsx(H,{size:"sm",variant:"ghost",className:"h-6 px-2",onClick:()=>{navigator.clipboard.writeText(p),w.success("Copied")},children:e.jsx(qe,{className:"h-3 w-3"})})]})]}),a.status==="approved"&&a.signature_name&&e.jsxs("div",{className:"mt-3 rounded-md border border-emerald-200 bg-emerald-50 p-2 text-xs",children:[e.jsxs("div",{className:"font-semibold text-emerald-800 inline-flex items-center gap-1",children:[e.jsx(Me,{className:"h-3.5 w-3.5"})," Signed by ",a.signature_name]}),a.signed_at&&e.jsxs("div",{className:"text-emerald-700 mt-0.5",children:[new Date(a.signed_at).toLocaleStr
5ing()," · ",a.signed_method?.replace("_"," ")]}),a.signature_data_url&&e.jsx("img",{src:a.signature_data_url,alt:"Signature",className:"mt-2 max-h-16 bg-white rounded border border-emerald-200"})]})]})})]}),e.jsx($e,{open:$,onOpenChange:B,title:`Estimate — ${a.number}`,filename:`estimate-${a.number}.pdf`,build:se}),e.jsx(Ze,{open:P,onOpenChange:L,companyName:d.name,onConfirm:async(r,l)=>{await G();const{error:c}=await N.from("estimates").update({status:"approved",approved_at:new Date().toISOString(),signature_name:r,signature_data_url:l,signed_at:new Date().toISOString(),signed_method:"in_field"}).eq("id",a.id);if(c){w.error(c.message);return}w.success("Estimate accepted in field — work order created"),L(!1),await q(),g({to:"/estimates/$id",params:{id:a.id}})}})]})}function oe({label:n,value:g}){return e.jsxs("div",{className:"flex justify-between text-muted-foreground",children:[e.jsx("span",{children:n}),e.jsx("span",{className:"text-foreground tabular-nums",children:g})]})}function Je({status:n}){const g={draft:"bg-muted text-foreground",sent:"bg-primary/15 text-primary",approved:"bg-emerald-100 text-emerald-700",declined:"bg-destructive/10 text-destructive",expired:"bg-amber-100 text-amber-700"};return e.jsx("span",{className:`text-[10px] uppercase tracking-wide px-2 py-0.5 rounded-full font-semibold ${g[n]}`,children:n})}function Ze({open:n,onOpenChange:g,companyName:a,onConfirm:m}){const[t,i]=x.useState(""),[d,o]=x.useState(!1),[C,F]=x.useState(null),[S,s]=x.useState(!1);return x.useEffect(()=>{n||(i(""),o(!1),F(null))},[n]),e.jsx(Ne,{open:n,onOpenChange:g,children:e.jsxs(Ce,{children:[e.jsxs(Se,{children:[e.jsx(He,{children:"Accept estimate in field"}),e.jsx(Pe,{children:"Have the homeowner sign on-site. We'll record the signature, timestamp, and create the work order."})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx(D,{children:"Homeowner full name"}),e.jsx(E,{value:t,onChange:u=>i(u.target.value),placeholder:"As they'd sign it"})]}),e.jsxs("div",{children:[e.jsx(D,{children:"Signature"}),e.jsx(Qe,{value:C,onChange:F})]}),e.jsxs("label",{className:"flex items-start gap-2 text-sm",children:[e.jsx(Fe,{checked:d,onCheckedChange:u=>o(!!u),className:"mt-0.5"}),e.jsxs("span",{className:"text-muted-foreground",children:["I authorize ",e.jsx("span",{className:"font-medium text-foreground",children:a})," to perform the work described in this estimate at the stated price. Additional work discovered during service will be quoted separately."]})]})]}),e.jsxs(Te,{children:[e.jsx(H,{variant:"outline",onClick:()=>g(!1),children:"Cancel"}),e.jsxs(H,{disabled:!t.trim()||!d||!C||S,onClick:async()=>{s(!0);try{await m(t.trim(),C)}finally{s(!1)}},className:"gap-1.5",children:[e.jsx(le,{className:"h-4 w-4"})," Accept & create work order"]})]})]})})}const bt=()=>e.jsx(ze,{allowed:["owner","office","technician"],children:e.jsx(Ye,{})});export{bt 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.