@wealthx/shadcn 1.5.41 → 1.5.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +204 -199
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-5FHBC6DY.mjs → chunk-33WZ5NCW.mjs} +1 -1
- package/dist/{chunk-C35JMOII.mjs → chunk-3G6JUYRE.mjs} +4 -4
- package/dist/{chunk-LBXIYS34.mjs → chunk-4PVCJ3JD.mjs} +1 -1
- package/dist/{chunk-EHQL64B7.mjs → chunk-4SUXTO2Z.mjs} +4 -4
- package/dist/{chunk-BAONSY54.mjs → chunk-5RYH7SOQ.mjs} +1 -1
- package/dist/{chunk-3C4DZTGA.mjs → chunk-5XD7A7YC.mjs} +1 -1
- package/dist/{chunk-5DAQU3B6.mjs → chunk-66JXT7NY.mjs} +1 -1
- package/dist/{chunk-NGKTJRFN.mjs → chunk-6DO4EGT2.mjs} +2 -2
- package/dist/{chunk-C7ZTZTEW.mjs → chunk-6XJWL2E5.mjs} +1 -1
- package/dist/{chunk-FQUT5XD6.mjs → chunk-A4UP4QFB.mjs} +1 -1
- package/dist/{chunk-USIRKDYQ.mjs → chunk-BFCX7ADE.mjs} +1 -1
- package/dist/{chunk-XGRSPFFC.mjs → chunk-CQHKU24Z.mjs} +1 -1
- package/dist/{chunk-HONTZFLO.mjs → chunk-DP4ER6TJ.mjs} +1 -1
- package/dist/{chunk-VLVEZHFE.mjs → chunk-EFSLAMHI.mjs} +4 -4
- package/dist/{chunk-FYZBGWYR.mjs → chunk-FVSOFXJQ.mjs} +1 -1
- package/dist/{chunk-JUMEIPII.mjs → chunk-G2MOZZPE.mjs} +8 -8
- package/dist/{chunk-D3HKFRQO.mjs → chunk-GQWKBESP.mjs} +8 -5
- package/dist/{chunk-MD66TGX7.mjs → chunk-GXDKWCMV.mjs} +1 -1
- package/dist/{chunk-77L3UPBW.mjs → chunk-H7NOUDU3.mjs} +5 -5
- package/dist/{chunk-4LLTZ45R.mjs → chunk-HOXTEU5K.mjs} +8 -7
- package/dist/{chunk-ZA37ZWZW.mjs → chunk-IXW77PMI.mjs} +7 -7
- package/dist/{chunk-XHZONBL4.mjs → chunk-JLEQU5BO.mjs} +1 -1
- package/dist/{chunk-6UKOJLXO.mjs → chunk-JSFWRD7K.mjs} +4 -4
- package/dist/{chunk-7PTRHNUV.mjs → chunk-JY3FUGNL.mjs} +1 -1
- package/dist/{chunk-3ZU5BH6X.mjs → chunk-KEOAPKJO.mjs} +3 -3
- package/dist/{chunk-4QTHK7ML.mjs → chunk-KWYFJQV6.mjs} +1 -1
- package/dist/{chunk-FGMDBJCF.mjs → chunk-LDPCSE7J.mjs} +4 -4
- package/dist/chunk-LFWNKXZU.mjs +109 -0
- package/dist/{chunk-IRZWYTGV.mjs → chunk-M32YSAWL.mjs} +8 -7
- package/dist/{chunk-LLAGF6BA.mjs → chunk-MUB2G36A.mjs} +1 -1
- package/dist/{chunk-DQNNP6I4.mjs → chunk-NIETQFJQ.mjs} +1 -1
- package/dist/{chunk-RUX3OLVZ.mjs → chunk-OTFG57ZF.mjs} +1 -1
- package/dist/{chunk-OKIWXOJL.mjs → chunk-OWTW5WAJ.mjs} +1 -1
- package/dist/{chunk-WWIWRNBK.mjs → chunk-P7NSCTAW.mjs} +1 -1
- package/dist/{chunk-BZWQU52U.mjs → chunk-QZREZL2F.mjs} +1 -1
- package/dist/{chunk-E432NK23.mjs → chunk-RBQ4BZUV.mjs} +6 -6
- package/dist/{chunk-I2EKKSEF.mjs → chunk-RKBLVNDC.mjs} +4 -7
- package/dist/{chunk-LHQACMZY.mjs → chunk-SPPQFW32.mjs} +106 -50
- package/dist/{chunk-OSSS56CB.mjs → chunk-SUXJWKRI.mjs} +4 -4
- package/dist/{chunk-SCGCGVDN.mjs → chunk-SZXIPE5J.mjs} +1 -1
- package/dist/{chunk-GIQGZFP6.mjs → chunk-THOHFAW2.mjs} +28 -12
- package/dist/{chunk-VVURVETY.mjs → chunk-TOQRA2TD.mjs} +1 -1
- package/dist/{chunk-GYWOD2YI.mjs → chunk-TZSDYQFH.mjs} +4 -4
- package/dist/{chunk-S7SBLNX4.mjs → chunk-UB3WG6I4.mjs} +1 -1
- package/dist/{chunk-PGJRZHN7.mjs → chunk-UVZ3JWFG.mjs} +1 -1
- package/dist/{chunk-UD5UF5OC.mjs → chunk-W7OPFKTZ.mjs} +4 -4
- package/dist/{chunk-YEWNFK5S.mjs → chunk-WLXP4OOF.mjs} +5 -5
- package/dist/{chunk-ORMC3TV3.mjs → chunk-XYXYTTNW.mjs} +1 -1
- package/dist/{chunk-CZOGJC76.mjs → chunk-YACFZWRR.mjs} +7 -7
- package/dist/{chunk-UTCW5YUX.mjs → chunk-YPATB6YQ.mjs} +9 -9
- package/dist/{chunk-BZGFW6L7.mjs → chunk-YWJAIPUA.mjs} +1 -1
- package/dist/{chunk-MHBQJVHE.mjs → chunk-Z65BGSHI.mjs} +5 -5
- package/dist/{chunk-PCULNQWA.mjs → chunk-ZGSFRUVI.mjs} +3 -3
- package/dist/{chunk-7NQKFPXE.mjs → chunk-ZRYG6ICN.mjs} +1 -1
- package/dist/{chunk-ZFKAYRFQ.mjs → chunk-ZUHFYW65.mjs} +1 -1
- package/dist/components/ui/about-you-form.mjs +2 -2
- package/dist/components/ui/account-list-carousel.mjs +2 -2
- package/dist/components/ui/add-column-modal.mjs +4 -4
- package/dist/components/ui/add-lead-modal.mjs +4 -4
- package/dist/components/ui/advisor-card.mjs +2 -2
- package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
- package/dist/components/ui/ai-builder/index.mjs +4 -4
- package/dist/components/ui/ai-conversations/index.mjs +4 -4
- package/dist/components/ui/alert-dialog.mjs +3 -3
- package/dist/components/ui/applicant-expenses-section.mjs +1 -1
- package/dist/components/ui/appointment-action-dialogs.mjs +5 -5
- package/dist/components/ui/appointment-availability-settings.mjs +4 -4
- package/dist/components/ui/appointment-book-dialog.mjs +4 -4
- package/dist/components/ui/appointment-detail-sheet.mjs +6 -6
- package/dist/components/ui/appointment-upcoming-card.mjs +4 -4
- package/dist/components/ui/asset-accordion.mjs +7 -7
- package/dist/components/ui/assets-liabilities-side-card.js +19 -66
- package/dist/components/ui/assets-liabilities-side-card.mjs +22 -69
- package/dist/components/ui/backoffice-alert-history-chart.js +1 -1
- package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -5
- package/dist/components/ui/backoffice-alert-matching-chart.js +1 -1
- package/dist/components/ui/backoffice-alert-matching-chart.mjs +5 -5
- package/dist/components/ui/backoffice-alerts-chart.js +1 -1
- package/dist/components/ui/backoffice-alerts-chart.mjs +5 -5
- package/dist/components/ui/backoffice-connections-chart.js +1 -1
- package/dist/components/ui/backoffice-connections-chart.mjs +5 -5
- package/dist/components/ui/backoffice-contact-history-chart.js +1 -1
- package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -5
- package/dist/components/ui/backoffice-contact-matching-chart.js +1 -1
- package/dist/components/ui/backoffice-contact-matching-chart.mjs +5 -5
- package/dist/components/ui/backoffice-signup-steps.mjs +4 -4
- package/dist/components/ui/bank-statement-generate-dialog.mjs +4 -4
- package/dist/components/ui/bank-statement-pdf-viewer.mjs +4 -4
- package/dist/components/ui/borrowing-capacity-atoms.js +3 -6
- package/dist/components/ui/borrowing-capacity-atoms.mjs +2 -2
- package/dist/components/ui/borrowing-capacity-card.js +5 -5
- package/dist/components/ui/borrowing-capacity-card.mjs +6 -6
- package/dist/components/ui/borrowing-capacity-line-chart.js +5 -5
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -5
- package/dist/components/ui/calculator-section.mjs +4 -4
- package/dist/components/ui/cash-balance-line-chart.js +102 -46
- package/dist/components/ui/cash-balance-line-chart.mjs +5 -5
- package/dist/components/ui/cashflow-bar-chart.js +7 -4
- package/dist/components/ui/cashflow-bar-chart.mjs +5 -5
- package/dist/components/ui/category-edit-dialog.mjs +4 -4
- package/dist/components/ui/color-picker.mjs +2 -2
- package/dist/components/ui/contact-alert-dialog/index.mjs +4 -4
- package/dist/components/ui/create-contact-modal.mjs +4 -4
- package/dist/components/ui/csv-import-modal.mjs +4 -4
- package/dist/components/ui/dashboard-expense-categories.js +96 -63
- package/dist/components/ui/dashboard-expense-categories.mjs +101 -66
- package/dist/components/ui/dashboard-transactions-table.js +37 -44
- package/dist/components/ui/dashboard-transactions-table.mjs +45 -52
- package/dist/components/ui/data-table.mjs +2 -2
- package/dist/components/ui/date-picker.mjs +2 -2
- package/dist/components/ui/debt-accordion.mjs +7 -7
- package/dist/components/ui/delete-contact-component.mjs +4 -4
- package/dist/components/ui/dialog.mjs +3 -3
- package/dist/components/ui/document-checklist-template.mjs +2 -2
- package/dist/components/ui/expense-bar-chart.js +8 -7
- package/dist/components/ui/expense-bar-chart.mjs +5 -5
- package/dist/components/ui/expense-categories-bar.js +261 -0
- package/dist/components/ui/expense-categories-bar.mjs +12 -0
- package/dist/components/ui/expense-work-details.js +8 -7
- package/dist/components/ui/expense-work-details.mjs +7 -7
- package/dist/components/ui/file-preview-dialog.mjs +4 -4
- package/dist/components/ui/financial-cards.mjs +2 -2
- package/dist/components/ui/financial-drawers.mjs +2 -2
- package/dist/components/ui/financial-sections.mjs +3 -3
- package/dist/components/ui/frontend-signup-steps.mjs +2 -2
- package/dist/components/ui/income-bar-chart.js +8 -7
- package/dist/components/ui/income-bar-chart.mjs +5 -5
- package/dist/components/ui/income-sources-card.mjs +1 -1
- package/dist/components/ui/income-summary-component.mjs +1 -1
- package/dist/components/ui/income-work-details.js +8 -7
- package/dist/components/ui/income-work-details.mjs +6 -6
- package/dist/components/ui/incoming-outgoings-card.js +2 -2
- package/dist/components/ui/incoming-outgoings-card.mjs +3 -3
- package/dist/components/ui/interest-rate-section.mjs +1 -1
- package/dist/components/ui/kanban-column.mjs +5 -5
- package/dist/components/ui/loan-application-cards.mjs +3 -3
- package/dist/components/ui/loan-financials.mjs +3 -3
- package/dist/components/ui/money-input-with-slider.mjs +2 -2
- package/dist/components/ui/opportunity-card.mjs +4 -4
- package/dist/components/ui/opportunity-edit-modals.mjs +4 -4
- package/dist/components/ui/opportunity-summary-tab.mjs +8 -8
- package/dist/components/ui/pagination.mjs +2 -2
- package/dist/components/ui/pipeline-board.mjs +6 -6
- package/dist/components/ui/pipeline-chart.mjs +2 -2
- package/dist/components/ui/pipeline-dialogs.mjs +4 -4
- package/dist/components/ui/policy-ai/index.mjs +2 -2
- package/dist/components/ui/property-asset-card.mjs +4 -4
- package/dist/components/ui/property-cashflow-doughnut-chart.js +3 -3
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -5
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +3 -3
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -5
- package/dist/components/ui/property-list-carousel.mjs +2 -2
- package/dist/components/ui/property-mobile-estimate-line-chart.js +4 -4
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -5
- package/dist/components/ui/property-report-dialog.mjs +5 -5
- package/dist/components/ui/resource-center/index.mjs +4 -4
- package/dist/components/ui/review-alerts-dialog.mjs +4 -4
- package/dist/components/ui/savings-goal-modal.mjs +7 -7
- package/dist/components/ui/scenario-drawer.mjs +4 -4
- package/dist/components/ui/scenario-list.js +4 -7
- package/dist/components/ui/scenario-list.mjs +5 -5
- package/dist/components/ui/share-details-dialog.mjs +4 -4
- package/dist/components/ui/sidebar-nav.mjs +4 -4
- package/dist/components/ui/signup-form-primitives.mjs +2 -2
- package/dist/components/ui/stage-timeline.mjs +1 -1
- package/dist/components/ui/support-agent/index.js +27 -11
- package/dist/components/ui/support-agent/index.mjs +3 -3
- package/dist/components/ui/top-three-product.mjs +1 -1
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +3 -3
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -5
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -5
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +4 -4
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -5
- package/dist/components/ui/transactions-summary-block.js +13 -0
- package/dist/components/ui/transactions-summary-block.mjs +13 -0
- package/dist/index.js +2119 -1946
- package/dist/index.mjs +139 -135
- package/dist/lib/format-currency.js +54 -0
- package/dist/lib/format-currency.mjs +9 -0
- package/dist/styles.css +1 -1
- package/package.json +6 -1
- package/src/component-descriptions/assets-liabilities-side-card.md +19 -0
- package/src/component-descriptions/pipeline-chart.md +17 -0
- package/src/components/index.tsx +6 -0
- package/src/components/ui/assets-liabilities-side-card.tsx +43 -83
- package/src/components/ui/borrowing-capacity-atoms.tsx +4 -7
- package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -4
- package/src/components/ui/cash-balance-line-chart.tsx +123 -42
- package/src/components/ui/cashflow-bar-chart.tsx +7 -4
- package/src/components/ui/chart-shared.tsx +4 -4
- package/src/components/ui/dashboard-expense-categories.tsx +136 -60
- package/src/components/ui/dashboard-transactions-table.tsx +42 -28
- package/src/components/ui/expense-bar-chart.tsx +32 -19
- package/src/components/ui/expense-categories-bar.tsx +178 -0
- package/src/components/ui/income-bar-chart.tsx +32 -19
- package/src/components/ui/incoming-outgoings-card.tsx +2 -2
- package/src/components/ui/property-mobile-estimate-line-chart.tsx +4 -4
- package/src/components/ui/scenario-list.tsx +2 -2
- package/src/components/ui/support-agent/support-agent-panel.tsx +40 -11
- package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +7 -5
- package/src/components/ui/transactions-summary-block.tsx +39 -6
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +2 -0
- package/dist/{chunk-CEYEK3TI.mjs → chunk-B4R62ID3.mjs} +3 -3
- package/dist/{chunk-7LN5OGC2.mjs → chunk-E3VAK4EB.mjs} +3 -3
- package/dist/{chunk-EY36WDCF.mjs → chunk-EEZFXE3P.mjs} +3 -3
- package/dist/{chunk-T5FRVEJQ.mjs → chunk-JTMN36BK.mjs} +3 -3
- /package/dist/{chunk-MN5NYQCL.mjs → chunk-XQDTFNVL.mjs} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
2
|
+
import { ChevronDown, ChevronRight, ListFilter } from "lucide-react";
|
|
3
3
|
import { Card, CardContent, CardHeader, CardTitle } from "./card";
|
|
4
4
|
import { Spinner } from "./spinner";
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
@@ -38,16 +38,42 @@ export interface DashboardExpenseCategoriesProps {
|
|
|
38
38
|
selectedCategoryId?: string | null;
|
|
39
39
|
/** Called with the category ID to filter by, or null to clear */
|
|
40
40
|
onCategorySelect?: (id: string | null) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Color scheme for progress bars and active states.
|
|
43
|
+
* Use "secondary" for the Expense tab to distinguish from Income (primary).
|
|
44
|
+
* Defaults to "primary".
|
|
45
|
+
*/
|
|
46
|
+
colorScheme?: "primary" | "secondary";
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const SECONDARY_ACTIVE_BG =
|
|
52
|
+
"color-mix(in oklch, var(--brand-secondary) 8%, transparent)";
|
|
53
|
+
const SECONDARY_ACTIVE_HOVER_BG =
|
|
54
|
+
"color-mix(in oklch, var(--brand-secondary) 12%, transparent)";
|
|
55
|
+
|
|
43
56
|
// ─── Progress bar ─────────────────────────────────────────────────────────────
|
|
44
57
|
|
|
45
|
-
function ProgressBar({
|
|
58
|
+
function ProgressBar({
|
|
59
|
+
pct,
|
|
60
|
+
className,
|
|
61
|
+
colorScheme = "primary",
|
|
62
|
+
}: {
|
|
63
|
+
pct: number;
|
|
64
|
+
className?: string;
|
|
65
|
+
colorScheme?: "primary" | "secondary";
|
|
66
|
+
}) {
|
|
46
67
|
return (
|
|
47
68
|
<div className={cn("h-1.5 w-full overflow-hidden bg-muted", className)}>
|
|
48
69
|
<div
|
|
49
70
|
className="h-full bg-primary transition-all"
|
|
50
|
-
style={{
|
|
71
|
+
style={{
|
|
72
|
+
width: `${Math.min(100, Math.max(0, pct))}%`,
|
|
73
|
+
...(colorScheme === "secondary" && {
|
|
74
|
+
backgroundColor: "var(--brand-secondary)",
|
|
75
|
+
}),
|
|
76
|
+
}}
|
|
51
77
|
aria-hidden="true"
|
|
52
78
|
/>
|
|
53
79
|
</div>
|
|
@@ -59,36 +85,60 @@ function ProgressBar({ pct, className }: { pct: number; className?: string }) {
|
|
|
59
85
|
function SubCategoryRow({
|
|
60
86
|
sub,
|
|
61
87
|
isSelected,
|
|
88
|
+
colorScheme = "primary",
|
|
62
89
|
onClick,
|
|
63
90
|
}: {
|
|
64
91
|
sub: ExpenseSubCategory;
|
|
65
92
|
isSelected: boolean;
|
|
93
|
+
colorScheme?: "primary" | "secondary";
|
|
66
94
|
onClick: () => void;
|
|
67
95
|
}) {
|
|
96
|
+
const isSecondary = colorScheme === "secondary";
|
|
68
97
|
return (
|
|
69
|
-
<
|
|
70
|
-
type="button"
|
|
71
|
-
onClick={onClick}
|
|
98
|
+
<div
|
|
72
99
|
className={cn(
|
|
73
|
-
"flex w-full flex-col gap-1 py-1.5 pl-8
|
|
74
|
-
isSelected
|
|
100
|
+
"flex w-full flex-col gap-1 py-1.5 pl-8",
|
|
101
|
+
isSelected && !isSecondary && "bg-primary/5",
|
|
75
102
|
)}
|
|
103
|
+
style={
|
|
104
|
+
isSelected && isSecondary
|
|
105
|
+
? { backgroundColor: SECONDARY_ACTIVE_BG }
|
|
106
|
+
: undefined
|
|
107
|
+
}
|
|
76
108
|
>
|
|
77
109
|
<div className="flex items-center justify-between gap-2 text-xs">
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
<div className="flex min-w-0 items-center gap-1">
|
|
111
|
+
<span
|
|
112
|
+
className={cn(
|
|
113
|
+
"truncate text-muted-foreground",
|
|
114
|
+
isSelected && "font-medium",
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{sub.name}
|
|
118
|
+
</span>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={onClick}
|
|
122
|
+
aria-label={`Filter by ${sub.name}`}
|
|
123
|
+
aria-pressed={isSelected}
|
|
124
|
+
className={cn(
|
|
125
|
+
"shrink-0 transition-colors",
|
|
126
|
+
isSelected
|
|
127
|
+
? isSecondary
|
|
128
|
+
? "text-[var(--brand-secondary)]"
|
|
129
|
+
: "text-primary"
|
|
130
|
+
: "text-muted-foreground/50 hover:text-muted-foreground",
|
|
131
|
+
)}
|
|
132
|
+
>
|
|
133
|
+
<ListFilter size={12} />
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
86
136
|
<span className="shrink-0 font-medium text-foreground">
|
|
87
137
|
{formatCurrency(sub.amount)}
|
|
88
138
|
</span>
|
|
89
139
|
</div>
|
|
90
|
-
<ProgressBar pct={sub.pct} />
|
|
91
|
-
</
|
|
140
|
+
<ProgressBar pct={sub.pct} colorScheme={colorScheme} />
|
|
141
|
+
</div>
|
|
92
142
|
);
|
|
93
143
|
}
|
|
94
144
|
|
|
@@ -97,48 +147,59 @@ function SubCategoryRow({
|
|
|
97
147
|
function CategoryRow({
|
|
98
148
|
item,
|
|
99
149
|
selectedCategoryId,
|
|
150
|
+
colorScheme = "primary",
|
|
100
151
|
onFilterClick,
|
|
101
152
|
}: {
|
|
102
153
|
item: ExpenseCategoryItem;
|
|
103
154
|
selectedCategoryId?: string | null;
|
|
155
|
+
colorScheme?: "primary" | "secondary";
|
|
104
156
|
onFilterClick: (id: string | null) => void;
|
|
105
157
|
}) {
|
|
106
158
|
const [expanded, setExpanded] = useState(false);
|
|
107
|
-
const
|
|
159
|
+
const [hovered, setHovered] = useState(false);
|
|
108
160
|
|
|
161
|
+
const hasChildren = (item.subCategories?.length ?? 0) > 0;
|
|
109
162
|
const isSelected = selectedCategoryId === item.id;
|
|
110
|
-
const isChildSelected =
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
const isChildSelected =
|
|
164
|
+
hasChildren &&
|
|
165
|
+
(item.subCategories?.some((s) => s.id === selectedCategoryId) ?? false);
|
|
113
166
|
const isActive = isSelected || isChildSelected;
|
|
167
|
+
const isSecondary = colorScheme === "secondary";
|
|
168
|
+
|
|
169
|
+
// Secondary scheme active background is set via inline style (color-mix not
|
|
170
|
+
// expressible as a Tailwind arbitrary value here). Hover darkens the tint.
|
|
171
|
+
const wrapperStyle =
|
|
172
|
+
isSecondary && isActive
|
|
173
|
+
? {
|
|
174
|
+
backgroundColor: hovered
|
|
175
|
+
? SECONDARY_ACTIVE_HOVER_BG
|
|
176
|
+
: SECONDARY_ACTIVE_BG,
|
|
177
|
+
}
|
|
178
|
+
: undefined;
|
|
114
179
|
|
|
115
180
|
return (
|
|
116
181
|
<div
|
|
117
182
|
className={cn(
|
|
118
183
|
"border-b border-border last:border-0 transition-colors",
|
|
119
|
-
isActive && "bg-primary/5",
|
|
184
|
+
isActive && !isSecondary && "bg-primary/5",
|
|
120
185
|
)}
|
|
186
|
+
style={wrapperStyle}
|
|
121
187
|
>
|
|
122
|
-
{/* Main row —
|
|
188
|
+
{/* Main row — click expands/collapses; filter icon inside triggers filter */}
|
|
123
189
|
<button
|
|
124
190
|
type="button"
|
|
125
191
|
className={cn(
|
|
126
|
-
"flex w-full items-start gap-3 py-3 text-left
|
|
127
|
-
|
|
192
|
+
"flex w-full items-start gap-3 py-3 text-left transition-colors",
|
|
193
|
+
!isSecondary && "hover:bg-muted/40",
|
|
194
|
+
!isSecondary && isActive && "hover:bg-primary/10",
|
|
128
195
|
)}
|
|
129
|
-
|
|
130
|
-
|
|
196
|
+
onMouseEnter={() => setHovered(true)}
|
|
197
|
+
onMouseLeave={() => setHovered(false)}
|
|
198
|
+
onClick={() => hasChildren && setExpanded((v) => !v)}
|
|
199
|
+
aria-expanded={hasChildren ? expanded : undefined}
|
|
131
200
|
>
|
|
132
|
-
{/* Expand chevron
|
|
133
|
-
<span
|
|
134
|
-
className="mt-0.5 shrink-0 text-muted-foreground"
|
|
135
|
-
onClick={(e) => {
|
|
136
|
-
if (!hasChildren) return;
|
|
137
|
-
e.stopPropagation();
|
|
138
|
-
setExpanded((v) => !v);
|
|
139
|
-
}}
|
|
140
|
-
aria-label={expanded ? "Collapse" : "Expand"}
|
|
141
|
-
>
|
|
201
|
+
{/* Expand chevron */}
|
|
202
|
+
<span className="mt-0.5 shrink-0 text-muted-foreground">
|
|
142
203
|
{hasChildren ? (
|
|
143
204
|
expanded ? (
|
|
144
205
|
<ChevronDown size={14} />
|
|
@@ -150,29 +211,41 @@ function CategoryRow({
|
|
|
150
211
|
)}
|
|
151
212
|
</span>
|
|
152
213
|
|
|
153
|
-
{/*
|
|
214
|
+
{/* Domain icon */}
|
|
154
215
|
{item.icon && (
|
|
155
|
-
<span
|
|
156
|
-
className={cn(
|
|
157
|
-
"mt-0.5 shrink-0",
|
|
158
|
-
isActive ? "text-primary" : "text-muted-foreground",
|
|
159
|
-
)}
|
|
160
|
-
>
|
|
216
|
+
<span className="mt-0.5 shrink-0 text-muted-foreground">
|
|
161
217
|
{item.icon}
|
|
162
218
|
</span>
|
|
163
219
|
)}
|
|
164
220
|
|
|
165
|
-
{/* Name +
|
|
221
|
+
{/* Name + filter icon + amounts */}
|
|
166
222
|
<div className="min-w-0 flex-1">
|
|
167
223
|
<div className="flex items-center justify-between gap-2">
|
|
168
|
-
<
|
|
169
|
-
className=
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
224
|
+
<div className="flex min-w-0 items-center gap-1">
|
|
225
|
+
<span className="truncate text-sm font-medium text-foreground">
|
|
226
|
+
{item.name}
|
|
227
|
+
</span>
|
|
228
|
+
{/* Filter icon — only this triggers the category filter */}
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onClick={(e) => {
|
|
232
|
+
e.stopPropagation();
|
|
233
|
+
onFilterClick(isSelected ? null : item.id);
|
|
234
|
+
}}
|
|
235
|
+
aria-label={`Filter by ${item.name}`}
|
|
236
|
+
aria-pressed={isSelected}
|
|
237
|
+
className={cn(
|
|
238
|
+
"shrink-0 transition-colors",
|
|
239
|
+
isActive
|
|
240
|
+
? isSecondary
|
|
241
|
+
? "text-[var(--brand-secondary)]"
|
|
242
|
+
: "text-primary"
|
|
243
|
+
: "text-muted-foreground/50 hover:text-muted-foreground",
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
<ListFilter size={13} />
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
176
249
|
<div className="flex shrink-0 items-center gap-2">
|
|
177
250
|
<span className="text-xs text-muted-foreground">
|
|
178
251
|
{item.pct.toFixed(0)}%
|
|
@@ -182,7 +255,11 @@ function CategoryRow({
|
|
|
182
255
|
</span>
|
|
183
256
|
</div>
|
|
184
257
|
</div>
|
|
185
|
-
<ProgressBar
|
|
258
|
+
<ProgressBar
|
|
259
|
+
pct={item.pct}
|
|
260
|
+
className="mt-1.5"
|
|
261
|
+
colorScheme={colorScheme}
|
|
262
|
+
/>
|
|
186
263
|
</div>
|
|
187
264
|
</button>
|
|
188
265
|
|
|
@@ -194,6 +271,7 @@ function CategoryRow({
|
|
|
194
271
|
key={sub.id}
|
|
195
272
|
sub={sub}
|
|
196
273
|
isSelected={selectedCategoryId === sub.id}
|
|
274
|
+
colorScheme={colorScheme}
|
|
197
275
|
onClick={() =>
|
|
198
276
|
onFilterClick(selectedCategoryId === sub.id ? null : sub.id)
|
|
199
277
|
}
|
|
@@ -215,11 +293,8 @@ export function DashboardExpenseCategories({
|
|
|
215
293
|
className,
|
|
216
294
|
selectedCategoryId,
|
|
217
295
|
onCategorySelect,
|
|
296
|
+
colorScheme = "primary",
|
|
218
297
|
}: DashboardExpenseCategoriesProps) {
|
|
219
|
-
const handleFilterClick = (id: string | null) => {
|
|
220
|
-
onCategorySelect?.(id);
|
|
221
|
-
};
|
|
222
|
-
|
|
223
298
|
return (
|
|
224
299
|
<Card className={cn("flex flex-col", className)}>
|
|
225
300
|
<CardHeader className="pb-2">
|
|
@@ -251,7 +326,8 @@ export function DashboardExpenseCategories({
|
|
|
251
326
|
key={item.id}
|
|
252
327
|
item={item}
|
|
253
328
|
selectedCategoryId={selectedCategoryId}
|
|
254
|
-
|
|
329
|
+
colorScheme={colorScheme}
|
|
330
|
+
onFilterClick={(id) => onCategorySelect?.(id)}
|
|
255
331
|
/>
|
|
256
332
|
))}
|
|
257
333
|
</div>
|
|
@@ -58,6 +58,18 @@ export interface DashboardTransactionsTableProps {
|
|
|
58
58
|
categoryId: string,
|
|
59
59
|
applyToFuture: boolean,
|
|
60
60
|
) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Show or hide the card header (title + "Account Transaction" tab).
|
|
63
|
+
* Set to `false` when the surrounding layout already provides context.
|
|
64
|
+
* Defaults to `true`.
|
|
65
|
+
*/
|
|
66
|
+
showHeader?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Show or hide the "Account Transaction" underlined tab below the title.
|
|
69
|
+
* Set to `false` when the surrounding layout already provides tab context.
|
|
70
|
+
* Defaults to `true`.
|
|
71
|
+
*/
|
|
72
|
+
showTab?: boolean;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
// ─── Category chip ────────────────────────────────────────────────────────────
|
|
@@ -76,7 +88,7 @@ function CategoryChip({
|
|
|
76
88
|
<Badge
|
|
77
89
|
asChild
|
|
78
90
|
variant="secondary"
|
|
79
|
-
className="cursor-pointer transition-colors hover:
|
|
91
|
+
className="cursor-pointer transition-colors hover:bg-muted"
|
|
80
92
|
>
|
|
81
93
|
<button type="button" onClick={onClick}>
|
|
82
94
|
{label}
|
|
@@ -93,12 +105,10 @@ function CategoryChip({
|
|
|
93
105
|
|
|
94
106
|
function TransactionRow({
|
|
95
107
|
tx,
|
|
96
|
-
isDimmed,
|
|
97
108
|
canEdit,
|
|
98
109
|
onChipClick,
|
|
99
110
|
}: {
|
|
100
111
|
tx: DashboardTransaction;
|
|
101
|
-
isDimmed: boolean;
|
|
102
112
|
canEdit: boolean;
|
|
103
113
|
onChipClick?: () => void;
|
|
104
114
|
}) {
|
|
@@ -106,12 +116,7 @@ function TransactionRow({
|
|
|
106
116
|
const categoryLabel = tx.editedCategoryName ?? tx.category;
|
|
107
117
|
|
|
108
118
|
return (
|
|
109
|
-
<div
|
|
110
|
-
className={cn(
|
|
111
|
-
"flex items-start justify-between gap-4 border-b border-border py-3 last:border-0 transition-opacity",
|
|
112
|
-
isDimmed && "opacity-30",
|
|
113
|
-
)}
|
|
114
|
-
>
|
|
119
|
+
<div className="flex items-start justify-between gap-4 border-b border-border py-3 last:border-0">
|
|
115
120
|
<div className="min-w-0 flex-1">
|
|
116
121
|
<p className="text-xs text-muted-foreground">
|
|
117
122
|
{formatDateShort(tx.date)}
|
|
@@ -161,6 +166,8 @@ export function DashboardTransactionsTable({
|
|
|
161
166
|
selectedCategoryId,
|
|
162
167
|
categories,
|
|
163
168
|
onCategoryChange,
|
|
169
|
+
showHeader = true,
|
|
170
|
+
showTab = true,
|
|
164
171
|
}: DashboardTransactionsTableProps) {
|
|
165
172
|
const isFiltering = selectedCategoryId != null;
|
|
166
173
|
const canEdit = !!categories?.length;
|
|
@@ -177,16 +184,20 @@ export function DashboardTransactionsTable({
|
|
|
177
184
|
return (
|
|
178
185
|
<>
|
|
179
186
|
<Card className={cn("flex flex-col", className)}>
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
{showHeader && (
|
|
188
|
+
<CardHeader className="pb-0">
|
|
189
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
190
|
+
{title}
|
|
191
|
+
</CardTitle>
|
|
192
|
+
{showTab && (
|
|
193
|
+
<div className="mt-2 flex border-b border-border">
|
|
194
|
+
<span className="border-b-2 border-foreground pb-1.5 text-xs font-semibold text-foreground">
|
|
195
|
+
Account Transaction
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</CardHeader>
|
|
200
|
+
)}
|
|
190
201
|
|
|
191
202
|
<CardContent className="flex flex-1 flex-col px-4 pb-0 pt-0">
|
|
192
203
|
{isLoading ? (
|
|
@@ -199,15 +210,18 @@ export function DashboardTransactionsTable({
|
|
|
199
210
|
</p>
|
|
200
211
|
) : (
|
|
201
212
|
<div className="flex flex-col">
|
|
202
|
-
{transactions
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
{transactions
|
|
214
|
+
.filter(
|
|
215
|
+
(tx) => !isFiltering || tx.categoryId === selectedCategoryId,
|
|
216
|
+
)
|
|
217
|
+
.map((tx) => (
|
|
218
|
+
<TransactionRow
|
|
219
|
+
key={tx.id}
|
|
220
|
+
tx={tx}
|
|
221
|
+
canEdit={canEdit}
|
|
222
|
+
onChipClick={() => setEditingTx(tx)}
|
|
223
|
+
/>
|
|
224
|
+
))}
|
|
211
225
|
</div>
|
|
212
226
|
)}
|
|
213
227
|
|
|
@@ -88,6 +88,12 @@ export interface ExpenseBarChartProps {
|
|
|
88
88
|
className?: string;
|
|
89
89
|
/** Show skeleton loading state instead of the chart */
|
|
90
90
|
isLoading?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Show the internal period selector buttons (3M / 6M / 12M).
|
|
93
|
+
* Set to `false` when the chart is driven by an external period control.
|
|
94
|
+
* Defaults to `true`.
|
|
95
|
+
*/
|
|
96
|
+
showPeriodSelector?: boolean;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
// ---------------------------------------------------------------------------
|
|
@@ -108,6 +114,7 @@ export function ExpenseBarChart({
|
|
|
108
114
|
width = "100%",
|
|
109
115
|
className,
|
|
110
116
|
isLoading = false,
|
|
117
|
+
showPeriodSelector = true,
|
|
111
118
|
}: ExpenseBarChartProps) {
|
|
112
119
|
const periods = CHART_PERIODS[granularity];
|
|
113
120
|
const [period, setPeriod] = useState<ExpensePeriod>(defaultPeriod);
|
|
@@ -215,7 +222,7 @@ export function ExpenseBarChart({
|
|
|
215
222
|
stacked: true,
|
|
216
223
|
grid: { display: false },
|
|
217
224
|
border: { display: false },
|
|
218
|
-
ticks: { font: { size:
|
|
225
|
+
ticks: { font: { size: 12 }, color: FALLBACK_TICK },
|
|
219
226
|
},
|
|
220
227
|
y: {
|
|
221
228
|
display: showYAxis,
|
|
@@ -223,7 +230,7 @@ export function ExpenseBarChart({
|
|
|
223
230
|
grid: { display: false },
|
|
224
231
|
border: { display: false },
|
|
225
232
|
ticks: {
|
|
226
|
-
font: { size:
|
|
233
|
+
font: { size: 12 },
|
|
227
234
|
color: FALLBACK_TICK,
|
|
228
235
|
maxTicksLimit: 5,
|
|
229
236
|
padding: 8,
|
|
@@ -240,23 +247,29 @@ export function ExpenseBarChart({
|
|
|
240
247
|
className={cn("w-full py-4 sm:py-6 gap-2", className)}
|
|
241
248
|
style={{ maxWidth: width, fontFamily }}
|
|
242
249
|
>
|
|
243
|
-
|
|
244
|
-
<
|
|
245
|
-
{title
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
{(title || showPeriodSelector) && (
|
|
251
|
+
<CardHeader className="px-3 sm:px-6">
|
|
252
|
+
{title && (
|
|
253
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
254
|
+
{title}
|
|
255
|
+
</CardTitle>
|
|
256
|
+
)}
|
|
257
|
+
{showPeriodSelector && (
|
|
258
|
+
<CardAction>
|
|
259
|
+
<div className="flex gap-0.5 sm:gap-1">
|
|
260
|
+
{periods.map((p) => (
|
|
261
|
+
<ChartPeriodButton
|
|
262
|
+
key={p}
|
|
263
|
+
period={p}
|
|
264
|
+
active={period === p}
|
|
265
|
+
onClick={() => setPeriod(p)}
|
|
266
|
+
/>
|
|
267
|
+
))}
|
|
268
|
+
</div>
|
|
269
|
+
</CardAction>
|
|
270
|
+
)}
|
|
271
|
+
</CardHeader>
|
|
272
|
+
)}
|
|
260
273
|
|
|
261
274
|
<CardContent className="px-3 sm:px-6">
|
|
262
275
|
{isLoading ? (
|