@burdenoff/microfe-housingvista 2026.625.2 → 2026.626.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/pages/amc/AMCManagementPage.js +109 -82
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +57 -31
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +190 -162
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +57 -31
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +36 -13
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +36 -11
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +95 -69
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,10 +9,11 @@ import "../../components/index.js";
|
|
|
9
9
|
import { useAMCContracts as l } from "../../hooks/index.js";
|
|
10
10
|
import { useMemo as u, useState as d } from "react";
|
|
11
11
|
import { AlertTriangle as f, Calendar as p, Clock as m, DollarSign as h, Download as g, Plus as _, RotateCcw as v, Shield as y, Wrench as b } from "lucide-react";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { useNavigate as x } from "react-router-dom";
|
|
13
|
+
import { jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
14
|
+
import { EmphasisPanel as w, NextSteps as T } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/housingvista/pages/amc/AMCManagementPage.tsx
|
|
15
|
-
function
|
|
16
|
+
function E(e) {
|
|
16
17
|
switch (e) {
|
|
17
18
|
case "active": return "success";
|
|
18
19
|
case "expiring_soon": return "warning";
|
|
@@ -21,94 +22,120 @@ function w(e) {
|
|
|
21
22
|
default: return "muted";
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
|
-
function
|
|
25
|
-
let { data:
|
|
26
|
-
if (!
|
|
27
|
-
let e =
|
|
28
|
-
return
|
|
29
|
-
}, [
|
|
30
|
-
activeContracts:
|
|
31
|
-
pendingRenewals:
|
|
32
|
-
totalMonthlySpent:
|
|
33
|
-
upcomingServices:
|
|
25
|
+
function D() {
|
|
26
|
+
let D = x(), { data: O } = l(), [k, A] = d(""), j = u(() => {
|
|
27
|
+
if (!k) return O;
|
|
28
|
+
let e = k.toLowerCase();
|
|
29
|
+
return O.filter((t) => t.propertyTitle.toLowerCase().includes(e) || t.providerName.toLowerCase().includes(e) || t.packageName.toLowerCase().includes(e));
|
|
30
|
+
}, [O, k]), M = u(() => ({
|
|
31
|
+
activeContracts: O.filter((e) => e.status === "active" || e.status === "expiring_soon").length,
|
|
32
|
+
pendingRenewals: O.filter((e) => e.status === "expiring_soon").length,
|
|
33
|
+
totalMonthlySpent: O.filter((e) => e.status === "active" || e.status === "expiring_soon").reduce((e, t) => e + t.monthlyCost, 0),
|
|
34
|
+
upcomingServices: O.filter((e) => {
|
|
34
35
|
if (!e.nextServiceDate) return !1;
|
|
35
36
|
let t = new Date(e.nextServiceDate), n = /* @__PURE__ */ new Date(), r = (t.getTime() - n.getTime()) / (1e3 * 60 * 60 * 24);
|
|
36
37
|
return r >= 0 && r <= 30;
|
|
37
38
|
}).length
|
|
38
|
-
}), [
|
|
39
|
-
return /* @__PURE__ */
|
|
39
|
+
}), [O]), N = u(() => O.filter((e) => e.nextServiceDate && (e.status === "active" || e.status === "expiring_soon")).sort((e, t) => new Date(e.nextServiceDate).getTime() - new Date(t.nextServiceDate).getTime()), [O]);
|
|
40
|
+
return /* @__PURE__ */ C(r, {
|
|
40
41
|
title: "AMC Management",
|
|
41
42
|
description: "Manage Annual Maintenance Contracts and service packages",
|
|
42
43
|
purpose: "Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.",
|
|
43
|
-
actions: /* @__PURE__ */
|
|
44
|
+
actions: /* @__PURE__ */ C("div", {
|
|
44
45
|
className: "flex items-center gap-2",
|
|
45
|
-
children: [/* @__PURE__ */
|
|
46
|
+
children: [/* @__PURE__ */ C("button", {
|
|
46
47
|
className: "inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
47
|
-
children: [/* @__PURE__ */
|
|
48
|
-
}), /* @__PURE__ */
|
|
48
|
+
children: [/* @__PURE__ */ S(g, { className: "h-4 w-4" }), "Export"]
|
|
49
|
+
}), /* @__PURE__ */ C("button", {
|
|
49
50
|
className: "inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90",
|
|
50
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ S(_, { className: "h-4 w-4" }), "New Contract"]
|
|
51
52
|
})]
|
|
52
53
|
}),
|
|
53
54
|
children: [
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ S(w, {
|
|
55
56
|
className: "p-4",
|
|
56
|
-
children: /* @__PURE__ */
|
|
57
|
+
children: /* @__PURE__ */ C("div", {
|
|
57
58
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
58
59
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ S(i, {
|
|
60
61
|
label: "Active Contracts",
|
|
61
|
-
value:
|
|
62
|
-
icon: /* @__PURE__ */
|
|
62
|
+
value: M.activeContracts,
|
|
63
|
+
icon: /* @__PURE__ */ S(y, { className: "h-4 w-4" })
|
|
63
64
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
+
/* @__PURE__ */ S(i, {
|
|
65
66
|
label: "Pending Renewals",
|
|
66
|
-
value:
|
|
67
|
-
icon: /* @__PURE__ */
|
|
68
|
-
trend:
|
|
69
|
-
change:
|
|
67
|
+
value: M.pendingRenewals,
|
|
68
|
+
icon: /* @__PURE__ */ S(v, { className: "h-4 w-4" }),
|
|
69
|
+
trend: M.pendingRenewals > 0 ? "up" : "stable",
|
|
70
|
+
change: M.pendingRenewals > 0 ? M.pendingRenewals : 0
|
|
70
71
|
}),
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ S(i, {
|
|
72
73
|
label: "Total Monthly Spent",
|
|
73
|
-
value:
|
|
74
|
+
value: M.totalMonthlySpent,
|
|
74
75
|
prefix: "$",
|
|
75
|
-
icon: /* @__PURE__ */
|
|
76
|
+
icon: /* @__PURE__ */ S(h, { className: "h-4 w-4" })
|
|
76
77
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
+
/* @__PURE__ */ S(i, {
|
|
78
79
|
label: "Upcoming Services",
|
|
79
|
-
value:
|
|
80
|
-
icon: /* @__PURE__ */
|
|
80
|
+
value: M.upcomingServices,
|
|
81
|
+
icon: /* @__PURE__ */ S(p, { className: "h-4 w-4" })
|
|
81
82
|
})
|
|
82
83
|
]
|
|
83
84
|
})
|
|
84
85
|
}),
|
|
85
|
-
/* @__PURE__ */ S(
|
|
86
|
+
/* @__PURE__ */ S(T, {
|
|
87
|
+
storageKey: "housingvista-amc",
|
|
88
|
+
steps: [
|
|
89
|
+
{
|
|
90
|
+
id: "pick-property",
|
|
91
|
+
label: "Add coverage for a property",
|
|
92
|
+
description: "Choose a property to set up a new maintenance contract.",
|
|
93
|
+
onClick: () => D("/properties")
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "compare-providers",
|
|
97
|
+
label: "Compare your providers",
|
|
98
|
+
description: "Search by provider name to weigh coverage and monthly cost.",
|
|
99
|
+
onClick: () => {
|
|
100
|
+
let e = O[0]?.providerName;
|
|
101
|
+
e && A(e);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "check-legal",
|
|
106
|
+
label: "Review contract paperwork",
|
|
107
|
+
description: "Open the legal hub to manage the underlying documents.",
|
|
108
|
+
onClick: () => D("/legal")
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ C("div", {
|
|
86
113
|
className: "rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
114
|
+
children: [/* @__PURE__ */ S("h3", {
|
|
88
115
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
89
116
|
children: "Upcoming Service Schedule"
|
|
90
|
-
}),
|
|
117
|
+
}), N.length === 0 ? /* @__PURE__ */ S(s, {
|
|
91
118
|
illustration: "empty-generic",
|
|
92
119
|
title: "No services scheduled",
|
|
93
120
|
description: "Once your active contracts have upcoming service dates, they'll be listed here in order of priority.",
|
|
94
121
|
className: "border-0 bg-transparent p-6"
|
|
95
|
-
}) : /* @__PURE__ */
|
|
122
|
+
}) : /* @__PURE__ */ S("div", {
|
|
96
123
|
className: "space-y-3",
|
|
97
|
-
children:
|
|
124
|
+
children: N.map((t) => {
|
|
98
125
|
let r = new Date(t.nextServiceDate), i = /* @__PURE__ */ new Date(), a = Math.ceil((r.getTime() - i.getTime()) / (1e3 * 60 * 60 * 24)), s = a <= 7, c = a < 0;
|
|
99
|
-
return /* @__PURE__ */
|
|
126
|
+
return /* @__PURE__ */ C("div", {
|
|
100
127
|
className: "flex flex-col gap-3 rounded-lg bg-bg-sunken/50 p-4 sm:flex-row sm:items-center sm:justify-between",
|
|
101
|
-
children: [/* @__PURE__ */
|
|
128
|
+
children: [/* @__PURE__ */ C("div", {
|
|
102
129
|
className: "flex items-start gap-3",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ S("div", {
|
|
104
131
|
className: e("rounded-lg p-2", c ? "bg-red-100 dark:bg-red-900/30" : s ? "bg-amber-100 dark:bg-amber-900/30" : "bg-blue-100 dark:bg-blue-900/30"),
|
|
105
|
-
children: c ? /* @__PURE__ */
|
|
106
|
-
}), /* @__PURE__ */
|
|
107
|
-
/* @__PURE__ */
|
|
132
|
+
children: c ? /* @__PURE__ */ S(f, { className: "h-4 w-4 text-red-600 dark:text-red-400" }) : s ? /* @__PURE__ */ S(m, { className: "h-4 w-4 text-amber-600 dark:text-amber-400" }) : /* @__PURE__ */ S(b, { className: "h-4 w-4 text-blue-600 dark:text-blue-400" })
|
|
133
|
+
}), /* @__PURE__ */ C("div", { children: [
|
|
134
|
+
/* @__PURE__ */ S("p", {
|
|
108
135
|
className: "font-medium text-text-primary",
|
|
109
136
|
children: t.propertyTitle
|
|
110
137
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ C("p", {
|
|
112
139
|
className: "text-sm text-text-secondary",
|
|
113
140
|
children: [
|
|
114
141
|
t.packageName,
|
|
@@ -116,12 +143,12 @@ function T() {
|
|
|
116
143
|
t.providerName
|
|
117
144
|
]
|
|
118
145
|
}),
|
|
119
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ C("div", {
|
|
120
147
|
className: "mt-1 flex flex-wrap gap-1",
|
|
121
|
-
children: [t.servicesIncluded.slice(0, 3).map((e, t) => /* @__PURE__ */
|
|
148
|
+
children: [t.servicesIncluded.slice(0, 3).map((e, t) => /* @__PURE__ */ S("span", {
|
|
122
149
|
className: "inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
123
150
|
children: e
|
|
124
|
-
}, t)), t.servicesIncluded.length > 3 && /* @__PURE__ */
|
|
151
|
+
}, t)), t.servicesIncluded.length > 3 && /* @__PURE__ */ C("span", {
|
|
125
152
|
className: "inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
126
153
|
children: [
|
|
127
154
|
"+",
|
|
@@ -131,47 +158,47 @@ function T() {
|
|
|
131
158
|
})]
|
|
132
159
|
})
|
|
133
160
|
] })]
|
|
134
|
-
}), /* @__PURE__ */
|
|
161
|
+
}), /* @__PURE__ */ C("div", {
|
|
135
162
|
className: "flex items-center gap-3",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ C("div", {
|
|
137
164
|
className: "text-right",
|
|
138
|
-
children: [/* @__PURE__ */
|
|
165
|
+
children: [/* @__PURE__ */ S("p", {
|
|
139
166
|
className: "text-sm font-medium text-text-primary",
|
|
140
167
|
children: n(t.nextServiceDate)
|
|
141
|
-
}), /* @__PURE__ */
|
|
168
|
+
}), /* @__PURE__ */ S("p", {
|
|
142
169
|
className: e("text-xs", c ? "text-red-600 dark:text-red-400" : s ? "text-amber-600 dark:text-amber-400" : "text-text-secondary"),
|
|
143
170
|
children: c ? `${Math.abs(a)} days overdue` : a === 0 ? "Today" : `In ${a} days`
|
|
144
171
|
})]
|
|
145
|
-
}), /* @__PURE__ */
|
|
172
|
+
}), /* @__PURE__ */ S(o, {
|
|
146
173
|
label: t.status.replace("_", " "),
|
|
147
|
-
variant:
|
|
174
|
+
variant: E(t.status)
|
|
148
175
|
})]
|
|
149
176
|
})]
|
|
150
177
|
}, t.id);
|
|
151
178
|
})
|
|
152
179
|
})]
|
|
153
180
|
}),
|
|
154
|
-
/* @__PURE__ */
|
|
155
|
-
/* @__PURE__ */
|
|
181
|
+
/* @__PURE__ */ C("div", { children: [
|
|
182
|
+
/* @__PURE__ */ S("h2", {
|
|
156
183
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
157
184
|
children: "All Contracts"
|
|
158
185
|
}),
|
|
159
|
-
/* @__PURE__ */
|
|
160
|
-
searchValue:
|
|
161
|
-
onSearchChange:
|
|
186
|
+
/* @__PURE__ */ S(c, {
|
|
187
|
+
searchValue: k,
|
|
188
|
+
onSearchChange: A,
|
|
162
189
|
searchPlaceholder: "Search contracts, providers, properties..."
|
|
163
190
|
}),
|
|
164
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ S("div", {
|
|
165
192
|
className: "mt-4",
|
|
166
|
-
children:
|
|
193
|
+
children: j.length > 0 ? /* @__PURE__ */ S(a, {
|
|
167
194
|
columns: [
|
|
168
195
|
{
|
|
169
196
|
key: "packageName",
|
|
170
197
|
header: "Contract",
|
|
171
|
-
render: (e) => /* @__PURE__ */
|
|
198
|
+
render: (e) => /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("div", {
|
|
172
199
|
className: "font-medium text-text-primary",
|
|
173
200
|
children: e.packageName
|
|
174
|
-
}), /* @__PURE__ */
|
|
201
|
+
}), /* @__PURE__ */ S("div", {
|
|
175
202
|
className: "text-xs text-text-secondary",
|
|
176
203
|
children: e.propertyTitle
|
|
177
204
|
})] })
|
|
@@ -179,7 +206,7 @@ function T() {
|
|
|
179
206
|
{
|
|
180
207
|
key: "providerName",
|
|
181
208
|
header: "Provider",
|
|
182
|
-
render: (e) => /* @__PURE__ */
|
|
209
|
+
render: (e) => /* @__PURE__ */ S("span", {
|
|
183
210
|
className: "text-sm text-text-primary",
|
|
184
211
|
children: e.providerName
|
|
185
212
|
})
|
|
@@ -187,12 +214,12 @@ function T() {
|
|
|
187
214
|
{
|
|
188
215
|
key: "servicesIncluded",
|
|
189
216
|
header: "Coverage",
|
|
190
|
-
render: (e) => /* @__PURE__ */
|
|
217
|
+
render: (e) => /* @__PURE__ */ C("div", {
|
|
191
218
|
className: "max-w-[200px]",
|
|
192
|
-
children: [/* @__PURE__ */
|
|
219
|
+
children: [/* @__PURE__ */ C("span", {
|
|
193
220
|
className: "text-sm text-text-primary",
|
|
194
221
|
children: [e.servicesIncluded.length, " services"]
|
|
195
|
-
}), /* @__PURE__ */
|
|
222
|
+
}), /* @__PURE__ */ C("p", {
|
|
196
223
|
className: "truncate text-xs text-text-secondary",
|
|
197
224
|
children: [e.servicesIncluded.slice(0, 2).join(", "), e.servicesIncluded.length > 2 && ` +${e.servicesIncluded.length - 2} more`]
|
|
198
225
|
})]
|
|
@@ -201,10 +228,10 @@ function T() {
|
|
|
201
228
|
{
|
|
202
229
|
key: "monthlyCost",
|
|
203
230
|
header: "Cost",
|
|
204
|
-
render: (e) => /* @__PURE__ */
|
|
231
|
+
render: (e) => /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("span", {
|
|
205
232
|
className: "font-medium text-text-primary",
|
|
206
233
|
children: t(e.monthlyCost)
|
|
207
|
-
}), /* @__PURE__ */
|
|
234
|
+
}), /* @__PURE__ */ S("span", {
|
|
208
235
|
className: "text-xs text-text-secondary",
|
|
209
236
|
children: "/mo"
|
|
210
237
|
})] })
|
|
@@ -212,7 +239,7 @@ function T() {
|
|
|
212
239
|
{
|
|
213
240
|
key: "startDate",
|
|
214
241
|
header: "Start",
|
|
215
|
-
render: (e) => /* @__PURE__ */
|
|
242
|
+
render: (e) => /* @__PURE__ */ S("span", {
|
|
216
243
|
className: "text-sm text-text-primary",
|
|
217
244
|
children: n(e.startDate)
|
|
218
245
|
})
|
|
@@ -220,7 +247,7 @@ function T() {
|
|
|
220
247
|
{
|
|
221
248
|
key: "endDate",
|
|
222
249
|
header: "End",
|
|
223
|
-
render: (e) => /* @__PURE__ */
|
|
250
|
+
render: (e) => /* @__PURE__ */ S("span", {
|
|
224
251
|
className: "text-sm text-text-primary",
|
|
225
252
|
children: n(e.endDate)
|
|
226
253
|
})
|
|
@@ -228,24 +255,24 @@ function T() {
|
|
|
228
255
|
{
|
|
229
256
|
key: "status",
|
|
230
257
|
header: "Status",
|
|
231
|
-
render: (e) => /* @__PURE__ */
|
|
258
|
+
render: (e) => /* @__PURE__ */ S(o, {
|
|
232
259
|
label: e.status.replace("_", " "),
|
|
233
|
-
variant:
|
|
260
|
+
variant: E(e.status),
|
|
234
261
|
dot: !0
|
|
235
262
|
})
|
|
236
263
|
}
|
|
237
264
|
],
|
|
238
|
-
data:
|
|
265
|
+
data: j,
|
|
239
266
|
keyField: "id",
|
|
240
267
|
pageSize: 10
|
|
241
|
-
}) : /* @__PURE__ */
|
|
268
|
+
}) : /* @__PURE__ */ S(s, {
|
|
242
269
|
illustration: "empty-search",
|
|
243
|
-
icon: /* @__PURE__ */
|
|
270
|
+
icon: /* @__PURE__ */ S(y, { className: "h-12 w-12" }),
|
|
244
271
|
title: "No contracts found",
|
|
245
272
|
description: "Try adjusting your search criteria or create a new contract.",
|
|
246
|
-
action: /* @__PURE__ */
|
|
273
|
+
action: /* @__PURE__ */ C("button", {
|
|
247
274
|
className: "inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90",
|
|
248
|
-
children: [/* @__PURE__ */
|
|
275
|
+
children: [/* @__PURE__ */ S(_, { className: "h-4 w-4" }), "New Contract"]
|
|
249
276
|
})
|
|
250
277
|
})
|
|
251
278
|
})
|
|
@@ -254,6 +281,6 @@ function T() {
|
|
|
254
281
|
});
|
|
255
282
|
}
|
|
256
283
|
//#endregion
|
|
257
|
-
export {
|
|
284
|
+
export { D as AMCManagementPage };
|
|
258
285
|
|
|
259
286
|
//# sourceMappingURL=AMCManagementPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Shield,\n FileText,\n DollarSign,\n Calendar,\n Plus,\n Download,\n CheckCircle,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n Eye,\n} from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { AMCContract } from '../../types';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n const { data: amcContracts } = useAMCContracts();\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n if (!search) return amcContracts;\n const q = search.toLowerCase();\n return amcContracts.filter(\n (c) =>\n c.propertyTitle.toLowerCase().includes(q) ||\n c.providerName.toLowerCase().includes(q) ||\n c.packageName.toLowerCase().includes(q)\n );\n }, [amcContracts, search]);\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = amcContracts.filter((c) => {\n if (!c.nextServiceDate) return false;\n const serviceDate = new Date(c.nextServiceDate);\n const now = new Date();\n const daysUntil = (serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts]);\n\n const scheduledServices = useMemo(\n () =>\n amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime()),\n [amcContracts]\n );\n\n const contractColumns: Column<AMCContract>[] = [\n {\n key: 'packageName',\n header: 'Contract',\n render: (item) => (\n <div>\n <div className=\"font-medium text-text-primary\">{item.packageName}</div>\n <div className=\"text-xs text-text-secondary\">{item.propertyTitle}</div>\n </div>\n ),\n },\n {\n key: 'providerName',\n header: 'Provider',\n render: (item) => <span className=\"text-sm text-text-primary\">{item.providerName}</span>,\n },\n {\n key: 'servicesIncluded',\n header: 'Coverage',\n render: (item) => (\n <div className=\"max-w-[200px]\">\n <span className=\"text-sm text-text-primary\">{item.servicesIncluded.length} services</span>\n <p className=\"truncate text-xs text-text-secondary\">\n {item.servicesIncluded.slice(0, 2).join(', ')}\n {item.servicesIncluded.length > 2 && ` +${item.servicesIncluded.length - 2} more`}\n </p>\n </div>\n ),\n },\n {\n key: 'monthlyCost',\n header: 'Cost',\n render: (item) => (\n <div>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.monthlyCost)}</span>\n <span className=\"text-xs text-text-secondary\">/mo</span>\n </div>\n ),\n },\n {\n key: 'startDate',\n header: 'Start',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.startDate)}</span>,\n },\n {\n key: 'endDate',\n header: 'End',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.endDate)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => (\n <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} dot />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n purpose=\"Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\">\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n </div>\n }\n >\n {/* 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 label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Pending Renewals\"\n value={stats.pendingRenewals}\n icon={<RotateCcw className=\"h-4 w-4\" />}\n trend={stats.pendingRenewals > 0 ? 'up' : 'stable'}\n change={stats.pendingRenewals > 0 ? stats.pendingRenewals : 0}\n />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n </EmphasisPanel>\n\n {/* Service Schedule Section */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n {scheduledServices.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No services scheduled\"\n description=\"Once your active contracts have upcoming service dates, they'll be listed here in order of priority.\"\n className=\"border-0 bg-transparent p-6\"\n />\n ) : (\n <div className=\"space-y-3\">\n {scheduledServices\n .map((contract) => {\n const serviceDate = new Date(contract.nextServiceDate);\n const now = new Date();\n const daysUntil = Math.ceil((serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n\n return (\n <div\n key={contract.id}\n className=\"flex flex-col gap-3 rounded-lg bg-bg-sunken/50 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-start gap-3\">\n <div\n className={cn(\n 'rounded-lg p-2',\n isPast\n ? 'bg-red-100 dark:bg-red-900/30'\n : isUrgent\n ? 'bg-amber-100 dark:bg-amber-900/30'\n : 'bg-blue-100 dark:bg-blue-900/30'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-red-600 dark:text-red-400\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-blue-600 dark:text-blue-400\" />\n )}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{contract.propertyTitle}</p>\n <p className=\"text-sm text-text-secondary\">\n {contract.packageName} · {contract.providerName}\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1\">\n {contract.servicesIncluded.slice(0, 3).map((service, idx) => (\n <span\n key={idx}\n className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {service}\n </span>\n ))}\n {contract.servicesIncluded.length > 3 && (\n <span className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n +{contract.servicesIncluded.length - 3} more\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{formatDate(contract.nextServiceDate)}</p>\n <p\n className={cn(\n 'text-xs',\n isPast\n ? 'text-red-600 dark:text-red-400'\n : isUrgent\n ? 'text-amber-600 dark:text-amber-400'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n {/* Contracts Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n <div className=\"mt-4\">\n {filtered.length > 0 ? (\n <DataTable columns={contractColumns} data={filtered} keyField=\"id\" pageSize={10} />\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n }\n />\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Shield,\n FileText,\n DollarSign,\n Calendar,\n Plus,\n Download,\n CheckCircle,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n Eye,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { DataTable, type Column } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { AMCContract } from '../../types';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n const navigate = useNavigate();\n const { data: amcContracts } = useAMCContracts();\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n if (!search) return amcContracts;\n const q = search.toLowerCase();\n return amcContracts.filter(\n (c) =>\n c.propertyTitle.toLowerCase().includes(q) ||\n c.providerName.toLowerCase().includes(q) ||\n c.packageName.toLowerCase().includes(q)\n );\n }, [amcContracts, search]);\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = amcContracts.filter((c) => {\n if (!c.nextServiceDate) return false;\n const serviceDate = new Date(c.nextServiceDate);\n const now = new Date();\n const daysUntil = (serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts]);\n\n const scheduledServices = useMemo(\n () =>\n amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime()),\n [amcContracts]\n );\n\n const contractColumns: Column<AMCContract>[] = [\n {\n key: 'packageName',\n header: 'Contract',\n render: (item) => (\n <div>\n <div className=\"font-medium text-text-primary\">{item.packageName}</div>\n <div className=\"text-xs text-text-secondary\">{item.propertyTitle}</div>\n </div>\n ),\n },\n {\n key: 'providerName',\n header: 'Provider',\n render: (item) => <span className=\"text-sm text-text-primary\">{item.providerName}</span>,\n },\n {\n key: 'servicesIncluded',\n header: 'Coverage',\n render: (item) => (\n <div className=\"max-w-[200px]\">\n <span className=\"text-sm text-text-primary\">{item.servicesIncluded.length} services</span>\n <p className=\"truncate text-xs text-text-secondary\">\n {item.servicesIncluded.slice(0, 2).join(', ')}\n {item.servicesIncluded.length > 2 && ` +${item.servicesIncluded.length - 2} more`}\n </p>\n </div>\n ),\n },\n {\n key: 'monthlyCost',\n header: 'Cost',\n render: (item) => (\n <div>\n <span className=\"font-medium text-text-primary\">{formatCurrency(item.monthlyCost)}</span>\n <span className=\"text-xs text-text-secondary\">/mo</span>\n </div>\n ),\n },\n {\n key: 'startDate',\n header: 'Start',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.startDate)}</span>,\n },\n {\n key: 'endDate',\n header: 'End',\n render: (item) => <span className=\"text-sm text-text-primary\">{formatDate(item.endDate)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (item) => (\n <StatusBadge label={item.status.replace('_', ' ')} variant={getStatusVariant(item.status)} dot />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n purpose=\"Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\">\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n </div>\n }\n >\n {/* 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 label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Pending Renewals\"\n value={stats.pendingRenewals}\n icon={<RotateCcw className=\"h-4 w-4\" />}\n trend={stats.pendingRenewals > 0 ? 'up' : 'stable'}\n change={stats.pendingRenewals > 0 ? stats.pendingRenewals : 0}\n />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-amc\"\n steps={[\n {\n id: 'pick-property',\n label: 'Add coverage for a property',\n description: 'Choose a property to set up a new maintenance contract.',\n onClick: () => navigate('/properties'),\n },\n {\n id: 'compare-providers',\n label: 'Compare your providers',\n description: 'Search by provider name to weigh coverage and monthly cost.',\n onClick: () => {\n const firstProvider = amcContracts[0]?.providerName;\n if (firstProvider) setSearch(firstProvider);\n },\n },\n {\n id: 'check-legal',\n label: 'Review contract paperwork',\n description: 'Open the legal hub to manage the underlying documents.',\n onClick: () => navigate('/legal'),\n },\n ]}\n />\n\n {/* Service Schedule Section */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-lg font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n {scheduledServices.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No services scheduled\"\n description=\"Once your active contracts have upcoming service dates, they'll be listed here in order of priority.\"\n className=\"border-0 bg-transparent p-6\"\n />\n ) : (\n <div className=\"space-y-3\">\n {scheduledServices\n .map((contract) => {\n const serviceDate = new Date(contract.nextServiceDate);\n const now = new Date();\n const daysUntil = Math.ceil((serviceDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n\n return (\n <div\n key={contract.id}\n className=\"flex flex-col gap-3 rounded-lg bg-bg-sunken/50 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-start gap-3\">\n <div\n className={cn(\n 'rounded-lg p-2',\n isPast\n ? 'bg-red-100 dark:bg-red-900/30'\n : isUrgent\n ? 'bg-amber-100 dark:bg-amber-900/30'\n : 'bg-blue-100 dark:bg-blue-900/30'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-red-600 dark:text-red-400\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-blue-600 dark:text-blue-400\" />\n )}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{contract.propertyTitle}</p>\n <p className=\"text-sm text-text-secondary\">\n {contract.packageName} · {contract.providerName}\n </p>\n <div className=\"mt-1 flex flex-wrap gap-1\">\n {contract.servicesIncluded.slice(0, 3).map((service, idx) => (\n <span\n key={idx}\n className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {service}\n </span>\n ))}\n {contract.servicesIncluded.length > 3 && (\n <span className=\"inline-block rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n +{contract.servicesIncluded.length - 3} more\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{formatDate(contract.nextServiceDate)}</p>\n <p\n className={cn(\n 'text-xs',\n isPast\n ? 'text-red-600 dark:text-red-400'\n : isUrgent\n ? 'text-amber-600 dark:text-amber-400'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n {/* Contracts Table */}\n <div>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n <div className=\"mt-4\">\n {filtered.length > 0 ? (\n <DataTable columns={contractColumns} data={filtered} keyField=\"id\" pageSize={10} />\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </button>\n }\n />\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -12,9 +12,9 @@ import { useMemo as u, useState as d } from "react";
|
|
|
12
12
|
import { ArrowDownRight as f, ArrowUpRight as p, BarChart3 as m, Building as h, DollarSign as g, Download as _, Eye as v, MapPin as y, Plus as b, TrendingUp as x } from "lucide-react";
|
|
13
13
|
import { useNavigate as S } from "react-router-dom";
|
|
14
14
|
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
15
|
-
import { EmphasisPanel as T } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
import { EmphasisPanel as T, NextSteps as E } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/housingvista/pages/assets/AssetManagementPage.tsx
|
|
17
|
-
function
|
|
17
|
+
function D(e) {
|
|
18
18
|
switch (e) {
|
|
19
19
|
case "active": return "success";
|
|
20
20
|
case "under_renovation": return "warning";
|
|
@@ -23,20 +23,20 @@ function E(e) {
|
|
|
23
23
|
default: return "muted";
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
let
|
|
28
|
-
if (!
|
|
29
|
-
let e =
|
|
30
|
-
return
|
|
31
|
-
}, [
|
|
32
|
-
let e =
|
|
26
|
+
function O() {
|
|
27
|
+
let O = S(), k = l(10), { data: A, loading: j } = c(k.pagination), [M, N] = d(""), P = u(() => {
|
|
28
|
+
if (!M) return A;
|
|
29
|
+
let e = M.toLowerCase();
|
|
30
|
+
return A.filter((t) => t.title.toLowerCase().includes(e) || t.location.toLowerCase().includes(e) || t.type.toLowerCase().includes(e));
|
|
31
|
+
}, [A, M]), F = u(() => {
|
|
32
|
+
let e = A.reduce((e, t) => e + t.currentValue, 0), t = A.filter((e) => e.rentalYield).reduce((e, t) => e + t.currentValue * (t.rentalYield || 0) / 100 / 12, 0), n = A.length > 0 ? A.reduce((e, t) => e + t.appreciationPercent, 0) / A.length : 0, r = A.reduce((e, t) => e + t.purchasePrice, 0);
|
|
33
33
|
return {
|
|
34
34
|
totalValue: e,
|
|
35
35
|
totalMonthlyIncome: t,
|
|
36
36
|
avgAppreciation: n,
|
|
37
37
|
totalROI: r > 0 ? (e - r) / r * 100 : 0
|
|
38
38
|
};
|
|
39
|
-
}, [
|
|
39
|
+
}, [A]);
|
|
40
40
|
return /* @__PURE__ */ w(n, {
|
|
41
41
|
title: "Asset Management",
|
|
42
42
|
description: "Monitor and manage your property portfolio performance",
|
|
@@ -59,7 +59,7 @@ function D() {
|
|
|
59
59
|
children: [
|
|
60
60
|
/* @__PURE__ */ C(r, {
|
|
61
61
|
label: "Total Value",
|
|
62
|
-
value:
|
|
62
|
+
value: F.totalValue,
|
|
63
63
|
prefix: "$",
|
|
64
64
|
icon: /* @__PURE__ */ C(g, { className: "h-4 w-4" }),
|
|
65
65
|
trend: "up",
|
|
@@ -67,7 +67,7 @@ function D() {
|
|
|
67
67
|
}),
|
|
68
68
|
/* @__PURE__ */ C(r, {
|
|
69
69
|
label: "Monthly Income",
|
|
70
|
-
value: Math.round(
|
|
70
|
+
value: Math.round(F.totalMonthlyIncome),
|
|
71
71
|
prefix: "$",
|
|
72
72
|
icon: /* @__PURE__ */ C(x, { className: "h-4 w-4" }),
|
|
73
73
|
trend: "up",
|
|
@@ -75,14 +75,14 @@ function D() {
|
|
|
75
75
|
}),
|
|
76
76
|
/* @__PURE__ */ C(r, {
|
|
77
77
|
label: "Avg. Appreciation",
|
|
78
|
-
value: `${
|
|
78
|
+
value: `${F.avgAppreciation.toFixed(1)}%`,
|
|
79
79
|
icon: /* @__PURE__ */ C(m, { className: "h-4 w-4" }),
|
|
80
80
|
trend: "up",
|
|
81
81
|
change: 2
|
|
82
82
|
}),
|
|
83
83
|
/* @__PURE__ */ C(r, {
|
|
84
84
|
label: "Overall ROI",
|
|
85
|
-
value: `${
|
|
85
|
+
value: `${F.totalROI.toFixed(1)}%`,
|
|
86
86
|
icon: /* @__PURE__ */ C(h, { className: "h-4 w-4" }),
|
|
87
87
|
trend: "up",
|
|
88
88
|
change: 3
|
|
@@ -90,13 +90,39 @@ function D() {
|
|
|
90
90
|
]
|
|
91
91
|
})
|
|
92
92
|
}),
|
|
93
|
+
/* @__PURE__ */ C(E, {
|
|
94
|
+
storageKey: "housingvista-assets",
|
|
95
|
+
steps: [
|
|
96
|
+
{
|
|
97
|
+
id: "open-asset",
|
|
98
|
+
label: "Open an asset to see full performance",
|
|
99
|
+
description: "Drill into value history, yield, and ROI for any property.",
|
|
100
|
+
onClick: () => {
|
|
101
|
+
let e = A[0];
|
|
102
|
+
e && O(`/assets/${e.id}`);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "compare-market",
|
|
107
|
+
label: "Compare against market trends",
|
|
108
|
+
description: "Check market analytics to benchmark your portfolio.",
|
|
109
|
+
onClick: () => O("/market-analytics")
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "add-asset",
|
|
113
|
+
label: "Add a property you already own",
|
|
114
|
+
description: "Bring an existing holding into your portfolio view.",
|
|
115
|
+
onClick: () => O("/properties/new")
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}),
|
|
93
119
|
/* @__PURE__ */ C("div", {
|
|
94
120
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
95
|
-
children:
|
|
121
|
+
children: A.map((n) => {
|
|
96
122
|
let r = n.currentValue - n.purchasePrice >= 0;
|
|
97
123
|
return /* @__PURE__ */ w("div", {
|
|
98
124
|
className: "cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-4 shadow-sm transition-shadow hover:shadow-md",
|
|
99
|
-
onClick: () =>
|
|
125
|
+
onClick: () => O(`/assets/${n.id}`),
|
|
100
126
|
children: [/* @__PURE__ */ w("div", {
|
|
101
127
|
className: "mb-3 flex items-start justify-between",
|
|
102
128
|
children: [/* @__PURE__ */ w("div", {
|
|
@@ -114,7 +140,7 @@ function D() {
|
|
|
114
140
|
})] })]
|
|
115
141
|
}), /* @__PURE__ */ C(a, {
|
|
116
142
|
label: n.status.replace("_", " "),
|
|
117
|
-
variant:
|
|
143
|
+
variant: D(n.status)
|
|
118
144
|
})]
|
|
119
145
|
}), /* @__PURE__ */ w("div", {
|
|
120
146
|
className: "grid grid-cols-2 gap-3",
|
|
@@ -162,13 +188,13 @@ function D() {
|
|
|
162
188
|
children: "All Assets"
|
|
163
189
|
}),
|
|
164
190
|
/* @__PURE__ */ C(s, {
|
|
165
|
-
searchValue:
|
|
166
|
-
onSearchChange:
|
|
191
|
+
searchValue: M,
|
|
192
|
+
onSearchChange: N,
|
|
167
193
|
searchPlaceholder: "Search assets by name, location, or type..."
|
|
168
194
|
}),
|
|
169
195
|
/* @__PURE__ */ C("div", {
|
|
170
196
|
className: "mt-4",
|
|
171
|
-
children:
|
|
197
|
+
children: P.length > 0 ? /* @__PURE__ */ C(i, {
|
|
172
198
|
columns: [
|
|
173
199
|
{
|
|
174
200
|
key: "title",
|
|
@@ -239,7 +265,7 @@ function D() {
|
|
|
239
265
|
header: "Status",
|
|
240
266
|
render: (e) => /* @__PURE__ */ C(a, {
|
|
241
267
|
label: e.status.replace("_", " "),
|
|
242
|
-
variant:
|
|
268
|
+
variant: D(e.status)
|
|
243
269
|
})
|
|
244
270
|
},
|
|
245
271
|
{
|
|
@@ -248,24 +274,24 @@ function D() {
|
|
|
248
274
|
sortable: !1,
|
|
249
275
|
render: (e) => /* @__PURE__ */ w("button", {
|
|
250
276
|
onClick: (t) => {
|
|
251
|
-
t.stopPropagation(),
|
|
277
|
+
t.stopPropagation(), O(`/assets/${e.id}`);
|
|
252
278
|
},
|
|
253
279
|
className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
254
280
|
children: [/* @__PURE__ */ C(v, { className: "h-3 w-3" }), "View"]
|
|
255
281
|
})
|
|
256
282
|
}
|
|
257
283
|
],
|
|
258
|
-
data:
|
|
284
|
+
data: P,
|
|
259
285
|
keyField: "id",
|
|
260
|
-
onRowClick: (e) =>
|
|
286
|
+
onRowClick: (e) => O(`/assets/${e.id}`),
|
|
261
287
|
pageSize: 10,
|
|
262
288
|
serverPagination: {
|
|
263
|
-
page:
|
|
264
|
-
pageSize:
|
|
265
|
-
hasNextPage:
|
|
266
|
-
loading:
|
|
267
|
-
onPageChange:
|
|
268
|
-
onPageSizeChange:
|
|
289
|
+
page: k.page,
|
|
290
|
+
pageSize: k.pageSize,
|
|
291
|
+
hasNextPage: A.length >= k.pageSize,
|
|
292
|
+
loading: j,
|
|
293
|
+
onPageChange: k.setPage,
|
|
294
|
+
onPageSizeChange: k.setPageSize
|
|
269
295
|
}
|
|
270
296
|
}) : /* @__PURE__ */ C(o, {
|
|
271
297
|
illustration: "empty-search",
|
|
@@ -283,6 +309,6 @@ function D() {
|
|
|
283
309
|
});
|
|
284
310
|
}
|
|
285
311
|
//#endregion
|
|
286
|
-
export {
|
|
312
|
+
export { O as AssetManagementPage };
|
|
287
313
|
|
|
288
314
|
//# sourceMappingURL=AssetManagementPage.js.map
|