PageSourceSearch

https://cently.ai/assets/budget.lazy-vZ6jOVNG.js

js cently.ai collected 2026-09-24 14:08:03 UTC 15,175 bytes, 2 lines download raw bytes

1import{a as e,n as t,t as s}from"./jsx-runtime-CYBR3w23.js";import{n as r}from"./fileRoute-cMfqMByr.js";import{r as a,t as n}from"./useMutation-BlU_izrx.js";import{t as i}from"./useQuery-CyJgmpl9.js";import{t as o}from"./createLucideIcon-DHmLFieT.js";import{t as l}from"./chevron-down-CkJHdr3S.js";import{t as d}from"./chevron-left-S1kg-ECM.js";import{t as c}from"./chevron-right-COXB2oD6.js";import{t as u}from"./chevron-up-BtWKEEox.js";import{t as m}from"./circle-alert-BYBOe0Lr.js";import{t as g}from"./plus-C310BvH4.js";import{t as p}from"./trash-2-BcRBd9qH.js";import{t as x}from"./button-DXWhjQtx.js";import{n as h}from"./dist-DWu9JBMs.js";import{a as f,n as j,o as y,t as b}from"./card-DY64C3Fv.js";import{t as v}from"./input-CaTPnGjB.js";import{t as N}from"./badge-joInD_v8.js";import{t as w}from"./checkbox-lZeyDut-.js";import{t as _}from"./src-DuyaD3uj.js";import{i as C}from"./client-GUL8pnf2.js";import{n as k}from"./auth-context-NrRk_Vtl.js";import"./use-auth-CRIdgVhL.js";import{n as M}from"./src-CjrizOZa2.js";var z=o("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]),E=e(t(),1),S=e(s(),1),q="__uncategorized__",U=M.split("\n").map(e=>e.trim()).filter(Boolean);function D(e){return new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),1)).toISOString().slice(0,10)}function T(e,t){const s=new Date(`${e}T00:00:00Z`);return D(new Date(Date.UTC(s.getUTCFullYear(),s.getUTCMonth()+t,1)))}function F(e){const t=new Date(`${e}T00:00:00Z`);return new Intl.DateTimeFormat(void 0,{month:"long",year:"numeric",timeZone:"UTC"}).format(t)}function $(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:2}).format(e)}function K(e){const t=e?.trim();return t||q}function L(e){return e===q?"Uncategorized":e}function Z({label:e,value:t,tone:s="default"}){return(0,S.jsxs)("div",{className:"rounded-lg border bg-background px-3 py-2",children:[(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:e}),(0,S.jsx)("p",{className:"negative"===s?"text-lg font-semibold text-destructive":"text-lg font-semibold",children:t})]})}function A({status:e,atRisk:t}){return"over_budget"===e?(0,S.jsx)(N,{variant:"destructive",children:"Over budget"}):t?(0,S.jsx)(N,{variant:"outline",className:"border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",children:"At risk"}):(0,S.jsx)(N,{variant:"outline",children:"On track"})}function B({title:e,detail:t,tone:s}){return(0,S.jsx)(b,{className:"rounded-xl "+("danger"===s?"border-destructive/40 bg-destructive/5 text-destructive":"border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300"),children:(0,S.jsxs)(j,{className:"flex items-start gap-2 pt-6",children:[(0,S.jsx)(m,{className:"mt-0.5 size-4 shrink-0"}),(0,S.jsxs)("div",{className:"space-y-1",children:[(0,S.jsx)("p",{className:"text-sm font-semibold",children:e}),(0,S.jsx)("p",{className:"text-xs",children:t})]})]})})}var O=r("/_layout-app/app/budget")({component:function(){const e=a(),{user:t}=k(),[s,r]=(0,E.useState)(()=>D(new Date)),[o,m]=(0,E.useState)(null),[N,M]=(0,E.useState)(null),[O,Q]=(0,E.useState)(!1),[R,I]=(0,E.useState)(""),[P,G]=(0,E.useState)(""),[Y,H]=(0,E.useState)([]),[W,J]=(0,E.useState)(""),V=`${s}T00:00:00.000Z`,X=`${T(s,1)}T00:00:00.000Z`,ee=i({queryKey:["budget-month-groups",s],queryFn:async()=>{const{data:e,error:t}=await C.schema("api").rpc("get_budget_month_groups",{p_month:s});if(t)throw t;return e??[]}}),te=i({queryKey:["budget-month-transactions",s],queryFn:async()=>{const{data:e,error:t}=await C.schema("api").from("transaction").select("category,amount").gte("purchase_date",V).lt("purchase_date",X);if(t)throw t;return e??[]}}),se=i({queryKey:["budget-category-keys",t?.id],enabled:!!t?.id,queryFn:async()=>{const{data:e,error:s}=await C.schema("api").from("budget_group_category").select("category_key").eq("user_id",t.id);if(s)throw s;return(e??[]).map(e=>e.category_key)}}),re=i({queryKey:["budget-month-group-categories",s,o],enabled:null!=o,queryFn:async()=>{const{data:e,error:t}=await C.schema("api").rpc("get_budget_month_group_categories",{p_month:s,p_group_id:o});if(t)throw t;return e??[]}}),ae=ee.data??[],ne=te.data??[],ie=(0,E.useMemo)(()=>{const e=new Map;for(const t of ne){if(t.amount<=0)continue;const s=K(t.category);
1e.set(s,(e.get(s)??0)+t.amount)}return e},[ne]),oe=(0,E.useMemo)(()=>{const e=new Set([q,...U,...Array.from(ie.keys())]);return Array.from(e).sort((e,t)=>L(e).localeCompare(L(t),void 0,{sensitivity:"base"}))},[ie]),le=(0,E.useMemo)(()=>{const e=W.trim().toLowerCase();return e?oe.filter(t=>L(t).toLowerCase().includes(e)):oe},[oe,W]),de=(0,E.useMemo)(()=>new Set(se.data??[]),[se.data]),ce=ae.reduce((e,t)=>e+t.monthly_limit,0),ue=ne.reduce((e,t)=>t.amount<=0?e:e+t.amount,0),me=(0,E.useMemo)(()=>{let e=0;for(const t of de)e+=ie.get(t)??0;return e},[de,ie]),ge=ce-me,pe=ue>0?Math.round(me/ue*100):0,xe=(0,E.useMemo)(()=>function(e){const t=new Date(`${e}T00:00:00Z`),s=new Date(`${T(e,1)}T00:00:00Z`),r=new Date;if(r<t)return{fractionElapsed:0,isCurrentMonth:!1};if(r>=s)return{fractionElapsed:1,isCurrentMonth:!1};const a=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+1,0)).getUTCDate();return{fractionElapsed:(n=r.getUTCDate()/Math.max(a,1),Math.min(1,Math.max(0,n))),isCurrentMonth:!0};var n}(s),[s]),he=ie.get(q)??0,fe=n({mutationKey:["save-budget-group"],mutationFn:async()=>{if(!t?.id)throw new Error("Sign in to update budget groups.");if(!R.trim())throw new Error("Group name is required.");if(0===Y.length)throw new Error("Choose at least one category.");const e=Number(P);if(Number.isNaN(e)||e<0)throw new Error("Monthly limit must be zero or greater.");const s=C.schema("api");let r=N?.group_id;if(N){const{error:r}=await s.from("budget_group").update({name:R.trim(),monthly_limit:Number(e.toFixed(2))}).eq("id",N.group_id).eq("user_id",t.id);if(r)throw r;const{error:a}=await s.from("budget_group_category").delete().eq("group_id",N.group_id).eq("user_id",t.id);if(a)throw a}else{const{data:a,error:n}=await s.from("budget_group").insert({user_id:t.id,name:R.trim(),monthly_limit:Number(e.toFixed(2))}).select("id").single();if(n)throw n;r=a.id}const a=Y.map(e=>({user_id:t.id,group_id:r,category_key:e})),{error:n}=await s.from("budget_group_category").insert(a);if(n)throw n},onSuccess:async()=>{M(null),Q(!1),I(""),G(""),H([]),await e.invalidateQueries({queryKey:["budget-month-groups",s]}),await e.invalidateQueries({queryKey:["budget-month-group-categories"]})},onError:e=>{const t=e instanceof Error?e.message:"Unable to save budget group.";h.error(t)}}),je=n({mutationKey:["delete-budget-group"],mutationFn:async e=>{if(!t?.id)throw new Error("Sign in to delete budget groups.");const{error:s}=await C.schema("api").from("budget_group").delete().eq("id",e).eq("user_id",t.id);if(s)throw s},onSuccess:async(t,r)=>{o===r&&m(null),await e.invalidateQueries({queryKey:["budget-month-groups",s]}),await e.invalidateQueries({queryKey:["budget-month-group-categories"]})},onError:e=>{const t=e instanceof Error?e.message:"Unable to delete budget group.";h.error(t)}});return(0,S.jsxs)("div",{className:"flex min-w-0 flex-col gap-4 overflow-x-hidden px-4 pb-16",children:[(0,S.jsxs)(b,{className:"rounded-xl bg-muted/50",children:[(0,S.jsx)(f,{className:"pb-3",children:(0,S.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,S.jsx)(y,{children:"Budget"}),(0,S.jsxs)("div",{className:"flex items-center gap-2",children:[(0,S.jsxs)(x,{size:"icon",variant:"outline",onClick:()=>r(e=>T(e,-1)),children:[(0,S.jsx)(d,{className:"size-4"}),(0,S.jsx)("span",{className:"sr-only",children:"Previous month"})]}),(0,S.jsx)("span",{className:"min-w-36 text-center text-sm font-medium",children:F(s)}),(0,S.jsxs)(x,{size:"icon",variant:"outline",onClick:()=>r(e=>T(e,1)),children:[(0,S.jsx)(c,{className:"size-4"}),(0,S.jsx)("span",{className:"sr-only",children:"Next month"})]})]})]})}),(0,S.jsxs)(j,{className:"space-y-3",children:[(0,S.jsxs)("div",{className:"grid min-w-0 grid-cols-1 gap-4 md:grid-cols-3",children:[(0,S.jsx)(Z,{label:"Budgeted",value:$(ce)}),(0,S.jsx)(Z,{label:"Spent",value:$(me)}),(0,S.jsx)(Z,{label:"Remaining",value:$(ge),tone:ge<0?"negative":"default"})]}),(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:"Each category counts once, even when it's in more than one group."}),ue>0&&ae.length>0?(0,S.jsxs)("p",{className:"text-xs text-muted-foreground",children:[$(me)," of your ",$(ue)," spent this month is inside a budget (",pe,"%)."]}):null]})]}),he>0?(0,S.jsx)(B,{tone:"warning",title:"Uncategorized spending found",detail
1:`${$(he)} is uncategorized this month. Add it to a group if you want to track it.`}):null,(0,S.jsxs)(b,{className:"rounded-xl bg-muted/50",children:[(0,S.jsx)(f,{className:"pb-3",children:(0,S.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,S.jsx)(y,{children:"Budget Groups"}),O?(0,S.jsx)(x,{size:"sm",variant:"outline",onClick:function(){M(null),Q(!1),I(""),G(""),H([]),J("")},children:"Cancel"}):(0,S.jsxs)(x,{size:"sm",onClick:()=>Q(!0),children:[(0,S.jsx)(g,{className:"size-4 mr-1"})," Create group"]})]})}),(0,S.jsxs)(j,{className:"space-y-4",children:[O?(0,S.jsxs)("div",{className:"rounded-lg border bg-background p-3 space-y-3",children:[(0,S.jsxs)("div",{className:"grid min-w-0 grid-cols-1 gap-3 md:grid-cols-3",children:[(0,S.jsxs)("div",{className:"md:col-span-2",children:[(0,S.jsx)("p",{className:"mb-1 text-xs text-muted-foreground",children:"Group name"}),(0,S.jsx)(v,{value:R,onChange:e=>I(e.target.value),placeholder:"Family spending"})]}),(0,S.jsxs)("div",{children:[(0,S.jsx)("p",{className:"mb-1 text-xs text-muted-foreground",children:"Monthly limit"}),(0,S.jsx)(v,{inputMode:"decimal",value:P,onChange:e=>G(e.target.value),placeholder:"0.00"})]})]}),(0,S.jsxs)("div",{className:"space-y-2",children:[(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:"Categories (choose one or more)"}),(0,S.jsx)(v,{value:W,onChange:e=>J(e.target.value),placeholder:"Search categories..."}),(0,S.jsx)("div",{className:"max-h-56 overflow-auto rounded-md border p-2 space-y-2",children:0===le.length?(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:"No categories found."}):le.map(e=>(0,S.jsxs)("label",{className:"flex items-center justify-between gap-2 text-sm",children:[(0,S.jsxs)("div",{className:"flex items-center gap-2 min-w-0",children:[(0,S.jsx)(w,{checked:Y.includes(e),onCheckedChange:t=>function(e,t){H(s=>t?s.includes(e)?s:[...s,e]:s.filter(t=>t!==e))}(e,!!t)}),(0,S.jsx)("span",{className:"truncate",children:L(e)})]}),(0,S.jsx)("span",{className:"text-xs text-muted-foreground",children:$(ie.get(e)??0)})]},e))})]}),(0,S.jsx)("div",{className:"flex justify-end",children:(0,S.jsx)(x,{size:"sm",disabled:fe.isPending,onClick:()=>{fe.mutateAsync()},children:N?"Save group":"Create group"})})]}):null,ee.isLoading||te.isLoading?(0,S.jsx)("p",{className:"text-sm text-muted-foreground",children:"Loading budget groups…"}):null,ee.isError||te.isError?(0,S.jsx)("p",{className:"text-sm text-destructive",children:"We could not load your budget groups right now. Please try again."}):null,ee.isLoading||0!==ae.length?null:(0,S.jsx)("p",{className:"text-sm text-muted-foreground",children:"Create your first budget group to start tracking combined categories."}),ae.map(e=>{const s=e.monthly_limit<=0?e.spent>0?1:0:e.spent/e.monthly_limit,r=Math.min(100*s,100),a=xe.isCurrentMonth&&e.monthly_limit>0&&"over_budget"!==e.status&&e.spent>e.monthly_limit*xe.fractionElapsed,n=function(e,t,s){return"over_budget"===e||t>=1?"bg-destructive":s?"bg-amber-500":"bg-primary"}(e.status,s,a),i=o===e.group_id,d=xe.isCurrentMonth&&xe.fractionElapsed>0&&xe.fractionElapsed<1&&e.monthly_limit>0?e.spent/xe.fractionElapsed:null,c=null!=d?Math.max(d-e.monthly_limit,0):0;return(0,S.jsxs)("div",{className:"rounded-lg border bg-background p-3 space-y-3",children:[(0,S.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,S.jsxs)("div",{className:"flex items-center gap-2",children:[(0,S.jsx)("p",{className:"text-sm font-medium",children:e.name}),(0,S.jsx)(A,{status:e.status,atRisk:a})]}),(0,S.jsxs)("div",{className:"flex items-center gap-2",children:[(0,S.jsx)(x,{size:"sm",variant:"outline",onClick:()=>m(t=>t===e.group_id?null:e.group_id),children:i?(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(u,{className:"size-4 mr-1"})," Hide categories"]}):(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(l,{className:"size-4 mr-1"})," Show categories"]})}),(0,S.jsxs)(x,{size:"icon",variant:"outline",onClick:()=>{!async function(e){if(!t?.id)return;const{data:s,error:r}=await C.schema("api").from("budget_group_category").select("category_key").eq("group_id",e.group_id).eq("user_id",t.id);r?h.error("Unable to load categories for this group."):(M(e),Q(!0),I(e.name),G(e.monthly_limit.toFixed(2)),H((s??[]).map(e=>e.category_key)))}(e)},children:[(0,S.jsx)(z,{className:"size-4"}),(0,S.jsx)("span",{className:"sr-only",children:"Edit group"})]}),(0,S.jsxs)(x,{size:"icon",variant:"outline",disabled:je.isPending,onClick:()=>{window.confirm(`Delete "${e.name}"?`)&&je.mutateAsync(e.group_id)},children:[(0,S.jsx)(p,{className:"size-4"}),(0,S.jsx)("span",{className:"sr-only",children:"Delete group"})]})]})]}),(0,S.jsxs)("div",{className:"grid min-w-0 grid-cols-1 gap-3 md:grid-cols-3",children:[(0,S.jsxs)("div",{className:"space-y-1 md:col-span-2",children:[(0,S.jsxs)("div",{className:"flex items-center justify-between text-xs text-muted-foreground",children:[(0,S.jsx)("span",{children:"Spent"}),(0,S.jsx)("span",{children:$(e.spent)}
1)]}),(0,S.jsxs)("div",{className:"relative",children:[(0,S.jsx)(_,{value:r,className:"h-2",indicatorClassName:n}),xe.isCurrentMonth?(0,S.jsx)("div",{"aria-hidden":!0,className:"pointer-events-none absolute inset-y-0 w-px bg-muted-foreground/60",style:{left:`${Math.round(100*xe.fractionElapsed)}%`}}):null]}),null!=d?(0,S.jsxs)("p",{className:c>0?"text-xs text-destructive":"text-xs text-muted-foreground",children:["On pace to spend ",$(d),c>0?` (${$(c)} over budget)`:"","."]}):null]}),(0,S.jsxs)("div",{className:"text-sm",children:[(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:"Remaining"}),(0,S.jsx)("p",{className:e.remaining<0?"font-medium text-destructive":"font-medium",children:$(e.remaining)})]})]}),i?(0,S.jsxs)("div",{className:"rounded-md border p-2 space-y-2",children:[re.isLoading?(0,S.jsx)("p",{className:"text-xs text-muted-foreground",children:"Loading categories…"}):null,re.isError?(0,S.jsx)("p",{className:"text-xs text-destructive",children:"Unable to load categories for this group."}):null,(re.data??[]).map(t=>(0,S.jsxs)("div",{className:"flex items-center justify-between text-sm",children:[(0,S.jsx)("span",{children:t.category_label}),(0,S.jsx)("span",{className:"text-muted-foreground",children:$(t.spent)})]},`${e.group_id}:${t.category_key}`))]}):null]},e.group_id)})]})]})]})}});export{O as Route};
2//# sourceMappingURL=budget.lazy-vZ6jOVNG.js.map

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.