PageSourceSearch

https://estimatekit.com/assets/TemplateBuilder-dxL1b4LL.js

js estimatekit.com collected 2026-10-04 03:34:33 UTC 18,844 bytes, 1 lines download raw bytes

1import{ab as Qe,k as Ue,r as i,aa as We,K as o,o as p,j as t,Q as le,B as f,z as ce,I as T,ah as W,g as $e,an as Ve}from"./index-CbvCveKN.js";import{D as de,P as me,L as He,h as Ke,i as Ge,j as Ye,k as Je,n as pe}from"./PageContainer-Bx7SVsdy.js";import{P as Xe}from"./PageHeader-ohyr15TW.js";import{F as ue}from"./FilterChips-CYAfenlT.js";import{E as xe}from"./EmptyState-CvKdOS6b.js";import{S as Ze}from"./SearchInput-CwtZ1bZ_.js";import{T as et}from"./textarea-BGGCYgxW.js";import{S as he,a as ge,b as fe,c as ye,d as F}from"./select-C3XbO4_1.js";import{M as tt,a as at}from"./MaterialCard-v-Ll-5t7.js";import{f as st,l as rt,T as q,t as be,c as it,e as nt,b as ot}from"./templates-DNmeeYY2.js";import{F as lt}from"./FolderNameDialog-OCuqrjWx.js";import{C as ve}from"./copy-Bx-FLJpv.js";import{C as je}from"./check-Bjw6cCk6.js";import{P as ct}from"./play-cOhvd8eb.js";import{E as dt}from"./ellipsis-G-oFyayU.js";import{P as Ne}from"./plus-kYCBPFxU.js";import{T as mt}from"./trash-2-CEFy1AEt.js";import{S as pt}from"./search-fqqyqjqf.js";import"./index-Jf4OHjGl.js";import"./chevron-right-f4vsMuAf.js";import"./triangle-alert-BJBV5PBt.js";import"./en-US-wdCzBwgh.js";import"./file-text-DDhZ7a0t.js";import"./users-BkZzL3s4.js";import"./layout-dashboard-rEexJXnQ.js";import"./ek-mark-light-Y4_w-cFG.js";import"./parseISO-B0WEjCD3.js";import"./plug-BAhKXAxN.js";import"./chevron-down-yKD6BMcU.js";import"./GrainGradient-kJbOtAvv.js";import"./index-CKxf6gP2.js";import"./index-DlFerWUd.js";const ut=["Accessories","Air Quality","Chemicals","Controls","Drainage","Ductwork","Electrical","Equipment","Filtration","Hardware","Insulation","Labor","Maintenance","Refrigerant","Refrigerant Lines","Safety","Service","Supplies","Venting","Wiring"],xt=j=>({Equipment:"bg-blue-500 text-white",Refrigerant:"bg-cyan-500 text-white","Refrigerant Lines":"bg-cyan-600 text-white",Electrical:"bg-yellow-500 text-white",Wiring:"bg-yellow-600 text-white",Labor:"bg-purple-500 text-white",Service:"bg-purple-600 text-white",Controls:"bg-emerald-500 text-white"})[j]||"bg-slate-600 text-white",Ht=()=>{var ne,oe;const{id:j}=Qe(),x=Ue(),[_e,we]=i.useState(!0),{requireWrite:E}=We(),[l,D]=i.useState(null),[$,A]=i.useState([]),[Ce,V]=i.useState(!1),[M,H]=i.useState(!1),[y,K]=i.useState([]),[h,b]=i.useState([]),[c,L]=i.useState(""),[N,ke]=i.useState([]),[P,Se]=i.useState(""),[C,Te]=i.useState("All"),[Fe,I]=i.useState(!1),[B,G]=i.useState(""),[Y,J]=i.useState(""),[X,Z]=i.useState("1"),[ee,qe]=i.useState("Labor"),[te,ae]=i.useState(!1),[_,O]=i.useState(""),[R,k]=i.useState({}),S=i.useCallback(async()=>{if(j)try{const e=await st(j);D(e.template),rt().then(A).catch(()=>A([])),K(e.tiers),b(e.items),O(e.template.name),k(Object.fromEntries(e.tiers.map(a=>[a.id,{name:a.tier_name,description:a.tier_description||""}]))),L(a=>{var s;return a&&e.tiers.some(r=>r.id===a)?a:((s=e.tiers[0])==null?void 0:s.id)||""})}catch(e){console.error("Error loading template:",e),o({title:"Couldn't load template",variant:"destructive"}),x("/templates")}finally{we(!1)}},[j,x]);i.useEffect(()=>{S(),p.from("materials").select("id, item_name, category, unit, cost, unit_cost, image_url, user_material_prices!left(custom_price)").order("item_name").then(({data:e})=>ke((e||[]).map(({user_material_prices:a,...s})=>{var r;return{...s,cost:((r=a==null?void 0:a[0])==null?void 0:r.custom_price)??s.cost}})))},[S]);const m=y.find(e=>e.id===c)||null,u=i.useMemo(()=>h.filter(e=>e.template_tier_id===c),[h,c]),g=y.length>1,Ee=i.useMemo(()=>{const e=Array.from(new Set(N.map(a=>a.category))).sort();return[{value:"All",label:"All"},...e.map(a=>({value:a,label:a}))]},[N]),se=i.useMemo(()=>{const e=P.trim().toLowerCase();return N.filter(a=>(C==="All"||a.category===C)&&(!e||a.item_name.toLowerCase().includes(e)||a.category.toLowerCase().includes(e)))},[N,P,C]),De=async()=>{if(!l||_.trim()===l.name||_.trim().length<2){O((l==null?void 0:l.name)||"");return}const{error:e}=await p.from("templates").update({name:_.trim()}).eq("id",l.id);e?o({title:"Couldn't rename template",variant:"destructive"}):D({...l,name:_.trim()})},z=async e=>{if(!(!l||!E())){D({...l,folder_id:(e==null?void 0:e.id)??null,category:(e==null?void 0:e.name)??"Custom"});try{await ot(l.id,e)}catch(a){o({title:"Couldn't move the template",description:a instanceof Error?a.message:"Try again.",variant:"destructive"})}}},Ae=e=>{if(e==="__new__"){V(!0);return}if(e==="__none__"){z(null);return}const a=$.find(s=>
1s.id===e);a&&z(a)},Me=async e=>{if(E())try{const a=await it(e);A(s=>s.some(r=>r.id===a.id)?s:[...s,a]),await z(a)}catch(a){o({title:"Couldn't create the folder",description:a instanceof Error?a.message:"Try again.",variant:"destructive"})}},Le=async()=>{if(!(!l||M||!E())){H(!0);try{const e=await nt(l.id);o({title:"Template duplicated",description:"Swap what is different and rename it."}),x(`/templates/${e}/edit`)}catch(e){o({title:"Couldn't duplicate the template",description:e instanceof Error?e.message:"Try again.",variant:"destructive"})}finally{H(!1)}}},re=async(e,a)=>{const s=R[e],r=y.find(v=>v.id===e);if(!s||!r)return;const n=a==="tier_name"?s.name.trim():s.description.trim(),d=a==="tier_name"?r.tier_name:r.tier_description||"";if(n===d)return;if(a==="tier_name"&&n.length===0){k(v=>({...v,[e]:{...v[e],name:r.tier_name}}));return}const{error:Q}=await p.from("template_tiers").update({[a]:a==="tier_description"?n||null:n}).eq("id",e);Q?o({title:"Couldn't save option",variant:"destructive"}):K(v=>v.map(U=>U.id===e?{...U,[a]:a==="tier_description"?n||null:n}:U))},Pe=async e=>{if(!c)return;const a=u.find(n=>n.material_id===e.id);if(a){await w(a.id,{quantity:Number(a.quantity)+1});return}const{data:s,error:r}=await p.from("template_items").insert({template_tier_id:c,material_id:e.id,custom_description:e.item_name,quantity:1,unit_price:e.cost,cost_price:e.unit_cost??null,category:e.category,display_order:u.length}).select("*").single();if(r){o({title:"Couldn't add item",variant:"destructive"});return}b(n=>[...n,s])},ie=async()=>{if(!c)return;const e=parseFloat(Y),a=parseFloat(X);if(B.trim().length<2||isNaN(e)||e<0||isNaN(a)||a<=0){o({title:"Enter a name, price, and quantity",variant:"destructive"});return}ae(!0);const{data:s,error:r}=await p.from("template_items").insert({template_tier_id:c,material_id:null,custom_description:B.trim(),quantity:a,unit_price:e,cost_price:null,category:ee,display_order:u.length}).select("*").single();if(ae(!1),r){o({title:"Couldn't add item",variant:"destructive"});return}b(n=>[...n,s]),G(""),J(""),Z("1"),I(!1)},w=async(e,a)=>{b(r=>r.map(n=>n.id===e?{...n,...a}:n));const{error:s}=await p.from("template_items").update(a).eq("id",e);s&&o({title:"Couldn't save change",variant:"destructive"})},Ie=async e=>{const a=h.find(r=>r.id===e);b(r=>r.filter(n=>n.id!==e));const{error:s}=await p.from("template_items").delete().eq("id",e);if(s){o({title:"Couldn't remove item",variant:"destructive"}),S();return}a&&o({title:"Item removed",description:a.custom_description||void 0,duration:8e3,action:t.jsx(Ve,{altText:"Undo",onClick:()=>void Be(a),children:"Undo"})})},Be=async e=>{const{id:a,...s}=e;delete s.created_at;const{data:r,error:n}=await p.from("template_items").insert(s).select("*").single();if(n){o({title:"Couldn't put it back",description:n.message,variant:"destructive"});return}b(d=>[...d,r])},Oe=async e=>{if(!c||e===c)return;const a=h.filter(d=>d.template_tier_id===e);if(a.length===0){o({title:"That option has no items to copy",variant:"destructive"});return}const{error:s}=await p.from("template_items").delete().eq("template_tier_id",c);if(s){o({title:"Couldn't replace the items",description:s.message,variant:"destructive"});return}const r=a.map((d,Q)=>({template_tier_id:c,material_id:d.material_id,custom_description:d.custom_description,quantity:d.quantity,unit_price:d.unit_price,cost_price:d.cost_price,category:d.category,display_order:Q})),{error:n}=await p.from("template_items").insert(r);n?o({title:"Couldn't copy items",variant:"destructive"}):o({title:`Copied ${r.length} items`,variant:"success"}),S()};if(_e||!l)return t.jsxs("div",{className:"min-h-screen bg-background",children:[t.jsx(de,{}),t.jsx(me,{width:"wide",children:t.jsx("div",{className:"flex h-64 items-center justify-center text-muted-foreground",children:t.jsx(le,{className:"h-5 w-5 animate-spin"})})})]});const Re=y.map(e=>({value:e.id,label:g&&q[e.tier_level]||e.tier_name,count:h.filter(a=>a.template_tier_id===e.id).length})),ze=be(u);return t.jsxs("div",{className:"min-h-screen bg-background",children:[t.jsx(de,{}),t.jsxs(me,{width:"wide",children:[t.jsx(Xe,{actionsPlacement:"below",backTo:"/templates",backLabel:"Templates",area:"templates",icon:He,crumb:l.name||"Template",title:t.jsx("span",{className:"inline-flex w-full items-center",children:t.jsx("input",{value:_,onCh
1ange:e=>O(e.target.value),onBlur:De,onKeyDown:e=>{e.key==="Enter"&&e.target.blur()},"aria-label":"Template name",style:{fontSize:22},className:"ek-title-input min-w-0 flex-1 rounded-none border-b border-transparent bg-transparent text-[22px] font-medium leading-[1.15] tracking-[-0.018em] outline-none hover:border-ink/[0.1] focus:border-ink/[0.25]"})}),description:g?"Good, Better, and Best options. Each becomes a pricing option on the estimate.":"A single set of line items to start estimates from.",actions:t.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[t.jsxs(he,{value:l.folder_id??"__none__",onValueChange:Ae,children:[t.jsx(ge,{className:"h-9 w-[170px]","aria-label":"Folder",children:t.jsx(fe,{placeholder:"No folder"})}),t.jsxs(ye,{children:[$.map(e=>t.jsx(F,{value:e.id,children:e.name.charAt(0).toUpperCase()+e.name.slice(1)},e.id)),t.jsx(F,{value:"__none__",children:"No folder"}),t.jsx(F,{value:"__new__",children:"New folder…"})]})]}),t.jsxs(f,{variant:"outline",className:"px-3.5",onClick:Le,disabled:M,children:[t.jsx(ve,{className:"mr-2 h-4 w-4"})," ",M?"Duplicating…":"Duplicate"]}),t.jsxs(f,{variant:"outline",className:"px-3.5",onClick:()=>{o({title:"Template saved",description:`${l.name||"Your template"} is ready to use on any new estimate.`}),x("/templates")},children:[t.jsx(je,{className:"mr-2 h-4 w-4"})," Save and close"]}),t.jsxs(f,{className:"px-3.5",onClick:()=>x(`/estimates/new?template=${l.id}`),disabled:h.length===0,children:[t.jsx(ct,{className:"mr-2 h-4 w-4"})," Use template"]})]})}),t.jsxs("div",{className:"grid grid-cols-1 gap-6 lg:grid-cols-5",children:[t.jsxs("div",{className:"space-y-5 lg:col-span-3",children:[g&&t.jsx(ue,{options:Re,value:c,onChange:L,ariaLabel:"Pricing option"}),m&&t.jsxs("div",{className:"overflow-hidden rounded-xl border border-ink/[0.06] bg-card",children:[t.jsx("div",{className:"space-y-3 p-4 md:p-5",children:t.jsxs("div",{className:"flex items-start justify-between gap-3",children:[t.jsxs("div",{className:"min-w-0 flex-1 space-y-3",children:[t.jsxs("div",{className:"space-y-1.5",children:[t.jsx(ce,{className:"text-[12.5px] font-normal text-foreground-secondary",children:g?`${q[m.tier_level]||"Option"} option name`:"Option name"}),t.jsx(T,{value:((ne=R[m.id])==null?void 0:ne.name)??"",onChange:e=>k(a=>({...a,[m.id]:{...a[m.id],name:e.target.value}})),onBlur:()=>re(m.id,"tier_name"),placeholder:g?"e.g. Better: 16 SEER two-stage":"e.g. Standard install",className:"h-11 md:h-9 text-[14px] font-medium"})]}),t.jsxs("div",{className:"space-y-1.5",children:[t.jsx(ce,{className:"text-[12.5px] font-normal text-foreground-secondary",children:"What the customer sees"}),t.jsx(et,{value:((oe=R[m.id])==null?void 0:oe.description)??"",onChange:e=>k(a=>({...a,[m.id]:{...a[m.id],description:e.target.value}})),onBlur:()=>re(m.id,"tier_description"),placeholder:"One or two sentences on why a homeowner would pick this option.",rows:2,className:"resize-none"})]})]}),g&&t.jsxs(Ke,{children:[t.jsx(Ge,{asChild:!0,children:t.jsx(f,{variant:"outline",size:"icon",className:"h-11 md:h-9 w-11 md:w-9 shrink-0 rounded-full text-foreground-secondary","aria-label":"Option actions",children:t.jsx(dt,{className:"h-4 w-4"})})}),t.jsx(Ye,{align:"end",children:y.filter(e=>e.id!==m.id).map(e=>t.jsxs(Je,{onClick:()=>Oe(e.id),children:[t.jsx(ve,{className:"mr-2 h-4 w-4"})," Copy items from ",q[e.tier_level]||e.tier_name]},e.id))})]})]})}),t.jsx("div",{className:"border-t border-ink/[0.06]",children:u.length===0?t.jsxs("div",{className:"px-4 py-10 text-center",children:[t.jsx(pe,{className:"mx-auto mb-2 h-7 w-7 text-foreground-tertiary",strokeWidth:1.8}),t.jsx("p",{className:"text-[14px] font-semibold",children:"No items yet"}),t.jsx("p",{className:"mt-1 text-[13px] text-foreground-secondary",children:"Add from your price book on the right, or add a custom line."})]}):t.jsx("ul",{className:"flex flex-col gap-px bg-ink/[0.05]",children:u.map(e=>t.jsxs("li",{className:"flex flex-wrap items-center gap-x-2.5 gap-y-2 bg-card py-2.5 pl-4 pr-2.5 md:pl-[18px]",children:[t.jsxs("div",{className:"flex min-w-0 flex-[1_1_160px] flex-col gap-0.5",children:[t.jsx("p",{className:"line-clamp-2 text-[13.5px] leading-snug",children:e.custom_description}),t.jsxs("p",{className:"text-[12px] text-foreground-tertiary",children:[e.category,e.material_id?"":" · custom"]})]}),t.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[t.jsxs("div",{className:"flex items-center rounded-[8px] border border-ink/[0.09]",children:[t.jsx("button",{type:"button",className:"flex h-8 w-7 items-center justify-center text-foreground-secondary hover:text-foreground disabled:opacity-40",onClick:()=>w(e.id,{quantity:Math.max(1,Number(e.quantity)-1)}),disabled:Number(e.quantity)<=1,"aria-label":"Decrease quantity",children:t.jsx(tt,{className:"h-3.5 w-3.5"})}),t.jsx("input",{type:"number",inputMode:"decimal",min:.01,step:"any",value:e.quantity,onChange:a=>{const s=parseFloat(a.target.value);!isNaN(s)&&s>0&&w(e.id,{quantity:s})},className:"ek-num h-8 w-10 bg-transparent text-center text-[13px] outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none","aria-label":"Quantity"}),t.jsx("button",{type:"button",className:"flex h-8 w-7 items-center justify-center text-foreground-secondary hover:text-foreground",onClick:()=>w(e.id,{quantity:Number(e.quantity)+1}),"aria-label":"Increase quantity",children:t.jsx(Ne,{className:"h-3.5 w-3.5"})})]}),t.jsxs("div",{className:"relative w-[84px]",children:[t.jsx("span",{className:"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-[12.5px] text-foreground-tertiary",children:"$"}),t.jsx("input",{type:"number",inputMode:"decimal",min:0,step:"0.01",value:e.unit_price,onChange:a=>{const s=parseFloat(a.target.value);!isNaN(s)&&s>=0&&w(e.id,{unit_price:s})},className:"ek-num h-8 w-full rounded-[8px] border border-ink/[0.09] bg-card pl-5 pr-2.5 text-right text-[13px] outline-none focus:border-ink/30 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none","aria-label":"Unit price"})]}),t.jsx("p",{className:"ek-num hidden w-[84px] text-right text-[13.5px] sm:block",children:W(Number(e.unit_price)*Number(e.quantity))}),t.jsx("button",{type:"button",onClick:()=>Ie(e.id),className:"flex h-8 w-8 items-center justify-center rounded-[8px] text-foreground-tertiary hover:bg-bad/10 hover:text-bad","aria-label":"Remove item",children:t.jsx(mt,{className:"h-4 w-4",strokeWidth:1.8})})]})]},e.id))})}),t.jsxs("div",{className:"flex items-baseline justify-between border-t border-ink/[0.06] bg-[#FAFAF9] px-4 py-3 md:px-5",children:[t.jsxs("span",{className:"text-[13px] text-foreground-secondary",children:[u.length," item",u.length===1?"":"s"," · before tax"]}),t.jsx("span",{className:"ek-num text-[18px] font-medium tracking-[-0.02em]",children:W(ze)})]})]}),g&&t.jsx("div",{className:"grid grid-cols-3 gap-2",children:y.map(e=>{const a=be(h.filter(r=>r.template_tier_id===e.id)),s=e.id===c;return t.jsxs("button",{type:"button",onClick:()=>L(e.id),className:$e("flex min-w-0 flex-col gap-1.5 rounded-[14px] border-[1.5px] p-3 text-left transition-colors md:p-3.5",
1s?"border-foreground bg-white":"border-ink/[0.06] bg-card hover:bg-ink/[0.04]"),children:[t.jsx("p",{className:"text-[12px] text-foreground-tertiary",children:q[e.tier_level]||e.tier_name}),t.jsx("p",{className:"truncate text-[13.5px] font-medium",children:e.tier_name}),t.jsx("p",{className:"ek-num truncate text-[14px] md:text-[16px]",children:W(a)})]},e.id)})})]}),t.jsx("div",{className:"space-y-4 lg:col-span-2",children:t.jsxs("div",{className:"rounded-xl border border-ink/[0.06] bg-card p-4 md:p-5",children:[t.jsxs("div",{className:"mb-3 flex items-center justify-between",children:[t.jsx("h2",{className:"text-[15px] font-medium",children:"Price book"}),t.jsxs(f,{variant:"ghost",size:"sm",className:"h-10 rounded-lg text-info hover:text-info hover:underline",onClick:()=>I(e=>!e),children:[t.jsx(Ne,{className:"mr-1.5 h-4 w-4"})," Custom line"]})]}),Fe&&t.jsxs("div",{className:"mb-4 space-y-3 rounded-xl border border-ink/[0.08] bg-card p-3",onKeyDown:e=>{e.key==="Enter"&&e.target.tagName==="INPUT"&&(e.preventDefault(),ie())},children:[t.jsx(T,{value:B,onChange:e=>G(e.target.value),placeholder:"Item name (e.g. Install labor)",className:"h-11 md:h-9",autoFocus:!0}),t.jsxs("div",{className:"grid grid-cols-3 gap-2",children:[t.jsx(T,{value:Y,onChange:e=>J(e.target.value),placeholder:"Price",type:"number",inputMode:"decimal",min:0,step:"0.01",className:"h-11 md:h-9"}),t.jsx(T,{value:X,onChange:e=>Z(e.target.value),placeholder:"Qty",type:"number",inputMode:"decimal",min:.01,step:"any",className:"h-11 md:h-9"}),t.jsxs(he,{value:ee,onValueChange:qe,children:[t.jsx(ge,{className:"h-11 md:h-9",children:t.jsx(fe,{})}),t.jsx(ye,{children:ut.map(e=>t.jsx(F,{value:e,children:e},e))})]})]}),t.jsxs("div",{className:"flex justify-end gap-2",children:[t.jsx(f,{variant:"ghost",size:"sm",onClick:()=>I(!1),children:"Cancel"}),t.jsx(f,{size:"sm",onClick:ie,disabled:te,children:te?t.jsx(le,{className:"h-4 w-4 animate-spin"}):t.jsxs(t.Fragment,{children:[t.jsx(je,{className:"mr-1.5 h-4 w-4"})," Add"]})})]})]}),t.jsx(Ze,{value:P,onChange:Se,placeholder:"Search your price book",className:"mb-3"}),t.jsx(ue,{options:Ee,value:C,onChange:Te,className:"mb-3",ariaLabel:"Category"}),N.length===0?t.jsx(xe,{icon:pe,title:"Your price book is empty",description:"Add equipment and services to your price book first, or use custom lines.",action:{label:"Open price book",onClick:()=>x("/materials")}}):se.length===0?t.jsx(xe,{icon:pt,title:"Nothing matches",description:"Try a different search or category."}):t.jsx("div",{className:"-mx-4 max-h-[60vh] divide-y divide-ink/[0.05] overflow-y-auto border-t border-ink/[0.05] md:-mx-5",children:se.map(e=>t.jsx(at,{material:e,onQuickAdd:()=>Pe(e),getCategoryBadgeColor:xt},e.id))})]})})]})]}),t.jsx(lt,{open:Ce,onOpenChange:V,onSubmit:Me})]})};export{Ht 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.