@burdenoff/microfe-housingvista 2026.624.2 → 2026.625.2
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/dist/housingvista/components/ChartCard.js +11 -9
- package/dist/housingvista/components/ChartCard.js.map +1 -1
- package/dist/housingvista/components/DataTable.js +63 -60
- package/dist/housingvista/components/DataTable.js.map +1 -1
- package/dist/housingvista/components/EmptyState.js +16 -9
- package/dist/housingvista/components/EmptyState.js.map +1 -1
- package/dist/housingvista/components/PageLayout.js +21 -14
- package/dist/housingvista/components/PageLayout.js.map +1 -1
- package/dist/housingvista/components/PropertyCard.js +2 -2
- package/dist/housingvista/components/PropertyCard.js.map +1 -1
- package/dist/housingvista/components/StatCard.js +20 -17
- package/dist/housingvista/components/StatCard.js.map +1 -1
- package/dist/housingvista/components/StatusBadge.js +1 -1
- package/dist/housingvista/components/StatusBadge.js.map +1 -1
- package/dist/housingvista/pages/HomePage.js +101 -58
- package/dist/housingvista/pages/HomePage.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +59 -48
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/analytics/AnalyticsPage.js +113 -90
- package/dist/housingvista/pages/analytics/AnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +66 -60
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +60 -58
- package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +23 -20
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +114 -81
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +65 -59
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +17 -14
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.js +13 -10
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +155 -123
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +15 -12
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/notifications/NotificationsPage.js +24 -21
- package/dist/housingvista/pages/notifications/NotificationsPage.js.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.js +73 -71
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertiesPage.js +141 -135
- package/dist/housingvista/pages/properties/PropertiesPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertyDetailPage.js +192 -154
- package/dist/housingvista/pages/properties/PropertyDetailPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +185 -160
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +51 -49
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +56 -54
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +183 -142
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +90 -84
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.js +49 -42
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { cn as e } from "../utils/helpers.js";
|
|
2
2
|
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
import { GlassCard as r } from "@burdenoff/fe-libs/ui";
|
|
3
4
|
//#region src/housingvista/components/ChartCard.tsx
|
|
4
|
-
var
|
|
5
|
-
|
|
5
|
+
var i = ({ title: i, description: a, actions: o, children: s, className: c }) => /* @__PURE__ */ n(r, {
|
|
6
|
+
treatment: "insight",
|
|
7
|
+
className: e("border-border-seam p-4", c),
|
|
6
8
|
children: [/* @__PURE__ */ n("div", {
|
|
7
9
|
className: "mb-4 flex items-center justify-between",
|
|
8
10
|
children: [/* @__PURE__ */ n("div", { children: [/* @__PURE__ */ t("h3", {
|
|
9
11
|
className: "text-sm font-medium text-text-primary",
|
|
10
|
-
children: r
|
|
11
|
-
}), i && /* @__PURE__ */ t("p", {
|
|
12
|
-
className: "text-xs text-text-secondary mt-0.5",
|
|
13
12
|
children: i
|
|
14
|
-
})
|
|
15
|
-
className: "
|
|
13
|
+
}), a && /* @__PURE__ */ t("p", {
|
|
14
|
+
className: "text-xs text-text-secondary mt-0.5",
|
|
16
15
|
children: a
|
|
16
|
+
})] }), o && /* @__PURE__ */ t("div", {
|
|
17
|
+
className: "flex items-center gap-2",
|
|
18
|
+
children: o
|
|
17
19
|
})]
|
|
18
|
-
}),
|
|
20
|
+
}), s]
|
|
19
21
|
});
|
|
20
22
|
//#endregion
|
|
21
|
-
export {
|
|
23
|
+
export { i as ChartCard };
|
|
22
24
|
|
|
23
25
|
//# sourceMappingURL=ChartCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartCard.js","names":[],"sources":["../../../src/housingvista/components/ChartCard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { cn } from '../utils/helpers';\n\ninterface ChartCardProps {\n title: string;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const ChartCard: FC<ChartCardProps> = ({ title, description, actions, children, className }) => {\n return (\n <
|
|
1
|
+
{"version":3,"file":"ChartCard.js","names":[],"sources":["../../../src/housingvista/components/ChartCard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/helpers';\n\ninterface ChartCardProps {\n title: string;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const ChartCard: FC<ChartCardProps> = ({ title, description, actions, children, className }) => {\n return (\n <GlassCard treatment=\"insight\" className={cn('border-border-seam p-4', className)}>\n <div className=\"mb-4 flex items-center justify-between\">\n <div>\n <h3 className=\"text-sm font-medium text-text-primary\">{title}</h3>\n {description && <p className=\"text-xs text-text-secondary mt-0.5\">{description}</p>}\n </div>\n {actions && <div className=\"flex items-center gap-2\">{actions}</div>}\n </div>\n {children}\n </GlassCard>\n );\n};\n"],"mappings":";;;;AAYA,IAAa,KAAiC,EAAE,UAAO,gBAAa,YAAS,aAAU,mBAEnF,kBAAC,GAAD;CAAW,WAAU;CAAU,WAAW,EAAG,0BAA0B,EAAU;WAAjF,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAAyC;GAAW,CAAA,EACjE,KAAe,kBAAC,KAAD;GAAG,WAAU;aAAsC;GAAgB,CAAA,CAC/E,EAAA,CAAA,EACL,KAAW,kBAAC,OAAD;GAAK,WAAU;aAA2B;GAAc,CAAA,CAChE;KACL,EACS"}
|
|
@@ -2,80 +2,83 @@ import { cn as e } from "../utils/helpers.js";
|
|
|
2
2
|
import { useCallback as t, useMemo as n, useState as r } from "react";
|
|
3
3
|
import { ChevronDown as i, ChevronLeft as a, ChevronRight as o, ChevronUp as s, ChevronsUpDown as c } from "lucide-react";
|
|
4
4
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
|
+
import { IllustratedEmptyState as d } from "@burdenoff/fe-libs/ui";
|
|
5
6
|
//#region src/housingvista/components/DataTable.tsx
|
|
6
|
-
function
|
|
7
|
-
let [
|
|
8
|
-
|
|
9
|
-
}, [
|
|
10
|
-
let n = e[
|
|
7
|
+
function f({ columns: f, data: p, keyField: m, onRowClick: h, emptyMessage: g = "No data available", emptyDescription: _, emptyIllustration: v = "empty-data", emptyAction: y, className: b, pageSize: x = 10, selectable: S = !1, selectedKeys: C, onSelectionChange: w, serverPagination: T }) {
|
|
8
|
+
let [E, D] = r(null), [O, k] = r(null), [A, j] = r(0), M = t((e) => {
|
|
9
|
+
E === e ? O === "asc" ? k("desc") : O === "desc" ? (D(null), k(null)) : k("asc") : (D(e), k("asc")), j(0);
|
|
10
|
+
}, [E, O]), N = n(() => !E || !O ? p : [...p].sort((e, t) => {
|
|
11
|
+
let n = e[E], r = t[E];
|
|
11
12
|
if (n == null && r == null) return 0;
|
|
12
13
|
if (n == null) return 1;
|
|
13
14
|
if (r == null) return -1;
|
|
14
15
|
let i = typeof n == "number" && typeof r == "number" ? n - r : String(n).localeCompare(String(r));
|
|
15
|
-
return
|
|
16
|
+
return O === "asc" ? i : -i;
|
|
16
17
|
}), [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]),
|
|
21
|
-
if (!
|
|
22
|
-
let e = new Set(
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
if (!
|
|
26
|
-
let t = String(e[
|
|
27
|
-
n.has(t) ? n.delete(t) : n.add(t),
|
|
18
|
+
p,
|
|
19
|
+
E,
|
|
20
|
+
O
|
|
21
|
+
]), P = T?.pageSize ?? x, F = Math.max(1, Math.ceil(N.length / P)), I = T ? N : N.slice(A * P, (A + 1) * P), L = S && C ? I.every((e) => C.has(String(e[m]))) : !1, R = () => {
|
|
22
|
+
if (!w || !C) return;
|
|
23
|
+
let e = new Set(C);
|
|
24
|
+
L ? I.forEach((t) => e.delete(String(t[m]))) : I.forEach((t) => e.add(String(t[m]))), w(e);
|
|
25
|
+
}, z = (e) => {
|
|
26
|
+
if (!w || !C) return;
|
|
27
|
+
let t = String(e[m]), n = new Set(C);
|
|
28
|
+
n.has(t) ? n.delete(t) : n.add(t), w(n);
|
|
28
29
|
};
|
|
29
|
-
return
|
|
30
|
-
className: "
|
|
31
|
-
children: /* @__PURE__ */ l(
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
return p.length === 0 ? /* @__PURE__ */ l("div", {
|
|
31
|
+
className: "rounded-card border border-dashed border-border-seam bg-bg-surface/50",
|
|
32
|
+
children: /* @__PURE__ */ l(d, {
|
|
33
|
+
illustration: v,
|
|
34
|
+
title: g,
|
|
35
|
+
description: _,
|
|
36
|
+
action: y
|
|
34
37
|
})
|
|
35
38
|
}) : /* @__PURE__ */ u("div", {
|
|
36
|
-
className: e("space-y-3",
|
|
39
|
+
className: e("space-y-3", b),
|
|
37
40
|
children: [/* @__PURE__ */ l("div", {
|
|
38
|
-
className: "overflow-x-auto rounded-
|
|
41
|
+
className: "overflow-x-auto rounded-card border border-border-seam bg-bg-surface shadow-elevation-1",
|
|
39
42
|
children: /* @__PURE__ */ u("table", {
|
|
40
43
|
className: "w-full",
|
|
41
44
|
children: [/* @__PURE__ */ l("thead", { children: /* @__PURE__ */ u("tr", {
|
|
42
45
|
className: "border-b border-border-subtle bg-bg-sunken/50",
|
|
43
|
-
children: [
|
|
46
|
+
children: [S && /* @__PURE__ */ l("th", {
|
|
44
47
|
className: "w-10 px-3 py-3",
|
|
45
48
|
children: /* @__PURE__ */ l("input", {
|
|
46
49
|
type: "checkbox",
|
|
47
|
-
checked:
|
|
48
|
-
onChange:
|
|
50
|
+
checked: L && I.length > 0,
|
|
51
|
+
onChange: R,
|
|
49
52
|
className: "h-4 w-4 rounded border-border-subtle"
|
|
50
53
|
})
|
|
51
|
-
}),
|
|
52
|
-
let n = t.sortable !== !1, r =
|
|
54
|
+
}), f.map((t) => {
|
|
55
|
+
let n = t.sortable !== !1, r = E === t.key;
|
|
53
56
|
return /* @__PURE__ */ l("th", {
|
|
54
57
|
className: e("px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-text-secondary", n && "cursor-pointer select-none hover:text-text-primary", t.className),
|
|
55
|
-
onClick: n ? () =>
|
|
58
|
+
onClick: n ? () => M(t.key) : void 0,
|
|
56
59
|
children: /* @__PURE__ */ u("span", {
|
|
57
60
|
className: "inline-flex items-center gap-1",
|
|
58
|
-
children: [t.header, n && (r ? l(
|
|
61
|
+
children: [t.header, n && (r ? l(O === "asc" ? s : i, { className: "h-3 w-3" }) : /* @__PURE__ */ l(c, { className: "h-3 w-3 opacity-30" }))]
|
|
59
62
|
})
|
|
60
63
|
}, t.key);
|
|
61
64
|
})]
|
|
62
65
|
}) }), /* @__PURE__ */ l("tbody", {
|
|
63
66
|
className: "divide-y divide-border bg-bg-surface",
|
|
64
|
-
children:
|
|
65
|
-
let n = String(t[
|
|
67
|
+
children: I.map((t) => {
|
|
68
|
+
let n = String(t[m]), r = S && C?.has(n);
|
|
66
69
|
return /* @__PURE__ */ u("tr", {
|
|
67
|
-
className: e("transition-colors",
|
|
68
|
-
onClick: () =>
|
|
69
|
-
children: [
|
|
70
|
+
className: e("transition-colors", h && "cursor-pointer hover:bg-bg-sunken/50", r && "bg-action-primary-bg/5"),
|
|
71
|
+
onClick: () => h?.(t),
|
|
72
|
+
children: [S && /* @__PURE__ */ l("td", {
|
|
70
73
|
className: "w-10 px-3 py-3",
|
|
71
74
|
onClick: (e) => e.stopPropagation(),
|
|
72
75
|
children: /* @__PURE__ */ l("input", {
|
|
73
76
|
type: "checkbox",
|
|
74
77
|
checked: r ?? !1,
|
|
75
|
-
onChange: () =>
|
|
78
|
+
onChange: () => z(t),
|
|
76
79
|
className: "h-4 w-4 rounded border-border-subtle"
|
|
77
80
|
})
|
|
78
|
-
}),
|
|
81
|
+
}), f.map((n) => /* @__PURE__ */ l("td", {
|
|
79
82
|
className: e("px-4 py-3 text-sm text-text-primary", n.className),
|
|
80
83
|
children: n.render ? n.render(t) : String(t[n.key] ?? "")
|
|
81
84
|
}, n.key))]
|
|
@@ -83,18 +86,18 @@ function d({ columns: d, data: f, keyField: p, onRowClick: m, emptyMessage: h =
|
|
|
83
86
|
})
|
|
84
87
|
})]
|
|
85
88
|
})
|
|
86
|
-
}),
|
|
89
|
+
}), T ? /* @__PURE__ */ u("div", {
|
|
87
90
|
className: "flex flex-col gap-3 rounded-lg border border-border-subtle bg-bg-surface p-3 text-sm sm:flex-row sm:items-center sm:justify-between",
|
|
88
91
|
children: [/* @__PURE__ */ u("span", {
|
|
89
92
|
className: "text-text-secondary",
|
|
90
|
-
children: ["Page ",
|
|
93
|
+
children: ["Page ", T.page]
|
|
91
94
|
}), /* @__PURE__ */ u("div", {
|
|
92
95
|
className: "flex flex-wrap items-center gap-2",
|
|
93
96
|
children: [
|
|
94
|
-
|
|
95
|
-
value:
|
|
96
|
-
onChange: (e) =>
|
|
97
|
-
disabled:
|
|
97
|
+
T.onPageSizeChange ? /* @__PURE__ */ l("select", {
|
|
98
|
+
value: T.pageSize,
|
|
99
|
+
onChange: (e) => T.onPageSizeChange?.(Number(e.target.value)),
|
|
100
|
+
disabled: T.loading,
|
|
98
101
|
className: "h-8 rounded-md border border-border-subtle bg-bg-surface px-2 text-sm text-text-primary disabled:opacity-50",
|
|
99
102
|
"aria-label": "Rows per page",
|
|
100
103
|
children: [
|
|
@@ -109,49 +112,49 @@ function d({ columns: d, data: f, keyField: p, onRowClick: m, emptyMessage: h =
|
|
|
109
112
|
}) : null,
|
|
110
113
|
/* @__PURE__ */ u("button", {
|
|
111
114
|
type: "button",
|
|
112
|
-
onClick: () =>
|
|
113
|
-
disabled:
|
|
115
|
+
onClick: () => T.onPageChange(T.page - 1),
|
|
116
|
+
disabled: T.loading || T.page <= 1,
|
|
114
117
|
className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40",
|
|
115
118
|
children: [/* @__PURE__ */ l(a, { className: "h-4 w-4" }), "Previous"]
|
|
116
119
|
}),
|
|
117
120
|
/* @__PURE__ */ u("button", {
|
|
118
121
|
type: "button",
|
|
119
|
-
onClick: () =>
|
|
120
|
-
disabled:
|
|
122
|
+
onClick: () => T.onPageChange(T.page + 1),
|
|
123
|
+
disabled: T.loading || !(T.hasNextPage ?? N.length >= T.pageSize),
|
|
121
124
|
className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40",
|
|
122
125
|
children: ["Next", /* @__PURE__ */ l(o, { className: "h-4 w-4" })]
|
|
123
126
|
})
|
|
124
127
|
]
|
|
125
128
|
})]
|
|
126
|
-
}) :
|
|
129
|
+
}) : N.length > x ? /* @__PURE__ */ u("div", {
|
|
127
130
|
className: "flex items-center justify-between text-sm",
|
|
128
131
|
children: [/* @__PURE__ */ u("span", {
|
|
129
132
|
className: "text-text-secondary",
|
|
130
133
|
children: [
|
|
131
134
|
"Showing ",
|
|
132
|
-
|
|
135
|
+
A * x + 1,
|
|
133
136
|
"-",
|
|
134
|
-
Math.min((
|
|
137
|
+
Math.min((A + 1) * x, N.length),
|
|
135
138
|
" of ",
|
|
136
|
-
|
|
139
|
+
N.length
|
|
137
140
|
]
|
|
138
141
|
}), /* @__PURE__ */ u("div", {
|
|
139
142
|
className: "flex items-center gap-1",
|
|
140
143
|
children: [
|
|
141
144
|
/* @__PURE__ */ l("button", {
|
|
142
|
-
onClick: () =>
|
|
143
|
-
disabled:
|
|
145
|
+
onClick: () => j((e) => Math.max(0, e - 1)),
|
|
146
|
+
disabled: A === 0,
|
|
144
147
|
className: "rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40",
|
|
145
148
|
children: /* @__PURE__ */ l(a, { className: "h-4 w-4" })
|
|
146
149
|
}),
|
|
147
|
-
Array.from({ length:
|
|
148
|
-
onClick: () =>
|
|
149
|
-
className: e("h-8 w-8 rounded-md text-xs font-medium transition-colors", n ===
|
|
150
|
+
Array.from({ length: F }).map((t, n) => /* @__PURE__ */ l("button", {
|
|
151
|
+
onClick: () => j(n),
|
|
152
|
+
className: e("h-8 w-8 rounded-md text-xs font-medium transition-colors", n === A ? "bg-foreground text-background" : "text-text-secondary hover:bg-bg-sunken"),
|
|
150
153
|
children: n + 1
|
|
151
154
|
}, n)),
|
|
152
155
|
/* @__PURE__ */ l("button", {
|
|
153
|
-
onClick: () =>
|
|
154
|
-
disabled:
|
|
156
|
+
onClick: () => j((e) => Math.min(F - 1, e + 1)),
|
|
157
|
+
disabled: A >= F - 1,
|
|
155
158
|
className: "rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40",
|
|
156
159
|
children: /* @__PURE__ */ l(o, { className: "h-4 w-4" })
|
|
157
160
|
})
|
|
@@ -161,6 +164,6 @@ function d({ columns: d, data: f, keyField: p, onRowClick: m, emptyMessage: h =
|
|
|
161
164
|
});
|
|
162
165
|
}
|
|
163
166
|
//#endregion
|
|
164
|
-
export {
|
|
167
|
+
export { f as DataTable };
|
|
165
168
|
|
|
166
169
|
//# sourceMappingURL=DataTable.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/housingvista/components/DataTable.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { cn } from '../utils/helpers';\n\nexport interface Column<T> {\n key: string;\n header: string;\n render?: (item: T) => ReactNode;\n className?: string;\n sortable?: boolean;\n}\n\ntype SortDirection = 'asc' | 'desc' | null;\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n data: T[];\n keyField: string;\n onRowClick?: (item: T) => void;\n emptyMessage?: string;\n className?: string;\n pageSize?: number;\n selectable?: boolean;\n selectedKeys?: Set<string>;\n onSelectionChange?: (keys: Set<string>) => void;\n serverPagination?: {\n page: number;\n pageSize: number;\n hasNextPage?: boolean;\n loading?: boolean;\n onPageChange: (page: number) => void;\n onPageSizeChange?: (pageSize: number) => void;\n };\n}\n\nexport function DataTable<T extends object>({\n columns,\n data,\n keyField,\n onRowClick,\n emptyMessage = 'No data available',\n className,\n pageSize = 10,\n selectable = false,\n selectedKeys,\n onSelectionChange,\n serverPagination,\n}: DataTableProps<T>) {\n const [sortKey, setSortKey] = useState<string | null>(null);\n const [sortDir, setSortDir] = useState<SortDirection>(null);\n const [page, setPage] = useState(0);\n\n const handleSort = useCallback(\n (key: string) => {\n if (sortKey === key) {\n if (sortDir === 'asc') setSortDir('desc');\n else if (sortDir === 'desc') {\n setSortKey(null);\n setSortDir(null);\n } else setSortDir('asc');\n } else {\n setSortKey(key);\n setSortDir('asc');\n }\n setPage(0);\n },\n [sortKey, sortDir]\n );\n\n const sorted = useMemo(() => {\n if (!sortKey || !sortDir) return data;\n return [...data].sort((a, b) => {\n const av = (a as Record<string, unknown>)[sortKey];\n const bv = (b as Record<string, unknown>)[sortKey];\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n const cmp = typeof av === 'number' && typeof bv === 'number' ? av - bv : String(av).localeCompare(String(bv));\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [data, sortKey, sortDir]);\n\n const effectivePageSize = serverPagination?.pageSize ?? pageSize;\n const totalPages = Math.max(1, Math.ceil(sorted.length / effectivePageSize));\n const paged = serverPagination ? sorted : sorted.slice(page * effectivePageSize, (page + 1) * effectivePageSize);\n\n const allPageKeysSelected =\n selectable && selectedKeys\n ? paged.every((item) => selectedKeys.has(String((item as Record<string, unknown>)[keyField])))\n : false;\n\n const handleSelectAll = () => {\n if (!onSelectionChange || !selectedKeys) return;\n const keys = new Set(selectedKeys);\n if (allPageKeysSelected) {\n paged.forEach((item) => keys.delete(String((item as Record<string, unknown>)[keyField])));\n } else {\n paged.forEach((item) => keys.add(String((item as Record<string, unknown>)[keyField])));\n }\n onSelectionChange(keys);\n };\n\n const handleSelectRow = (item: T) => {\n if (!onSelectionChange || !selectedKeys) return;\n const key = String((item as Record<string, unknown>)[keyField]);\n const keys = new Set(selectedKeys);\n if (keys.has(key)) keys.delete(key);\n else keys.add(key);\n onSelectionChange(keys);\n };\n\n if (data.length === 0) {\n return (\n <div className=\"flex items-center justify-center rounded-lg border border-border-subtle bg-bg-surface p-12\">\n <p className=\"text-sm text-text-secondary\">{emptyMessage}</p>\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-3', className)}>\n <div className=\"overflow-x-auto rounded-lg border border-border-subtle\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken/50\">\n {selectable && (\n <th className=\"w-10 px-3 py-3\">\n <input\n type=\"checkbox\"\n checked={allPageKeysSelected && paged.length > 0}\n onChange={handleSelectAll}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable !== false;\n const isActive = sortKey === col.key;\n return (\n <th\n key={col.key}\n className={cn(\n 'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-text-secondary',\n isSortable && 'cursor-pointer select-none hover:text-text-primary',\n col.className\n )}\n onClick={isSortable ? () => handleSort(col.key) : undefined}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.header}\n {isSortable &&\n (isActive ? (\n sortDir === 'asc' ? (\n <ChevronUp className=\"h-3 w-3\" />\n ) : (\n <ChevronDown className=\"h-3 w-3\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3 w-3 opacity-30\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border bg-bg-surface\">\n {paged.map((item) => {\n const rowKey = String((item as Record<string, unknown>)[keyField]);\n const isSelected = selectable && selectedKeys?.has(rowKey);\n return (\n <tr\n key={rowKey}\n className={cn(\n 'transition-colors',\n onRowClick && 'cursor-pointer hover:bg-bg-sunken/50',\n isSelected && 'bg-action-primary-bg/5'\n )}\n onClick={() => onRowClick?.(item)}\n >\n {selectable && (\n <td className=\"w-10 px-3 py-3\" onClick={(e) => e.stopPropagation()}>\n <input\n type=\"checkbox\"\n checked={isSelected ?? false}\n onChange={() => handleSelectRow(item)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </td>\n )}\n {columns.map((col) => (\n <td key={col.key} className={cn('px-4 py-3 text-sm text-text-primary', col.className)}>\n {col.render ? col.render(item) : String((item as Record<string, unknown>)[col.key] ?? '')}\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {serverPagination ? (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-subtle bg-bg-surface p-3 text-sm sm:flex-row sm:items-center sm:justify-between\">\n <span className=\"text-text-secondary\">Page {serverPagination.page}</span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {serverPagination.onPageSizeChange ? (\n <select\n value={serverPagination.pageSize}\n onChange={(event) => serverPagination.onPageSizeChange?.(Number(event.target.value))}\n disabled={serverPagination.loading}\n className=\"h-8 rounded-md border border-border-subtle bg-bg-surface px-2 text-sm text-text-primary disabled:opacity-50\"\n aria-label=\"Rows per page\"\n >\n {[10, 25, 50, 100].map((option) => (\n <option key={option} value={option}>\n {option} / page\n </option>\n ))}\n </select>\n ) : null}\n <button\n type=\"button\"\n onClick={() => serverPagination.onPageChange(serverPagination.page - 1)}\n disabled={serverPagination.loading || serverPagination.page <= 1}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n Previous\n </button>\n <button\n type=\"button\"\n onClick={() => serverPagination.onPageChange(serverPagination.page + 1)}\n disabled={\n serverPagination.loading ||\n !(serverPagination.hasNextPage ?? sorted.length >= serverPagination.pageSize)\n }\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ) : sorted.length > pageSize ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * pageSize + 1}-{Math.min((page + 1) * pageSize, sorted.length)} of {sorted.length}\n </span>\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </button>\n {Array.from({ length: totalPages }).map((_, i) => (\n <button\n key={i}\n onClick={() => setPage(i)}\n className={cn(\n 'h-8 w-8 rounded-md text-xs font-medium transition-colors',\n i === page ? 'bg-foreground text-background' : 'text-text-secondary hover:bg-bg-sunken'\n )}\n >\n {i + 1}\n </button>\n ))}\n <button\n onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={page >= totalPages - 1}\n className=\"rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;AAmCA,SAAgB,EAA4B,EAC1C,YACA,SACA,aACA,eACA,kBAAe,qBACf,cACA,cAAW,IACX,gBAAa,IACb,iBACA,sBACA,uBACoB;CACpB,IAAM,CAAC,GAAS,KAAc,EAAwB,KAAK,EACrD,CAAC,GAAS,KAAc,EAAwB,KAAK,EACrD,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,IAAa,GAChB,MAAgB;AAWf,EAVI,MAAY,IACV,MAAY,QAAO,EAAW,OAAO,GAChC,MAAY,UACnB,EAAW,KAAK,EAChB,EAAW,KAAK,IACX,EAAW,MAAM,IAExB,EAAW,EAAI,EACf,EAAW,MAAM,GAEnB,EAAQ,EAAE;IAEZ,CAAC,GAAS,EAAQ,CACnB,EAEK,IAAS,QACT,CAAC,KAAW,CAAC,IAAgB,IAC1B,CAAC,GAAG,EAAK,CAAC,MAAM,GAAG,MAAM;EAC9B,IAAM,IAAM,EAA8B,IACpC,IAAM,EAA8B;AAC1C,MAAI,KAAM,QAAQ,KAAM,KAAM,QAAO;AACrC,MAAI,KAAM,KAAM,QAAO;AACvB,MAAI,KAAM,KAAM,QAAO;EACvB,IAAM,IAAM,OAAO,KAAO,YAAY,OAAO,KAAO,WAAW,IAAK,IAAK,OAAO,EAAG,CAAC,cAAc,OAAO,EAAG,CAAC;AAC7G,SAAO,MAAY,QAAQ,IAAM,CAAC;GAClC,EACD;EAAC;EAAM;EAAS;EAAQ,CAAC,EAEtB,IAAoB,GAAkB,YAAY,GAClD,IAAa,KAAK,IAAI,GAAG,KAAK,KAAK,EAAO,SAAS,EAAkB,CAAC,EACtE,IAAQ,IAAmB,IAAS,EAAO,MAAM,IAAO,IAAoB,IAAO,KAAK,EAAkB,EAE1G,IACJ,KAAc,IACV,EAAM,OAAO,MAAS,EAAa,IAAI,OAAQ,EAAiC,GAAU,CAAC,CAAC,GAC5F,IAEA,UAAwB;AAC5B,MAAI,CAAC,KAAqB,CAAC,EAAc;EACzC,IAAM,IAAO,IAAI,IAAI,EAAa;AAMlC,EALI,IACF,EAAM,SAAS,MAAS,EAAK,OAAO,OAAQ,EAAiC,GAAU,CAAC,CAAC,GAEzF,EAAM,SAAS,MAAS,EAAK,IAAI,OAAQ,EAAiC,GAAU,CAAC,CAAC,EAExF,EAAkB,EAAK;IAGnB,KAAmB,MAAY;AACnC,MAAI,CAAC,KAAqB,CAAC,EAAc;EACzC,IAAM,IAAM,OAAQ,EAAiC,GAAU,EACzD,IAAO,IAAI,IAAI,EAAa;AAGlC,EAFI,EAAK,IAAI,EAAI,GAAE,EAAK,OAAO,EAAI,GAC9B,EAAK,IAAI,EAAI,EAClB,EAAkB,EAAK;;AAWzB,QARI,EAAK,WAAW,IAEhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAiB,CAAA;EACzD,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACG,KACC,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,KAAuB,EAAM,SAAS;OAC/C,UAAU;OACV,WAAU;OACV,CAAA;MACC,CAAA,EAEN,EAAQ,KAAK,MAAQ;MACpB,IAAM,IAAa,EAAI,aAAa,IAC9B,IAAW,MAAY,EAAI;AACjC,aACE,kBAAC,MAAD;OAEE,WAAW,EACT,wFACA,KAAc,sDACd,EAAI,UACL;OACD,SAAS,UAAmB,EAAW,EAAI,IAAI,GAAG,KAAA;iBAElD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,EAAI,QACJ,MACE,IAEG,EADF,MAAY,QACT,IAEA,GAFD,EAAW,WAAU,WAAY,CAEE,GAGrC,kBAAC,GAAD,EAAgB,WAAU,sBAAuB,CAAA,EAEhD;;OACJ,EArBE,EAAI,IAqBN;OAEP,CACC;QACC,CAAA,EACR,kBAAC,SAAD;KAAO,WAAU;eACd,EAAM,KAAK,MAAS;MACnB,IAAM,IAAS,OAAQ,EAAiC,GAAU,EAC5D,IAAa,KAAc,GAAc,IAAI,EAAO;AAC1D,aACE,kBAAC,MAAD;OAEE,WAAW,EACT,qBACA,KAAc,wCACd,KAAc,yBACf;OACD,eAAe,IAAa,EAAK;iBAPnC,CASG,KACC,kBAAC,MAAD;QAAI,WAAU;QAAiB,UAAU,MAAM,EAAE,iBAAiB;kBAChE,kBAAC,SAAD;SACE,MAAK;SACL,SAAS,KAAc;SACvB,gBAAgB,EAAgB,EAAK;SACrC,WAAU;SACV,CAAA;QACC,CAAA,EAEN,EAAQ,KAAK,MACZ,kBAAC,MAAD;QAAkB,WAAW,EAAG,uCAAuC,EAAI,UAAU;kBAClF,EAAI,SAAS,EAAI,OAAO,EAAK,GAAG,OAAQ,EAAiC,EAAI,QAAQ,GAAG;QACtF,EAFI,EAAI,IAER,CACL,CACC;SAvBE,EAuBF;OAEP;KACI,CAAA,CACF;;GACJ,CAAA,EAEL,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CAAsC,SAAM,EAAiB,KAAY;OACzE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,EAAiB,mBAChB,kBAAC,UAAD;MACE,OAAO,EAAiB;MACxB,WAAW,MAAU,EAAiB,mBAAmB,OAAO,EAAM,OAAO,MAAM,CAAC;MACpF,UAAU,EAAiB;MAC3B,WAAU;MACV,cAAW;gBAEV;OAAC;OAAI;OAAI;OAAI;OAAI,CAAC,KAAK,MACtB,kBAAC,UAAD;OAAqB,OAAO;iBAA5B,CACG,GAAO,UACD;SAFI,EAEJ,CACT;MACK,CAAA,GACP;KACJ,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,aAAa,EAAiB,OAAO,EAAE;MACvE,UAAU,EAAiB,WAAW,EAAiB,QAAQ;MAC/D,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAAA,WAE5B;;KACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,aAAa,EAAiB,OAAO,EAAE;MACvE,UACE,EAAiB,WACjB,EAAE,EAAiB,eAAe,EAAO,UAAU,EAAiB;MAEtE,WAAU;gBAPZ,CAQC,QAEC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;KACL;MACF;OACJ,EAAO,SAAS,IAClB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KAAsC;KAC3B,IAAO,IAAW;KAAE;KAAE,KAAK,KAAK,IAAO,KAAK,GAAU,EAAO,OAAO;KAAC;KAAK,EAAO;KACrF;OACP,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,UAAD;MACE,eAAe,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;MACjD,UAAU,MAAS;MACnB,WAAU;gBAEV,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MAC5B,CAAA;KACR,MAAM,KAAK,EAAE,QAAQ,GAAY,CAAC,CAAC,KAAK,GAAG,MAC1C,kBAAC,UAAD;MAEE,eAAe,EAAQ,EAAE;MACzB,WAAW,EACT,4DACA,MAAM,IAAO,kCAAkC,yCAChD;gBAEA,IAAI;MACE,EARF,EAQE,CACT;KACF,kBAAC,UAAD;MACE,eAAe,GAAS,MAAM,KAAK,IAAI,IAAa,GAAG,IAAI,EAAE,CAAC;MAC9D,UAAU,KAAQ,IAAa;MAC/B,WAAU;gBAEV,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;MAC7B,CAAA;KACL;MACF;OACJ,KACA"}
|
|
1
|
+
{"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/housingvista/components/DataTable.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/helpers';\n\nexport interface Column<T> {\n key: string;\n header: string;\n render?: (item: T) => ReactNode;\n className?: string;\n sortable?: boolean;\n}\n\ntype SortDirection = 'asc' | 'desc' | null;\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n data: T[];\n keyField: string;\n onRowClick?: (item: T) => void;\n emptyMessage?: string;\n /** Optional secondary line under the empty title. */\n emptyDescription?: string;\n /**\n * Boff UI 2.0 — on-brand illustration name (served from `/illustrations/<name>.webp`)\n * shown in the zero state. Standard names: empty-data, empty-search, empty-files,\n * empty-people, empty-notifications, empty-generic. Defaults to `empty-data`.\n */\n emptyIllustration?: string;\n /** Optional action rendered in the empty state (e.g. a create button). */\n emptyAction?: ReactNode;\n className?: string;\n pageSize?: number;\n selectable?: boolean;\n selectedKeys?: Set<string>;\n onSelectionChange?: (keys: Set<string>) => void;\n serverPagination?: {\n page: number;\n pageSize: number;\n hasNextPage?: boolean;\n loading?: boolean;\n onPageChange: (page: number) => void;\n onPageSizeChange?: (pageSize: number) => void;\n };\n}\n\nexport function DataTable<T extends object>({\n columns,\n data,\n keyField,\n onRowClick,\n emptyMessage = 'No data available',\n emptyDescription,\n emptyIllustration = 'empty-data',\n emptyAction,\n className,\n pageSize = 10,\n selectable = false,\n selectedKeys,\n onSelectionChange,\n serverPagination,\n}: DataTableProps<T>) {\n const [sortKey, setSortKey] = useState<string | null>(null);\n const [sortDir, setSortDir] = useState<SortDirection>(null);\n const [page, setPage] = useState(0);\n\n const handleSort = useCallback(\n (key: string) => {\n if (sortKey === key) {\n if (sortDir === 'asc') setSortDir('desc');\n else if (sortDir === 'desc') {\n setSortKey(null);\n setSortDir(null);\n } else setSortDir('asc');\n } else {\n setSortKey(key);\n setSortDir('asc');\n }\n setPage(0);\n },\n [sortKey, sortDir]\n );\n\n const sorted = useMemo(() => {\n if (!sortKey || !sortDir) return data;\n return [...data].sort((a, b) => {\n const av = (a as Record<string, unknown>)[sortKey];\n const bv = (b as Record<string, unknown>)[sortKey];\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n const cmp = typeof av === 'number' && typeof bv === 'number' ? av - bv : String(av).localeCompare(String(bv));\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [data, sortKey, sortDir]);\n\n const effectivePageSize = serverPagination?.pageSize ?? pageSize;\n const totalPages = Math.max(1, Math.ceil(sorted.length / effectivePageSize));\n const paged = serverPagination ? sorted : sorted.slice(page * effectivePageSize, (page + 1) * effectivePageSize);\n\n const allPageKeysSelected =\n selectable && selectedKeys\n ? paged.every((item) => selectedKeys.has(String((item as Record<string, unknown>)[keyField])))\n : false;\n\n const handleSelectAll = () => {\n if (!onSelectionChange || !selectedKeys) return;\n const keys = new Set(selectedKeys);\n if (allPageKeysSelected) {\n paged.forEach((item) => keys.delete(String((item as Record<string, unknown>)[keyField])));\n } else {\n paged.forEach((item) => keys.add(String((item as Record<string, unknown>)[keyField])));\n }\n onSelectionChange(keys);\n };\n\n const handleSelectRow = (item: T) => {\n if (!onSelectionChange || !selectedKeys) return;\n const key = String((item as Record<string, unknown>)[keyField]);\n const keys = new Set(selectedKeys);\n if (keys.has(key)) keys.delete(key);\n else keys.add(key);\n onSelectionChange(keys);\n };\n\n if (data.length === 0) {\n return (\n <div className=\"rounded-card border border-dashed border-border-seam bg-bg-surface/50\">\n <IllustratedEmptyState\n illustration={emptyIllustration}\n title={emptyMessage}\n description={emptyDescription}\n action={emptyAction}\n />\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-3', className)}>\n <div className=\"overflow-x-auto rounded-card border border-border-seam bg-bg-surface shadow-elevation-1\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken/50\">\n {selectable && (\n <th className=\"w-10 px-3 py-3\">\n <input\n type=\"checkbox\"\n checked={allPageKeysSelected && paged.length > 0}\n onChange={handleSelectAll}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable !== false;\n const isActive = sortKey === col.key;\n return (\n <th\n key={col.key}\n className={cn(\n 'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-text-secondary',\n isSortable && 'cursor-pointer select-none hover:text-text-primary',\n col.className\n )}\n onClick={isSortable ? () => handleSort(col.key) : undefined}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.header}\n {isSortable &&\n (isActive ? (\n sortDir === 'asc' ? (\n <ChevronUp className=\"h-3 w-3\" />\n ) : (\n <ChevronDown className=\"h-3 w-3\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3 w-3 opacity-30\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border bg-bg-surface\">\n {paged.map((item) => {\n const rowKey = String((item as Record<string, unknown>)[keyField]);\n const isSelected = selectable && selectedKeys?.has(rowKey);\n return (\n <tr\n key={rowKey}\n className={cn(\n 'transition-colors',\n onRowClick && 'cursor-pointer hover:bg-bg-sunken/50',\n isSelected && 'bg-action-primary-bg/5'\n )}\n onClick={() => onRowClick?.(item)}\n >\n {selectable && (\n <td className=\"w-10 px-3 py-3\" onClick={(e) => e.stopPropagation()}>\n <input\n type=\"checkbox\"\n checked={isSelected ?? false}\n onChange={() => handleSelectRow(item)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </td>\n )}\n {columns.map((col) => (\n <td key={col.key} className={cn('px-4 py-3 text-sm text-text-primary', col.className)}>\n {col.render ? col.render(item) : String((item as Record<string, unknown>)[col.key] ?? '')}\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {serverPagination ? (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-subtle bg-bg-surface p-3 text-sm sm:flex-row sm:items-center sm:justify-between\">\n <span className=\"text-text-secondary\">Page {serverPagination.page}</span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {serverPagination.onPageSizeChange ? (\n <select\n value={serverPagination.pageSize}\n onChange={(event) => serverPagination.onPageSizeChange?.(Number(event.target.value))}\n disabled={serverPagination.loading}\n className=\"h-8 rounded-md border border-border-subtle bg-bg-surface px-2 text-sm text-text-primary disabled:opacity-50\"\n aria-label=\"Rows per page\"\n >\n {[10, 25, 50, 100].map((option) => (\n <option key={option} value={option}>\n {option} / page\n </option>\n ))}\n </select>\n ) : null}\n <button\n type=\"button\"\n onClick={() => serverPagination.onPageChange(serverPagination.page - 1)}\n disabled={serverPagination.loading || serverPagination.page <= 1}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n Previous\n </button>\n <button\n type=\"button\"\n onClick={() => serverPagination.onPageChange(serverPagination.page + 1)}\n disabled={\n serverPagination.loading ||\n !(serverPagination.hasNextPage ?? sorted.length >= serverPagination.pageSize)\n }\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ) : sorted.length > pageSize ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * pageSize + 1}-{Math.min((page + 1) * pageSize, sorted.length)} of {sorted.length}\n </span>\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </button>\n {Array.from({ length: totalPages }).map((_, i) => (\n <button\n key={i}\n onClick={() => setPage(i)}\n className={cn(\n 'h-8 w-8 rounded-md text-xs font-medium transition-colors',\n i === page ? 'bg-foreground text-background' : 'text-text-secondary hover:bg-bg-sunken'\n )}\n >\n {i + 1}\n </button>\n ))}\n <button\n onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={page >= totalPages - 1}\n className=\"rounded-md border border-border-subtle p-1.5 text-text-secondary transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
import { cn as e } from "../utils/helpers.js";
|
|
2
2
|
import { Inbox as t } from "lucide-react";
|
|
3
3
|
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { IllustratedEmptyState as i } from "@burdenoff/fe-libs/ui";
|
|
4
5
|
//#region src/housingvista/components/EmptyState.tsx
|
|
5
|
-
var
|
|
6
|
-
|
|
6
|
+
var a = ({ icon: a, title: o, description: s, action: c, className: l, illustration: u }) => u ? /* @__PURE__ */ n(i, {
|
|
7
|
+
illustration: u,
|
|
8
|
+
title: o,
|
|
9
|
+
description: s,
|
|
10
|
+
action: c,
|
|
11
|
+
className: e("rounded-card border border-dashed border-border-seam bg-bg-surface/50 p-12", l)
|
|
12
|
+
}) : /* @__PURE__ */ r("div", {
|
|
13
|
+
className: e("flex flex-col items-center justify-center rounded-card border border-dashed border-border-seam bg-bg-surface/50 p-12 text-center", l),
|
|
7
14
|
children: [
|
|
8
15
|
/* @__PURE__ */ n("div", {
|
|
9
16
|
className: "mb-4 text-text-secondary",
|
|
10
|
-
children:
|
|
17
|
+
children: a || /* @__PURE__ */ n(t, { className: "h-12 w-12" })
|
|
11
18
|
}),
|
|
12
19
|
/* @__PURE__ */ n("h3", {
|
|
13
20
|
className: "text-lg font-medium text-text-primary",
|
|
14
|
-
children:
|
|
21
|
+
children: o
|
|
15
22
|
}),
|
|
16
|
-
|
|
23
|
+
s && /* @__PURE__ */ n("p", {
|
|
17
24
|
className: "mt-1 text-sm text-text-secondary max-w-md",
|
|
18
|
-
children:
|
|
25
|
+
children: s
|
|
19
26
|
}),
|
|
20
|
-
|
|
27
|
+
c && /* @__PURE__ */ n("div", {
|
|
21
28
|
className: "mt-4",
|
|
22
|
-
children:
|
|
29
|
+
children: c
|
|
23
30
|
})
|
|
24
31
|
]
|
|
25
32
|
});
|
|
26
33
|
//#endregion
|
|
27
|
-
export {
|
|
34
|
+
export { a as EmptyState };
|
|
28
35
|
|
|
29
36
|
//# sourceMappingURL=EmptyState.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/housingvista/components/EmptyState.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Inbox } from 'lucide-react';\nimport { cn } from '../utils/helpers';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({
|
|
1
|
+
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/housingvista/components/EmptyState.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Inbox } from 'lucide-react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/helpers';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n /**\n * Boff UI 2.0 — on-brand illustration name served by the host shell from\n * `/illustrations/<name>.webp`. When provided, renders the IllustratedEmptyState\n * primitive (preferred). Standard names: empty-data, empty-search, empty-files,\n * empty-people, empty-notifications, onboarding, empty-generic.\n */\n illustration?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({\n icon,\n title,\n description,\n action,\n className,\n illustration,\n}) => {\n if (illustration) {\n return (\n <IllustratedEmptyState\n illustration={illustration}\n title={title}\n description={description}\n action={action}\n className={cn('rounded-card border border-dashed border-border-seam bg-bg-surface/50 p-12', className)}\n />\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col items-center justify-center rounded-card border border-dashed border-border-seam bg-bg-surface/50 p-12 text-center',\n className\n )}\n >\n <div className=\"mb-4 text-text-secondary\">{icon || <Inbox className=\"h-12 w-12\" />}</div>\n <h3 className=\"text-lg font-medium text-text-primary\">{title}</h3>\n {description && <p className=\"mt-1 text-sm text-text-secondary max-w-md\">{description}</p>}\n {action && <div className=\"mt-4\">{action}</div>}\n </div>\n );\n};\n"],"mappings":";;;;;AAoBA,IAAa,KAAmC,EAC9C,SACA,UACA,gBACA,WACA,cACA,sBAEI,IAEA,kBAAC,GAAD;CACgB;CACP;CACM;CACL;CACR,WAAW,EAAG,8EAA8E,EAAU;CACtG,CAAA,GAKJ,kBAAC,OAAD;CACE,WAAW,EACT,oIACA,EACD;WAJH;EAME,kBAAC,OAAD;GAAK,WAAU;aAA4B,KAAQ,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GAAO,CAAA;EACzF,kBAAC,MAAD;GAAI,WAAU;aAAyC;GAAW,CAAA;EACjE,KAAe,kBAAC,KAAD;GAAG,WAAU;aAA6C;GAAgB,CAAA;EACzF,KAAU,kBAAC,OAAD;GAAK,WAAU;aAAQ;GAAa,CAAA;EAC3C"}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { cn as e } from "../utils/helpers.js";
|
|
2
2
|
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
import { GradientText as r, PagePurpose as i } from "@burdenoff/fe-libs/ui";
|
|
3
4
|
//#region src/housingvista/components/PageLayout.tsx
|
|
4
|
-
var
|
|
5
|
-
className: e("flex flex-col gap-6 p-6",
|
|
5
|
+
var a = ({ title: a, description: o, actions: s, children: c, className: l, purpose: u }) => /* @__PURE__ */ n("div", {
|
|
6
|
+
className: e("flex flex-col gap-6 p-6", l),
|
|
6
7
|
children: [/* @__PURE__ */ n("div", {
|
|
7
|
-
className: "flex
|
|
8
|
-
children: [/* @__PURE__ */ n("div", {
|
|
9
|
-
className: "
|
|
10
|
-
children:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
className: "flex flex-col gap-2",
|
|
9
|
+
children: [/* @__PURE__ */ n("div", {
|
|
10
|
+
className: "flex items-start justify-between gap-4",
|
|
11
|
+
children: [/* @__PURE__ */ n("div", { children: [/* @__PURE__ */ t("h1", {
|
|
12
|
+
className: "text-2xl font-semibold text-text-primary",
|
|
13
|
+
children: /* @__PURE__ */ t(r, { children: a })
|
|
14
|
+
}), o && /* @__PURE__ */ t("p", {
|
|
15
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
16
|
+
children: o
|
|
17
|
+
})] }), s && /* @__PURE__ */ t("div", {
|
|
18
|
+
className: "flex items-center gap-2 shrink-0",
|
|
19
|
+
children: s
|
|
20
|
+
})]
|
|
21
|
+
}), u && /* @__PURE__ */ t(i, {
|
|
22
|
+
className: "max-w-3xl",
|
|
23
|
+
children: u
|
|
17
24
|
})]
|
|
18
|
-
}),
|
|
25
|
+
}), c]
|
|
19
26
|
});
|
|
20
27
|
//#endregion
|
|
21
|
-
export {
|
|
28
|
+
export { a as PageLayout };
|
|
22
29
|
|
|
23
30
|
//# sourceMappingURL=PageLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/housingvista/components/PageLayout.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { cn } from '../utils/helpers';\n\ninterface PageLayoutProps {\n title: string;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({
|
|
1
|
+
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/housingvista/components/PageLayout.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { GradientText, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/helpers';\n\ninterface PageLayoutProps {\n title: string;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n /**\n * Boff UI 2.0 — a calm one/two-line \"story\" explaining WHAT this page is for\n * and the use-case, so a new user understands it at a glance. Rendered with\n * the {@link PagePurpose} primitive directly under the title.\n */\n purpose?: ReactNode;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n actions,\n children,\n className,\n purpose,\n}) => {\n return (\n <div className={cn('flex flex-col gap-6 p-6', className)}>\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n <GradientText>{title}</GradientText>\n </h1>\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n {actions && <div className=\"flex items-center gap-2 shrink-0\">{actions}</div>}\n </div>\n {purpose && <PagePurpose className=\"max-w-3xl\">{purpose}</PagePurpose>}\n </div>\n {children}\n </div>\n );\n};\n"],"mappings":";;;;AAkBA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,aACA,cACA,iBAGE,kBAAC,OAAD;CAAK,WAAW,EAAG,2BAA2B,EAAU;WAAxD,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,GAAD,EAAA,UAAe,GAAqB,CAAA;IACjC,CAAA,EACJ,KAAe,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAgB,CAAA,CAC7E,EAAA,CAAA,EACL,KAAW,kBAAC,OAAD;IAAK,WAAU;cAAoC;IAAc,CAAA,CACzE;MACL,KAAW,kBAAC,GAAD;GAAa,WAAU;aAAa;GAAsB,CAAA,CAClE;KACL,EACG"}
|
|
@@ -19,7 +19,7 @@ var f = {
|
|
|
19
19
|
}, p = ({ id: p, title: m, price: h, priceType: g, pricePeriod: _, location: v, bedrooms: y, bathrooms: b, area: x, imageUrl: S, status: C, type: w, onClick: T, className: E }) => {
|
|
20
20
|
let [D, O] = i(!1), k = g === "rent" ? `${t(h)}${_ ? `/${_}` : "/mo"}` : t(h), A = f[C.toLowerCase()] ?? "default";
|
|
21
21
|
return /* @__PURE__ */ d("div", {
|
|
22
|
-
className: e("group overflow-hidden rounded-
|
|
22
|
+
className: e("group overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2", T && "cursor-pointer", E),
|
|
23
23
|
onClick: T,
|
|
24
24
|
children: [/* @__PURE__ */ d("div", {
|
|
25
25
|
className: "relative aspect-[16/10] overflow-hidden",
|
|
@@ -27,7 +27,7 @@ var f = {
|
|
|
27
27
|
S ? /* @__PURE__ */ u("img", {
|
|
28
28
|
src: S,
|
|
29
29
|
alt: m,
|
|
30
|
-
className: "h-full w-full object-cover
|
|
30
|
+
className: "h-full w-full object-cover"
|
|
31
31
|
}) : /* @__PURE__ */ u("div", {
|
|
32
32
|
className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-muted to-muted/60",
|
|
33
33
|
children: /* @__PURE__ */ u(l, { className: "h-10 w-10 text-text-secondary/40" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PropertyCard.js","names":[],"sources":["../../../src/housingvista/components/PropertyCard.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { MapPin, Bed, Bath, Square, Heart } from 'lucide-react';\nimport { cn, formatCurrency, formatNumber } from '../utils/helpers';\nimport { StatusBadge } from './StatusBadge';\n\ninterface PropertyCardProps {\n id: string;\n title: string;\n price: number;\n priceType: 'sale' | 'rent';\n pricePeriod?: string;\n location: string;\n bedrooms?: number;\n bathrooms?: number;\n area: number;\n imageUrl?: string;\n status: string;\n type: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst statusVariantMap: Record<string, 'success' | 'warning' | 'error' | 'info' | 'default' | 'muted'> = {\n available: 'success',\n active: 'success',\n listed: 'success',\n pending: 'warning',\n 'under contract': 'warning',\n 'under offer': 'warning',\n sold: 'error',\n rented: 'error',\n off_market: 'muted',\n 'off market': 'muted',\n draft: 'muted',\n};\n\nexport const PropertyCard: FC<PropertyCardProps> = ({\n id,\n title,\n price,\n priceType,\n pricePeriod,\n location,\n bedrooms,\n bathrooms,\n area,\n imageUrl,\n status,\n type,\n onClick,\n className,\n}) => {\n const [isFavorited, setIsFavorited] = useState(false);\n\n const priceDisplay =\n priceType === 'rent' ? `${formatCurrency(price)}${pricePeriod ? `/${pricePeriod}` : '/mo'}` : formatCurrency(price);\n\n const badgeVariant = statusVariantMap[status.toLowerCase()] ?? 'default';\n\n return (\n <div\n className={cn(\n 'group overflow-hidden rounded-
|
|
1
|
+
{"version":3,"file":"PropertyCard.js","names":[],"sources":["../../../src/housingvista/components/PropertyCard.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { MapPin, Bed, Bath, Square, Heart } from 'lucide-react';\nimport { cn, formatCurrency, formatNumber } from '../utils/helpers';\nimport { StatusBadge } from './StatusBadge';\n\ninterface PropertyCardProps {\n id: string;\n title: string;\n price: number;\n priceType: 'sale' | 'rent';\n pricePeriod?: string;\n location: string;\n bedrooms?: number;\n bathrooms?: number;\n area: number;\n imageUrl?: string;\n status: string;\n type: string;\n onClick?: () => void;\n className?: string;\n}\n\nconst statusVariantMap: Record<string, 'success' | 'warning' | 'error' | 'info' | 'default' | 'muted'> = {\n available: 'success',\n active: 'success',\n listed: 'success',\n pending: 'warning',\n 'under contract': 'warning',\n 'under offer': 'warning',\n sold: 'error',\n rented: 'error',\n off_market: 'muted',\n 'off market': 'muted',\n draft: 'muted',\n};\n\nexport const PropertyCard: FC<PropertyCardProps> = ({\n id,\n title,\n price,\n priceType,\n pricePeriod,\n location,\n bedrooms,\n bathrooms,\n area,\n imageUrl,\n status,\n type,\n onClick,\n className,\n}) => {\n const [isFavorited, setIsFavorited] = useState(false);\n\n const priceDisplay =\n priceType === 'rent' ? `${formatCurrency(price)}${pricePeriod ? `/${pricePeriod}` : '/mo'}` : formatCurrency(price);\n\n const badgeVariant = statusVariantMap[status.toLowerCase()] ?? 'default';\n\n return (\n <div\n className={cn(\n 'group overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2',\n onClick && 'cursor-pointer',\n className\n )}\n onClick={onClick}\n >\n {/* Image Section */}\n <div className=\"relative aspect-[16/10] overflow-hidden\">\n {imageUrl ? (\n <img\n src={imageUrl}\n alt={title}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-muted to-muted/60\">\n <Square className=\"h-10 w-10 text-text-secondary/40\" />\n </div>\n )}\n\n {/* Overlays */}\n <div className=\"absolute left-3 top-3\">\n <StatusBadge label={status} variant={badgeVariant} dot />\n </div>\n <button\n onClick={(e) => {\n e.stopPropagation();\n setIsFavorited((prev) => !prev);\n }}\n className={cn(\n 'absolute right-3 top-3 rounded-full bg-bg-surface/80 p-1.5 backdrop-blur-sm transition-colors hover:bg-bg-surface',\n isFavorited ? 'text-red-500' : 'text-text-secondary'\n )}\n aria-label={isFavorited ? 'Remove from favorites' : 'Add to favorites'}\n >\n <Heart className={cn('h-4 w-4', isFavorited && 'fill-current')} />\n </button>\n <div className=\"absolute bottom-3 left-3\">\n <span className=\"rounded-md bg-bg-surface/80 px-2 py-1 text-xs font-medium text-text-primary backdrop-blur-sm\">\n {type}\n </span>\n </div>\n </div>\n\n {/* Content Section */}\n <div className=\"p-4\">\n {/* Price */}\n <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"text-lg font-bold text-text-primary\">{priceDisplay}</span>\n {priceType === 'sale' && <span className=\"text-xs font-medium text-text-secondary\">For Sale</span>}\n </div>\n\n {/* Title */}\n <h3 className=\"mt-1 truncate text-sm font-medium text-text-primary\">{title}</h3>\n\n {/* Location */}\n <div className=\"mt-1.5 flex items-center gap-1 text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" />\n <span className=\"truncate text-xs\">{location}</span>\n </div>\n\n {/* Specs Row */}\n <div className=\"mt-3 flex items-center gap-3 border-t border-border-subtle pt-3\">\n {bedrooms != null && (\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Bed className=\"h-3.5 w-3.5\" />\n <span>\n {bedrooms} {bedrooms === 1 ? 'Bed' : 'Beds'}\n </span>\n </div>\n )}\n {bathrooms != null && (\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Bath className=\"h-3.5 w-3.5\" />\n <span>\n {bathrooms} {bathrooms === 1 ? 'Bath' : 'Baths'}\n </span>\n </div>\n )}\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Square className=\"h-3.5 w-3.5\" />\n <span>{formatNumber(area)} sq ft</span>\n </div>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAsBA,IAAM,IAAmG;CACvG,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,kBAAkB;CAClB,eAAe;CACf,MAAM;CACN,QAAQ;CACR,YAAY;CACZ,cAAc;CACd,OAAO;CACR,EAEY,KAAuC,EAClD,OACA,UACA,UACA,cACA,gBACA,aACA,aACA,cACA,SACA,aACA,WACA,SACA,YACA,mBACI;CACJ,IAAM,CAAC,GAAa,KAAkB,EAAS,GAAM,EAE/C,IACJ,MAAc,SAAS,GAAG,EAAe,EAAM,GAAG,IAAc,IAAI,MAAgB,UAAU,EAAe,EAAM,EAE/G,IAAe,EAAiB,EAAO,aAAa,KAAK;AAE/D,QACE,kBAAC,OAAD;EACE,WAAW,EACT,0LACA,KAAW,kBACX,EACD;EACQ;YANX,CASE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,IACC,kBAAC,OAAD;KACE,KAAK;KACL,KAAK;KACL,WAAU;KACV,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAQ,WAAU,oCAAqC,CAAA;KACnD,CAAA;IAIR,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAa,OAAO;MAAQ,SAAS;MAAc,KAAA;MAAM,CAAA;KACrD,CAAA;IACN,kBAAC,UAAD;KACE,UAAU,MAAM;AAEd,MADA,EAAE,iBAAiB,EACnB,GAAgB,MAAS,CAAC,EAAK;;KAEjC,WAAW,EACT,qHACA,IAAc,iBAAiB,sBAChC;KACD,cAAY,IAAc,0BAA0B;eAEpD,kBAAC,GAAD,EAAO,WAAW,EAAG,WAAW,KAAe,eAAe,EAAI,CAAA;KAC3D,CAAA;IACT,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACH,CAAA;IACF;MAGN,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAuC;MAAoB,CAAA,EAC1E,MAAc,UAAU,kBAAC,QAAD;MAAM,WAAU;gBAA0C;MAAe,CAAA,CAC9F;;IAGN,kBAAC,MAAD;KAAI,WAAU;eAAuD;KAAW,CAAA;IAGhF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA,EAC3C,kBAAC,QAAD;MAAM,WAAU;gBAAoB;MAAgB,CAAA,CAChD;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KAAY,QACX,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAK,WAAU,eAAgB,CAAA,EAC/B,kBAAC,QAAD,EAAA,UAAA;QACG;QAAS;QAAE,MAAa,IAAI,QAAQ;QAChC,EAAA,CAAA,CACH;;MAEP,KAAa,QACZ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,EAChC,kBAAC,QAAD,EAAA,UAAA;QACG;QAAU;QAAE,MAAc,IAAI,SAAS;QACnC,EAAA,CAAA,CACH;;MAER,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA,EAClC,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAa,EAAK,EAAC,SAAa,EAAA,CAAA,CACnC;;MACF;;IACF;KACF"}
|
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
import { cn as e, formatCurrency as t, formatNumber as n } from "../utils/helpers.js";
|
|
2
2
|
import { Minus as r, TrendingDown as i, TrendingUp as a } from "lucide-react";
|
|
3
3
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
4
|
+
import { GlassCard as c } from "@burdenoff/fe-libs/ui";
|
|
4
5
|
//#region src/housingvista/components/StatCard.tsx
|
|
5
|
-
var
|
|
6
|
-
let
|
|
7
|
-
return /* @__PURE__ */ s(
|
|
8
|
-
|
|
6
|
+
var l = ({ label: l, value: u, previousValue: d, unit: f, prefix: p, icon: m, trend: h, change: g, className: _, featured: v = !1 }) => {
|
|
7
|
+
let y = g ?? (d ? Math.round(((typeof u == "number" ? u : 0) - d) / d * 100) : 0), b = h ?? (y > 0 ? "up" : y < 0 ? "down" : "stable"), x = typeof u == "string" ? u : p === "$" ? t(u) : `${n(u)}${f ? ` ${f}` : ""}`;
|
|
8
|
+
return /* @__PURE__ */ s(c, {
|
|
9
|
+
treatment: v ? "glass" : "metric",
|
|
10
|
+
glow: v,
|
|
11
|
+
className: e("border-border-seam p-4", _),
|
|
9
12
|
children: [
|
|
10
13
|
/* @__PURE__ */ s("div", {
|
|
11
14
|
className: "flex items-center justify-between",
|
|
12
15
|
children: [/* @__PURE__ */ o("span", {
|
|
13
16
|
className: "text-sm font-medium text-text-secondary",
|
|
14
|
-
children:
|
|
15
|
-
}),
|
|
17
|
+
children: l
|
|
18
|
+
}), m && /* @__PURE__ */ o("span", {
|
|
16
19
|
className: "text-text-secondary",
|
|
17
|
-
children:
|
|
20
|
+
children: m
|
|
18
21
|
})]
|
|
19
22
|
}),
|
|
20
23
|
/* @__PURE__ */ o("div", {
|
|
21
24
|
className: "mt-2 flex items-baseline gap-2",
|
|
22
25
|
children: /* @__PURE__ */ o("span", {
|
|
23
|
-
className: "text-2xl font-bold text-text-primary",
|
|
24
|
-
children:
|
|
26
|
+
className: "text-2xl font-bold tabular-nums text-text-primary",
|
|
27
|
+
children: x
|
|
25
28
|
})
|
|
26
29
|
}),
|
|
27
|
-
|
|
30
|
+
y !== 0 && /* @__PURE__ */ s("div", {
|
|
28
31
|
className: "mt-2 flex items-center gap-1 text-xs",
|
|
29
32
|
children: [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
b === "up" && /* @__PURE__ */ o(a, { className: "h-3 w-3 text-emerald-600 dark:text-emerald-400" }),
|
|
34
|
+
b === "down" && /* @__PURE__ */ o(i, { className: "h-3 w-3 text-red-600 dark:text-red-400" }),
|
|
35
|
+
b === "stable" && /* @__PURE__ */ o(r, { className: "h-3 w-3 text-text-secondary" }),
|
|
33
36
|
/* @__PURE__ */ s("span", {
|
|
34
|
-
className: e("font-medium",
|
|
37
|
+
className: e("font-medium tabular-nums", b === "up" && "text-emerald-600 dark:text-emerald-400", b === "down" && "text-red-600 dark:text-red-400", b === "stable" && "text-text-secondary"),
|
|
35
38
|
children: [
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
y > 0 ? "+" : "",
|
|
40
|
+
y,
|
|
38
41
|
"%"
|
|
39
42
|
]
|
|
40
43
|
}),
|
|
@@ -48,6 +51,6 @@ var c = ({ label: c, value: l, previousValue: u, unit: d, prefix: f, icon: p, tr
|
|
|
48
51
|
});
|
|
49
52
|
};
|
|
50
53
|
//#endregion
|
|
51
|
-
export {
|
|
54
|
+
export { l as StatCard };
|
|
52
55
|
|
|
53
56
|
//# sourceMappingURL=StatCard.js.map
|