@wealthx/shadcn 1.5.42 → 1.5.44
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 +207 -202
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-5FHBC6DY.mjs → chunk-33WZ5NCW.mjs} +1 -1
- 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-C35JMOII.mjs → chunk-7RMXM5O6.mjs} +5 -5
- 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-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.js +1 -1
- 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 +99 -66
- package/dist/components/ui/dashboard-expense-categories.mjs +104 -69
- package/dist/components/ui/dashboard-transactions-table.js +19 -9
- package/dist/components/ui/dashboard-transactions-table.mjs +26 -16
- 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.mjs +2 -2
- 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 +2092 -1935
- package/dist/index.mjs +138 -134
- 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/category-edit-dialog.tsx +1 -1
- package/src/components/ui/chart-shared.tsx +4 -4
- package/src/components/ui/dashboard-expense-categories.tsx +139 -63
- package/src/components/ui/dashboard-transactions-table.tsx +51 -15
- 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/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
|
-
<div className="flex items-center justify-between gap-2 text-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
<div className="flex items-center justify-between gap-2 text-sm">
|
|
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,39 +211,55 @@ 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-base 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
|
-
<span className="text-
|
|
250
|
+
<span className="text-sm text-muted-foreground">
|
|
178
251
|
{item.pct.toFixed(0)}%
|
|
179
252
|
</span>
|
|
180
|
-
<span className="text-
|
|
253
|
+
<span className="text-base font-semibold text-foreground">
|
|
181
254
|
{formatCurrency(item.amount)}
|
|
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,24 @@ export interface DashboardTransactionsTableProps {
|
|
|
58
58
|
categoryId: string,
|
|
59
59
|
applyToFuture: boolean,
|
|
60
60
|
) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Show or hide the card header (title + tab row).
|
|
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;
|
|
73
|
+
/**
|
|
74
|
+
* Colour scheme for the editable category chip hover state.
|
|
75
|
+
* - `"primary"` (default) — green hover
|
|
76
|
+
* - `"secondary"` — dark navy hover; use when the surrounding context uses the secondary palette (e.g. expense view)
|
|
77
|
+
*/
|
|
78
|
+
colorScheme?: "primary" | "secondary";
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
// ─── Category chip ────────────────────────────────────────────────────────────
|
|
@@ -65,10 +83,12 @@ export interface DashboardTransactionsTableProps {
|
|
|
65
83
|
function CategoryChip({
|
|
66
84
|
label,
|
|
67
85
|
canEdit,
|
|
86
|
+
colorScheme = "primary",
|
|
68
87
|
onClick,
|
|
69
88
|
}: {
|
|
70
89
|
label: string;
|
|
71
90
|
canEdit: boolean;
|
|
91
|
+
colorScheme?: "primary" | "secondary";
|
|
72
92
|
onClick?: () => void;
|
|
73
93
|
}) {
|
|
74
94
|
if (canEdit) {
|
|
@@ -76,7 +96,12 @@ function CategoryChip({
|
|
|
76
96
|
<Badge
|
|
77
97
|
asChild
|
|
78
98
|
variant="secondary"
|
|
79
|
-
className=
|
|
99
|
+
className={cn(
|
|
100
|
+
"cursor-pointer transition-colors",
|
|
101
|
+
colorScheme === "secondary"
|
|
102
|
+
? "hover:bg-brand-secondary/10 hover:text-[var(--brand-secondary)]"
|
|
103
|
+
: "hover:border-primary/40 hover:bg-primary/10 hover:text-primary",
|
|
104
|
+
)}
|
|
80
105
|
>
|
|
81
106
|
<button type="button" onClick={onClick}>
|
|
82
107
|
{label}
|
|
@@ -95,11 +120,13 @@ function TransactionRow({
|
|
|
95
120
|
tx,
|
|
96
121
|
isDimmed,
|
|
97
122
|
canEdit,
|
|
123
|
+
colorScheme = "primary",
|
|
98
124
|
onChipClick,
|
|
99
125
|
}: {
|
|
100
126
|
tx: DashboardTransaction;
|
|
101
127
|
isDimmed: boolean;
|
|
102
128
|
canEdit: boolean;
|
|
129
|
+
colorScheme?: "primary" | "secondary";
|
|
103
130
|
onChipClick?: () => void;
|
|
104
131
|
}) {
|
|
105
132
|
const isCredit = tx.amount >= 0;
|
|
@@ -113,14 +140,14 @@ function TransactionRow({
|
|
|
113
140
|
)}
|
|
114
141
|
>
|
|
115
142
|
<div className="min-w-0 flex-1">
|
|
116
|
-
<p className="text-
|
|
143
|
+
<p className="text-sm text-muted-foreground">
|
|
117
144
|
{formatDateShort(tx.date)}
|
|
118
145
|
</p>
|
|
119
|
-
<p className="mt-0.5 truncate text-
|
|
146
|
+
<p className="mt-0.5 truncate text-base font-medium text-foreground">
|
|
120
147
|
{tx.description}
|
|
121
148
|
</p>
|
|
122
149
|
{tx.merchant && (
|
|
123
|
-
<p className="mt-0.5 truncate text-
|
|
150
|
+
<p className="mt-0.5 truncate text-sm text-muted-foreground">
|
|
124
151
|
{tx.merchant}
|
|
125
152
|
</p>
|
|
126
153
|
)}
|
|
@@ -129,6 +156,7 @@ function TransactionRow({
|
|
|
129
156
|
<CategoryChip
|
|
130
157
|
label={categoryLabel}
|
|
131
158
|
canEdit={canEdit}
|
|
159
|
+
colorScheme={colorScheme}
|
|
132
160
|
onClick={onChipClick}
|
|
133
161
|
/>
|
|
134
162
|
</div>
|
|
@@ -137,7 +165,7 @@ function TransactionRow({
|
|
|
137
165
|
|
|
138
166
|
<span
|
|
139
167
|
className={cn(
|
|
140
|
-
"shrink-0 text-
|
|
168
|
+
"shrink-0 text-base font-semibold",
|
|
141
169
|
isCredit ? "text-success" : "text-foreground",
|
|
142
170
|
)}
|
|
143
171
|
>
|
|
@@ -161,6 +189,9 @@ export function DashboardTransactionsTable({
|
|
|
161
189
|
selectedCategoryId,
|
|
162
190
|
categories,
|
|
163
191
|
onCategoryChange,
|
|
192
|
+
showHeader = true,
|
|
193
|
+
showTab = true,
|
|
194
|
+
colorScheme = "primary",
|
|
164
195
|
}: DashboardTransactionsTableProps) {
|
|
165
196
|
const isFiltering = selectedCategoryId != null;
|
|
166
197
|
const canEdit = !!categories?.length;
|
|
@@ -177,16 +208,20 @@ export function DashboardTransactionsTable({
|
|
|
177
208
|
return (
|
|
178
209
|
<>
|
|
179
210
|
<Card className={cn("flex flex-col", className)}>
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
211
|
+
{showHeader && (
|
|
212
|
+
<CardHeader className="pb-0">
|
|
213
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
214
|
+
{title}
|
|
215
|
+
</CardTitle>
|
|
216
|
+
{showTab && (
|
|
217
|
+
<div className="mt-2 flex border-b border-border">
|
|
218
|
+
<span className="border-b-2 border-foreground pb-1.5 text-xs font-semibold text-foreground">
|
|
219
|
+
Account Transaction
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</CardHeader>
|
|
224
|
+
)}
|
|
190
225
|
|
|
191
226
|
<CardContent className="flex flex-1 flex-col px-4 pb-0 pt-0">
|
|
192
227
|
{isLoading ? (
|
|
@@ -205,6 +240,7 @@ export function DashboardTransactionsTable({
|
|
|
205
240
|
tx={tx}
|
|
206
241
|
isDimmed={isFiltering && tx.categoryId !== selectedCategoryId}
|
|
207
242
|
canEdit={canEdit}
|
|
243
|
+
colorScheme={colorScheme}
|
|
208
244
|
onChipClick={() => setEditingTx(tx)}
|
|
209
245
|
/>
|
|
210
246
|
))}
|
|
@@ -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 ? (
|