PageSourceSearch

https://recovery-automotive.lovable.app/assets/PartsStock-tyk5_lHl.js

js recovery-automotive.lovable.app collected 2026-10-03 03:42:49 UTC 12,581 bytes, 6 lines download raw bytes

1import{c as Z,an as ee,r as d,j as e,b as h,B as g,D as I,a as R,d as U,e as K,a8 as T,ae as N,s as te}from"./index-B8M9ps1T.js";import{u as se}from"./useQuery-CPNlGgE1.js";import{u as H}from"./useMutation-BuT7tv3u.js";import{C as m,a as j,c as ae,d as ne}from"./card-DzCf0Wp_.js";import{I as i}from"./input-CerV3gOV.js";import{L as c}from"./label-DF1VlBtP.js";import{S as V}from"./switch-DlX2D2PP.js";import{S as re,a as ie,b as ce,c as le,d as _}from"./select-B30Rlv9Z.js";import{T as oe}from"./triangle-alert-BeXbapDX.js";import{M as de}from"./minus-JQwQHGNZ.js";import{P as ue}from"./plus-D0Ml6N8h.js";import{P as xe}from"./pencil-BXpbd9n1.js";import"./index-CO7TY0pN.js";/**
2 * @license lucide-react v0.462.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 he=Z("PackagePlus",[["path",{d:"M16 16h6",key:"100bgy"}],["path",{d:"M19 13v6",key:"85cyf1"}],["path",{d:"M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14",key:"e7tb2h"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["line",{x1:"12",x2:"12",y1:"22",y2:"12",key:"a4e8g8"}]]),k=o=>{const u=o.quantity_on_hand??0;return o.min_qty!=null&&u<=0?"out":o.min_qty!=null&&u<=o.min_qty?"low":o.max_qty!=null&&u>o.max_qty?"over":"ok"},y=te,me={id:"",part_number:"",description:"",brand:"",category:"",cost_price:"0",quantity_on_hand:"0",bin_location:"",min_qty:"",max_qty:"",unit:"ea"},Se=()=>{const o=ee(),[u,$]=d.useState(""),[f,z]=d.useState(!1),[q,J]=d.useState(!1),[a,r]=d.useState(null),[l,w]=d.useState(null),[C,S]=d.useState("1"),[v,A]=d.useState("used"),[P,M]=d.useState(""),{data:p=[],isLoading:G}=se({queryKey:["parts-stock"],queryFn:async()=>{const{data:t,error:s}=await y.from("parts").select("id, part_number, description, brand, category, cost_price, quantity_on_hand, bin_location, min_qty, max_qty, unit, is_stocked").order("bin_location",{nullsFirst:!1}).order("description").limit(1e3);if(s)throw s;return t??[]}}),L=d.useMemo(()=>{const t=u.trim().toLowerCase();return p.filter(s=>!q&&!s.is_stocked||f&&!["low","out"].includes(k(s))?!1:t?[s.part_number,s.description,s.brand,s.category,s.bin_location].some(n=>n==null?void 0:n.toLowerCase().includes(t)):!0)},[p,u,f,q]),b=p.filter(t=>t.is_stocked&&["low","out"].includes(k(t))),W=p.filter(t=>t.is_stocked).reduce((t,s)=>t+(s.quantity_on_hand??0)*Number(s.cost_price||0),0),O=H({mutationFn:async t=>{const s=x=>x.trim()===""?null:Math.max(0,parseInt(x,10)||0),n={part_number:t.part_number.trim(),description:t.description.trim(),brand:t.brand.trim()||null,category:t.category.trim()||null,cost_price:Number(t.cost_price)||0,bin_location:t.bin_location.trim().toUpperCase()||null,min_qty:s(t.min_qty),max_qty:s(t.max_qty),unit:t.unit.trim()||"ea",is_stocked:!0};if(!n.part_number||!n.description)throw new Error("Part number and description are required");if(t.id){const{error:x}=await y.from("parts").update(n).eq("id",t.id);if(x)throw x}else{const{data:x,error:D}=await y.from("parts").insert({...n,retail_price:0,quantity_on_hand:0}).select("id").single();if(D)throw D;const F=s(t.quantity_on_hand)??0;if(F>0){const{error:B}=await y.rpc("adjust_part_stock",{p_part_id:x.id,p_change:F,p_reason:"count",p_note:"Starting count"});if(B)throw B}}},onSuccess:()=>{o.invalidateQueries({queryKey:["parts-stock"]}),o.invalidateQueries({queryKey:["parts-catalog"]}),r(null),N.success("Stock item saved")},onError:t=>N.error(t.message)}),Q=H({mutationFn:async()=>{if(!l)return;const t=parseInt(C,10);if(!t||t<0)throw new Error("Enter a quantity");let s=t*l.dir;v==="count"&&(s=t-(l.part.quantity_on_hand??0));const{error:n}=await y.rpc("adjust_part_stock",{p_part_id:l.part.id,p_change:s,p_reason:v,p_note:P||null});if(n)throw n},onSuccess:()=>{o.invalidateQueries({queryKey:["parts-stock"]}),w(null),N.success("Stock updated")},onError:t=>N.error(t.message)}),E=(t,s)=>{w({part:t,dir:s}),S("1"),M(""),A(s===1?"received":"used")},X=t=>{var s,n;return r({id:t.id,part_number:t.part_number,description:t.description,brand:t.brand??"",category:t.category??"",cost_price:String(t.cost_price??0),quantity_on_hand:String(t.quantity_on_hand??0),bin_location:t.bin_location??"",min_qty:((s=t.min_qty)==null?void 0:s.toString())??"",max_qty:((n=t.max_qty)==null?void 0:n.toString())??"",unit:t.unit??"ea"})},Y=t=>{const s=k(t);return s==="out"?e.jsx(g,{variant:"destructive",children:"Out"}):s==="low"?e.jsx(g,{variant:"destructive",children:"Low"}):s==="over"?e.jsx(g,{variant:"secondary",children:"Over max"}):e.jsx(g,{variant:"outline",children:"OK"})};return e.jsxs("div",{className:"container max-w-6xl py-8 space-y-6",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-3xl font-bold",children:"Parts Stock"}),e.jsx("p",{className:"text-muted-foreground text-sm",children:"Shelf stock & shop consumables — quantity on hand, bins and reorder alerts."})]}),e.jsxs(h,{onClick:()=>r({...me}),children:[e.jsx(he,{className:"mr-2 h-4 w-4"}),"Add stock item"]})]}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-3",children:[e.jsx(m,{children:e.jsxs(j,{className:"p-4",children:[e.jsx("div",{className:"text-sm text-muted-foreground",children:"Stocked items"}),e.jsx("div",{className:"text-2xl font-bold",children:p.filter(t=>t.is_stocked).length})]})}),e.jsx(m,{className:b.length?"border-destructive":"",children:e.jsxs(j,{className:"p-4",children:[e.jsx("div",{className:"text-sm text-muted-foreground",children:"Need reorder"}),e.jsx("div",{className:"text-2xl font-bold",children:b.length})]})}),e.jsx(m,{children:e.jsxs(j,{className:"p-4",children:[e.jsx("div",{className:"text-sm text-muted-foreground",children:"Stock value (cost)"}),e.jsxs("div",{className:"text-2xl font-bold",children:["$",W.toFixed(2)]})]})})]}),b.length>0&&e.jsxs(m,{className:"border-destructive",children:[e.jsx(ae,{className:"pb-2",children:e.jsxs(ne,{className:"flex items-center gap-2 text-destructive text-lg",children:[e.jsx(oe,{className:"h-5 w-5"}),"Reorder list"]})}),e.jsx(j,{className:"space-y-1 text-sm",children:b.map(t=>{const s=t.max_qty??t.min_qty??0,n=Math.max(s-(t.quantity_on_hand??0),1);return e.jsxs("div",{className:"flex justify-between gap-2 border-b last:border-0 py-1",children:[e.jsxs("span",{children:[t.description," ",e.jsxs("span",{className:"text-muted-foreground",children:["(",t.part_number,")",t.bin_location?` · Bin ${t.bin_location}`:""]})]}),e.jsxs("span",{className:"whitespace-nowrap",children:["Have ",t.quantity_on_hand??0," · Order ",e.jsx("strong",{children:n})," ",t.unit??""]})]},t.id)})})]}),e.jsx(m,{children:e.jsxs(j,{className:"p-4 flex flex-wrap gap-4 items-center",children:[e.jsx(i,{className:"max-w-xs",placeholder:"Search part #, name, bin…",value:u,onChange:t=>$(t.target.value)}),e.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[e.jsx(V,{checked:f,onCheckedChange:z}
6),"Low / out only"]}),e.jsxs("label",{className:"flex items-center gap-2 text-sm",children:[e.jsx(V,{checked:q,onCheckedChange:J}),"Include non-stocked catalog parts"]})]})}),e.jsx(m,{children:e.jsx(j,{className:"p-0",children:e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{className:"bg-muted text-left",children:e.jsxs("tr",{children:[e.jsx("th",{className:"p-2",children:"Bin"}),e.jsx("th",{className:"p-2",children:"Part"}),e.jsx("th",{className:"p-2 text-right",children:"QOH"}),e.jsx("th",{className:"p-2 text-right",children:"Min / Max"}),e.jsx("th",{className:"p-2",children:"Status"}),e.jsx("th",{className:"p-2 text-right",children:"Actions"})]})}),e.jsxs("tbody",{children:[L.map(t=>e.jsxs("tr",{className:"border-t",children:[e.jsx("td",{className:"p-2 font-mono text-xs",children:t.bin_location??"—"}),e.jsxs("td",{className:"p-2",children:[e.jsx("div",{className:"font-medium",children:t.description}),e.jsxs("div",{className:"text-xs text-muted-foreground",children:[t.part_number,t.brand?` · ${t.brand}`:"",t.category?` · ${t.category}`:""]})]}),e.jsxs("td",{className:"p-2 text-right font-semibold",children:[t.quantity_on_hand??0," ",e.jsx("span",{className:"text-xs font-normal text-muted-foreground",children:t.unit??""})]}),e.jsxs("td",{className:"p-2 text-right text-xs",children:[t.min_qty??"—"," / ",t.max_qty??"—"]}
6),e.jsx("td",{className:"p-2",children:t.is_stocked?Y(t):e.jsx(g,{variant:"outline",children:"Not stocked"})}),e.jsx("td",{className:"p-2",children:e.jsxs("div",{className:"flex justify-end gap-1",children:[e.jsx(h,{size:"icon",variant:"outline",className:"h-8 w-8","aria-label":"Use / remove",onClick:()=>E(t,-1),children:e.jsx(de,{className:"h-4 w-4"})}),e.jsx(h,{size:"icon",variant:"outline",className:"h-8 w-8","aria-label":"Add stock",onClick:()=>E(t,1),children:e.jsx(ue,{className:"h-4 w-4"})}),e.jsx(h,{size:"icon",variant:"ghost",className:"h-8 w-8","aria-label":"Edit",onClick:()=>X(t),children:e.jsx(xe,{className:"h-4 w-4"})})]})})]},t.id)),!G&&L.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:6,className:"p-8 text-center text-muted-foreground",children:"No stock items yet. Add your shop consumables to start tracking."})})]})]})})})}),e.jsx(I,{open:!!a,onOpenChange:t=>!t&&r(null),children:e.jsxs(R,{children:[e.jsx(U,{children:e.jsx(K,{children:a!=null&&a.id?"Edit stock item":"Add stock item"})}),a&&e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx(c,{children:"Part #"}),e.jsx(i,{value:a.part_number,onChange:t=>r({...a,part_number:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Bin"}),e.jsx(i,{placeholder:"A-03",value:a.bin_location,onChange:t=>r({...a,bin_location:t.target.value})})]}),e.jsxs("div",{className:"col-span-2",children:[e.jsx(c,{children:"Description"}),e.jsx(i,{value:a.description,onChange:t=>r({...a,description:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Brand"}),e.jsx(i,{value:a.brand,onChange:t=>r({...a,brand:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Category"}),e.jsx(i,{placeholder:"Shop supplies",value:a.category,onChange:t=>r({...a,category:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Unit cost"}),e.jsx(i,{type:"number",step:"0.01",value:a.cost_price,onChange:t=>r({...a,cost_price:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Unit"}),e.jsx(i,{placeholder:"ea, L, box",value:a.unit,onChange:t=>r({...a,unit:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Min (alert at)"}),e.jsx(i,{type:"number",value:a.min_qty,onChange:t=>r({...a,min_qty:t.target.value})})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Max (reorder up to)"}),e.jsx(i,{type:"number",value:a.max_qty,onChange:t=>r({...a,max_qty:t.target.value})})]}),!a.id&&e.jsxs("div",{className:"col-span-2",children:[e.jsx(c,{children:"Starting quantity on hand"}),e.jsx(i,{type:"number",value:a.quantity_on_hand,onChange:t=>r({...a,quantity_on_hand:t.target.value})})]}),a.id&&e.jsx("p",{className:"col-span-2 text-xs text-muted-foreground",children:"Change quantity with the + / − buttons so it's logged."})]}),e.jsx(T,{children:e.jsx(h,{onClick:()=>a&&O.mutate(a),disabled:O.isPending,children:"Save"})})]})}),e.jsx(I,{open:!!l,onOpenChange:t=>!t&&w(null),children:e.jsxs(R,{children:[e.jsx(U,{children:e.jsx(K,{children:l==null?void 0:l.part.description})}),l&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Currently on hand: ",e.jsx("strong",{children:l.part.quantity_on_hand??0})," ",l.part.unit??""]}),e.jsxs("div",{children:[e.jsx(c,{children:"Reason"}),e.jsxs(re,{value:v,onValueChange:A,children:[e.jsx(ie,{children:e.jsx(ce,{})}),e.jsxs(le,{children:[e.jsx(_,{value:"received",children:"Received"}),e.jsx(_,{value:"used",children:"Used in shop"}),e.jsx(_,{value:"returned",children:"Returned to supplier"}),e.jsx(_,{value:"damaged",children:"Damaged / lost"}),e.jsx(_,{value:"count",children:"Physical count (set exact)"})]})]})]}),e.jsxs("div",{children:[e.jsx(c,{children:v==="count"?"Counted quantity":l.dir===1?"Quantity to add":"Quantity to remove"}),e.jsx(i,{type:"number",min:0,value:C,onChange:t=>S(t.target.value)})]}),e.jsxs("div",{children:[e.jsx(c,{children:"Note (optional)"}),e.jsx(i,{value:P,onChange:t=>M(t.target.value)})]})]}),e.jsx(T,{children:e.jsx(h,{onClick:()=>Q.mutate(),disabled:Q.isPending,children:"Update stock"})})]})})]})};export{Se 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.