@burdenoff/microfe-housingvista 2026.709.2 → 2026.709.3
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/HousingVistaRoutes.d.ts.map +1 -1
- package/dist/housingvista/HousingVistaRoutes.js +136 -37
- package/dist/housingvista/HousingVistaRoutes.js.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js +4 -4
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +62 -60
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +141 -138
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +193 -191
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.d.ts.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +115 -113
- package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js +152 -149
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +141 -139
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +290 -287
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +64 -62
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +95 -93
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js +129 -126
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.js +99 -97
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +138 -136
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +99 -97
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js +137 -134
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.d.ts.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.js +94 -92
- package/dist/housingvista/pages/offers/OffersPage.js.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.js +280 -279
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +153 -151
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +52 -50
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +54 -52
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.js +43 -41
- package/dist/housingvista/pages/shortlist/ShortlistPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +211 -208
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +85 -83
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.js +25 -22
- package/dist/housingvista/pages/verification/VerificationDetailPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.js +123 -121
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.js +128 -126
- package/dist/housingvista/pages/viewings/ViewingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,8 +5,9 @@ import { StatusBadge as a } from "../../components/StatusBadge.js";
|
|
|
5
5
|
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
6
|
import { ChartCard as s } from "../../components/ChartCard.js";
|
|
7
7
|
import { FilterBar as c } from "../../components/FilterBar.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { useTrackPageView as l } from "../../hooks/useHousingVistaAnalytics.js";
|
|
9
|
+
import { useCreateInvestment as u, useInvestments as d, useProperties as ee } from "../../hooks/index.js";
|
|
10
|
+
import { notify as f } from "../../lib/toast.js";
|
|
10
11
|
import "../../components/index.js";
|
|
11
12
|
import { useServerPagination as te } from "../../hooks/useServerPagination.js";
|
|
12
13
|
import { Tooltip as ne } from "../../../node_modules/recharts/es6/component/Tooltip.js";
|
|
@@ -14,11 +15,11 @@ import { ResponsiveContainer as re } from "../../../node_modules/recharts/es6/co
|
|
|
14
15
|
import { Cell as ie } from "../../../node_modules/recharts/es6/component/Cell.js";
|
|
15
16
|
import { Pie as ae } from "../../../node_modules/recharts/es6/polar/Pie.js";
|
|
16
17
|
import { PieChart as oe } from "../../../node_modules/recharts/es6/chart/PieChart.js";
|
|
17
|
-
import { useEffect as se, useMemo as
|
|
18
|
-
import { ArrowDownRight as ce, ArrowUpRight as le, BarChart3 as ue, Building as de, Calendar as fe, DollarSign as pe, Eye as me, PieChart as
|
|
19
|
-
import { useNavigate as
|
|
20
|
-
import { Fragment as
|
|
21
|
-
import { Button as y, EmphasisPanel as
|
|
18
|
+
import { useEffect as se, useMemo as p, useState as m } from "react";
|
|
19
|
+
import { ArrowDownRight as ce, ArrowUpRight as le, BarChart3 as ue, Building as de, Calendar as fe, DollarSign as pe, Eye as me, PieChart as h, Plus as g, Target as he, TrendingUp as ge } from "lucide-react";
|
|
20
|
+
import { useNavigate as _e } from "react-router-dom";
|
|
21
|
+
import { Fragment as ve, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
22
|
+
import { Button as y, EmphasisPanel as ye, NextSteps as be, Pagination as xe, ResponsiveDialog as Se, ResponsiveDialogContent as Ce, ResponsiveDialogDescription as we, ResponsiveDialogFooter as Te, ResponsiveDialogHeader as Ee, ResponsiveDialogTitle as De } from "@burdenoff/fe-libs/ui";
|
|
22
23
|
//#region src/housingvista/pages/investments/InvestmentsPage.tsx
|
|
23
24
|
var b = "h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]", x = [
|
|
24
25
|
"var(--color-chart-1)",
|
|
@@ -26,19 +27,19 @@ var b = "h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 t
|
|
|
26
27
|
"var(--color-chart-3)",
|
|
27
28
|
"var(--color-chart-4)",
|
|
28
29
|
"var(--color-chart-5)"
|
|
29
|
-
],
|
|
30
|
+
], Oe = {
|
|
30
31
|
background: "var(--color-bg-surface)",
|
|
31
32
|
border: "1px solid var(--color-border-subtle)",
|
|
32
33
|
borderRadius: 10,
|
|
33
34
|
fontSize: 12,
|
|
34
35
|
boxShadow: "0 8px 24px rgb(0 0 0 / 0.12)"
|
|
35
|
-
},
|
|
36
|
+
}, ke = {
|
|
36
37
|
full_ownership: "Full ownership",
|
|
37
38
|
fractional: "Fractional",
|
|
38
39
|
reits: "REITs",
|
|
39
40
|
fund: "Fund"
|
|
40
41
|
};
|
|
41
|
-
function
|
|
42
|
+
function Ae(e) {
|
|
42
43
|
switch (e) {
|
|
43
44
|
case "low": return "success";
|
|
44
45
|
case "medium": return "warning";
|
|
@@ -46,7 +47,7 @@ function ke(e) {
|
|
|
46
47
|
default: return "muted";
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
var
|
|
50
|
+
var je = [
|
|
50
51
|
{
|
|
51
52
|
value: "all",
|
|
52
53
|
label: "All"
|
|
@@ -63,7 +64,7 @@ var Ae = [
|
|
|
63
64
|
value: "funding",
|
|
64
65
|
label: "Funding"
|
|
65
66
|
}
|
|
66
|
-
],
|
|
67
|
+
], Me = [
|
|
67
68
|
{
|
|
68
69
|
value: "full_ownership",
|
|
69
70
|
label: "Full ownership"
|
|
@@ -80,7 +81,7 @@ var Ae = [
|
|
|
80
81
|
value: "fund",
|
|
81
82
|
label: "Fund"
|
|
82
83
|
}
|
|
83
|
-
],
|
|
84
|
+
], Ne = [
|
|
84
85
|
{
|
|
85
86
|
value: "draft",
|
|
86
87
|
label: "Draft"
|
|
@@ -105,7 +106,7 @@ var Ae = [
|
|
|
105
106
|
value: "closed",
|
|
106
107
|
label: "Closed"
|
|
107
108
|
}
|
|
108
|
-
],
|
|
109
|
+
], Pe = [
|
|
109
110
|
{
|
|
110
111
|
value: "low",
|
|
111
112
|
label: "Low"
|
|
@@ -131,13 +132,13 @@ function w(e, t) {
|
|
|
131
132
|
function T(e) {
|
|
132
133
|
return e instanceof Date ? Number.isNaN(e.getTime()) ? "" : e.toISOString() : typeof e == "string" ? e : "";
|
|
133
134
|
}
|
|
134
|
-
function
|
|
135
|
+
function Fe(e) {
|
|
135
136
|
let t = T(e);
|
|
136
137
|
if (!t) return "—";
|
|
137
138
|
let r = new Date(t).getTime();
|
|
138
139
|
return Number.isNaN(r) ? "—" : n(t);
|
|
139
140
|
}
|
|
140
|
-
function
|
|
141
|
+
function Ie(e) {
|
|
141
142
|
switch (e) {
|
|
142
143
|
case "active": return "success";
|
|
143
144
|
case "funding": return "warning";
|
|
@@ -148,51 +149,52 @@ function Fe(e) {
|
|
|
148
149
|
default: return "default";
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
function
|
|
152
|
+
function Le(e) {
|
|
152
153
|
return e > 0 ? "text-status-success-text" : e < 0 ? "text-status-error-text" : "text-text-secondary";
|
|
153
154
|
}
|
|
154
155
|
function E() {
|
|
155
|
-
|
|
156
|
+
l("housingvista.investment.viewed");
|
|
157
|
+
let n = _e(), T = te(10), [E, Re] = m(""), [ze, Be] = m(""), [D, Ve] = m("all");
|
|
156
158
|
se(() => {
|
|
157
|
-
let e = setTimeout(() =>
|
|
159
|
+
let e = setTimeout(() => Be(E), 300);
|
|
158
160
|
return () => clearTimeout(e);
|
|
159
161
|
}, [E]);
|
|
160
|
-
let { data: O } =
|
|
162
|
+
let { data: O } = d({
|
|
161
163
|
filter: {
|
|
162
|
-
search:
|
|
164
|
+
search: ze.trim() || void 0,
|
|
163
165
|
status: D === "all" ? void 0 : D
|
|
164
166
|
},
|
|
165
167
|
pagination: T.pagination
|
|
166
|
-
}), { data: k } =
|
|
168
|
+
}), { data: k } = d({ pagination: {
|
|
167
169
|
limit: 100,
|
|
168
170
|
offset: 0
|
|
169
171
|
} }), { data: A } = ee({ pagination: {
|
|
170
172
|
limit: 50,
|
|
171
173
|
offset: 0
|
|
172
|
-
} }), { createInvestment:
|
|
173
|
-
|
|
174
|
+
} }), { createInvestment: He, loading: j } = u(), Ue = (e) => {
|
|
175
|
+
Re(e), T.setPage(1);
|
|
174
176
|
}, M = (e) => {
|
|
175
|
-
|
|
176
|
-
}, [
|
|
177
|
+
Ve(e), T.setPage(1);
|
|
178
|
+
}, [We, N] = m(!1), [P, F] = m(""), [I, L] = m(""), [R, z] = m("full_ownership"), [B, V] = m("draft"), [H, U] = m("medium"), [W, G] = m(""), [K, q] = m(""), [J, Y] = m(""), X = () => {
|
|
177
179
|
F(""), L(""), z("full_ownership"), V("draft"), U("medium"), G(""), q(""), Y("");
|
|
178
180
|
}, Z = () => {
|
|
179
181
|
X(), N(!0);
|
|
180
|
-
},
|
|
182
|
+
}, Ge = async (e) => {
|
|
181
183
|
if (e.preventDefault(), !P) {
|
|
182
|
-
|
|
184
|
+
f.warning("Select a property");
|
|
183
185
|
return;
|
|
184
186
|
}
|
|
185
187
|
if (!I.trim()) {
|
|
186
|
-
|
|
188
|
+
f.warning("Enter an investment title");
|
|
187
189
|
return;
|
|
188
190
|
}
|
|
189
191
|
let t = Number(W);
|
|
190
192
|
if (!Number.isFinite(t) || t <= 0) {
|
|
191
|
-
|
|
193
|
+
f.warning("Enter a valid total value");
|
|
192
194
|
return;
|
|
193
195
|
}
|
|
194
196
|
try {
|
|
195
|
-
await
|
|
197
|
+
await He({
|
|
196
198
|
propertyId: P,
|
|
197
199
|
title: I.trim(),
|
|
198
200
|
status: B,
|
|
@@ -201,11 +203,11 @@ function E() {
|
|
|
201
203
|
totalValue: t,
|
|
202
204
|
startDate: K ? new Date(K).toISOString() : void 0,
|
|
203
205
|
maturityDate: J ? new Date(J).toISOString() : void 0
|
|
204
|
-
}),
|
|
206
|
+
}), f.success("Investment created", `${I.trim()} was added to your portfolio.`), N(!1), X();
|
|
205
207
|
} catch (e) {
|
|
206
|
-
|
|
208
|
+
f.error("Could not create investment", e.message);
|
|
207
209
|
}
|
|
208
|
-
}, Q =
|
|
210
|
+
}, Q = p(() => {
|
|
209
211
|
let e = k.reduce((e, t) => e + w(t.investors, "investmentAmount"), 0), t = k.reduce((e, t) => e + w(t.returns, "amount"), 0);
|
|
210
212
|
return {
|
|
211
213
|
totalInvested: e,
|
|
@@ -214,7 +216,7 @@ function E() {
|
|
|
214
216
|
activeCount: k.filter((e) => e.status === "active").length,
|
|
215
217
|
returnPct: e > 0 ? t / e * 100 : 0
|
|
216
218
|
};
|
|
217
|
-
}, [k]), $ =
|
|
219
|
+
}, [k]), $ = p(() => {
|
|
218
220
|
let e = /* @__PURE__ */ new Map();
|
|
219
221
|
for (let t of k) {
|
|
220
222
|
let n = w(t.investors, "investmentAmount") + w(t.returns, "amount") || S(t.totalValue), r = t.investmentType;
|
|
@@ -222,7 +224,7 @@ function E() {
|
|
|
222
224
|
}
|
|
223
225
|
let t = Array.from(e.entries()).filter(([, e]) => e > 0).map(([e, t], n) => ({
|
|
224
226
|
type: e,
|
|
225
|
-
label:
|
|
227
|
+
label: ke[e] ?? e.replace("_", " "),
|
|
226
228
|
value: t,
|
|
227
229
|
color: x[n % x.length]
|
|
228
230
|
})).sort((e, t) => t.value - e.value);
|
|
@@ -238,10 +240,10 @@ function E() {
|
|
|
238
240
|
actions: /* @__PURE__ */ v("button", {
|
|
239
241
|
onClick: Z,
|
|
240
242
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover",
|
|
241
|
-
children: [/* @__PURE__ */ _(
|
|
243
|
+
children: [/* @__PURE__ */ _(g, { className: "h-4 w-4" }), "New Investment"]
|
|
242
244
|
}),
|
|
243
245
|
children: [
|
|
244
|
-
/* @__PURE__ */ _(
|
|
246
|
+
/* @__PURE__ */ _(ye, {
|
|
245
247
|
className: "p-4",
|
|
246
248
|
children: /* @__PURE__ */ v("div", {
|
|
247
249
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
@@ -256,13 +258,13 @@ function E() {
|
|
|
256
258
|
label: "Current Value",
|
|
257
259
|
value: Q.currentValue,
|
|
258
260
|
prefix: "$",
|
|
259
|
-
icon: /* @__PURE__ */ _(
|
|
261
|
+
icon: /* @__PURE__ */ _(ge, { className: "h-4 w-4" })
|
|
260
262
|
}),
|
|
261
263
|
/* @__PURE__ */ _(i, {
|
|
262
264
|
label: "Total Returns",
|
|
263
265
|
value: Q.totalReturns,
|
|
264
266
|
prefix: "$",
|
|
265
|
-
icon: /* @__PURE__ */ _(
|
|
267
|
+
icon: /* @__PURE__ */ _(h, { className: "h-4 w-4" })
|
|
266
268
|
}),
|
|
267
269
|
/* @__PURE__ */ _(i, {
|
|
268
270
|
label: "Active Investments",
|
|
@@ -272,7 +274,7 @@ function E() {
|
|
|
272
274
|
]
|
|
273
275
|
})
|
|
274
276
|
}),
|
|
275
|
-
/* @__PURE__ */ _(
|
|
277
|
+
/* @__PURE__ */ _(be, {
|
|
276
278
|
storageKey: "housingvista-investments",
|
|
277
279
|
steps: [
|
|
278
280
|
{
|
|
@@ -300,7 +302,7 @@ function E() {
|
|
|
300
302
|
}),
|
|
301
303
|
/* @__PURE__ */ _("div", {
|
|
302
304
|
className: "flex flex-wrap items-center gap-2",
|
|
303
|
-
children:
|
|
305
|
+
children: je.map((t) => /* @__PURE__ */ _("button", {
|
|
304
306
|
onClick: () => M(t.value),
|
|
305
307
|
className: e("rounded-md px-3 py-1.5 text-sm font-medium transition-colors", D === t.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
306
308
|
children: t.label
|
|
@@ -308,7 +310,7 @@ function E() {
|
|
|
308
310
|
}),
|
|
309
311
|
/* @__PURE__ */ _(c, {
|
|
310
312
|
searchValue: E,
|
|
311
|
-
onSearchChange:
|
|
313
|
+
onSearchChange: Ue,
|
|
312
314
|
searchPlaceholder: "Search by property or investment title..."
|
|
313
315
|
}),
|
|
314
316
|
/* @__PURE__ */ v("p", {
|
|
@@ -325,7 +327,7 @@ function E() {
|
|
|
325
327
|
className: "grid grid-cols-1 gap-6 xl:grid-cols-3",
|
|
326
328
|
children: [/* @__PURE__ */ _("div", {
|
|
327
329
|
className: "space-y-4 xl:col-span-2",
|
|
328
|
-
children: O.length > 0 ? /* @__PURE__ */ v(
|
|
330
|
+
children: O.length > 0 ? /* @__PURE__ */ v(ve, { children: [/* @__PURE__ */ _("div", {
|
|
329
331
|
className: "space-y-4",
|
|
330
332
|
children: O.map((r) => {
|
|
331
333
|
let i = r.property?.title ?? "Untitled property", o = w(r.investors, "investmentAmount"), s = w(r.returns, "amount"), c = o + s, l = o > 0 ? s / o * 100 : 0, u = C(r.returns).length;
|
|
@@ -353,7 +355,7 @@ function E() {
|
|
|
353
355
|
}),
|
|
354
356
|
/* @__PURE__ */ _(a, {
|
|
355
357
|
label: r.status,
|
|
356
|
-
variant:
|
|
358
|
+
variant: Ie(r.status)
|
|
357
359
|
}),
|
|
358
360
|
/* @__PURE__ */ _(a, {
|
|
359
361
|
label: r.investmentType.replace("_", " "),
|
|
@@ -382,7 +384,7 @@ function E() {
|
|
|
382
384
|
className: "text-text-secondary",
|
|
383
385
|
children: "Total Return"
|
|
384
386
|
}), /* @__PURE__ */ v("div", {
|
|
385
|
-
className: e("font-medium",
|
|
387
|
+
className: e("font-medium", Le(s)),
|
|
386
388
|
children: [/* @__PURE__ */ v("div", {
|
|
387
389
|
className: "flex items-center gap-1",
|
|
388
390
|
children: [
|
|
@@ -402,7 +404,7 @@ function E() {
|
|
|
402
404
|
className: "mt-1",
|
|
403
405
|
children: /* @__PURE__ */ _(a, {
|
|
404
406
|
label: `${r.riskLevel} risk`,
|
|
405
|
-
variant:
|
|
407
|
+
variant: Ae(r.riskLevel),
|
|
406
408
|
dot: !0
|
|
407
409
|
})
|
|
408
410
|
})] })
|
|
@@ -414,9 +416,9 @@ function E() {
|
|
|
414
416
|
/* @__PURE__ */ v("span", {
|
|
415
417
|
className: "flex items-center gap-1",
|
|
416
418
|
children: [
|
|
417
|
-
/* @__PURE__ */ _(
|
|
419
|
+
/* @__PURE__ */ _(ge, { className: "h-3 w-3" }),
|
|
418
420
|
"Start: ",
|
|
419
|
-
|
|
421
|
+
Fe(r.startDate)
|
|
420
422
|
]
|
|
421
423
|
}),
|
|
422
424
|
/* @__PURE__ */ v("span", {
|
|
@@ -454,7 +456,7 @@ function E() {
|
|
|
454
456
|
})
|
|
455
457
|
}, r.id);
|
|
456
458
|
})
|
|
457
|
-
}), /* @__PURE__ */ _(
|
|
459
|
+
}), /* @__PURE__ */ _(xe, {
|
|
458
460
|
page: T.page,
|
|
459
461
|
pageSize: T.pageSize,
|
|
460
462
|
hasNextPage: O.length >= T.pageSize,
|
|
@@ -463,13 +465,13 @@ function E() {
|
|
|
463
465
|
onPageSizeChange: T.setPageSize
|
|
464
466
|
})] }) : /* @__PURE__ */ _(o, {
|
|
465
467
|
illustration: "empty-search",
|
|
466
|
-
icon: /* @__PURE__ */ _(
|
|
468
|
+
icon: /* @__PURE__ */ _(h, { className: "h-12 w-12" }),
|
|
467
469
|
title: "No investments found",
|
|
468
470
|
description: "Try adjusting your search criteria or start a new investment.",
|
|
469
471
|
action: /* @__PURE__ */ v("button", {
|
|
470
472
|
onClick: Z,
|
|
471
473
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover",
|
|
472
|
-
children: [/* @__PURE__ */ _(
|
|
474
|
+
children: [/* @__PURE__ */ _(g, { className: "h-4 w-4" }), "New Investment"]
|
|
473
475
|
})
|
|
474
476
|
})
|
|
475
477
|
}), $.rows.length > 0 && /* @__PURE__ */ _("aside", {
|
|
@@ -493,7 +495,7 @@ function E() {
|
|
|
493
495
|
strokeWidth: 2,
|
|
494
496
|
children: $.rows.map((e) => /* @__PURE__ */ _(ie, { fill: e.color }, e.type))
|
|
495
497
|
}), /* @__PURE__ */ _(ne, {
|
|
496
|
-
contentStyle:
|
|
498
|
+
contentStyle: Oe,
|
|
497
499
|
formatter: (e, n, r) => [t(e), r?.payload?.label]
|
|
498
500
|
})] })
|
|
499
501
|
}), /* @__PURE__ */ v("div", {
|
|
@@ -528,15 +530,15 @@ function E() {
|
|
|
528
530
|
})
|
|
529
531
|
})]
|
|
530
532
|
}),
|
|
531
|
-
/* @__PURE__ */ _(
|
|
532
|
-
open:
|
|
533
|
+
/* @__PURE__ */ _(Se, {
|
|
534
|
+
open: We,
|
|
533
535
|
onOpenChange: N,
|
|
534
|
-
children: /* @__PURE__ */ _(
|
|
536
|
+
children: /* @__PURE__ */ _(Ce, {
|
|
535
537
|
className: "sm:max-w-md",
|
|
536
538
|
children: /* @__PURE__ */ v("form", {
|
|
537
|
-
onSubmit:
|
|
539
|
+
onSubmit: Ge,
|
|
538
540
|
children: [
|
|
539
|
-
/* @__PURE__ */ v(
|
|
541
|
+
/* @__PURE__ */ v(Ee, { children: [/* @__PURE__ */ _(De, { children: "New investment" }), /* @__PURE__ */ _(we, { children: "Register a new holding against one of your properties." })] }),
|
|
540
542
|
/* @__PURE__ */ v("div", {
|
|
541
543
|
className: "space-y-3 py-2",
|
|
542
544
|
children: [
|
|
@@ -577,7 +579,7 @@ function E() {
|
|
|
577
579
|
value: R,
|
|
578
580
|
onChange: (e) => z(e.target.value),
|
|
579
581
|
className: b,
|
|
580
|
-
children:
|
|
582
|
+
children: Me.map((e) => /* @__PURE__ */ _("option", {
|
|
581
583
|
value: e.value,
|
|
582
584
|
children: e.label
|
|
583
585
|
}, e.value))
|
|
@@ -588,7 +590,7 @@ function E() {
|
|
|
588
590
|
value: B,
|
|
589
591
|
onChange: (e) => V(e.target.value),
|
|
590
592
|
className: b,
|
|
591
|
-
children:
|
|
593
|
+
children: Ne.map((e) => /* @__PURE__ */ _("option", {
|
|
592
594
|
value: e.value,
|
|
593
595
|
children: e.label
|
|
594
596
|
}, e.value))
|
|
@@ -603,7 +605,7 @@ function E() {
|
|
|
603
605
|
value: H,
|
|
604
606
|
onChange: (e) => U(e.target.value),
|
|
605
607
|
className: b,
|
|
606
|
-
children:
|
|
608
|
+
children: Pe.map((e) => /* @__PURE__ */ _("option", {
|
|
607
609
|
value: e.value,
|
|
608
610
|
children: e.label
|
|
609
611
|
}, e.value))
|
|
@@ -643,7 +645,7 @@ function E() {
|
|
|
643
645
|
})
|
|
644
646
|
]
|
|
645
647
|
}),
|
|
646
|
-
/* @__PURE__ */ v(
|
|
648
|
+
/* @__PURE__ */ v(Te, { children: [/* @__PURE__ */ _(y, {
|
|
647
649
|
type: "button",
|
|
648
650
|
variant: "outline",
|
|
649
651
|
onClick: () => N(!1),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvestmentsPage.js","names":[],"sources":["../../../../src/housingvista/pages/investments/InvestmentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n DollarSign,\n TrendingUp,\n PieChart,\n BarChart3,\n Plus,\n Building,\n ArrowUpRight,\n ArrowDownRight,\n Eye,\n Target,\n Calendar,\n} from 'lucide-react';\nimport {\n EmphasisPanel,\n NextSteps,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { PieChart as RePieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';\nimport { PageLayout, StatCard, FilterBar, StatusBadge, EmptyState, ChartCard } from '../../components';\nimport { useInvestments, useCreateInvestment, useProperties } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { InvestmentStatus, InvestmentType } from '../../types';\n\nconst inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\n// Full oklch chart tokens (see fe-libs theme) used for the allocation donut.\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\n\nconst investmentTypeLabels: Record<InvestmentType, string> = {\n full_ownership: 'Full ownership',\n fractional: 'Fractional',\n reits: 'REITs',\n fund: 'Fund',\n};\n\nfunction riskBadgeVariant(risk: string): 'success' | 'warning' | 'error' | 'muted' {\n switch (risk) {\n case 'low':\n return 'success';\n case 'medium':\n return 'warning';\n case 'high':\n return 'error';\n default:\n return 'muted';\n }\n}\n\nconst statusTabs: { value: InvestmentStatus | 'all'; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'closed', label: 'Completed' },\n { value: 'funding', label: 'Funding' },\n];\n\nconst investmentTypeOptions: { value: InvestmentType; label: string }[] = [\n { value: 'full_ownership', label: 'Full ownership' },\n { value: 'fractional', label: 'Fractional' },\n { value: 'reits', label: 'REITs' },\n { value: 'fund', label: 'Fund' },\n];\n\nconst statusOptions: { value: InvestmentStatus; label: string }[] = [\n { value: 'draft', label: 'Draft' },\n { value: 'open', label: 'Open' },\n { value: 'funding', label: 'Funding' },\n { value: 'active', label: 'Active' },\n { value: 'mature', label: 'Mature' },\n { value: 'closed', label: 'Closed' },\n];\n\nconst riskOptions: { value: 'low' | 'medium' | 'high'; label: string }[] = [\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n];\n\n// ---------------------------------------------------------------------------\n// Safe accessors — `investors` and `returns` are opaque JSON columns and the\n// date fields arrive as ISO strings at runtime, so aggregates never trust the\n// domain type's shape.\n// ---------------------------------------------------------------------------\nfunction toNumber(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nfunction safeArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction sumField(value: unknown, field: string): number {\n return safeArray(value).reduce<number>((sum, item) => {\n const rec = typeof item === 'object' && item !== null ? (item as Record<string, unknown>) : {};\n return sum + toNumber(rec[field]);\n }, 0);\n}\n\nfunction toIsoString(value: unknown): string {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? '' : value.toISOString();\n if (typeof value === 'string') return value;\n return '';\n}\n\nfunction formatDateSafe(value: unknown): string {\n const iso = toIsoString(value);\n if (!iso) return '—';\n const ms = new Date(iso).getTime();\n return Number.isNaN(ms) ? '—' : formatDate(iso);\n}\n\nfunction getStatusVariant(status: InvestmentStatus): 'success' | 'warning' | 'error' | 'info' | 'muted' | 'default' {\n switch (status) {\n case 'active':\n return 'success';\n case 'funding':\n return 'warning';\n case 'closed':\n case 'mature':\n return 'info';\n case 'draft':\n case 'open':\n return 'muted';\n default:\n return 'default';\n }\n}\n\nfunction getReturnColor(returnValue: number) {\n if (returnValue > 0) return 'text-status-success-text';\n if (returnValue < 0) return 'text-status-error-text';\n return 'text-text-secondary';\n}\n\nexport function InvestmentsPage() {\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [activeTab, setActiveTab] = useState<InvestmentStatus | 'all'>('all');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: investments } = useInvestments({\n filter: {\n search: debouncedSearch.trim() || undefined,\n status: activeTab === 'all' ? undefined : activeTab,\n },\n pagination: pagination.pagination,\n });\n // Portfolio summary reflects the whole portfolio, not just the current page.\n const { data: allInvestments } = useInvestments({ pagination: { limit: 100, offset: 0 } });\n const { data: properties } = useProperties({ pagination: { limit: 50, offset: 0 } });\n const { createInvestment, loading: creating } = useCreateInvestment();\n\n // Search and tab changes narrow the result set — always return to the first page.\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleTabChange = (tab: InvestmentStatus | 'all') => {\n setActiveTab(tab);\n pagination.setPage(1);\n };\n\n // Create-investment form state\n const [createOpen, setCreateOpen] = useState(false);\n const [propertyId, setPropertyId] = useState('');\n const [title, setTitle] = useState('');\n const [investmentType, setInvestmentType] = useState<InvestmentType>('full_ownership');\n const [status, setStatus] = useState<InvestmentStatus>('draft');\n const [riskLevel, setRiskLevel] = useState<'low' | 'medium' | 'high'>('medium');\n const [totalValue, setTotalValue] = useState('');\n const [startDate, setStartDate] = useState('');\n const [maturityDate, setMaturityDate] = useState('');\n\n const resetForm = () => {\n setPropertyId('');\n setTitle('');\n setInvestmentType('full_ownership');\n setStatus('draft');\n setRiskLevel('medium');\n setTotalValue('');\n setStartDate('');\n setMaturityDate('');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n if (!propertyId) {\n notify.warning('Select a property');\n return;\n }\n if (!title.trim()) {\n notify.warning('Enter an investment title');\n return;\n }\n const value = Number(totalValue);\n if (!Number.isFinite(value) || value <= 0) {\n notify.warning('Enter a valid total value');\n return;\n }\n try {\n await createInvestment({\n propertyId,\n title: title.trim(),\n status,\n investmentType,\n riskLevel,\n totalValue: value,\n startDate: startDate ? new Date(startDate).toISOString() : undefined,\n maturityDate: maturityDate ? new Date(maturityDate).toISOString() : undefined,\n });\n notify.success('Investment created', `${title.trim()} was added to your portfolio.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not create investment', (err as Error).message);\n }\n };\n\n const portfolioStats = useMemo(() => {\n const totalInvested = allInvestments.reduce((sum, inv) => sum + sumField(inv.investors, 'investmentAmount'), 0);\n const totalReturns = allInvestments.reduce((sum, inv) => sum + sumField(inv.returns, 'amount'), 0);\n const currentValue = totalInvested + totalReturns;\n const activeCount = allInvestments.filter((inv) => inv.status === 'active').length;\n const returnPct = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n return { totalInvested, currentValue, totalReturns, activeCount, returnPct };\n }, [allInvestments]);\n\n // Portfolio allocation by investment type, weighted by current value (invested\n // + returns) — real figures from every holding, never fabricated.\n const allocation = useMemo(() => {\n const byType = new Map<string, number>();\n for (const inv of allInvestments) {\n const invested = sumField(inv.investors, 'investmentAmount');\n const returns = sumField(inv.returns, 'amount');\n const value = invested + returns || toNumber(inv.totalValue);\n const key = inv.investmentType;\n byType.set(key, (byType.get(key) ?? 0) + value);\n }\n const rows = Array.from(byType.entries())\n .filter(([, value]) => value > 0)\n .map(([type, value], idx) => ({\n type,\n label: investmentTypeLabels[type as InvestmentType] ?? type.replace('_', ' '),\n value,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }))\n .sort((a, b) => b.value - a.value);\n const total = rows.reduce((s, r) => s + r.value, 0);\n return { rows, total };\n }, [allInvestments]);\n\n return (\n <PageLayout\n title=\"Investments\"\n description=\"Track your real estate investment portfolio and performance\"\n purpose=\"Follow every real-estate investment you hold in one portfolio — how much you put in, what it is worth now, the returns it has paid, and its risk level. Use it to judge performance and decide where to put your next dollar.\"\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n >\n {/* Portfolio Summary Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Invested\"\n value={portfolioStats.totalInvested}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Current Value\"\n value={portfolioStats.currentValue}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Returns\"\n value={portfolioStats.totalReturns}\n prefix=\"$\"\n icon={<PieChart className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Active Investments\"\n value={portfolioStats.activeCount}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-investments\"\n steps={[\n {\n id: 'review-active',\n label: 'Review your active investments',\n description: 'Filter to the active deals currently paying returns.',\n onClick: () => handleTabChange('active'),\n },\n {\n id: 'find-funding',\n label: 'Find funding opportunities',\n description: 'See deals still raising capital that you can join.',\n onClick: () => handleTabChange('funding'),\n },\n {\n id: 'open-detail',\n label: 'Open an investment for full details',\n description: 'Drill into returns, investors, and risk for any holding.',\n onClick: () => {\n const first = investments[0];\n if (first) navigate(`../investments/${first.id}`);\n },\n },\n ]}\n />\n\n {/* Filter Tabs */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {statusTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => handleTabChange(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n activeTab === tab.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search by property or investment title...\"\n />\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n Showing {investments.length} {investments.length === 1 ? 'investment' : 'investments'} on this page\n </p>\n\n {/* Investment list (left) + portfolio allocation donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <div className=\"space-y-4 xl:col-span-2\">\n {investments.length > 0 ? (\n <>\n <div className=\"space-y-4\">\n {investments.map((investment) => {\n const propertyTitle = investment.property?.title ?? 'Untitled property';\n const totalInvested = sumField(investment.investors, 'investmentAmount');\n const totalReturns = sumField(investment.returns, 'amount');\n const currentValue = totalInvested + totalReturns;\n const returnPercentage = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n const returnsCount = safeArray(investment.returns).length;\n\n return (\n <div\n key={investment.id}\n className=\"cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\"\n onClick={() => navigate(`../investments/${investment.id}`)}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex-1\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2\">\n <Building className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"flex-1\">\n <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{propertyTitle}</h3>\n <StatusBadge label={investment.status} variant={getStatusVariant(investment.status)} />\n <StatusBadge label={investment.investmentType.replace('_', ' ')} variant=\"muted\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 text-sm sm:grid-cols-2 lg:grid-cols-4\">\n <div>\n <p className=\"text-text-secondary\">Invested Amount</p>\n <p className=\"text-lg font-medium text-text-primary\">\n {formatCurrency(totalInvested)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-medium text-text-primary\">\n {formatCurrency(currentValue)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Total Return</p>\n <div className={cn('font-medium', getReturnColor(totalReturns))}>\n <div className=\"flex items-center gap-1\">\n {totalReturns > 0 && <ArrowUpRight className=\"h-3 w-3\" />}\n {totalReturns < 0 && <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(totalReturns)}\n </div>\n <div className=\"text-sm\">{returnPercentage.toFixed(1)}%</div>\n </div>\n </div>\n <div>\n <p className=\"text-text-secondary\">Risk Level</p>\n <div className=\"mt-1\">\n <StatusBadge\n label={`${investment.riskLevel} risk`}\n variant={riskBadgeVariant(investment.riskLevel)}\n dot\n />\n </div>\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <TrendingUp className=\"h-3 w-3\" />\n Start: {formatDateSafe(investment.startDate)}\n </span>\n <span className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3\" />\n Type: {investment.investmentType.replace('_', ' ')}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Returns: {returnsCount} payments\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex shrink-0 gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`../investments/${investment.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-4 w-4\" />\n View Details\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={investments.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<PieChart className=\"h-12 w-12\" />}\n title=\"No investments found\"\n description=\"Try adjusting your search criteria or start a new investment.\"\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n />\n )}\n </div>\n\n {/* Portfolio Allocation — real value split by investment type */}\n {allocation.rows.length > 0 && (\n <aside className=\"xl:col-span-1\">\n <ChartCard title=\"Portfolio Allocation\" description=\"Current value by investment type\">\n <div className=\"relative h-52 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <RePieChart>\n <Pie\n data={allocation.rows}\n dataKey=\"value\"\n nameKey=\"label\"\n innerRadius={56}\n outerRadius={82}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {allocation.rows.map((entry) => (\n <Cell key={entry.type} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n formatter={(value: number, _n, item) => [formatCurrency(value), item?.payload?.label]}\n />\n </RePieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(allocation.total)}</span>\n </div>\n </div>\n <ul className=\"mt-4 space-y-2.5\">\n {allocation.rows.map((entry) => (\n <li key={entry.type} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.label}</span>\n </span>\n <span className=\"font-medium text-text-secondary\">{formatCurrency(entry.value)}</span>\n </li>\n ))}\n </ul>\n </ChartCard>\n </aside>\n )}\n </div>\n\n {/* Create Investment dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New investment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a new holding against one of your properties.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Property *</label>\n <select\n required\n value={propertyId}\n onChange={(e) => setPropertyId(e.target.value)}\n className={inputClass}\n >\n <option value=\"\" disabled>\n {properties.length > 0 ? 'Select a property' : 'No properties available'}\n </option>\n {properties.map((property) => (\n <option key={property.id} value={property.id}>\n {property.title}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Title *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Riverside Villa fractional stake\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</label>\n <select\n value={investmentType}\n onChange={(e) => setInvestmentType(e.target.value as InvestmentType)}\n className={inputClass}\n >\n {investmentTypeOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Status</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as InvestmentStatus)}\n className={inputClass}\n >\n {statusOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Risk level</label>\n <select\n value={riskLevel}\n onChange={(e) => setRiskLevel(e.target.value as 'low' | 'medium' | 'high')}\n className={inputClass}\n >\n {riskOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Total value (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={totalValue}\n onChange={(e) => setTotalValue(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Start date</label>\n <input\n type=\"date\"\n value={startDate}\n onChange={(e) => setStartDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Maturity date</label>\n <input\n type=\"date\"\n value={maturityDate}\n onChange={(e) => setMaturityDate(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setCreateOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={creating || !propertyId || !title.trim() || !totalValue}>\n {creating ? 'Creating…' : 'Create investment'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvestmentsPage.js","names":[],"sources":["../../../../src/housingvista/pages/investments/InvestmentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect, type FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n DollarSign,\n TrendingUp,\n PieChart,\n BarChart3,\n Plus,\n Building,\n ArrowUpRight,\n ArrowDownRight,\n Eye,\n Target,\n Calendar,\n} from 'lucide-react';\nimport {\n EmphasisPanel,\n NextSteps,\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { PieChart as RePieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';\nimport { PageLayout, StatCard, FilterBar, StatusBadge, EmptyState, ChartCard } from '../../components';\nimport { useInvestments, useCreateInvestment, useProperties } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { notify } from '../../lib/toast';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { InvestmentStatus, InvestmentType } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\n\nconst inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\n// Full oklch chart tokens (see fe-libs theme) used for the allocation donut.\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\nconst tooltipContentStyle: React.CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-subtle)',\n borderRadius: 10,\n fontSize: 12,\n boxShadow: '0 8px 24px rgb(0 0 0 / 0.12)',\n};\n\nconst investmentTypeLabels: Record<InvestmentType, string> = {\n full_ownership: 'Full ownership',\n fractional: 'Fractional',\n reits: 'REITs',\n fund: 'Fund',\n};\n\nfunction riskBadgeVariant(risk: string): 'success' | 'warning' | 'error' | 'muted' {\n switch (risk) {\n case 'low':\n return 'success';\n case 'medium':\n return 'warning';\n case 'high':\n return 'error';\n default:\n return 'muted';\n }\n}\n\nconst statusTabs: { value: InvestmentStatus | 'all'; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'closed', label: 'Completed' },\n { value: 'funding', label: 'Funding' },\n];\n\nconst investmentTypeOptions: { value: InvestmentType; label: string }[] = [\n { value: 'full_ownership', label: 'Full ownership' },\n { value: 'fractional', label: 'Fractional' },\n { value: 'reits', label: 'REITs' },\n { value: 'fund', label: 'Fund' },\n];\n\nconst statusOptions: { value: InvestmentStatus; label: string }[] = [\n { value: 'draft', label: 'Draft' },\n { value: 'open', label: 'Open' },\n { value: 'funding', label: 'Funding' },\n { value: 'active', label: 'Active' },\n { value: 'mature', label: 'Mature' },\n { value: 'closed', label: 'Closed' },\n];\n\nconst riskOptions: { value: 'low' | 'medium' | 'high'; label: string }[] = [\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n];\n\n// ---------------------------------------------------------------------------\n// Safe accessors — `investors` and `returns` are opaque JSON columns and the\n// date fields arrive as ISO strings at runtime, so aggregates never trust the\n// domain type's shape.\n// ---------------------------------------------------------------------------\nfunction toNumber(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nfunction safeArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction sumField(value: unknown, field: string): number {\n return safeArray(value).reduce<number>((sum, item) => {\n const rec = typeof item === 'object' && item !== null ? (item as Record<string, unknown>) : {};\n return sum + toNumber(rec[field]);\n }, 0);\n}\n\nfunction toIsoString(value: unknown): string {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? '' : value.toISOString();\n if (typeof value === 'string') return value;\n return '';\n}\n\nfunction formatDateSafe(value: unknown): string {\n const iso = toIsoString(value);\n if (!iso) return '—';\n const ms = new Date(iso).getTime();\n return Number.isNaN(ms) ? '—' : formatDate(iso);\n}\n\nfunction getStatusVariant(status: InvestmentStatus): 'success' | 'warning' | 'error' | 'info' | 'muted' | 'default' {\n switch (status) {\n case 'active':\n return 'success';\n case 'funding':\n return 'warning';\n case 'closed':\n case 'mature':\n return 'info';\n case 'draft':\n case 'open':\n return 'muted';\n default:\n return 'default';\n }\n}\n\nfunction getReturnColor(returnValue: number) {\n if (returnValue > 0) return 'text-status-success-text';\n if (returnValue < 0) return 'text-status-error-text';\n return 'text-text-secondary';\n}\n\nexport function InvestmentsPage() {\n useTrackPageView('housingvista.investment.viewed');\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [activeTab, setActiveTab] = useState<InvestmentStatus | 'all'>('all');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: investments } = useInvestments({\n filter: {\n search: debouncedSearch.trim() || undefined,\n status: activeTab === 'all' ? undefined : activeTab,\n },\n pagination: pagination.pagination,\n });\n // Portfolio summary reflects the whole portfolio, not just the current page.\n const { data: allInvestments } = useInvestments({ pagination: { limit: 100, offset: 0 } });\n const { data: properties } = useProperties({ pagination: { limit: 50, offset: 0 } });\n const { createInvestment, loading: creating } = useCreateInvestment();\n\n // Search and tab changes narrow the result set — always return to the first page.\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleTabChange = (tab: InvestmentStatus | 'all') => {\n setActiveTab(tab);\n pagination.setPage(1);\n };\n\n // Create-investment form state\n const [createOpen, setCreateOpen] = useState(false);\n const [propertyId, setPropertyId] = useState('');\n const [title, setTitle] = useState('');\n const [investmentType, setInvestmentType] = useState<InvestmentType>('full_ownership');\n const [status, setStatus] = useState<InvestmentStatus>('draft');\n const [riskLevel, setRiskLevel] = useState<'low' | 'medium' | 'high'>('medium');\n const [totalValue, setTotalValue] = useState('');\n const [startDate, setStartDate] = useState('');\n const [maturityDate, setMaturityDate] = useState('');\n\n const resetForm = () => {\n setPropertyId('');\n setTitle('');\n setInvestmentType('full_ownership');\n setStatus('draft');\n setRiskLevel('medium');\n setTotalValue('');\n setStartDate('');\n setMaturityDate('');\n };\n\n const openCreate = () => {\n resetForm();\n setCreateOpen(true);\n };\n\n const handleCreate = async (e: FormEvent) => {\n e.preventDefault();\n if (!propertyId) {\n notify.warning('Select a property');\n return;\n }\n if (!title.trim()) {\n notify.warning('Enter an investment title');\n return;\n }\n const value = Number(totalValue);\n if (!Number.isFinite(value) || value <= 0) {\n notify.warning('Enter a valid total value');\n return;\n }\n try {\n await createInvestment({\n propertyId,\n title: title.trim(),\n status,\n investmentType,\n riskLevel,\n totalValue: value,\n startDate: startDate ? new Date(startDate).toISOString() : undefined,\n maturityDate: maturityDate ? new Date(maturityDate).toISOString() : undefined,\n });\n notify.success('Investment created', `${title.trim()} was added to your portfolio.`);\n setCreateOpen(false);\n resetForm();\n } catch (err) {\n notify.error('Could not create investment', (err as Error).message);\n }\n };\n\n const portfolioStats = useMemo(() => {\n const totalInvested = allInvestments.reduce((sum, inv) => sum + sumField(inv.investors, 'investmentAmount'), 0);\n const totalReturns = allInvestments.reduce((sum, inv) => sum + sumField(inv.returns, 'amount'), 0);\n const currentValue = totalInvested + totalReturns;\n const activeCount = allInvestments.filter((inv) => inv.status === 'active').length;\n const returnPct = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n return { totalInvested, currentValue, totalReturns, activeCount, returnPct };\n }, [allInvestments]);\n\n // Portfolio allocation by investment type, weighted by current value (invested\n // + returns) — real figures from every holding, never fabricated.\n const allocation = useMemo(() => {\n const byType = new Map<string, number>();\n for (const inv of allInvestments) {\n const invested = sumField(inv.investors, 'investmentAmount');\n const returns = sumField(inv.returns, 'amount');\n const value = invested + returns || toNumber(inv.totalValue);\n const key = inv.investmentType;\n byType.set(key, (byType.get(key) ?? 0) + value);\n }\n const rows = Array.from(byType.entries())\n .filter(([, value]) => value > 0)\n .map(([type, value], idx) => ({\n type,\n label: investmentTypeLabels[type as InvestmentType] ?? type.replace('_', ' '),\n value,\n color: CHART_COLORS[idx % CHART_COLORS.length],\n }))\n .sort((a, b) => b.value - a.value);\n const total = rows.reduce((s, r) => s + r.value, 0);\n return { rows, total };\n }, [allInvestments]);\n\n return (\n <PageLayout\n title=\"Investments\"\n description=\"Track your real estate investment portfolio and performance\"\n purpose=\"Follow every real-estate investment you hold in one portfolio — how much you put in, what it is worth now, the returns it has paid, and its risk level. Use it to judge performance and decide where to put your next dollar.\"\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n >\n {/* Portfolio Summary Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Invested\"\n value={portfolioStats.totalInvested}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Current Value\"\n value={portfolioStats.currentValue}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Returns\"\n value={portfolioStats.totalReturns}\n prefix=\"$\"\n icon={<PieChart className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Active Investments\"\n value={portfolioStats.activeCount}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-investments\"\n steps={[\n {\n id: 'review-active',\n label: 'Review your active investments',\n description: 'Filter to the active deals currently paying returns.',\n onClick: () => handleTabChange('active'),\n },\n {\n id: 'find-funding',\n label: 'Find funding opportunities',\n description: 'See deals still raising capital that you can join.',\n onClick: () => handleTabChange('funding'),\n },\n {\n id: 'open-detail',\n label: 'Open an investment for full details',\n description: 'Drill into returns, investors, and risk for any holding.',\n onClick: () => {\n const first = investments[0];\n if (first) navigate(`../investments/${first.id}`);\n },\n },\n ]}\n />\n\n {/* Filter Tabs */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {statusTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => handleTabChange(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n activeTab === tab.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search by property or investment title...\"\n />\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n Showing {investments.length} {investments.length === 1 ? 'investment' : 'investments'} on this page\n </p>\n\n {/* Investment list (left) + portfolio allocation donut (aside) */}\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <div className=\"space-y-4 xl:col-span-2\">\n {investments.length > 0 ? (\n <>\n <div className=\"space-y-4\">\n {investments.map((investment) => {\n const propertyTitle = investment.property?.title ?? 'Untitled property';\n const totalInvested = sumField(investment.investors, 'investmentAmount');\n const totalReturns = sumField(investment.returns, 'amount');\n const currentValue = totalInvested + totalReturns;\n const returnPercentage = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n const returnsCount = safeArray(investment.returns).length;\n\n return (\n <div\n key={investment.id}\n className=\"cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\"\n onClick={() => navigate(`../investments/${investment.id}`)}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex-1\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2\">\n <Building className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"flex-1\">\n <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{propertyTitle}</h3>\n <StatusBadge label={investment.status} variant={getStatusVariant(investment.status)} />\n <StatusBadge label={investment.investmentType.replace('_', ' ')} variant=\"muted\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 text-sm sm:grid-cols-2 lg:grid-cols-4\">\n <div>\n <p className=\"text-text-secondary\">Invested Amount</p>\n <p className=\"text-lg font-medium text-text-primary\">\n {formatCurrency(totalInvested)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-medium text-text-primary\">\n {formatCurrency(currentValue)}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Total Return</p>\n <div className={cn('font-medium', getReturnColor(totalReturns))}>\n <div className=\"flex items-center gap-1\">\n {totalReturns > 0 && <ArrowUpRight className=\"h-3 w-3\" />}\n {totalReturns < 0 && <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(totalReturns)}\n </div>\n <div className=\"text-sm\">{returnPercentage.toFixed(1)}%</div>\n </div>\n </div>\n <div>\n <p className=\"text-text-secondary\">Risk Level</p>\n <div className=\"mt-1\">\n <StatusBadge\n label={`${investment.riskLevel} risk`}\n variant={riskBadgeVariant(investment.riskLevel)}\n dot\n />\n </div>\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <TrendingUp className=\"h-3 w-3\" />\n Start: {formatDateSafe(investment.startDate)}\n </span>\n <span className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3\" />\n Type: {investment.investmentType.replace('_', ' ')}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Returns: {returnsCount} payments\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex shrink-0 gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`../investments/${investment.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-4 w-4\" />\n View Details\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={investments.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<PieChart className=\"h-12 w-12\" />}\n title=\"No investments found\"\n description=\"Try adjusting your search criteria or start a new investment.\"\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n />\n )}\n </div>\n\n {/* Portfolio Allocation — real value split by investment type */}\n {allocation.rows.length > 0 && (\n <aside className=\"xl:col-span-1\">\n <ChartCard title=\"Portfolio Allocation\" description=\"Current value by investment type\">\n <div className=\"relative h-52 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <RePieChart>\n <Pie\n data={allocation.rows}\n dataKey=\"value\"\n nameKey=\"label\"\n innerRadius={56}\n outerRadius={82}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {allocation.rows.map((entry) => (\n <Cell key={entry.type} fill={entry.color} />\n ))}\n </Pie>\n <Tooltip\n contentStyle={tooltipContentStyle}\n formatter={(value: number, _n, item) => [formatCurrency(value), item?.payload?.label]}\n />\n </RePieChart>\n </ResponsiveContainer>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-xs text-text-secondary\">Total value</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatCurrency(allocation.total)}</span>\n </div>\n </div>\n <ul className=\"mt-4 space-y-2.5\">\n {allocation.rows.map((entry) => (\n <li key={entry.type} className=\"flex items-center justify-between text-sm\">\n <span className=\"flex items-center gap-2\">\n <span className=\"h-2.5 w-2.5 rounded-full\" style={{ background: entry.color }} />\n <span className=\"text-text-primary\">{entry.label}</span>\n </span>\n <span className=\"font-medium text-text-secondary\">{formatCurrency(entry.value)}</span>\n </li>\n ))}\n </ul>\n </ChartCard>\n </aside>\n )}\n </div>\n\n {/* Create Investment dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <form onSubmit={handleCreate}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New investment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a new holding against one of your properties.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Property *</label>\n <select\n required\n value={propertyId}\n onChange={(e) => setPropertyId(e.target.value)}\n className={inputClass}\n >\n <option value=\"\" disabled>\n {properties.length > 0 ? 'Select a property' : 'No properties available'}\n </option>\n {properties.map((property) => (\n <option key={property.id} value={property.id}>\n {property.title}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Title *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Riverside Villa fractional stake\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</label>\n <select\n value={investmentType}\n onChange={(e) => setInvestmentType(e.target.value as InvestmentType)}\n className={inputClass}\n >\n {investmentTypeOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Status</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as InvestmentStatus)}\n className={inputClass}\n >\n {statusOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Risk level</label>\n <select\n value={riskLevel}\n onChange={(e) => setRiskLevel(e.target.value as 'low' | 'medium' | 'high')}\n className={inputClass}\n >\n {riskOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Total value (USD) *</label>\n <input\n type=\"number\"\n required\n min={1}\n step=\"any\"\n placeholder=\"0\"\n value={totalValue}\n onChange={(e) => setTotalValue(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Start date</label>\n <input\n type=\"date\"\n value={startDate}\n onChange={(e) => setStartDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Maturity date</label>\n <input\n type=\"date\"\n value={maturityDate}\n onChange={(e) => setMaturityDate(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setCreateOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={creating || !propertyId || !title.trim() || !totalValue}>\n {creating ? 'Creating…' : 'Create investment'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegalPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LegalPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"names":[],"mappings":"AAkLA,wBAAgB,SAAS,gCAqHxB"}
|