PageSourceSearch

https://nwfloors.co.uk/assets/Deals-B6h4grNZ.js

js nwfloors.co.uk collected 2026-10-08 17:36:42 UTC 17,068 bytes, 6 lines download raw bytes

1import{h as J,r as u,p as Y,n as W,aZ as E,u as I,j as e,I as $,B as y,J as v,ah as z,m as X,Q as O,b as R,ao as H,x as Z,y as G,z as ee,D as se,E as U,O as te}from"./index-D2rUcUpe.js";import{u as K}from"./useMutation-N7h9ZAvw.js";import{H as ae}from"./Helmet-D7MYuSoa.js";import{A as ie,T as re}from"./AdminLayout-kkTddtqD.js";import{C as ne,c as le}from"./card-Bto263fv.js";import{L as T}from"./label-FpK1FRAL.js";import{T as ce,a as oe,b as k,c as N,d as de,e as f}from"./table-Bn0HNHfV.js";import{D as me,a as ue,d as xe,b as pe,c as he}from"./dialog-6d7Y_iu1.js";import{A as je,a as ge,b as fe,c as ye,d as Ne,e as ve,f as be,g as we}from"./alert-dialog-DtRwiLaq.js";import{T as Q}from"./tag-s72l0Uly.js";import{L as M}from"./loader-circle-BLb9ftDd.js";import{C as Ce}from"./circle-x-Bi2Smo7g.js";import{C as De}from"./copy-ClT1kzOb.js";import{E as Se}from"./external-link-BlWt7TzR.js";import{f as B}from"./format-BEMO4FpZ.js";import"./log-out-Bsz1vMOl.js";import"./clipboard-list-COd5Au4H.js";import"./building-2-DpPm7j8z.js";import"./star-Dj3WDnZ7.js";import"./upload-B90ZdJYW.js";import"./share-2-BMS9JZH2.js";import"./calendar-days-BLrdobId.js";import"./wrench-CCizyO6G.js";/**
2 * @license lucide-react v0.453.0 - ISC
3 *
4 * This source code is licensed under the ISC license.
5 * See the LICENSE file in the root directory of this source tree.
6 */const Te=J("TrendingDown",[["polyline",{points:"22 17 13.5 8.5 8.5 13.5 2 7",key:"1r2t7k"}],["polyline",{points:"16 17 22 17 22 11",key:"11uiuu"}]]),V={pending:"bg-amber-50 text-amber-700 border-amber-200",replied:"bg-blue-50 text-blue-700 border-blue-200",claimed:"bg-emerald-50 text-emerald-700 border-emerald-200",expired:"bg-gray-100 text-gray-600 border-gray-200",closed:"bg-gray-100 text-gray-600 border-gray-200"},Ae={asap:"ASAP","one-three-months":"1–3 months",flexible:"Flexible","just-pricing":"Just pricing"},L=(a,l)=>typeof l!="number"||l<=0||!a||a<=0?null:Math.round((a-l)/a*1e3)/10;function A({value:a}){if(a==null)return e.jsx("span",{className:"text-gray-400 text-sm",children:"No cost"});const l=a>=25;return e.jsxs("span",{className:`inline-flex items-center gap-1 text-sm font-medium ${l?"text-emerald-700":a>=10?"text-amber-700":"text-red-700"}`,children:[l?e.jsx(re,{className:"h-3.5 w-3.5"}):e.jsx(Te,{className:"h-3.5 w-3.5"}),a,"%"]})}function Pe({deal:a,onDone:l}){var q,S;const{toast:c}=O(),x=E(),[t,p]=u.useState("discount"),[d,g]=u.useState("percent"),[m,h]=u.useState(""),[n,F]=u.useState(""),[b,C]=u.useState("14"),s=((q=a.product)==null?void 0:q.price)??a.listUnitPrice??0,r=((S=a.product)==null?void 0:S.costPrice)??a.costUnitPrice??null,o=u.useMemo(()=>{if(t==="best-price")return Number(s.toFixed(2));const i=parseFloat(m);if(isNaN(i)||i<=0)return null;let j;if(d==="percent"){if(i>=100)return null;j=s*(1-i/100)}else j=s-i;return j=Number(j.toFixed(2)),j<=0||j>=s?null:j},[t,d,m,s]),D=o!=null?L(o,r):null,w=K({mutationFn:()=>R(`/api/deals/${a.id}/reply`,{method:"POST",data:t==="best-price"?{dealType:t,staffNote:n||void 0,expiryDays:Number(b)||14}:{dealType:t,discountType:d,discountValue:parseFloat(m),staffNote:n||void 0,expiryDays:Number(b)||14}}),onSuccess:()=>{c({title:"Deal sent",description:`An offer email has been sent to ${a.fullName}.`}),x.invalidateQueries({queryKey:["/api/deals"]}),x.invalidateQueries({queryKey:["/api/deals",a.id]}),H.invalidateQueries({queryKey:["/api/admin/counts"]}),l()},onError:i=>{var j;c({title:"Could not send deal",description:((j=i==null?void 0:i.message)==null?void 0:j.replace(/^\d+:\s*/,""))||"Please try again.",variant:"destructive"})}}),P=t==="best-price"||o!=null&&!isNaN(parseFloat(m));return e.jsxs("div",{className:"space-y-4 rounded-lg border bg-[#FAF8F4] p-4",children:[e.jsx("h3",{className:"font-semibold",children:"Fast reply"}),e.jsxs("div",{className:"grid grid-cols-3 gap-3 text-sm",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-gray-500",children:"List price"}),e.jsxs("div",{className:"font-medium",children:[v(s),"/m²"]})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-gray-500",children:"Cost"}),e.jsx("div",{className:"font-medium",children:r!=null?`${v(r)}/m²`:"—"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-gray-500",children:"Current margin"}),e.jsx(A,{value:L(s,r)})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{children:"Reply type"}),e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsx(y,{type:"button",variant:t==="discount"?"default":"outline",className:t==="discount"?"bg-[#3a4d36] hover:bg-[#2A3D26]":"",onClick:()=>p("discount"),children:"Offer a discount"}),e.jsx(y,{type:"button",variant:t==="best-price"?"default":"outline",className:t==="best-price"?"bg-[#3a4d36] hover:bg-[#2A3D26]":"",onClick:()=>p("best-price"),children:"Best price we can offer"})]})]}),t==="discount"&&e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{children:"Discount type"}),e.jsxs(Z,{value:d,onValueChange:i=>g(i),children:[e.jsx(G,{children:e.jsx(ee,{})}),e.jsxs(se,{children:[e.jsx(U,{value:"percent",children:"Percentage (%)"}),e.jsx(U,{value:"fixed",children:"Amount off (£/m²)"})]})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{children:d==="percent"?"Percent off":"£ off per m²"}),e.jsx($,{type:"number",min:0,step:d==="percent"?"1":"0.01",value:m,onChange:i=>h(i.target.value),placeholder:d==="percent"?"e.g. 10":"e.g. 2.50"})]})]}),e.jsxs("div",{className:"rounded-md border bg-white p-3 space-y-1",children:[e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsx("span",{className:"text-gray-500",children:"Customer pays"}),e.jsx("span",{className:"font-semibold",children:t==="best-price"?`${v(s)}/m²`:o!=null?`${v(o)}/m²`:"—"})]}),e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsx("span",{className:"text-gray-500",children:"Margin at this price"}),t==="best-price"?e.jsx(A,{value:L(s,r)}):e.jsx(A,{value:D})]}),a.areaSize&&o!=null&&e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsxs("span",{className:"text-gray-500",children:["Order total (",a.areaSize,"m²)"]}),e.jsx("span",{className:"font-medium",children:v(Number((o*a.areaSize).toFixed(2)))})]}),t==="discount"&&m&&o==null&&e.jsx("p",{className:"text-sm text-red-600",children:"That discount isn't valid — it must reduce the price to more than £0."})]}),e.jsx("div",{className:"grid grid-cols-2 gap-3",children:e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{children:"Hold price for (days)"}),e.jsx($,{type:"number",min:1,max:90,value:b,onChange:i=>C(i.target.value)})]})}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(T,{children:"Note to customer (optional)"}),e.jsx(te,{value:n,onChange:i=>F(i.target.value),placeholder:"A short, friendly note woven into the email.",className:"min-h-20"})]}),e.jsx(y,{className:"w-full bg-[#3a4d36] hover:bg-[#2A3D26] text-white",disabled:!P||w.isPending,onClick:()=>w.mutate(),children:w.isPending?e.jsxs(e.Fragment,{children:[e.jsx(M,{className:"h-4 w-4 mr-2 animate-spin"})," Sending…"]}):"Send deal email"})]})}function qe(){const{toast:a}=O(),l=E();return K({mutationFn:c=>R(`/api/deals/${c}`,{method:"PATCH",data:{status:"closed"}}),onSuccess:(c,x)=>{a({title:"Deal closed",description:"This request has been dismissed."}
6),l.invalidateQueries({queryKey:["/api/deals"]}),l.invalidateQueries({queryKey:["/api/deals",x]}),H.invalidateQueries({queryKey:["/api/admin/counts"]})},onError:c=>{var x;a({title:"Could not close deal",description:((x=c==null?void 0:c.message)==null?void 0:x.replace(/^\d+:\s*/,""))||"Please try again.",variant:"destructive"})}})}function _({dealId:a,customerName:l,onClosed:c,variant:x="outline",size:t="sm",label:p="Close",className:d=""}){const[g,m]=u.useState(!1),h=qe();return e.jsxs(e.Fragment,{children:[e.jsx(y,{variant:x,size:t,className:d,disabled:h.isPending,onClick:n=>{n.stopPropagation(),m(!0)},children:h.isPending?e.jsx(M,{className:"h-4 w-4 animate-spin"}):e.jsxs(e.Fragment,{children:[e.jsx(Ce,{className:"h-4 w-4 mr-1.5"}),p]})}),e.jsx(je,{open:g,onOpenChange:m,children:e.jsxs(ge,{onClick:n=>n.stopPropagation(),children:[e.jsxs(fe,{children:[e.jsx(ye,{children:"Close this deal request?"}),e.jsxs(Ne,{children:[l?`This dismisses ${l}'s request without replying. `:"This dismisses the request without replying. ","It will drop out of the pending count. You can still view it in the list."]})]}),e.jsxs(ve,{children:[e.jsx(be,{children:"Keep open"}),e.jsx(we,{className:"bg-red-600 hover:bg-red-700",onClick:n=>{n.stopPropagation(),h.mutate(a,{onSuccess:()=>c==null?void 0:c()})},children:"Close request"})]})]})})]})}function Le({id:a,onClose:l}){var g,m,h;const[c,x]=u.useState(!1),{data:t,isLoading:p}=I({queryKey:["/api/deals",a],queryFn:async()=>{const n=await fetch(`/api/deals/${a}`,{credentials:"include"});if(!n.ok)throw new Error(`${n.status}: ${await n.text()}`);return n.json()}}),d=()=>{t!=null&&t.claimUrl&&(navigator.clipboard.writeText(t.claimUrl),x(!0),setTimeout(()=>x(!1),1500))};return e.jsx(me,{open:!0,onOpenChange:n=>{n||l()},children:e.jsxs(ue,{className:"max-w-lg max-h-[90vh] overflow-y-auto",children:[e.jsxs(xe,{children:[e.jsxs(pe,{children:["Deal request #",a]}),e.jsx(he,{children:"Review the request and reply with a price the customer can claim in one click."})]}),p||!t?e.jsx("div",{className:"flex items-center justify-center py-12 text-gray-500",children:e.jsx(M,{className:"h-6 w-6 animate-spin"})}):e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("div",{className:"w-16 h-16 rounded-md overflow-hidden bg-gray-100 flex-shrink-0",children:(g=t.product)!=null&&g.imageUrl?e.jsx("img",{src:t.product.imageUrl,alt:t.product.name,className:"w-full h-full object-cover"}):e.jsx("div",{className:"w-full h-full flex items-center justify-center",children:e.jsx(Q,{className:"h-6 w-6 text-gray-400"})})}),e.jsxs("div",{className:"flex-grow",children:[e.jsx("div",{className:"font-medium",children:((m=t.product)==null?void 0:m.name)??`Product #${t.productId}`}),e.jsxs("div",{className:"text-sm text-gray-500",children:[t.areaSize?`${t.areaSize}m² needed`:"Area not specified",t.timeframe?` · ${Ae[t.timeframe]??t.timeframe}`:""]}),e.jsx(z,{variant:"outline",className:`mt-1 ${V[t.status]}`,children:t.status})]})]}),e.jsxs("div",{className:"rounded-lg border p-3 text-sm space-y-1",children:[e.jsx("div",{className:"font-medium",children:t.fullName}),e.jsx("div",{className:"text-gray-600",children:t.email}),t.phone&&e.jsx("div",{className:"text-gray-600",children:t.phone}),t.message&&e.jsxs("div",{className:"mt-2 text-gray-700 whitespace-pre-wrap",children:['"',t.message,'"']})]}),t.status==="pending"&&e.jsxs(e.Fragment,{children:[e.jsx(Pe,{deal:t,onDone:l}),e.jsxs("div",{className:"flex items-center justify-between gap-3 rounded-lg border border-dashed p-3",children:[e.jsx("p",{className:"text-sm text-gray-500",children:"Not going to reply? Dismiss this request so it drops out of the pending count."}),e.jsx(_,{dealId:t.id,customerName:t.fullName,onClosed:l,label:"Close request"})]})]}),t.status!=="pending"&&e.jsxs("div",{className:"rounded-lg border bg-[#FAF8F4] p-4 space-y-2 text-sm",children:[e.jsx("h3",{className:"font-semibold",children:"Reply sent"}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-500",children:"Type"}),e.jsx("span",{className:"font-medium",children:t.dealType==="best-price"?"Best price":"Discount"})]}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-500",children:"Agreed price"}),e.jsx("span",{className:"font-medium",children:t.agreedUnitPrice!=null?`${v(t.agreedUnitPrice)}/m²`:"—"})]}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-500",children:"Margin at agreed price"}),e.jsx(A,{value:t.agreedUnitPrice!=null?L(t.agreedUnitPrice,((h=t.product)==null?void 0:h.costPrice)??t.costUnitPrice):null})]}),t.expiresAt&&e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-500",children:"Expires"}),e.jsx("span",{className:"font-medium",children:B(new Date(t.expiresAt),"MMM d, yyyy")})]}),t.staffNote&&e.jsxs("div",{className:"pt-1 text-gray-700 whitespace-pre-wrap",children:["Note: ",t.staffNote]}),t.claimUrl&&e.jsxs("div",{className:"flex items-center gap-2 pt-2",children:[e.jsx($,{readOnly:!0,value:t.claimUrl,className:"text-xs"}),e.jsx(y,{type:"button",variant:"outline",size:"icon",onClick:d,title:"Copy claim link",children:c?e.jsx(X,{className:"h-4 w-4 text-green-600"}):e.jsx(De,{className:"h-4 w-4"})}),e.jsx("a",{href:t.claimUrl,target:"_blank",rel:"noreferrer",children:e.jsx(y,{type:"button",variant:"outline",size:"icon",title:"Open claim page",children:e.jsx(Se,{className:"h-4 w-4"})})})]})]})]})]})})}function ss(){const[a,l]=u.useState(""),[c,x]=u.useState(!1),[t,p]=u.useState(null),d=Y(),[,g]=W(),m=E(),h=()=>{p(null),new URLSearchParams(d).has("open")&&g("/admin/deals",{replace:!0})};u.useEffect(()=>{const s=new URLSearchParams(d).get("open"),r=s?Number(s):NaN;Number.isFinite(r)&&r>0&&p(r)},[d]);const{data:n=[],isLoading:F}=I({queryKey:["/api/deals"],queryFn:async()=>{const s=await fetch("/api/deals",{credentials:"include"});if(!s.ok)throw new Error(`${s.status}: ${await s.text()}`);return s.json()}});u.useEffect(()=>{if(typeof window>"u")return;let s=null,r=!1,o=1e3,D=null;const w=()=>{const q=window.location.protocol==="https:"?"wss:":"ws:";try{s=new WebSocket(`${q}//${window.location.host}/ws/admin`)}catch{P();return}s.addEventListener("open",()=>{o=1e3}),s.addEventListener("message",S=>
6{try{const i=JSON.parse(typeof S.data=="string"?S.data:"");((i==null?void 0:i.type)==="deal:new"||(i==null?void 0:i.type)==="deal:update")&&m.invalidateQueries({queryKey:["/api/deals"]})}catch{}}),s.addEventListener("close",()=>{r||P()}),s.addEventListener("error",()=>{try{s==null||s.close()}catch{}})},P=()=>{r||(D=setTimeout(w,o),o=Math.min(o*2,3e4))};return w(),()=>{r=!0,D&&clearTimeout(D);try{s==null||s.close()}catch{}}},[m]);const b=u.useMemo(()=>{const s=a.trim().toLowerCase();return n.filter(r=>{var o;return c&&(r.status==="closed"||r.status==="expired")?!1:s?r.fullName.toLowerCase().includes(s)||r.email.toLowerCase().includes(s)||(((o=r.product)==null?void 0:o.name)??"").toLowerCase().includes(s):!0})},[n,a,c]),C=u.useMemo(()=>n.filter(s=>s.status==="closed"||s.status==="expired").length,[n]);return e.jsxs(ie,{children:[e.jsx(ae,{children:e.jsx("title",{children:"Deal requests — Admin"})}),e.jsxs("div",{className:"space-y-4",children:[e.jsx("div",{className:"flex items-center justify-between flex-wrap gap-2",children:e.jsxs("h1",{className:"text-xl font-semibold flex items-center gap-2",children:[e.jsx(Q,{className:"h-5 w-5"})," Deal requests"]})}),e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[e.jsx($,{placeholder:"Search by name, email or product…",value:a,onChange:s=>l(s.target.value),className:"max-w-sm"}),C>0&&e.jsx(y,{variant:"outline",size:"sm",onClick:()=>x(s=>!s),children:c?`Show closed & expired (${C})`:`Hide closed & expired (${C})`})]}),e.jsx(ne,{children:e.jsx(le,{className:"p-0",children:e.jsxs(ce,{children:[e.jsx(oe,{children:e.jsxs(k,{children:[e.jsx(N,{children:"Customer"}),e.jsx(N,{children:"Product"}),e.jsx(N,{children:"Area"}),e.jsx(N,{children:"List / margin"}),e.jsx(N,{children:"Status"}),e.jsx(N,{children:"Date"}),e.jsx(N,{className:"text-right",children:"Actions"})]})}),e.jsxs(de,{children:[b.map(s=>{var r;return e.jsxs(k,{className:`cursor-pointer ${s.status==="closed"||s.status==="expired"?"opacity-60 bg-gray-50/60":""}`,onClick:()=>p(s.id),children:[e.jsxs(f,{children:[e.jsx("div",{className:"font-medium",children:s.fullName}),e.jsx("div",{className:"text-sm text-gray-500",children:s.email})]}),e.jsx(f,{className:"max-w-[14rem]",children:e.jsx("div",{className:"truncate",children:((r=s.product)==null?void 0:r.name)??`#${s.productId}`})}),e.jsx(f,{children:s.areaSize?`${s.areaSize}m²`:"—"}),e.jsxs(f,{children:[e.jsx("div",{className:"text-sm",children:s.product?`${v(s.product.price)}/m²`:"—"}),e.jsx(A,{value:s.currentMarginPct})]}),e.jsx(f,{children:e.jsx(z,{variant:"outline",className:V[s.status],children:s.status})}),e.jsx(f,{className:"text-sm text-gray-500",children:B(new Date(s.createdAt),"MMM d, yyyy")}),e.jsx(f,{className:"text-right",children:e.jsxs("div",{className:"flex items-center justify-end gap-1",children:[e.jsx(y,{variant:"ghost",size:"sm",onClick:o=>{o.stopPropagation(),p(s.id)},children:s.status==="pending"?"Reply":"View"}),s.status==="pending"&&e.jsx(_,{dealId:s.id,customerName:s.fullName,variant:"ghost",className:"text-gray-500 hover:text-red-600"})]})})]},s.id)}),b.length===0&&e.jsx(k,{children:e.jsx(f,{colSpan:7,className:"text-center py-6 text-gray-500",children:F?"Loading…":"No deal requests yet."})})]})]})})})]}),t!=null&&e.jsx(Le,{id:t,onClose:h})]})}export{ss 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.