PageSourceSearch

https://incommand.ai/assets/HistoricalFinancialsPanel-ATO1ETnB.js

js incommand.ai collected 2026-10-05 15:02:48 UTC 17,690 bytes, 4 lines download raw bytes

1import{j as e}from"./vendor-radix-BnNg3BH5.js";import{r as c}from"./vendor-react-67mgcWXU.js";import{M as ne}from"./metric-DK0Tcl4m.js";import{b as ue,u as xe,D as Z,e as ee,f as se,g as te,U as ve,V as Ne,W as ie,F as be,Y as oe,B as u,X as ye,L as V,C as he,G as ae,s as $,a0 as we,_ as le,a7 as Ce,T as _e,l as Te,m as De,v as k,I as A}from"./index-Epr9psQo.js";import{C as ce,d as de,a as Ee,b as Se,c as Ie}from"./card-DpKBVi42.js";import{B as W}from"./badge-Cl7apleB.js";import{A as Pe,b as Fe}from"./alert-BbCCz1lQ.js";import{T as pe,a as je,b as q,c as N,d as fe,e as b}from"./table-023rHGnC.js";import{P as ke}from"./progress-OXAVVpg3.js";import{S as Ae}from"./scroll-area-OnGFamwr.js";import{F as $e}from"./file-spreadsheet-Q6qsWG3M.js";import{U as re}from"./upload-2-rh2XwF.js";import{D as Ue}from"./download-CDzT20jl.js";import{C as ge}from"./circle-alert-CFN03ksH.js";import{D as me}from"./dollar-sign-CC-jyV1q.js";import{P as Me}from"./plus-DRTmwF2W.js";import{I as Oe}from"./info-DuvYtGx9.js";import{P as Le}from"./pen-BjcYRxrI.js";import"./vendor-supabase-CTlrDMdV.js";import"./vendor-charts-Dck3qfzI.js";import"./vendor-query-PMAhJHCS.js";import"./vendor-motion-CSzk-Fqg.js";function Re({open:P,onOpenChange:D,projectId:_,targetMargin:y,onComplete:G}){const{currentTenant:z}=ue(),{toast:U}=xe(),[j,Y]=c.useState("pdf"),[x,w]=c.useState([]),[i,g]=c.useState([]),[M,E]=c.useState(!1),[o,T]=c.useState(!1),[p,S]=c.useState({current:0,total:0}),O=c.useCallback(s=>{const n=Array.from(s.target.files||[]);w(a=>[...a,...n].slice(0,20))},[]),L=c.useCallback(s=>{s.preventDefault();const n=Array.from(s.dataTransfer.files).slice(0,20-x.length);w(a=>[...a,...n])},[x.length]),K=s=>{w(n=>n.filter((a,r)=>r!==s))},R=()=>{const r=`period,revenue,cost,notes
22024-01,45000,,Invoice #1001
32024-02,52000,33800,Invoice #1002 - actual cost`,d=new Blob([r],{type:"text/csv"}),m=URL.createObjectURL(d),h=document.createElement("a");h.href=m,h.download="invoice_import_template.csv",h.click(),URL.revokeObjectURL(m)},C=async s=>{const a=(await s.text()).split(`
4`).filter(f=>f.trim());if(a.length<2)return[];const r=a[0].toLowerCase().split(",").map(f=>f.trim()),d=r.indexOf("period"),m=r.indexOf("revenue"),h=r.indexOf("cost"),F=r.indexOf("notes");return a.slice(1).map((f,B)=>{const v=f.split(",").map(H=>H.trim());return{id:crypto.randomUUID(),fileName:s.name,status:"ready",period:d>=0?v[d]:"",invoiceNumber:F>=0?v[F]:null,revenue:m>=0&&Number(v[m])||0,cost:h>=0&&v[h]?Number(v[h]):null}}).filter(f=>f.period&&f.revenue>0)},X=async s=>{const n=await le(()=>import("./pdf-DxqAUlXS.js"),[]),a=await le(()=>import("./pdf.worker.min-D8eRykn8.js"),[]);n.GlobalWorkerOptions.workerSrc=a.default;const r=await s.arrayBuffer(),d=await n.getDocument({data:r}).promise,m=[],h=Math.min(d.numPages,5);for(let F=1;F<=h;F++){const f=await d.getPage(F),B=f.getViewport({scale:1.5}),v=document.createElement("canvas");v.width=B.width,v.height=B.height;const H=v.getContext("2d");H&&(await f.render({canvasContext:H,viewport:B}).promise,m.push(v.toDataURL("image/jpeg",.8)))}return m},J=async()=>{if(x.length===0)return;E(!0),S({current:0,total:x.length});const s=[];for(let n=0;n<x.length;n++){const a=x[n];if(S({current:n+1,total:x.length}),a.name.endsWith(".csv")||a.name.endsWith(".xlsx")){const d=await C(a);s.push(...d.map(m=>({...m,status:"ready"}))),g([...s]);continue}const r={id:crypto.randomUUID(),fileName:a.name,status:"parsing",period:"",invoiceNumber:null,revenue:0};s.push(r),g([...s]);try{const d=await X(a),m=await $.functions.invoke("parse-invoice",{body:{pageImages:d}});if(m.error||!m.data?.success)throw new Error(m.data?.error||"Parse failed");const h=m.data.data;r.status="ready",r.period=h.period||"",r.invoiceNumber=h.invoice_number||null,r.revenue=h.amount||0}catch(d){r.status="error",r.error=d instanceof Error?d.message:"Parse failed"}g([...s])}E(!1),w([])},Q=async()=>{const s=i.filter(a=>a.status==="ready"&&a.revenue>0);if(s.length===0)return;if(!z?.id){U({title:"Error",description:"Tenant context not loaded. Please try again.",variant:"destructive"});return}T(!0);const n=z.id;try{for(const a of s){let r;if(a.period.match(/^\d{4}-\d{2}$/)?r=new Date(a.period+"-01"):r=new Date(a.period),isNaN(r.getTime())){we.warn(`Invalid period: ${a.peri
4od}`);continue}const d=new Date(r.getFullYear(),r.getMonth()+1,0),m=a.revenue*(1-y/100);await $.from("historical_project_financials").insert({project_id:_,tenant_id:n,period_start:r.toISOString().split("T")[0],period_end:d.toISOString().split("T")[0],role_name:"Invoice",hours:1,bill_rate:a.revenue,cost_rate:0,source:"invoice_upload",notes:`Cost estimated at ${y}% margin. Invoice: ${a.invoiceNumber||"N/A"}`})}U({title:"Invoices saved",description:`Added ${s.length} historical entries`}),G()}catch(a){console.error("Error saving invoices:",a),U({title:"Error",description:"Failed to save some invoices",variant:"destructive"})}finally{T(!1)}},t=i.filter(s=>s.status==="ready"&&s.revenue>0).length,l=i.filter(s=>s.status==="ready").reduce((s,n)=>s+n.revenue,0),I=s=>s>=1e6?`$${(s/1e6).toFixed(1)}M`:s>=1e3?`$${(s/1e3).toFixed(1)}K`:`$${s.toFixed(0)}`;return e.jsx(Z,{open:P,onOpenChange:D,children:e.jsxs(ee,{className:"max-w-3xl max-h-[80vh] overflow-hidden flex flex-col",children:[e.jsx(se,{children:e.jsx(te,{children:"Upload Invoices"})}),e.jsxs(ve,{value:j,onValueChange:s=>Y(s),className:"flex-1 overflow-hidden flex flex-col",children:[e.jsxs(Ne,{className:"grid w-full grid-cols-2",children:[e.jsxs(ie,{value:"pdf",className:"gap-2",children:[e.jsx(be,{className:"h-4 w-4"}),"PDF Invoices"]}),e.jsxs(ie,{value:"excel",className:"gap-2",children:[e.jsx($e,{className:"h-4 w-4"}),"Excel/CSV"]})]}),e.jsx(oe,{value:"pdf",className:"flex-1 overflow-hidden mt-4 space-y-4",children:i.length===0&&e.jsxs("div",{className:"border-2 border-dashed rounded-lg p-6 text-center hover:border-primary/50 transition-colors",onDragOver:s=>s.preventDefault(),onDrop:L,children:[e.jsx(re,{className:"h-8 w-8 mx-auto text-muted-foreground mb-3"}),e.jsx("p",{className:"font-medium mb-1",children:"Drop PDF invoices here"}),e.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:"AI will extract date and amount"}),x.length>0&&e.jsx("div",{className:"mb-3 space-y-1 max-h-32 overflow-y-auto",children:x.map((s,n)=>e.jsxs("div",{className:"flex items-center justify-between bg-muted/50 rounded px-2 py-1 text-sm",children:[e.jsx("span",{className:"truncate",children:s.name}),e.jsx(u,{variant:"ghost",size:"icon",className:"h-5 w-5",onClick:()=>K(n),"aria-label":"Remove file",children:e.jsx(ye,{className:"h-3 w-3"})})]},n))}),e.jsxs("div",{className:"flex gap-2 justify-center",children:[e.jsx(u,{variant:"outline",size:"sm",asChild:!0,children:e.jsxs("label",{className:"cursor-pointer",children:["Add Files",e.jsx("input",{type:"file",className:"hidden",multiple:!0,accept:".pdf",onChange:O})]})}),x.length>0&&e.jsx(u,{size:"sm",onClick:J,disabled:M,children:M?e.jsxs(e.Fragment,{children:[e.jsx(V,{className:"h-4 w-4 mr-1 animate-spin"}),p.current,"/",p.total]}):e.jsxs(e.Fragment,{children:["Parse ",x.length," Invoices"]})})]})]})}),e.jsx(oe,{value:"excel",className:"flex-1 overflow-hidden mt-4 space-y-4",children:i.length===0&&e.jsxs("div",{className:"space-y-4",children:[e.jsxs(u,{variant:"outline",size:"sm",onClick:R,children:[e.jsx(Ue,{className:"h-4 w-4 mr-2"}),"Download Template"]}),e.jsxs("div",{className:"border-2 border-dashed rounded-lg p-6 text-center hover:border-primary/50 transition-colors",onDragOver:s=>s.preventDefault(),onDrop:L,children:[e.jsx(re,{className:"h-8 w-8 mx-auto text-muted-foreground mb-3"}),e.jsx("p",{className:"font-medium mb-1",children:"Drop CSV file here"}),e.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:"Columns: period, revenue, cost (optional), notes"}),e.jsx(u,{variant:"outline",size:"sm",asChild:!0,children:e.jsxs("label",{className:"cursor-pointer",children:["Upload CSV",e.jsx("input",{type:"file",className:"hidden",accept:".csv",onChange:O})]})})]})]})}),M&&e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex justify-between text-sm",children:[e.jsx("span",{children:"Parsing invoices..."}),e.jsxs("span",{children:[p.current," of ",p.total]})]}),e.jsx(ke,{value:p.current/p.total*100})]}),i.length>0&&e.jsx(Ae,{className:"flex-1 border rounded-lg",children:e.jsxs(pe,{children:[e.jsx(je,{children:e.jsxs(q,{children:[e.jsx(N,{children:"File"}),e.jsx(N,{children:"Period"}),e.jsx(N,{children:"Invoice #"}),e.jsx(N,{className:"text-right",children:"Amount"}),e.jsx(N,{children:"Status"})]})}),e.jsx(fe,{children:i.map(s=>e.jsxs(q,{children:[e.jsx(b,{className:"font-medium max-w-[120px] truncate",children:s.fileName}),e.jsx(b,{children:s.period||"-"}),e.jsx(b,{children:s.invoiceNumber||"-"}),e.jsx(b,{className:"text-right",children:s.revenue>0?I(s.revenue):"-"}),e.jsxs(b,{children:[s.status==="parsing"&&e.jsxs(W,{variant:"secondary",children:[e.jsx(V,{className:"h-3 w-3 mr-1 animate-spin"}),"Parsing"]}),s.status==="ready"&&e.jsxs(W,{variant:"excellent",children:[e.jsx(he,{className:"h-3 w-3 mr-1"}),"Ready"]}),s.status==="error"&&e.jsxs(W,{variant:"destructive",children:[e.jsx(ge,{className:"h-3 w-3 mr-1"}),"Error"]})]})]},s.id))})]})})]}),i.length>0&&e.jsxs(ae,{className:"border-t pt-4",children:[e.jsxs("div",{className:"flex-1 text-sm text-muted-foreground",children:[t," invoices • Total: ",I(l)]}),e.jsx(u,{variant:"outline",onClick:()=>{g([]),w([])},children:"Clear"}),e.jsxs(u,{onClick:Q,disabled:t===0||o,children:[o?e.jsx(V,{className:"h-4 w-4 mr-2 animate-spin"}):null,"Save ",t," Invoices"]})]})]})})}function cs({projectId:P}){const{currentTenant:D}
4=ue(),{toast:_}=xe(),[y,G]=c.useState([]),[z,U]=c.useState(!0),[j,Y]=c.useState(35),[x,w]=c.useState(!1),[i,g]=c.useState(null),[M,E]=c.useState(!1),[o,T]=c.useState({period:"",revenue:"",cost:"",notes:""}),p=c.useCallback(async()=>{try{const{data:t,error:l}=await $.from("historical_project_financials").select("*").eq("project_id",P).order("created_at",{ascending:!1});if(l)throw l;const I=(t||[]).map(s=>({id:s.id,period_start:s.period_start||s.created_at?.split("T")[0],period_end:s.period_end||s.created_at?.split("T")[0],revenue:(s.hours||0)*(s.bill_rate||0),cost:(s.hours||0)*(s.cost_rate||0),cost_rate:s.cost_rate||0,source:s.source||"manual",notes:s.notes}));if(G(I),D?.id){const{data:s}=await $.from("tenants").select("target_margin_percent").eq("id",D.id).single();s?.target_margin_percent&&Y(s.target_margin_percent)}}catch(t){console.error("Error loading financials:",t)}finally{U(!1)}},[P,D?.id]);c.useEffect(()=>{p()},[p]);const S=y.reduce((t,l)=>t+l.revenue,0),O=y.reduce((t,l)=>t+l.cost,0),L=S-O,K=S>0?Math.round(L/S*100):0,R=y.some(t=>t.cost_rate===0),C=t=>t>=1e6?`$${(t/1e6).toFixed(1)}M`:t>=1e3?`$${(t/1e3).toFixed(1)}K`:`$${t.toFixed(0)}`,X=(t,l)=>new Date(t).toLocaleDateString("en-US",{month:"short",year:"numeric"}),J=async()=>{if(!o.period||!o.revenue){_({title:"Missing fields",description:"Period and revenue are required",variant:"destructive"});return}if(!D?.id){_({title:"Error",description:"Tenant context not loaded. Please try again.",variant:"destructive"});return}const t=D.id;try{const l=new Date(o.period+"-01"),I=new Date(l.getFullYear(),l.getMonth()+1,0),s=Number(o.revenue),n=o.cost?Number(o.cost):s*(1-j/100),a=!o.cost,{error:r}=await $.from("historical_project_financials").insert({project_id:P,tenant_id:t,period_start:l.toISOString().split("T")[0],period_end:I.toISOString().split("T")[0],role_name:"Historical",hours:1,bill_rate:s,cost_rate:a?0:n,source:"manual",notes:a?`Cost estimated at ${j}% margin. ${o.notes||""}`:o.notes||null});if(r)throw r;_({title:"Entry added"}),E(!1),T({period:"",revenue:"",cost:"",notes:""}),p()}catch(l){console.error("Error adding entry:",l),_({title:"Error",description:"Failed to add entry",variant:"destructive"})}},Q=async()=>{if(i)try{const{error:t}=await $.from("historical_project_financials").update({cost_rate:i.cost,notes:`Actual cost entered. ${i.notes||""}`}).eq("id",i.id);if(t)throw t;_({title:"Cost updated"}),g(null),p()}catch(t){console.error("Error updating cost:",t),_({title:"Error",description:"Failed to update",variant:"destructive"})}};return z?e.jsx(ce,{children:e.jsx(de,{className:"pt-6",children:e.jsx("div",{className:"flex items-center justify-center py-8",children:e.jsx(V,{className:"h-6 w-6 animate-spin text-muted-foreground"})})})}):e.jsxs(Ce,{children:[e.jsxs(ce,{children:[e.jsx(Ee,{children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsxs(Se,{className:"flex items-center gap-2",children:[e.jsx(me,{className:"h-5 w-5"}),"Historical Financials"]}),e.jsx(Ie,{children:"Past revenue and cost data for this project"})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>E(!0),children:[e.jsx(Me,{className:"h-4 w-4 mr-1"}),"Add Entry"]}),e.jsxs(u,{size:"sm",onClick:()=>w(!0),children:[e.jsx(re,{className:"h-4 w-4 mr-1"}),"Upload Invoices"]})]})]})}),e.jsxs(de,{className:"space-y-4",children:[R&&e.jsxs(Pe,{children:[e.jsx(Oe,{className:"h-4 w-4"}),e.jsxs(Fe,{children:["Some costs are estimated based on org target margin (",j,"%). Click the edit button to enter actual costs and recalculate margin."]})]}),y.length>0&&e.jsxs("div",{className:"grid grid-cols-3 gap-4 p-4 bg-muted/50 rounded-lg",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:"Total Revenue"}),e.jsx(ne,{size:"md",children:C(S)})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-sm text-muted-foreground flex items-center gap-1",children:[R?"Estimated Cost":"Total Cost",R&&e.jsxs(_e,{children:[e.jsx(Te,{children:e.jsx(ge,{className:"h-3 w-3 text-warning"})}),e.jsxs(De,{children:["Based on ",j,"% target margin"]})]})]}),e.jsx(ne,{size:"md",children:C(O)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:"Gross Margin"}),e.jsxs("p",{className:"text-2xl font-bold",children:[C(L),e.jsxs("span",{className:"text-sm font-normal text-muted-foreground ml-1",children:["(",K,"%)"]})]})]})]}),y.length>0?e.jsxs(pe,{children:[e.jsx(je,{children:e.jsxs(q,{children:[e.jsx(N,{children:"Period"}),e.jsx(N,{className:"text-right",children:"Revenue"}),e.jsx(N,{className:"text-right",children:"Cost"}),e.jsx(N,{children:"Source"}),e.jsx(N,{className:"w-[60px]"})]})}),e.jsx(fe,{children:y.map(t=>{const l=t.cost_rate===0;return e.jsxs(q,{children:[e.jsx(b,{className:"font-medium",children:X(t.period_start,t.period_end)}),e.jsx(b,{className:"text-right",children:C(t.revenue)}),e.jsx(b,{className:"text-right",children:l?e.jsxs("span",{className:"text-muted-foreground italic",children:["~",C(t.revenue*(1-j/100))]}):e.jsxs("span",{className:"flex items-center justify-end gap-1",children:[C(t.cost),e.jsx(he,{className:"h-3 w-3 text-success"})]})}),e.jsx(b,{children:e.jsx(W,{variant:"outline",className:"text-xs",children:t.source})}),e.jsx(b,{children:e.jsx(u,{variant:"ghost",size:"icon",className:"h-7 w-7",onClick:()=>g({...t,cost:l?t.revenue*(1-j/100):t.cost}),children:e.jsx(Le,{className:"h-3 w-3"})})})]},t.id)})})]}):e.jsxs("div",{className:"text-center py-8 text-muted-foreground",children:[e.jsx(me,{className:"h-10 w-10 mx-auto mb-2 opacity-30"}),e.jsx("p",{children:"No historical data yet"}),e.jsx("p",{className:"text-sm",children:"Upload invoices or add entries manually"})]})]})]}),e.jsx(Re,{open:x,onOpenChange:w,projectId:P,targetMargin:j,onComplete:()=>{w(!1),p()}}),e.jsx(Z,{open:M,onOpenChange:E,children:e.jsxs(ee,{children:[e.jsx(se,{children:e.jsx(te,{children:"Add Historical Entry"})}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Period (Month) *"}),e.jsx(A,{type:"month",value:o.period,onChange:t=>T({...o,period:t.target.value})})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Revenue *"}),e.jsx(A,{type:"number",placeholder:"e.g., 45000",value:o.revenue,onChange:t=>T({...o,revenue:t.target.value})})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Cost (optional)"}),e.jsx(A,{type:"number",placeholder:`Leave blank to estimate at ${j}% margin`,value:o.cost,onChange:t=>T({...o,cost:t.target.value})}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["If left blank, cost will be estimated based on ",j,"% target margin"]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Notes"}),e.jsx(A,{placeholder:"Optional notes",value:o.notes,onChange:t=>T({...o,notes:t.target.value})})]})]}),e.jsxs(ae,{children:[e.jsx(u,{variant:"outline",onClick:()=>E(!1),children:"Cancel"}),e.jsx(u,{onClick:J,children:"Add Entry"})]})]})}),e.jsx(Z,{open:!!i,onOpenChange:()=>g(null),children:e.jsxs(ee,{children:[e.jsx(se,{children:e.jsx(te,{children:"Update Cost"})}),i&&e.jsxs("div",{className:"space-y-4",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:"Enter the actual cost for this period to recalculate margin."}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Revenue"}),e.jsx(A,{value:C(i.revenue),disabled:!0})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(k,{children:"Actual Cost *"}),e.jsx(A,{type:"number",value:i.cost,onChange:t=>g({...i,cost:Number(t.target.value)})})]}),e.jsx("div",{className:"p-3 bg-muted rounded-lg",children:e.jsxs("p",{className:"text-sm",children:[e.jsx("strong",{children:"New Margin:"})," ",Math.round((i.revenue-i.cost)/i.revenue*100),"%"]})})]}),e.jsxs(ae,{children:[e.jsx(u,{variant:"outline",onClick:()=>g(null),children:"Cancel"}),e.jsx(u,{onClick:Q,children:"Update"})]})]})})]})}export{cs as HistoricalFinancialsPanel};

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.