1import{r as x,j as e}from"./vendor-react-DKbe1Umz.js";import{a as me,u as ue,r as de,B as h,l as N,I as he,S as K,g as V,h as Q,i as Y,j as v,C as le,v as ce,D as z,c as X,d as W,e as J,f as Z,T as ee,n as se,q as te,o as ie,s as pe,t as Ne}from"./index-DZ74KeH3.js";import{u as fe,a as ae}from"./vendor-data-CjSe2wkl.js";import{C as je}from"./ContextualHelpButton-DKr5Uxvh.js";import{c1 as ve,bP as M,c0 as B,i as Ee,a$ as ge,J as be,Y as $,ce as q,E as ne}from"./vendor-ui-DMaW9-Ov.js";import{a as G}from"./vendor-dates-5UNeOyFW.js";import"./vendor-charts-DWX9qznF.js";import"./vendor-forms-C2vWC2ab.js";const E={MISSING_SHEET:"Missing Sheet",SHEET_NOT_INDEXED:"Sheet Not Indexed",REFERENCED_SHEET_ABSENT:"Referenced Sheet Absent",DUPLICATE_REVISION:"Duplicate Revision",SCALE_INDETERMINATE:"Scale Indeterminate",SCALE_MISMATCH:"Scale Mismatch",DANGLING_CALLOUT:"Dangling Callout",SCHEDULE_UNPARSED:"Schedule Unparsed",NO_GEOMETRY_ISSUED:"No Geometry Issued",LOW_CONFIDENCE:"Low Confidence",UNVERIFIED_PLAN_QUANTITY:"Unverified Plan Quantity",MISSING_EVIDENCE:"Missing Evidence",INCIDENTAL_SCOPE_DETECTED:"Incidental Scope Detected",QUANTITY_VARIANCE:"Quantity Variance",MISSING_ITEM_EXTENSION:"Missing Item Extension",DEFERRED_DESIGN:"Deferred Design",BIM_MISMATCH:"BIM Mismatch"},Ie={SCALE_INDETERMINATE:"AI could not determine a reliable drawing scale. Quantities from this sheet cannot be trusted.",SHEET_NOT_INDEXED:"Sheet has no discipline assigned â excluded from AI takeoff draft.",NO_GEOMETRY_ISSUED:"AI abstained: no geometry derivable from the source sheet.",SCHEDULE_UNPARSED:"Door/window/fixture schedule could not be extracted; referenced quantities may be wrong.",LOW_CONFIDENCE:"AI confidence below threshold â quantity requires estimator verification.",DANGLING_CALLOUT:"A callout references a sheet that is missing from the plan set.",DEFERRED_DESIGN:"AI detected 'by others' / design-build language â scope may be unquantifiable.",MISSING_SHEET:"Expected sheet is absent from the uploaded plan set.",REFERENCED_SHEET_ABSENT:"A sheet referenced from another drawing is not in the plan set."};function P(t){return["SCALE_INDETERMINATE","NO_GEOMETRY_ISSUED","MISSING_SHEET","REFERENCED_SHEET_ABSENT","DANGLING_CALLOUT"].includes(t)?"destructive":"secondary"}function Ce(t){return t.sheetNumber?t.sheetNumber:t.sheetName?t.sheetName:t.sheetIndex!=null?`Sheet ${t.sheetIndex+1}`:null}function _e(){const[t]=me(),{toast:r}=ue(),{projectId:m,bidId:p,bidCode:u}=x.useMemo(()=>{const s=t.indexOf("?");if(s<0)return{projectId:null,bidId:null,bidCode:null};const i=new URLSearchParams(t.slice(s+1));return{projectId:i.get("projectId"),bidId:i.get("bidId"),bidCode:i.get("bid")}},[t]),[l,j]=x.useState(""),[n,L]=x.useState("ALL"),[c,oe]=x.useState("open"),[k,re]=x.useState(new Set),[f,g]=x.useState(null),[a,b]=x.useState(null),[I,d]=x.useState(""),T=x.useMemo(()=>{const s=new URLSearchParams;return m?s.set("projectId",m):p&&s.set("bidId",p),c!=="ALL"&&s.set("status",c),n!=="ALL"&&s.set("code",n),s.toString()},[m,p,c,n]),D=!m&&!p,xe=["/api/takeoff/exceptions",T,D],{data:U,isLoading:C,error:S}=fe({queryKey:xe,queryFn:async()=>{const s=D?`/api/takeoff/exceptions/org-list?${T}`:`/api/takeoff/exceptions?${T}`,i=await fetch(s,{credentials:"include"});if(!i.ok)throw new Error(`Failed to load exceptions: ${i.status}`);return i.json()}}),A=x.useMemo(()=>{let s=U??[];if(l.trim()){const i=l.toLowerCase();s=s.filter(o=>o.title.toLowerCase().includes(i)||E[o.exceptionCode].toLowerCase().includes(i)||(o.sheetName??"").toLowerCase().includes(i)||(o.sheetNumber??"").toLowerCase().includes(i))}return s},[U,l]),y=A.filter(s=>s.severity==="BLOCKING"&&s.status==="open"),F=A.filter(s=>s.severity==="ADVISORY"&&s.status==="open"),H=A.filter(s=>s.status!=="open"),R=ae({mutationFn:async({id:s,note:i})=>(await ie("POST",`/api/takeoff/exceptions/${s}/resolve`,{note:i})).json(),onSuccess:()=>{te.invalidateQueries({queryKey:["/api/takeoff/exceptions"]}),g(null),d(""),r({title:"Exception resolved"})},onError:s=>r({title:"Resolve failed",description:s.message,variant:"destructive"})}),O=ae({mutationFn:async({id:s,note:i})=>(await ie("POST",`/api/takeoff/exceptions/${s}/dismiss`,{note:i})).json(),onSuccess:()=>
1{te.invalidateQueries({queryKey:["/api/takeoff/exceptions"]}),b(null),d(""),r({title:"Exception dismissed"})},onError:s=>r({title:"Dismiss failed",description:s.message,variant:"destructive"})}),_=s=>{re(i=>{const o=new Set(i);return o.has(s)?o.delete(s):o.add(s),o})};return e.jsxs("div",{className:"min-h-screen bg-background",children:[e.jsxs("main",{className:"mx-auto max-w-5xl px-4 py-6 sm:px-6 sm:py-8",children:[e.jsx("div",{className:"mb-4",children:e.jsx(de,{href:u?`/takeoff/${u}`:"/takeoff",children:e.jsxs(h,{variant:"ghost",size:"sm","data-testid":"button-back-to-takeoff",children:[e.jsx(ve,{className:"w-4 h-4 mr-1"})," Back to Takeoff"]})})}),e.jsx("div",{className:"mb-6",children:e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("div",{children:[e.jsxs("h1",{className:"text-2xl font-semibold flex items-center gap-2",children:[e.jsx(M,{className:"w-6 h-6 text-destructive"}),"Exception Queue",D&&e.jsx("span",{className:"text-base font-normal text-muted-foreground ml-1",children:"â All Projects"})]}),e.jsxs("p",{className:"text-sm text-muted-foreground mt-1",children:["Named AI abstentions and blocking conditions that must be resolved before a project is Bid-Ready. A correctly refused quantity is a ",e.jsx("span",{className:"font-medium",children:"successful"})," outcome.",!D&&u&&e.jsxs(e.Fragment,{children:[" Scoped to bid ",e.jsx(N,{variant:"outline",className:"font-mono ml-1",children:u})]})]})]}),e.jsx(je,{})]})}),y.length>0&&c==="open"&&e.jsxs("div",{className:"mb-4 flex items-center gap-3 rounded-md border border-destructive/60 bg-destructive/8 px-4 py-3 text-sm","data-testid":"banner-blocking-exceptions",children:[e.jsx(M,{className:"w-5 h-5 text-destructive shrink-0"}),e.jsxs("span",{className:"font-semibold text-destructive",children:[y.length," BLOCKING exception",y.length!==1?"s":""," â project cannot be marked Bid-Ready until resolved."]})]}),e.jsxs("div",{className:"mb-4 flex flex-col sm:flex-row gap-2",children:[e.jsx(he,{value:l,onChange:s=>j(s.target.value),placeholder:"Search exceptionsâ¦",className:"flex-1","data-testid":"input-exception-search"}),e.jsxs(K,{value:c,onValueChange:oe,children:[e.jsx(V,{className:"w-40","data-testid":"select-exception-status",children:e.jsx(Q,{placeholder:"Status"})}),e.jsxs(Y,{children:[e.jsx(v,{value:"open",children:"Open"}),e.jsx(v,{value:"resolved",children:"Resolved"}),e.jsx(v,{value:"dismissed",children:"Dismissed"}),e.jsx(v,{value:"ALL",children:"All statuses"})]})]}),e.jsxs(K,{value:n,onValueChange:L,children:[e.jsx(V,{className:"w-52","data-testid":"select-exception-code",children:e.jsx(Q,{placeholder:"Exception type"})}),e.jsxs(Y,{children:[e.jsx(v,{value:"ALL",children:"All types"}),Object.keys(E).map(s=>e.jsx(v,{value:s,children:E[s]},s))]})]})]}),C&&e.jsxs("div",{className:"flex items-center gap-2 text-muted-foreground py-12 justify-center","data-testid":"text-exceptions-loading",children:[e.jsx(B,{className:"w-4 h-4 animate-spin"})," Loading exceptionsâ¦"]}),S&&e.jsxs("div",{className:"flex items-center gap-2 text-destructive py-12 justify-center",children:[e.jsx(Ee,{className:"w-4 h-4"})," Failed to load exception queue."]}),!C&&!S&&A.length===0&&e.jsx(le,{"data-testid":"card-exceptions-empty",children:e.jsxs(ce,{className:"py-12 text-center text-muted-foreground",children:[e.jsx(ge,{className:"w-10 h-10 mx-auto mb-2 opacity-50"}),c==="open"?"No open exceptions â the AI found nothing to flag.":"No exceptions match the current filters."]})}),!C&&!S&&y.length>0&&e.jsx(w,{title:"Blocking",icon:e.jsx(M,{className:"w-4 h-4 text-destructive"}),rows:y,expandedIds:k,onToggleExpand:_,onResolve:s=>{g(s),d("")},onDismiss:s=>{b(s),d("")},variant:"blocking"}),!C&&!S&&F.length>0&&e.jsx(w,{title:"Advisory",icon:e.jsx(be,{className:"w-4 h-4 text-amber-600"}),rows:F,expandedIds:k,onToggleExpand:_,onResolve:s=>{g(s),d("")},onDismiss:s=>{b(s),d("")},variant:"advisory"}),!C&&!S&&H.length>0&&e.jsx(w,{title:"Resolved / Dismissed",icon:e.jsx($,{className:"w-4 h-4 text-muted-foreground"}),rows:H,expandedIds:k,onToggleExpand:_,onResolve:()=>{},onDismiss:()=>{},variant:"closed"})]}),e.jsx(z,{open:!!f,onOpenChange:s=>
1{s||(g(null),d(""))},children:e.jsxs(X,{children:[e.jsxs(W,{children:[e.jsx(J,{children:"Resolve exception"}),e.jsx(Z,{children:"Record how this exception was addressed. The resolution note is permanent."})]}),f&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"rounded border p-3 bg-muted/40 text-sm",children:[e.jsx("div",{className:"font-medium",children:f.title}),e.jsx(N,{variant:P(f.exceptionCode),className:"mt-1 text-[10px]",children:E[f.exceptionCode]})]}),e.jsx(ee,{value:I,onChange:s=>d(s.target.value),placeholder:"Resolution note (e.g. verified scale against title block, confirmed sheet includedâ¦)",rows:3,className:"resize-none","data-testid":"input-resolve-note"})]}),e.jsxs(se,{children:[e.jsx(h,{variant:"outline",onClick:()=>{g(null),d("")},children:"Cancel"}),e.jsxs(h,{onClick:()=>f&&R.mutate({id:f.id,note:I}),disabled:R.isPending,"data-testid":"button-confirm-resolve",children:[R.isPending?e.jsx(B,{className:"w-4 h-4 mr-1 animate-spin"}):e.jsx($,{className:"w-4 h-4 mr-1"}),"Resolve"]})]})]})}),e.jsx(z,{open:!!a,onOpenChange:s=>{s||(b(null),d(""))},children:e.jsxs(X,{children:[e.jsxs(W,{children:[e.jsxs(J,{children:["Dismiss exception",(a==null?void 0:a.severity)==="BLOCKING"&&e.jsx(N,{variant:"destructive",className:"ml-2 text-xs",children:"BLOCKING â supervisor required"})]}),e.jsx(Z,{children:(a==null?void 0:a.severity)==="BLOCKING"?"Dismissing a BLOCKING exception requires ADMIN or MANAGER role. Your approval will be recorded.":"Advisory exceptions can be dismissed when the risk is understood and accepted."})]}),a&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"rounded border p-3 bg-muted/40 text-sm",children:[e.jsx("div",{className:"font-medium",children:a.title}),e.jsx(N,{variant:P(a.exceptionCode),className:"mt-1 text-[10px]",children:E[a.exceptionCode]})]}),e.jsx(ee,{value:I,onChange:s=>d(s.target.value),placeholder:"Reason for dismissal (required for BLOCKING exceptions)â¦",rows:3,className:"resize-none","data-testid":"input-dismiss-note"})]}),e.jsxs(se,{children:[e.jsx(h,{variant:"outline",onClick:()=>{b(null),d("")},children:"Cancel"}),e.jsxs(h,{variant:"destructive",onClick:()=>a&&O.mutate({id:a.id,note:I}),disabled:O.isPending||(a==null?void 0:a.severity)==="BLOCKING"&&!I.trim(),"data-testid":"button-confirm-dismiss",children:[O.isPending?e.jsx(B,{className:"w-4 h-4 mr-1 animate-spin"}):e.jsx(q,{className:"w-4 h-4 mr-1"}),"Dismiss"]})]})]})})]})}function w({title:t,icon:r,rows:m,expandedIds:p,onToggleExpand:u,onResolve:l,onDismiss:j,variant:n}){const L=n==="blocking"?"bg-destructive/6 border-destructive/30":n==="advisory"?"bg-amber-50/50 border-amber-200/50 dark:bg-amber-950/10 dark:border-amber-800/30":"bg-muted/30";return e.jsxs(le,{className:"mb-4","data-testid":`section-${n}`,children:[e.jsx(pe,{className:`py-3 px-4 border-b ${L}`,children:e.jsxs(Ne,{className:"text-sm font-semibold flex items-center gap-2",children:[r,t,e.jsx(N,{variant:"secondary",className:"ml-1",children:m.length})]})}),e.jsx(ce,{className:"p-0 divide-y",children:m.map(c=>e.jsx(Se,{row:c,expanded:p.has(c.id),onToggle:()=>u(c.id),onResolve:l,onDismiss:j,variant:n},c.id))})]})}function Se({row:t,expanded:r,onToggle:m,onResolve:p,onDismiss:u,variant:l}){const j=Ce(t),n=Ie[t.exceptionCode];return e.jsxs("div",{className:"px-4 py-3 space-y-2","data-testid":`row-exception-${t.id}`,children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("button",{className:"flex-1 text-left min-w-0",onClick:m,"aria-expanded":r,"data-testid":`button-expand-${t.id}`,children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsx("span",{className:"text-sm font-medium truncate","data-testid":`text-exception-title-${t.id}`,children:t.title}),e.jsx(N,{variant:P(t.exceptionCode),className:"text-[10px] shrink-0",children:E[t.exceptionCode]}),t.severity==="BLOCKING"&&l!=="closed"&&e.jsx(N,{variant:"destructive",className:"text-[10px] shrink-0",children:"BLOCKING"}),l==="closed"&&e.jsx(N,{variant:"outline",className:"text-[10px] shrink-0 capitalize",children:t.status})]}),e.jsxs("div",{className:"mt-0.5 flex items-center gap-3 text-[11px] text-muted-foreground flex-wrap",children:[j&&e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx(ne,{className:"w-3 h-3"}),j]}),t.measurementId&&e.jsxs("span",{className:"font-mono",children:["meas:",t.measurementId.slice(0,8)]}),e.jsx("span",{children:G(new Date(t.autoDetectedAt),"MMM d, h:mm a")}),e.jsx("span",{className:"italic",children:t.detectedBy})]})]}),l!=="closed"&&e.jsxs("div",{className:"flex gap-1.5 shrink-0",children:[t.bidId&&t.takeoffSheetId&&e.jsx(de,{href:`/takeoff?bid=${t.bidId}&sheet=${t.takeoffSheetId}`,children:e.jsxs(h,{size:"sm",variant:"outline",className:"h-7 text-[11px]","data-testid":`button-open-sheet-${t.id}`,children:[e.jsx(ne,{className:"w-3 h-3 mr-1"})," Open"]})}),e.jsxs(h,{size:"sm",variant:"default",className:"h-7 text-[11px]",onClick:()=>p(t),"data-testid":`button-resolve-${t.id}`,children:[e.jsx($,{className:"w-3 h-3 mr-1"})," Resolve"]}),t.severity==="ADVISORY"&&e.jsxs(h,{size:"sm",variant:"outline",className:"h-7 text-[11px]",onClick:()=>u(t),"data-testid":`button-dismiss-${t.id}`,children:[e.jsx(q,{className:"w-3 h-3 mr-1"})," Dismiss"]}),t.severity==="BLOCKING"&&e.jsxs(h,{size:"sm",variant:"outline",className:"h-7 text-[11px] text-destructive hover:text-destructive",onClick:()=>u(t),"data-testid":`button-dismiss-blocking-${t.id}`,title:"Dismissing a BLOCKING exception requires supervisor approval",children:[e.jsx(q,{className:"w-3 h-3 mr-1"})," Dismiss (supervisor)"]})]})]}),r&&e.jsxs("div",{className:"pl-0 pt-1 space-y-2","data-testid":`detail-${t.id}`,children:[n&&e.jsx("p",{className:"text-xs text-muted-foreground italic",children:n}
1),t.detail&&e.jsx("p",{className:"text-xs text-foreground/80 border-l-2 border-muted pl-3",children:t.detail}),l==="closed"&&e.jsxs("div",{className:"text-[11px] text-muted-foreground space-y-0.5",children:[t.resolvedAt&&e.jsxs("div",{children:["Resolved by ",t.resolvedByName??"unknown"," on ",G(new Date(t.resolvedAt),"MMM d, yyyy")]}),t.dismissedAt&&e.jsxs("div",{children:["Dismissed by ",t.dismissedByName??"unknown"," on ",G(new Date(t.dismissedAt),"MMM d, yyyy")]}),t.resolutionNote&&e.jsxs("div",{className:"italic",children:['"',t.resolutionNote,'"']})]})]})]})}export{_e 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.