@schandlergarcia/sf-web-components 2.2.1 → 2.3.1

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/brands/engine/app/api/graphql-operations-types.ts +11260 -0
  3. package/brands/engine/app/api/graphqlClient.ts +25 -0
  4. package/brands/engine/app/api/partnerQueries.ts +212 -0
  5. package/brands/engine/app/appLayout.tsx +13 -0
  6. package/brands/engine/app/components/AgentforceConversationClient.tsx +201 -0
  7. package/brands/engine/app/components/__inherit_AgentforceConversationClient.tsx +3 -0
  8. package/brands/engine/app/components/alerts/status-alert.tsx +49 -0
  9. package/brands/engine/app/components/layouts/card-layout.tsx +29 -0
  10. package/brands/engine/app/components/workspace/CommandCenter.tsx +16 -0
  11. package/brands/engine/app/config/agentApi.ts +36 -0
  12. package/brands/engine/app/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  13. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  14. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  15. package/brands/engine/app/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  16. package/brands/engine/app/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  17. package/brands/engine/app/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  18. package/brands/engine/app/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
  19. package/brands/engine/app/features/object-search/__examples__/pages/Home.tsx +34 -0
  20. package/brands/engine/app/features/object-search/api/objectSearchService.ts +84 -0
  21. package/brands/engine/app/features/object-search/components/ActiveFilters.tsx +89 -0
  22. package/brands/engine/app/features/object-search/components/FilterContext.tsx +83 -0
  23. package/brands/engine/app/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  24. package/brands/engine/app/features/object-search/components/PaginationControls.tsx +109 -0
  25. package/brands/engine/app/features/object-search/components/SearchBar.tsx +41 -0
  26. package/brands/engine/app/features/object-search/components/SortControl.tsx +143 -0
  27. package/brands/engine/app/features/object-search/components/filters/BooleanFilter.tsx +78 -0
  28. package/brands/engine/app/features/object-search/components/filters/DateFilter.tsx +128 -0
  29. package/brands/engine/app/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
  30. package/brands/engine/app/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  31. package/brands/engine/app/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
  32. package/brands/engine/app/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
  33. package/brands/engine/app/features/object-search/components/filters/SearchFilter.tsx +50 -0
  34. package/brands/engine/app/features/object-search/components/filters/SelectFilter.tsx +97 -0
  35. package/brands/engine/app/features/object-search/components/filters/TextFilter.tsx +91 -0
  36. package/brands/engine/app/features/object-search/hooks/useAsyncData.ts +54 -0
  37. package/brands/engine/app/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  38. package/brands/engine/app/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  39. package/brands/engine/app/features/object-search/hooks/useObjectSearchParams.ts +252 -0
  40. package/brands/engine/app/features/object-search/utils/debounce.ts +25 -0
  41. package/brands/engine/app/features/object-search/utils/fieldUtils.ts +29 -0
  42. package/brands/engine/app/features/object-search/utils/filterUtils.ts +404 -0
  43. package/brands/engine/app/features/object-search/utils/sortUtils.ts +38 -0
  44. package/brands/engine/app/hooks/useEngineLiveData.ts +49 -0
  45. package/brands/engine/app/hooks/useEvaAgent.ts +288 -0
  46. package/brands/engine/app/hooks/usePartnerDashboardData.ts +141 -0
  47. package/brands/engine/app/navigationMenu.tsx +80 -0
  48. package/brands/engine/app/pages/AccountObjectDetailPage.tsx +361 -0
  49. package/brands/engine/app/pages/AccountSearch.tsx +305 -0
  50. package/brands/engine/app/pages/BlankDashboard.tsx +15 -0
  51. package/brands/engine/app/pages/DataTest.tsx +78 -0
  52. package/brands/engine/app/pages/Home.tsx +5 -0
  53. package/brands/engine/app/pages/NotFound.tsx +19 -0
  54. package/brands/engine/app/pages/PartnerHubDashboard.tsx +2010 -0
  55. package/brands/engine/app/pages/Search.tsx +13 -0
  56. package/brands/engine/app/router-utils.tsx +35 -0
  57. package/brands/engine/app/routes.tsx +39 -0
  58. package/brands/engine/app/styles/global.css +270 -0
  59. package/package.json +1 -1
  60. package/scripts/apply-brand.mjs +160 -76
  61. package/scripts/postinstall.mjs +6 -0
@@ -0,0 +1,2010 @@
1
+ import { ListCard, ActivityCard, D3Chart, Dropdown, Button, Modal, CardSkeleton } from "@/components/library";
2
+ import useDataSource from "@/components/library/data/useDataSource";
3
+ import { useThemeMode } from "@/components/library/theme/AppThemeProvider";
4
+ import { toast } from "sonner";
5
+ import React from "react";
6
+ import { usePartnerDashboardData } from "@/hooks/usePartnerDashboardData";
7
+ import { ENABLE_SAMPLE_DATA_CACHE } from "@/lib/dataStrategy";
8
+ import {
9
+ PENALTY_TABLE_ITEMS,
10
+ DISPUTE_CARDS,
11
+ INVOICE_CARDS,
12
+ RECENT_ACTIVITY,
13
+ REVENUE_TREND_BY_PROPERTY,
14
+ PROPERTY_LEADERBOARD,
15
+ } from "@/data/partner-hub-sample-data";
16
+ import {
17
+ BuildingOfficeIcon,
18
+ BanknotesIcon,
19
+ ExclamationTriangleIcon,
20
+ ClockIcon,
21
+ ShieldCheckIcon,
22
+ MoonIcon,
23
+ SunIcon,
24
+ UserCircleIcon,
25
+ Cog6ToothIcon,
26
+ ArrowRightOnRectangleIcon,
27
+ TrophyIcon,
28
+ StarIcon,
29
+ RocketLaunchIcon,
30
+ LightBulbIcon,
31
+ DocumentArrowDownIcon,
32
+ } from "@heroicons/react/24/outline";
33
+ import * as d3 from "d3";
34
+ import engineLogo from "@/assets/images/engine_logo.png";
35
+
36
+ /**
37
+ * Partner Hub Dashboard
38
+ *
39
+ * Partner-facing portal where hotel partners (Marriott, Hilton, etc.) log in
40
+ * to view their business relationship with Engine:
41
+ * - Their properties and performance
42
+ * - Their invoices and payments
43
+ * - Their attrition penalties and disputes
44
+ * - Their contract details
45
+ * - Communication with Engine
46
+ */
47
+ export default function PartnerHubDashboard() {
48
+ const { mode, toggle } = useThemeMode();
49
+ const [selectedPenalty, setSelectedPenalty] = React.useState(null);
50
+ const [isPenaltyModalOpen, setIsPenaltyModalOpen] = React.useState(false);
51
+ const [isPropertiesModalOpen, setIsPropertiesModalOpen] = React.useState(false);
52
+ const [isRevenueModalOpen, setIsRevenueModalOpen] = React.useState(false);
53
+ const [isReservationsModalOpen, setIsReservationsModalOpen] = React.useState(false);
54
+ const [isDisputesModalOpen, setIsDisputesModalOpen] = React.useState(false);
55
+ const [isInvoicesModalOpen, setIsInvoicesModalOpen] = React.useState(false);
56
+
57
+ // Simulated logged-in partner (in real app, this comes from auth context)
58
+ const currentPartner = {
59
+ name: "Summit Hotels & Resorts",
60
+ tier: "Gold",
61
+ logo: null, // Could add partner logo here
62
+ };
63
+
64
+ // Fetch live data from Salesforce
65
+ const { data: liveData, loading: liveLoading, error: liveError } = usePartnerDashboardData(
66
+ ENABLE_SAMPLE_DATA_CACHE ? null : null // Fetch for current partner
67
+ );
68
+
69
+ // Determine if we're loading (only in live mode)
70
+ const isLoading = !ENABLE_SAMPLE_DATA_CACHE && liveLoading;
71
+
72
+ // Show error toast if live data fails
73
+ React.useEffect(() => {
74
+ if (!ENABLE_SAMPLE_DATA_CACHE && liveError) {
75
+ toast.error(`Failed to load data: ${liveError.message}`);
76
+ console.error("Live Data Error:", liveError);
77
+ }
78
+ }, [liveError]);
79
+
80
+ // Log live data status
81
+ React.useEffect(() => {
82
+ if (!ENABLE_SAMPLE_DATA_CACHE) {
83
+ console.log("Live Data Mode - Loading:", liveLoading);
84
+ console.log("Live Data Mode - Error:", liveError);
85
+ console.log("Live Data Mode - Data:", liveData);
86
+ }
87
+ }, [liveLoading, liveError, liveData]);
88
+
89
+ // Transform live data to match sample data format
90
+ const transformLiveData = React.useCallback((liveData: any) => {
91
+ if (!liveData) return null;
92
+
93
+ // Transform penalties to match expected format
94
+ const penalties = (liveData.penalties || []).map((p: any) => ({
95
+ id: p.Id,
96
+ name: p.Name?.value || p.Name,
97
+ partner: currentPartner.name,
98
+ property: p.Property__r?.Property_Name__c?.value || p.Property__r?.Name?.value || "Unknown Property",
99
+ customer: p.Customer_Company__r?.Name?.value || "Unknown Customer",
100
+ status: p.Penalty_Status__c?.value || "Unknown",
101
+ penalty: p.Final_Penalty_Amount__c?.value || 0,
102
+ credit: p.Resale_Credit_Applied__c?.value || 0,
103
+ method: "Per Night", // From contract
104
+ isHero: p.Resale_Credit_Applied__c?.value === 0 && (p.Rooms_Resold__c?.value || 0) > 0,
105
+ originalRoomBlock: p.Original_Room_Block__c?.value || 0,
106
+ actualRoomsUsed: p.Actual_Rooms_Used__c?.value || 0,
107
+ unusedRooms: p.Unused_Rooms__c?.value || 0,
108
+ roomRate: p.Room_Rate__c?.value || 0,
109
+ numberOfNights: p.Number_of_Nights__c?.value || 0,
110
+ roomsResold: p.Rooms_Resold__c?.value || 0,
111
+ penaltyCalculated: p.Penalty_Amount_Calculated__c?.value || 0,
112
+ resalePolicy: "Partial Credit (50%)", // From contract
113
+ }));
114
+
115
+ // Transform disputes to match expected format
116
+ const disputes = (liveData.disputes || []).map((d: any) => ({
117
+ id: d.Id,
118
+ title: d.Subject?.value || "Dispute",
119
+ description: `${currentPartner.name} · ${d.Dispute_Type__c?.value || "General"}`,
120
+ status: d.Status === "Open" || d.Status === "Escalated" ? "critical" : d.Priority === "High" ? "critical" : d.Priority === "Medium" ? "warning" : "info",
121
+ badge: d.Status?.value || d.Status || "Open",
122
+ amount: d.Disputed_Amount__c?.value || 0,
123
+ agentHandled: d.Agent_Handled__c?.value || false,
124
+ }));
125
+
126
+ // Transform invoices to match expected format
127
+ const invoices = (liveData.invoices || []).filter((i: any) => {
128
+ const status = i.Invoice_Status__c?.value || i.Invoice_Status__c;
129
+ return status !== "Paid";
130
+ }).map((i: any) => ({
131
+ id: i.Id,
132
+ title: `${i.Name?.value || i.Name} — ${currentPartner.name}`,
133
+ description: `${i.Invoice_Period_Start__c?.value || ""} to ${i.Invoice_Period_End__c?.value || ""}`,
134
+ status: (i.Invoice_Status__c?.value || i.Invoice_Status__c) === "Overdue" ? "critical" : "default",
135
+ badge: i.Invoice_Status__c?.value || i.Invoice_Status__c || "Draft",
136
+ amount: i.Invoice_Total__c?.value || 0,
137
+ due: i.Due_Date__c?.value || "",
138
+ }));
139
+
140
+ // Transform activity
141
+ const activity = [
142
+ ...disputes.slice(0, 2).map((d: any) => ({
143
+ id: `dispute-${d.id}`,
144
+ title: "Dispute Created",
145
+ description: d.title,
146
+ status: "alert",
147
+ timestamp: "Recently",
148
+ partner: currentPartner.name,
149
+ })),
150
+ ...invoices.slice(0, 2).map((i: any) => ({
151
+ id: `invoice-${i.id}`,
152
+ title: "Invoice Sent",
153
+ description: `${i.title} ($${i.amount.toLocaleString()})`,
154
+ status: i.status === "critical" ? "warning" : "info",
155
+ timestamp: "Recently",
156
+ partner: currentPartner.name,
157
+ })),
158
+ ];
159
+
160
+ return {
161
+ penalties,
162
+ disputes,
163
+ invoices,
164
+ activity,
165
+ };
166
+ }, [currentPartner.name]);
167
+
168
+ // Load data - use live data if available, otherwise sample
169
+ const transformedLiveData = React.useMemo(() => {
170
+ if (!ENABLE_SAMPLE_DATA_CACHE && liveData) {
171
+ return transformLiveData(liveData);
172
+ }
173
+ return null;
174
+ }, [liveData, transformLiveData]);
175
+
176
+ const allPenalties = useDataSource({
177
+ sample: PENALTY_TABLE_ITEMS,
178
+ live: transformedLiveData?.penalties || []
179
+ });
180
+ const allDisputes = useDataSource({
181
+ sample: DISPUTE_CARDS,
182
+ live: transformedLiveData?.disputes || []
183
+ });
184
+ const allInvoices = useDataSource({
185
+ sample: INVOICE_CARDS,
186
+ live: transformedLiveData?.invoices || []
187
+ });
188
+ const allActivity = useDataSource({
189
+ sample: RECENT_ACTIVITY,
190
+ live: transformedLiveData?.activity || []
191
+ });
192
+
193
+ // Calculate revenue trend from invoices
194
+ const revenueTrendByProperty = React.useMemo(() => {
195
+ if (ENABLE_SAMPLE_DATA_CACHE) {
196
+ return REVENUE_TREND_BY_PROPERTY;
197
+ }
198
+
199
+ if (!liveData?.invoices || liveData.invoices.length === 0 || !liveData?.properties) {
200
+ return REVENUE_TREND_BY_PROPERTY;
201
+ }
202
+
203
+ // Group invoices by month and calculate totals
204
+ const monthlyData = new Map();
205
+ liveData.invoices.forEach((inv: any) => {
206
+ const periodStart = inv.Invoice_Period_Start__c?.value;
207
+ if (periodStart) {
208
+ const date = new Date(periodStart);
209
+ const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
210
+ const monthLabel = date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
211
+ const amount = inv.Invoice_Total__c?.value || 0;
212
+
213
+ if (!monthlyData.has(monthKey)) {
214
+ monthlyData.set(monthKey, { key: monthKey, label: monthLabel, total: 0 });
215
+ }
216
+ monthlyData.get(monthKey).total += amount;
217
+ }
218
+ });
219
+
220
+ // Sort by date and convert to array
221
+ const sortedMonths = Array.from(monthlyData.values())
222
+ .sort((a, b) => a.key.localeCompare(b.key));
223
+
224
+ const months = sortedMonths.map(m => m.label);
225
+ const colors = ["#7DCBD9", "#1E9D6D", "#FF6B6B", "#FFA726"];
226
+
227
+ // Distribute monthly revenue across properties (weighted)
228
+ const properties = (liveData.properties || []).map((prop: any, idx: number) => {
229
+ const weights = [0.35, 0.28, 0.22, 0.15];
230
+ const weight = weights[idx] || 0.10;
231
+
232
+ return {
233
+ name: prop.Property_Name__c?.value || prop.Name?.value || `Property ${idx + 1}`,
234
+ color: colors[idx] || "#999",
235
+ values: sortedMonths.map(month => Math.floor(month.total * weight))
236
+ };
237
+ });
238
+
239
+ return { months, properties };
240
+ }, [liveData?.invoices, liveData?.properties]);
241
+
242
+ // Calculate total revenue first (needed by leaderboard)
243
+ const myRevenue = React.useMemo(() => {
244
+ if (ENABLE_SAMPLE_DATA_CACHE) {
245
+ return 238000;
246
+ }
247
+ return (liveData?.invoices || []).reduce((sum: number, inv: any) => sum + (inv.Invoice_Total__c?.value || 0), 0);
248
+ }, [liveData?.invoices]);
249
+
250
+ // Calculate property leaderboard from real data
251
+ const propertyLeaderboard = React.useMemo(() => {
252
+ if (ENABLE_SAMPLE_DATA_CACHE) {
253
+ return PROPERTY_LEADERBOARD;
254
+ }
255
+
256
+ if (!liveData?.properties || liveData.properties.length === 0) {
257
+ return [];
258
+ }
259
+
260
+ // For demo purposes, distribute total revenue across properties
261
+ // In a real system, you'd have property-level revenue tracking
262
+ const totalRevenue = myRevenue;
263
+ const properties = liveData.properties;
264
+
265
+ return properties.map((prop: any, idx: number) => {
266
+ // Distribute revenue with weighted randomness for demo
267
+ const weights = [0.35, 0.28, 0.22, 0.15]; // First property gets most
268
+ const weight = weights[idx] || 0.10;
269
+ const propRevenue = Math.floor(totalRevenue * weight);
270
+ const latestRevenue = Math.floor(propRevenue * 0.20); // 20% in latest period
271
+ const growthRates = [60, 55, 75, 100];
272
+ const growth = growthRates[idx] || 50;
273
+
274
+ return {
275
+ name: prop.Property_Name__c?.value || prop.Name?.value || `Property ${idx + 1}`,
276
+ city: prop.City__c?.value || "",
277
+ state: prop.State__c?.value || "",
278
+ revenue: propRevenue,
279
+ latestRevenue: latestRevenue,
280
+ growth: growth,
281
+ insight: idx === 0 ? `Highest booking volume through Engine — ${Math.round(weight * 100)}% of total` :
282
+ idx === properties.length - 1 ? `Bookings doubled since October — ${growth}% growth` :
283
+ `Strong booking growth in Q1 2026`
284
+ };
285
+ }).sort((a, b) => b.revenue - a.revenue);
286
+ }, [liveData?.properties, myRevenue]);
287
+
288
+ // Filter data for current partner (sample data needs filtering, live data is already filtered)
289
+ const myPenalties = ENABLE_SAMPLE_DATA_CACHE
290
+ ? allPenalties.filter((p: any) => p.partner === currentPartner.name)
291
+ : allPenalties;
292
+ const myDisputes = ENABLE_SAMPLE_DATA_CACHE
293
+ ? allDisputes.filter((d: any) => d.description.includes(currentPartner.name))
294
+ : allDisputes;
295
+ const myInvoices = ENABLE_SAMPLE_DATA_CACHE
296
+ ? allInvoices.filter((i: any) => i.title.includes(currentPartner.name))
297
+ : allInvoices;
298
+ const myActivity = ENABLE_SAMPLE_DATA_CACHE
299
+ ? allActivity.filter((a: any) => a.partner === currentPartner.name)
300
+ : allActivity;
301
+
302
+ // Calculate partner-specific metrics
303
+ const myProperties = ENABLE_SAMPLE_DATA_CACHE
304
+ ? 4 // Sample data
305
+ : (liveData?.partner?.Total_Properties__c?.value || liveData?.properties?.length || 0);
306
+ const myReservations = ENABLE_SAMPLE_DATA_CACHE
307
+ ? 10 // Sample data
308
+ : (liveData?.partner?.Total_Reservations__c?.value || 0);
309
+ const myOpenDisputes = myDisputes.length;
310
+ const myPendingInvoices = myInvoices.length;
311
+
312
+ // Custom horizontal bar chart renderer (unused for now, but kept for future use)
313
+ const renderHorizontalBarChart = (svgEl: any, data: any, { width, height }: any, options: any = {}) => {
314
+ const margin = options.margin || { top: 10, right: 30, bottom: 30, left: 150 };
315
+ const innerWidth = width - margin.left - margin.right;
316
+ const innerHeight = height - margin.top - margin.bottom;
317
+
318
+ const svg = d3.select(svgEl);
319
+ svg.selectAll("*").remove();
320
+
321
+ const g = svg
322
+ .append("g")
323
+ .attr("transform", `translate(${margin.left},${margin.top})`);
324
+
325
+ const x = d3
326
+ .scaleLinear()
327
+ .domain([0, d3.max(data, (d) => d.value) || 0])
328
+ .range([0, innerWidth]);
329
+
330
+ const y = d3
331
+ .scaleBand()
332
+ .domain(data.map((d) => d.label))
333
+ .range([0, innerHeight])
334
+ .padding(0.2);
335
+
336
+ // Bars with gradient
337
+ const gradient = svg.append("defs")
338
+ .append("linearGradient")
339
+ .attr("id", "barGradient")
340
+ .attr("x1", "0%")
341
+ .attr("x2", "100%");
342
+
343
+ gradient.append("stop")
344
+ .attr("offset", "0%")
345
+ .attr("stop-color", "#7DCBD9");
346
+
347
+ gradient.append("stop")
348
+ .attr("offset", "100%")
349
+ .attr("stop-color", "#1E9D6D");
350
+
351
+ g.selectAll(".bar")
352
+ .data(data)
353
+ .join("rect")
354
+ .attr("class", "bar")
355
+ .attr("x", 0)
356
+ .attr("y", (d) => y(d.label) || 0)
357
+ .attr("width", (d) => x(d.value))
358
+ .attr("height", y.bandwidth())
359
+ .attr("fill", "url(#barGradient)")
360
+ .attr("rx", 4);
361
+
362
+ // Y axis
363
+ g.append("g")
364
+ .call(d3.axisLeft(y))
365
+ .selectAll("text")
366
+ .style("font-size", "12px")
367
+ .style("fill", "currentColor");
368
+
369
+ // X axis
370
+ g.append("g")
371
+ .attr("transform", `translate(0,${innerHeight})`)
372
+ .call(d3.axisBottom(x).ticks(5).tickFormat(d3.format("$~s")))
373
+ .selectAll("text")
374
+ .style("font-size", "11px")
375
+ .style("fill", "currentColor");
376
+ };
377
+
378
+ // Custom donut chart renderer (unused for now, but kept for future use)
379
+ const renderDonutChart = (svgEl: any, data: any, { width, height }: any) => {
380
+ const svg = d3.select(svgEl);
381
+ svg.selectAll("*").remove();
382
+
383
+ const radius = Math.min(width, height) / 2 - 20;
384
+ const g = svg
385
+ .append("g")
386
+ .attr("transform", `translate(${width / 2},${height / 2})`);
387
+
388
+ const pie = d3.pie().value((d) => d.value);
389
+ const arc = d3
390
+ .arc()
391
+ .innerRadius(radius * 0.6)
392
+ .outerRadius(radius);
393
+
394
+ const arcs = g
395
+ .selectAll(".arc")
396
+ .data(pie(data))
397
+ .join("g")
398
+ .attr("class", "arc");
399
+
400
+ arcs
401
+ .append("path")
402
+ .attr("d", arc)
403
+ .attr("fill", (d) => d.data.color)
404
+ .attr("stroke", "var(--color-background)")
405
+ .attr("stroke-width", 2);
406
+
407
+ // Labels
408
+ arcs
409
+ .append("text")
410
+ .attr("transform", (d) => `translate(${arc.centroid(d)})`)
411
+ .attr("text-anchor", "middle")
412
+ .attr("font-size", "14px")
413
+ .attr("font-weight", "600")
414
+ .attr("fill", "#ffffff")
415
+ .attr("paint-order", "stroke")
416
+ .attr("stroke", "rgba(0,0,0,0.3)")
417
+ .attr("stroke-width", "2px")
418
+ .text((d) => d.data.label);
419
+
420
+ arcs
421
+ .append("text")
422
+ .attr("transform", (d) => {
423
+ const [x, y] = arc.centroid(d);
424
+ return `translate(${x},${y + 16})`;
425
+ })
426
+ .attr("text-anchor", "middle")
427
+ .attr("font-size", "12px")
428
+ .attr("fill", "#ffffff")
429
+ .attr("paint-order", "stroke")
430
+ .attr("stroke", "rgba(0,0,0,0.3)")
431
+ .attr("stroke-width", "2px")
432
+ .text((d) => d.data.value);
433
+ };
434
+
435
+ // Custom area chart renderer for invoice trend (unused for now, but kept for future use)
436
+ const renderAreaChart = (svgEl: any, data: any, { width, height }: any) => {
437
+ const margin = { top: 20, right: 20, bottom: 30, left: 60 };
438
+ const innerWidth = width - margin.left - margin.right;
439
+ const innerHeight = height - margin.top - margin.bottom;
440
+
441
+ const svg = d3.select(svgEl);
442
+ svg.selectAll("*").remove();
443
+
444
+ const g = svg
445
+ .append("g")
446
+ .attr("transform", `translate(${margin.left},${margin.top})`);
447
+
448
+ const x = d3
449
+ .scaleBand()
450
+ .domain(data.map((d) => d.month))
451
+ .range([0, innerWidth])
452
+ .padding(0.1);
453
+
454
+ const y = d3
455
+ .scaleLinear()
456
+ .domain([0, d3.max(data, (d) => Math.max(d.total, d.commission)) || 0])
457
+ .range([innerHeight, 0])
458
+ .nice();
459
+
460
+ // Area generator
461
+ const areaTotal = d3
462
+ .area()
463
+ .x((d, i) => (x(d.month) || 0) + x.bandwidth() / 2)
464
+ .y0(innerHeight)
465
+ .y1((d) => y(d.total));
466
+
467
+ const areaCommission = d3
468
+ .area()
469
+ .x((d, i) => (x(d.month) || 0) + x.bandwidth() / 2)
470
+ .y0(innerHeight)
471
+ .y1((d) => y(d.commission));
472
+
473
+ // Total area
474
+ g.append("path")
475
+ .datum(data)
476
+ .attr("fill", "#7DCBD9")
477
+ .attr("fill-opacity", 0.3)
478
+ .attr("d", areaTotal);
479
+
480
+ // Commission area
481
+ g.append("path")
482
+ .datum(data)
483
+ .attr("fill", "#1E9D6D")
484
+ .attr("fill-opacity", 0.5)
485
+ .attr("d", areaCommission);
486
+
487
+ // Total line
488
+ g.append("path")
489
+ .datum(data)
490
+ .attr("fill", "none")
491
+ .attr("stroke", "#7DCBD9")
492
+ .attr("stroke-width", 2)
493
+ .attr("d", d3.line()
494
+ .x((d) => (x(d.month) || 0) + x.bandwidth() / 2)
495
+ .y((d) => y(d.total))
496
+ );
497
+
498
+ // Commission line
499
+ g.append("path")
500
+ .datum(data)
501
+ .attr("fill", "none")
502
+ .attr("stroke", "#1E9D6D")
503
+ .attr("stroke-width", 2)
504
+ .attr("d", d3.line()
505
+ .x((d) => (x(d.month) || 0) + x.bandwidth() / 2)
506
+ .y((d) => y(d.commission))
507
+ );
508
+
509
+ // X axis
510
+ g.append("g")
511
+ .attr("transform", `translate(0,${innerHeight})`)
512
+ .call(d3.axisBottom(x))
513
+ .selectAll("text")
514
+ .style("font-size", "11px")
515
+ .style("fill", "currentColor");
516
+
517
+ // Y axis
518
+ g.append("g")
519
+ .call(d3.axisLeft(y).ticks(5).tickFormat(d3.format("$~s")))
520
+ .selectAll("text")
521
+ .style("font-size", "11px")
522
+ .style("fill", "currentColor");
523
+
524
+ // Legend
525
+ const legend = svg
526
+ .append("g")
527
+ .attr("transform", `translate(${width - 180}, 10)`);
528
+
529
+ legend
530
+ .append("rect")
531
+ .attr("width", 12)
532
+ .attr("height", 12)
533
+ .attr("fill", "#7DCBD9");
534
+
535
+ legend
536
+ .append("text")
537
+ .attr("x", 18)
538
+ .attr("y", 10)
539
+ .attr("font-size", "12px")
540
+ .attr("fill", "currentColor")
541
+ .text("Total Invoiced");
542
+
543
+ legend
544
+ .append("rect")
545
+ .attr("y", 20)
546
+ .attr("width", 12)
547
+ .attr("height", 12)
548
+ .attr("fill", "#1E9D6D");
549
+
550
+ legend
551
+ .append("text")
552
+ .attr("x", 18)
553
+ .attr("y", 30)
554
+ .attr("font-size", "12px")
555
+ .attr("fill", "currentColor")
556
+ .text("Commission");
557
+ };
558
+
559
+ // Multi-line revenue trend by property renderer
560
+ const renderRevenueTrendByProperty = React.useCallback((svgEl: any, data: any, { width, height }: any) => {
561
+ if (!data || !data.months || !data.properties) {
562
+ return;
563
+ }
564
+
565
+ const { months, properties } = data;
566
+ const margin = { top: 20, right: 20, bottom: 40, left: 70 };
567
+ const innerWidth = width - margin.left - margin.right;
568
+ const innerHeight = height - margin.top - margin.bottom;
569
+
570
+ if (innerWidth <= 0 || innerHeight <= 0) {
571
+ return;
572
+ }
573
+
574
+ const svg = d3.select(svgEl);
575
+ svg.selectAll("*").remove();
576
+
577
+ const g = svg
578
+ .append("g")
579
+ .attr("transform", `translate(${margin.left},${margin.top})`);
580
+
581
+ const x = d3
582
+ .scalePoint()
583
+ .domain(months)
584
+ .range([0, innerWidth])
585
+ .padding(0.1);
586
+
587
+ const allValues = properties.flatMap((p) => p.values);
588
+ const y = d3
589
+ .scaleLinear()
590
+ .domain([0, (d3.max(allValues) || 0) * 1.1])
591
+ .range([innerHeight, 0])
592
+ .nice();
593
+
594
+ // Subtle grid lines
595
+ g.append("g")
596
+ .attr("class", "grid")
597
+ .call(
598
+ d3.axisLeft(y)
599
+ .ticks(5)
600
+ .tickSize(-innerWidth)
601
+ .tickFormat(() => "")
602
+ )
603
+ .selectAll("line")
604
+ .style("stroke", "currentColor")
605
+ .style("stroke-opacity", "0.08");
606
+ g.select(".grid .domain").remove();
607
+
608
+ // Area + line per property
609
+ properties.forEach((prop) => {
610
+ const areaGen = d3
611
+ .area<number>()
612
+ .x((_d, i) => x(months[i]) ?? 0)
613
+ .y0(innerHeight)
614
+ .y1((d) => y(d))
615
+ .curve(d3.curveMonotoneX);
616
+
617
+ const lineGen = d3
618
+ .line<number>()
619
+ .x((_d, i) => x(months[i]) ?? 0)
620
+ .y((d) => y(d))
621
+ .curve(d3.curveMonotoneX);
622
+
623
+ g.append("path")
624
+ .datum(prop.values)
625
+ .attr("fill", prop.color)
626
+ .attr("fill-opacity", 0.08)
627
+ .attr("d", areaGen);
628
+
629
+ g.append("path")
630
+ .datum(prop.values)
631
+ .attr("fill", "none")
632
+ .attr("stroke", prop.color)
633
+ .attr("stroke-width", 2.5)
634
+ .attr("d", lineGen);
635
+
636
+ // Dots at last point
637
+ const lastIdx = prop.values.length - 1;
638
+ g.append("circle")
639
+ .attr("cx", x(months[lastIdx]) ?? 0)
640
+ .attr("cy", y(prop.values[lastIdx]))
641
+ .attr("r", 4)
642
+ .attr("fill", prop.color)
643
+ .attr("stroke", "var(--color-background, #fff)")
644
+ .attr("stroke-width", 2);
645
+ });
646
+
647
+ // X axis
648
+ g.append("g")
649
+ .attr("transform", `translate(0,${innerHeight})`)
650
+ .call(d3.axisBottom(x))
651
+ .selectAll("text")
652
+ .style("font-size", "11px")
653
+ .style("fill", "currentColor")
654
+ .attr("dy", "1.2em");
655
+ g.select(".domain").style("stroke-opacity", "0.2");
656
+
657
+ // Y axis
658
+ g.append("g")
659
+ .call(d3.axisLeft(y).ticks(5).tickFormat(d3.format("$~s")))
660
+ .selectAll("text")
661
+ .style("font-size", "11px")
662
+ .style("fill", "currentColor");
663
+ }, []);
664
+
665
+ return (
666
+ <div className="heroui-scope min-h-screen bg-[#F3F3F4] dark:bg-[#0D1117] transition-colors duration-300">
667
+ {/* Header - Refined Engine Brand */}
668
+ <header className="bg-white/95 dark:bg-[#0D1117]/95 backdrop-blur-xl border-b border-[#B0B1B3]/20 dark:border-[#616368]/30 sticky top-0 z-50 shadow-sm">
669
+ <div className="max-w-[1600px] mx-auto px-8 py-5">
670
+ <div className="flex items-center justify-between">
671
+ <div className="flex items-center gap-8">
672
+ <img
673
+ src={engineLogo}
674
+ alt="Engine"
675
+ className="h-11 w-auto dark:invert dark:brightness-0 dark:contrast-100 transition-all duration-300"
676
+ />
677
+ <div className="flex items-center gap-4">
678
+ <div className="h-10 w-px bg-gradient-to-b from-transparent via-[#B0B1B3]/40 to-transparent" />
679
+ <div className="space-y-0.5">
680
+ <p className="text-sm font-semibold text-[#0D1117] dark:text-white transition-colors">
681
+ {currentPartner.name}
682
+ </p>
683
+ <div className="flex items-center gap-2">
684
+ <span className="relative flex h-2 w-2">
685
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#1E9D6D] opacity-75"></span>
686
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-[#1E9D6D]"></span>
687
+ </span>
688
+ <p className="text-xs font-medium text-[#B0B1B3] uppercase tracking-wide">
689
+ {currentPartner.tier} Partner
690
+ </p>
691
+ </div>
692
+ </div>
693
+ </div>
694
+ </div>
695
+ <div className="flex items-center gap-3">
696
+ <button
697
+ onClick={toggle}
698
+ className="group p-3 rounded-xl hover:bg-[#F3F3F4] dark:hover:bg-[#616368]/20 transition-colors duration-200"
699
+ aria-label="Toggle theme"
700
+ >
701
+ {mode === "dark" ? (
702
+ <SunIcon className="h-5 w-5 text-[#7DCBD9] transition-transform group-hover:rotate-45 duration-300" />
703
+ ) : (
704
+ <MoonIcon className="h-5 w-5 text-[#616368] transition-transform group-hover:-rotate-12 duration-300" />
705
+ )}
706
+ </button>
707
+
708
+ <Dropdown>
709
+ <Button variant="ghost" size="sm" className="p-2">
710
+ <UserCircleIcon className="h-6 w-6 text-[#616368] dark:text-[#B0B1B3]" />
711
+ </Button>
712
+ <Dropdown.Popover className="min-w-[200px]">
713
+ <Dropdown.Menu
714
+ className="p-2"
715
+ onAction={(key) => {
716
+ if (key === "settings") {
717
+ toast.info("Opening settings...");
718
+ } else if (key === "logout") {
719
+ toast.success("Logged out successfully");
720
+ }
721
+ }}
722
+ >
723
+ <Dropdown.Item
724
+ id="settings"
725
+ textValue="Settings"
726
+ className="px-3 py-2 rounded-lg hover:bg-[#F3F3F4] dark:hover:bg-[#616368]/20 cursor-pointer"
727
+ >
728
+ <div className="flex items-center gap-3">
729
+ <Cog6ToothIcon className="h-5 w-5 text-[#616368] dark:text-[#B0B1B3]" />
730
+ <span className="text-sm font-medium text-[#0D1117] dark:text-white">Settings</span>
731
+ </div>
732
+ </Dropdown.Item>
733
+ <Dropdown.Item
734
+ id="logout"
735
+ textValue="Logout"
736
+ variant="danger"
737
+ className="px-3 py-2 rounded-lg hover:bg-[#FD4B23]/10 cursor-pointer"
738
+ >
739
+ <div className="flex items-center gap-3">
740
+ <ArrowRightOnRectangleIcon className="h-5 w-5 text-[#FD4B23]" />
741
+ <span className="text-sm font-medium text-[#FD4B23]">Logout</span>
742
+ </div>
743
+ </Dropdown.Item>
744
+ </Dropdown.Menu>
745
+ </Dropdown.Popover>
746
+ </Dropdown>
747
+ </div>
748
+ </div>
749
+ </div>
750
+ </header>
751
+
752
+ {/* Hero Section - Partnership Overview */}
753
+ <div className="relative bg-gradient-to-br from-[#0D1117] via-[#1a1f2e] to-[#0D1117] dark:from-[#0D1117] dark:via-[#0D1117] dark:to-[#0a0d12] overflow-hidden">
754
+ {/* Subtle background pattern */}
755
+ <div className="absolute inset-0 opacity-5">
756
+ <div className="absolute inset-0" style={{
757
+ backgroundImage: 'radial-gradient(circle at 1px 1px, white 1px, transparent 0)',
758
+ backgroundSize: '40px 40px'
759
+ }} />
760
+ </div>
761
+
762
+ {/* Gradient orbs for depth */}
763
+ <div className="absolute top-0 right-0 w-96 h-96 bg-[#7DCBD9]/10 rounded-full blur-3xl" />
764
+ <div className="absolute bottom-0 left-0 w-96 h-96 bg-[#1E9D6D]/10 rounded-full blur-3xl" />
765
+
766
+ <div className="relative max-w-[1600px] mx-auto px-8 py-12 pb-20">
767
+ <div className="max-w-3xl space-y-3 animate-fade-in">
768
+ <h1 className="text-3xl lg:text-4xl font-bold text-white tracking-tight leading-tight">
769
+ Hey there! Here's what's happening with your properties
770
+ </h1>
771
+ <p className="text-lg text-white/70 leading-relaxed">
772
+ Everything you need to manage your partnership with Engine — invoices, bookings, and any items that need your attention.
773
+ </p>
774
+ </div>
775
+ </div>
776
+ </div>
777
+
778
+ {/* Main Content */}
779
+ <div className="max-w-[1600px] mx-auto px-8 -mt-12 space-y-10">
780
+ {/* Quick Stats - Uniform metrics grid */}
781
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-5 animate-slide-up relative z-10">
782
+ {/* Revenue */}
783
+ <div
784
+ onClick={() => !isLoading && setIsRevenueModalOpen(true)}
785
+ className={isLoading ? "" : "cursor-pointer"}
786
+ >
787
+ <div className="bg-white dark:bg-[#0D1117] rounded-xl p-6 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#B0B1B3]/20 dark:border-[#616368]/30 h-full">
788
+ {isLoading ? (
789
+ <div className="space-y-3">
790
+ <div className="flex items-center justify-between">
791
+ <div className="bg-[#B0B1B3]/20 rounded-lg h-9 w-9 animate-pulse"></div>
792
+ <div className="bg-[#B0B1B3]/20 rounded-full h-5 w-16 animate-pulse"></div>
793
+ </div>
794
+ <div className="h-3 w-24 bg-[#B0B1B3]/20 rounded animate-pulse"></div>
795
+ <div className="h-10 w-32 bg-[#B0B1B3]/20 rounded animate-pulse"></div>
796
+ <div className="h-3 w-28 bg-[#B0B1B3]/20 rounded animate-pulse"></div>
797
+ </div>
798
+ ) : (
799
+ <>
800
+ <div className="flex items-center justify-between mb-3">
801
+ <div className="bg-[#1E9D6D]/10 rounded-lg p-2">
802
+ <BanknotesIcon className="h-5 w-5 text-[#1E9D6D]" />
803
+ </div>
804
+ <span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-bold bg-[#1E9D6D]/10 text-[#1E9D6D]">
805
+ +45%
806
+ </span>
807
+ </div>
808
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xs font-semibold mb-2 uppercase tracking-wider">Total Revenue</p>
809
+ <p className="text-4xl font-black text-[#0D1117] dark:text-white mb-1">${(myRevenue / 1000).toFixed(0)}K</p>
810
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">earned with Engine</p>
811
+ </>
812
+ )}
813
+ </div>
814
+ </div>
815
+
816
+ {/* Items to Review */}
817
+ <div
818
+ onClick={() => !isLoading && setIsDisputesModalOpen(true)}
819
+ className={isLoading ? "" : "cursor-pointer"}
820
+ >
821
+ <div className="bg-white dark:bg-[#0D1117] rounded-xl p-6 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#FFB200]/50 dark:border-[#FFB200]/30 h-full">
822
+ {isLoading ? (
823
+ <CardSkeleton lines={4} />
824
+ ) : (
825
+ <>
826
+ <div className="flex items-center justify-between mb-3">
827
+ <div className="bg-[#FFB200]/10 rounded-lg p-2">
828
+ <ExclamationTriangleIcon className="h-5 w-5 text-[#FFB200]" />
829
+ </div>
830
+ <span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-bold bg-[#FFB200] text-white">
831
+ REVIEW
832
+ </span>
833
+ </div>
834
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xs font-semibold mb-2 uppercase tracking-wider">Things to Review</p>
835
+ <p className="text-4xl font-black text-[#0D1117] dark:text-white mb-1">{myOpenDisputes}</p>
836
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">items need attention</p>
837
+ </>
838
+ )}
839
+ </div>
840
+ </div>
841
+
842
+ {/* Properties */}
843
+ <div
844
+ onClick={() => !isLoading && setIsPropertiesModalOpen(true)}
845
+ className={isLoading ? "" : "cursor-pointer"}
846
+ >
847
+ <div className="bg-white dark:bg-[#0D1117] rounded-xl p-6 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#B0B1B3]/20 dark:border-[#616368]/30 h-full">
848
+ {isLoading ? (
849
+ <CardSkeleton lines={4} />
850
+ ) : (
851
+ <>
852
+ <div className="flex items-center justify-between mb-3">
853
+ <div className="bg-[#7DCBD9]/10 rounded-lg p-2">
854
+ <BuildingOfficeIcon className="h-5 w-5 text-[#7DCBD9]" />
855
+ </div>
856
+ </div>
857
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xs font-semibold mb-2 uppercase tracking-wider">Properties</p>
858
+ <p className="text-4xl font-black text-[#0D1117] dark:text-white mb-1">{myProperties}</p>
859
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">active locations</p>
860
+ </>
861
+ )}
862
+ </div>
863
+ </div>
864
+
865
+ {/* Reservations */}
866
+ <div
867
+ onClick={() => !isLoading && setIsReservationsModalOpen(true)}
868
+ className={isLoading ? "" : "cursor-pointer"}
869
+ >
870
+ <div className="bg-white dark:bg-[#0D1117] rounded-xl p-6 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#B0B1B3]/20 dark:border-[#616368]/30 h-full">
871
+ {isLoading ? (
872
+ <CardSkeleton lines={4} />
873
+ ) : (
874
+ <>
875
+ <div className="flex items-center justify-between mb-3">
876
+ <div className="bg-[#157DE5]/10 rounded-lg p-2">
877
+ <ClockIcon className="h-5 w-5 text-[#157DE5]" />
878
+ </div>
879
+ </div>
880
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xs font-semibold mb-2 uppercase tracking-wider">Reservations</p>
881
+ <p className="text-4xl font-black text-[#0D1117] dark:text-white mb-1">{myReservations}</p>
882
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">through Engine</p>
883
+ </>
884
+ )}
885
+ </div>
886
+ </div>
887
+
888
+ {/* Invoices */}
889
+ <div
890
+ onClick={() => !isLoading && setIsInvoicesModalOpen(true)}
891
+ className={isLoading ? "" : "cursor-pointer"}
892
+ >
893
+ <div className="bg-white dark:bg-[#0D1117] rounded-xl p-6 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#B0B1B3]/20 dark:border-[#616368]/30 h-full">
894
+ {isLoading ? (
895
+ <CardSkeleton lines={4} />
896
+ ) : (
897
+ <>
898
+ <div className="flex items-center justify-between mb-3">
899
+ <div className="bg-[#FD4B23]/10 rounded-lg p-2">
900
+ <ShieldCheckIcon className="h-5 w-5 text-[#FD4B23]" />
901
+ </div>
902
+ </div>
903
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xs font-semibold mb-2 uppercase tracking-wider">Invoices</p>
904
+ <p className="text-4xl font-black text-[#0D1117] dark:text-white mb-1">{myPendingInvoices}</p>
905
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">ready to pay</p>
906
+ </>
907
+ )}
908
+ </div>
909
+ </div>
910
+ </div>
911
+
912
+ {/* Property Leaderboard - NEW SECTION */}
913
+ {isLoading ? (
914
+ <div className="bg-gradient-to-br from-white via-[#F3F3F4] to-white dark:from-[#0D1117] dark:via-[#1a1f2e] dark:to-[#0D1117] rounded-2xl p-8 shadow-xl border border-[#B0B1B3]/20 dark:border-[#616368]/30">
915
+ <div className="mb-6">
916
+ <div className="h-9 w-96 bg-[#B0B1B3]/20 rounded animate-pulse mb-2"></div>
917
+ <div className="h-6 w-72 bg-[#B0B1B3]/20 rounded animate-pulse"></div>
918
+ </div>
919
+ <div className="space-y-4">
920
+ <CardSkeleton lines={4} />
921
+ <CardSkeleton lines={4} />
922
+ <CardSkeleton lines={4} />
923
+ <CardSkeleton lines={4} />
924
+ </div>
925
+ </div>
926
+ ) : (
927
+ <div className="bg-gradient-to-br from-white via-[#F3F3F4] to-white dark:from-[#0D1117] dark:via-[#1a1f2e] dark:to-[#0D1117] rounded-2xl p-8 shadow-xl border border-[#B0B1B3]/20 dark:border-[#616368]/30">
928
+ <div className="mb-6">
929
+ <h2 className="text-3xl font-black text-[#0D1117] dark:text-white tracking-tight mb-2">
930
+ Property Performance Leaderboard
931
+ </h2>
932
+ <p className="text-lg text-[#616368] dark:text-[#B0B1B3]">
933
+ Your 4 properties ranked by total revenue (last 6 months)
934
+ </p>
935
+ </div>
936
+
937
+ <div className="space-y-4">
938
+ {propertyLeaderboard.map((property, idx) => {
939
+ // Determine icon and color based on rank
940
+ let RankIcon = StarIcon;
941
+ let iconColor = "text-[#B0B1B3]";
942
+ let bgColor = "bg-[#B0B1B3]/10";
943
+
944
+ if (idx === 0) {
945
+ RankIcon = TrophyIcon;
946
+ iconColor = "text-[#FFB200]";
947
+ bgColor = "bg-[#FFB200]/10";
948
+ } else if (idx === 1) {
949
+ RankIcon = StarIcon;
950
+ iconColor = "text-[#90A4AE]";
951
+ bgColor = "bg-[#90A4AE]/10";
952
+ } else if (idx === 2) {
953
+ RankIcon = StarIcon;
954
+ iconColor = "text-[#CD7F32]";
955
+ bgColor = "bg-[#CD7F32]/10";
956
+ } else {
957
+ RankIcon = RocketLaunchIcon;
958
+ iconColor = "text-[#7DCBD9]";
959
+ bgColor = "bg-[#7DCBD9]/10";
960
+ }
961
+
962
+ return (
963
+ <div
964
+ key={idx}
965
+ className="group bg-white dark:bg-[#0D1117] rounded-xl p-6 border-2 border-[#B0B1B3]/20 dark:border-[#616368]/30 hover:border-[#1E9D6D] hover:shadow-lg transition-all duration-300"
966
+ >
967
+ <div className="flex items-center justify-between gap-6">
968
+ <div className="flex items-center gap-4 flex-1">
969
+ {/* Rank Icon */}
970
+ <div className={`flex-shrink-0 ${bgColor} rounded-xl p-3`}>
971
+ <RankIcon className={`h-8 w-8 ${iconColor}`} />
972
+ </div>
973
+
974
+ {/* Property info */}
975
+ <div className="flex-1">
976
+ <div className="flex items-center gap-3 mb-2">
977
+ <h3 className="text-xl font-bold text-[#0D1117] dark:text-white">
978
+ {property.name}
979
+ </h3>
980
+ <span className="inline-flex items-center rounded-full px-3 py-1 text-xs font-bold bg-[#1E9D6D]/20 text-[#1E9D6D] border border-[#1E9D6D]/30">
981
+ +{property.growth}% growth
982
+ </span>
983
+ </div>
984
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-1">
985
+ {property.insight}
986
+ </p>
987
+ <div className="flex items-center gap-6 text-sm">
988
+ <span className="text-[#616368] dark:text-[#B0B1B3]">
989
+ Total: <strong className="text-[#0D1117] dark:text-white">${property.revenue.toLocaleString()}</strong>
990
+ </span>
991
+ <span className="text-[#616368] dark:text-[#B0B1B3]">
992
+ Latest: <strong className="text-[#0D1117] dark:text-white">${property.latestRevenue.toLocaleString()}</strong>
993
+ </span>
994
+ </div>
995
+ </div>
996
+
997
+ {/* Growth indicator */}
998
+ <div className="text-right flex-shrink-0">
999
+ <p className="text-4xl font-black text-[#1E9D6D] mb-1">
1000
+ {property.growth}%
1001
+ </p>
1002
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1003
+ Growth
1004
+ </p>
1005
+ </div>
1006
+ </div>
1007
+ </div>
1008
+ </div>
1009
+ );
1010
+ })}
1011
+ </div>
1012
+
1013
+ {/* Summary insight */}
1014
+ <div className="mt-6 bg-[#157DE5]/10 border-l-4 border-[#157DE5] rounded-r-lg p-5">
1015
+ <div className="flex items-start gap-3">
1016
+ <div className="flex-shrink-0">
1017
+ <LightBulbIcon className="h-5 w-5 text-[#157DE5]" />
1018
+ </div>
1019
+ <p className="text-base text-[#0D1117] dark:text-white font-medium">
1020
+ <strong>Key Insight:</strong> Austin is driving 41% of bookings through Engine and growing 60%.
1021
+ SF Bay doubled in 6 months (100% growth) — strong performance across your portfolio.
1022
+ </p>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+ )}
1027
+
1028
+ {/* Penalty Calculation Issue Card */}
1029
+ {!isLoading && myPenalties.filter((p: any) => p.isHero).length > 0 && (
1030
+ <div className="bg-white dark:bg-[#0D1117] border-2 border-[#FFB200]/50 rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow duration-300">
1031
+ <div className="flex items-start gap-4 mb-6">
1032
+ <div className="flex-shrink-0">
1033
+ <div className="bg-[#FFB200]/10 rounded-xl p-3">
1034
+ <ExclamationTriangleIcon className="h-8 w-8 text-[#FFB200]" />
1035
+ </div>
1036
+ </div>
1037
+ <div className="flex-1">
1038
+ <div className="flex items-start justify-between gap-4 mb-2">
1039
+ <div>
1040
+ <h3 className="text-2xl font-bold text-[#0D1117] dark:text-white mb-1">
1041
+ Penalty Calculation Issue
1042
+ </h3>
1043
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3]">
1044
+ ATR-00001 · Summit Austin Convention Center · TechCorp Inc. booking
1045
+ </p>
1046
+ </div>
1047
+ <span className="inline-flex items-center rounded-full px-3 py-1 text-xs font-bold bg-[#FFB200] text-white flex-shrink-0">
1048
+ NEEDS REVIEW
1049
+ </span>
1050
+ </div>
1051
+ </div>
1052
+ </div>
1053
+
1054
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
1055
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-5 border border-[#B0B1B3]/20 dark:border-[#616368]/30">
1056
+ <p className="text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Current Penalty</p>
1057
+ <p className="text-3xl font-black text-[#0D1117] dark:text-white mb-1">$9,000</p>
1058
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">You owe Engine</p>
1059
+ </div>
1060
+ <div className="bg-[#1E9D6D]/5 dark:bg-[#1E9D6D]/10 rounded-xl p-5 border border-[#1E9D6D]/30">
1061
+ <p className="text-xs font-semibold text-[#1E9D6D] mb-2 uppercase tracking-wider">Resale Credit Missing</p>
1062
+ <p className="text-3xl font-black text-[#1E9D6D] mb-1">-$2,400</p>
1063
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">Should reduce penalty</p>
1064
+ </div>
1065
+ <div className="bg-[#7DCBD9]/5 dark:bg-[#7DCBD9]/10 rounded-xl p-5 border border-[#7DCBD9]/30">
1066
+ <p className="text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Corrected Amount</p>
1067
+ <p className="text-3xl font-black text-[#7DCBD9] mb-1">$6,600</p>
1068
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3]">What you should owe</p>
1069
+ </div>
1070
+ </div>
1071
+
1072
+ <div className="bg-[#FFB200]/10 border-l-4 border-[#FFB200] rounded-r-lg p-4 mb-6">
1073
+ <p className="text-sm text-[#0D1117] dark:text-white leading-relaxed">
1074
+ Your contract specifies a <strong>50% resale credit</strong> for rooms that were resold. Based on <strong>8 rooms at $200/night for 3 nights</strong>,
1075
+ you should receive a <strong className="text-[#1E9D6D]">$2,400 credit</strong> that wasn't applied. This should reduce your penalty from $9,000 to <strong>$6,600</strong>.
1076
+ </p>
1077
+ </div>
1078
+
1079
+ <button
1080
+ onClick={() => {
1081
+ setSelectedPenalty(myPenalties.find((p: any) => p.isHero));
1082
+ setIsPenaltyModalOpen(true);
1083
+ }}
1084
+ className="inline-flex items-center gap-2 px-6 py-3 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-xl transition-all duration-200 hover:shadow-lg group"
1085
+ >
1086
+ <ExclamationTriangleIcon className="h-5 w-5" />
1087
+ Review This Calculation
1088
+ <svg className="h-4 w-4 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1089
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
1090
+ </svg>
1091
+ </button>
1092
+ </div>
1093
+ )}
1094
+
1095
+ {/* Section - Conversational with Better Spacing */}
1096
+ <div className="pt-8 space-y-2">
1097
+ <h2 className="text-3xl font-bold text-[#0D1117] dark:text-white tracking-tight">
1098
+ Recent activity
1099
+ </h2>
1100
+ <p className="text-lg text-[#616368] dark:text-[#B0B1B3]">
1101
+ Here's what's been happening with your properties
1102
+ </p>
1103
+ </div>
1104
+
1105
+ {/* Hero penalty section removed - now at top of page */}
1106
+
1107
+ {/* Action Items Grid */}
1108
+ {isLoading ? (
1109
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
1110
+ <CardSkeleton lines={4} />
1111
+ <CardSkeleton lines={4} />
1112
+ </div>
1113
+ ) : myDisputes.length > 0 ? (
1114
+ <div className="space-y-5">
1115
+ <h3 className="text-xl font-bold text-[#0D1117] dark:text-white">
1116
+ Items that need your attention
1117
+ </h3>
1118
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
1119
+ {myDisputes.map((d) => (
1120
+ <div
1121
+ key={d.id}
1122
+ onClick={() => {
1123
+ toast.info(`Opening ${d.title}`);
1124
+ setSelectedDispute(d);
1125
+ }}
1126
+ className="group bg-white dark:bg-[#0D1117] border border-[#B0B1B3]/20 dark:border-[#616368]/30 rounded-xl p-6 hover:border-[#7DCBD9] dark:hover:border-[#7DCBD9] transition-all duration-300 hover:shadow-lg cursor-pointer"
1127
+ >
1128
+ <div className="flex items-start justify-between gap-3 mb-3">
1129
+ <h4 className="font-semibold text-[#0D1117] dark:text-white flex-1">
1130
+ {d.title}
1131
+ </h4>
1132
+ <span
1133
+ className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium flex-shrink-0 ${
1134
+ d.status === "critical"
1135
+ ? "bg-[#FD4B23]/10 text-[#FD4B23] border border-[#FD4B23]/30"
1136
+ : d.status === "warning"
1137
+ ? "bg-[#FFB200]/10 text-[#FFB200] border border-[#FFB200]/30"
1138
+ : "bg-[#157DE5]/10 text-[#157DE5] border border-[#157DE5]/30"
1139
+ }`}
1140
+ >
1141
+ {d.badge}
1142
+ </span>
1143
+ </div>
1144
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-4">
1145
+ {d.description}
1146
+ </p>
1147
+ <div className="flex items-center justify-between">
1148
+ <span className="text-lg font-bold text-[#0D1117] dark:text-white">
1149
+ ${d.amount.toLocaleString()}
1150
+ </span>
1151
+ {d.agentHandled && (
1152
+ <span className="text-xs text-[#7DCBD9] flex items-center gap-1">
1153
+ <svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
1154
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
1155
+ </svg>
1156
+ Agent reviewed
1157
+ </span>
1158
+ )}
1159
+ </div>
1160
+ </div>
1161
+ ))}
1162
+ </div>
1163
+ </div>
1164
+ ) : null}
1165
+
1166
+ {/* All Penalties - Enhanced Table */}
1167
+ {isLoading ? (
1168
+ <CardSkeleton lines={8} />
1169
+ ) : (
1170
+ <div className="bg-white dark:bg-[#0D1117] border border-[#B0B1B3]/20 dark:border-[#616368]/30 rounded-xl overflow-hidden shadow-sm hover:shadow-lg transition-shadow duration-300">
1171
+ <div className="p-8 border-b border-[#B0B1B3]/20 dark:border-[#616368]/30">
1172
+ <h3 className="text-2xl font-bold text-[#0D1117] dark:text-white mb-2">
1173
+ All attrition penalties
1174
+ </h3>
1175
+ <p className="text-base text-[#616368] dark:text-[#B0B1B3]">
1176
+ {myPenalties.length} {myPenalties.length === 1 ? 'penalty' : 'penalties'} · Showing recent calculations and adjustments
1177
+ </p>
1178
+ </div>
1179
+ <div className="overflow-x-auto">
1180
+ <table className="w-full">
1181
+ <thead className="bg-[#F3F3F4] dark:bg-[#616368]/10 border-b border-[#B0B1B3]/20 dark:border-[#616368]/30">
1182
+ <tr>
1183
+ <th className="px-6 py-3 text-left text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1184
+ Penalty ID
1185
+ </th>
1186
+ <th className="px-6 py-3 text-left text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1187
+ Property
1188
+ </th>
1189
+ <th className="px-6 py-3 text-left text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1190
+ Customer
1191
+ </th>
1192
+ <th className="px-6 py-3 text-left text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1193
+ Method
1194
+ </th>
1195
+ <th className="px-6 py-3 text-right text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1196
+ Penalty
1197
+ </th>
1198
+ <th className="px-6 py-3 text-right text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1199
+ Credit
1200
+ </th>
1201
+ <th className="px-6 py-3 text-center text-xs font-semibold text-[#616368] dark:text-[#B0B1B3] uppercase tracking-wider">
1202
+ Status
1203
+ </th>
1204
+ </tr>
1205
+ </thead>
1206
+ <tbody className="divide-y divide-[#B0B1B3]/20 dark:divide-[#616368]/30">
1207
+ {myPenalties.map((penalty) => (
1208
+ <tr
1209
+ key={penalty.id}
1210
+ onClick={() => {
1211
+ setSelectedPenalty(penalty);
1212
+ setIsPenaltyModalOpen(true);
1213
+ }}
1214
+ className={`hover:bg-[#F3F3F4]/50 dark:hover:bg-[#616368]/5 transition-colors cursor-pointer ${
1215
+ penalty.isHero ? "bg-[#FFB200]/5" : ""
1216
+ }`}
1217
+ >
1218
+ <td className="px-6 py-4 whitespace-nowrap">
1219
+ <div className="flex items-center gap-2">
1220
+ <span className="text-sm font-medium text-[#0D1117] dark:text-white">
1221
+ {penalty.name}
1222
+ </span>
1223
+ {penalty.isHero && (
1224
+ <span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-[#FFB200]/20 text-[#FFB200] border border-[#FFB200]/30">
1225
+ Review
1226
+ </span>
1227
+ )}
1228
+ </div>
1229
+ </td>
1230
+ <td className="px-6 py-4 text-sm text-[#616368] dark:text-[#B0B1B3]">
1231
+ {penalty.property}
1232
+ </td>
1233
+ <td className="px-6 py-4 text-sm text-[#616368] dark:text-[#B0B1B3]">
1234
+ {penalty.customer}
1235
+ </td>
1236
+ <td className="px-6 py-4 text-sm text-[#616368] dark:text-[#B0B1B3]">
1237
+ {penalty.method}
1238
+ </td>
1239
+ <td className="px-6 py-4 text-sm font-semibold text-right text-[#0D1117] dark:text-white">
1240
+ ${penalty.penalty.toLocaleString()}
1241
+ </td>
1242
+ <td className="px-6 py-4 text-sm font-semibold text-right text-[#1E9D6D]">
1243
+ ${penalty.credit.toLocaleString()}
1244
+ </td>
1245
+ <td className="px-6 py-4 text-center">
1246
+ <span
1247
+ className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium ${
1248
+ penalty.status === "Approved"
1249
+ ? "bg-[#1E9D6D]/10 text-[#1E9D6D] border border-[#1E9D6D]/30"
1250
+ : penalty.status === "Reviewed"
1251
+ ? "bg-[#157DE5]/10 text-[#157DE5] border border-[#157DE5]/30"
1252
+ : penalty.status === "Calculated"
1253
+ ? "bg-[#B0B1B3]/10 text-[#616368] border border-[#B0B1B3]/30"
1254
+ : "bg-[#FD4B23]/10 text-[#FD4B23] border border-[#FD4B23]/30"
1255
+ }`}
1256
+ >
1257
+ {penalty.status}
1258
+ </span>
1259
+ </td>
1260
+ </tr>
1261
+ ))}
1262
+ </tbody>
1263
+ </table>
1264
+ </div>
1265
+ </div>
1266
+ )}
1267
+
1268
+ {/* Section */}
1269
+ <div className="pt-8 space-y-2">
1270
+ <h2 className="text-3xl font-bold text-[#0D1117] dark:text-white tracking-tight">
1271
+ Billing & contract details
1272
+ </h2>
1273
+ <p className="text-lg text-[#616368] dark:text-[#B0B1B3]">
1274
+ Keep track of invoices and your partnership terms
1275
+ </p>
1276
+ </div>
1277
+
1278
+ {/* Revenue Trend by Property */}
1279
+ {isLoading ? (
1280
+ <CardSkeleton lines={6} />
1281
+ ) : (
1282
+ <div className="bg-white dark:bg-[#0D1117] border border-[#B0B1B3]/20 dark:border-[#616368]/30 rounded-xl shadow-sm hover:shadow-lg transition-shadow duration-300 overflow-hidden">
1283
+ <div className="p-8 border-b border-[#B0B1B3]/20 dark:border-[#616368]/30">
1284
+ <div className="flex items-start justify-between gap-4">
1285
+ <div>
1286
+ <h3 className="text-2xl font-bold text-[#0D1117] dark:text-white mb-1">
1287
+ Revenue trend by property
1288
+ </h3>
1289
+ <p className="text-base text-[#616368] dark:text-[#B0B1B3]">
1290
+ Monthly revenue earned through Engine across your {revenueTrendByProperty.properties?.length || 4} properties
1291
+ </p>
1292
+ </div>
1293
+ </div>
1294
+ {/* Legend */}
1295
+ <div className="flex flex-wrap gap-4 mt-5">
1296
+ {revenueTrendByProperty.properties.map((prop) => (
1297
+ <div key={prop.name} className="flex items-center gap-2">
1298
+ <span
1299
+ className="inline-block h-2.5 w-2.5 rounded-full flex-shrink-0"
1300
+ style={{ backgroundColor: prop.color }}
1301
+ />
1302
+ <span className="text-xs font-medium text-[#616368] dark:text-[#B0B1B3]">
1303
+ {prop.name}
1304
+ </span>
1305
+ </div>
1306
+ ))}
1307
+ </div>
1308
+ </div>
1309
+ <div className="p-6 w-full">
1310
+ {revenueTrendByProperty && revenueTrendByProperty.months && revenueTrendByProperty.properties ? (
1311
+ <div className="w-full" style={{ minHeight: '260px' }}>
1312
+ <D3Chart
1313
+ data={revenueTrendByProperty}
1314
+ renderChart={renderRevenueTrendByProperty}
1315
+ height={260}
1316
+ responsive={true}
1317
+ ariaLabel="Revenue trend by property"
1318
+ />
1319
+ </div>
1320
+ ) : (
1321
+ <div className="h-[260px] flex items-center justify-center text-[#616368] dark:text-[#B0B1B3]">
1322
+ Loading chart data...
1323
+ </div>
1324
+ )}
1325
+ </div>
1326
+ </div>
1327
+ )}
1328
+
1329
+ {/* Invoices & Contract */}
1330
+ {isLoading ? (
1331
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
1332
+ <CardSkeleton lines={5} />
1333
+ <CardSkeleton lines={5} />
1334
+ </div>
1335
+ ) : (
1336
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
1337
+ <ListCard
1338
+ title="Your invoices"
1339
+ subtitle="Recent statements"
1340
+ items={myInvoices.map((inv) => ({
1341
+ id: inv.id,
1342
+ title: inv.title,
1343
+ description: inv.description,
1344
+ status: inv.status,
1345
+ value: `$${inv.amount.toLocaleString()}`,
1346
+ timestamp: `Due ${inv.due}`,
1347
+ }))}
1348
+ maxBodyHeight={300}
1349
+ showStatus={true}
1350
+ showTimestamp={true}
1351
+ dense={false}
1352
+ divided={true}
1353
+ onItemClick={(item) => {
1354
+ toast.info(`Opening invoice ${item.title.split(' — ')[0]}`);
1355
+ }}
1356
+ emptyMessage="No invoices right now."
1357
+ loading={isLoading}
1358
+ />
1359
+
1360
+ <div className="bg-white dark:bg-[#0D1117] border border-[#B0B1B3]/20 dark:border-[#616368]/30 rounded-xl shadow-sm hover:shadow-lg transition-shadow duration-300 overflow-hidden">
1361
+ <div className="p-6 border-b border-[#B0B1B3]/20 dark:border-[#616368]/30">
1362
+ <h3 className="text-xl font-bold text-[#0D1117] dark:text-white mb-1">
1363
+ Your Contract
1364
+ </h3>
1365
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3]">
1366
+ Partnership terms
1367
+ </p>
1368
+ </div>
1369
+ <div className="p-6 space-y-4">
1370
+ <div className="flex items-center justify-between pb-4 border-b border-[#B0B1B3]/20 dark:border-[#616368]/30">
1371
+ <div>
1372
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-1 uppercase tracking-wider">Contract ID</p>
1373
+ <p className="text-lg font-bold text-[#0D1117] dark:text-white">CNTR-00002</p>
1374
+ </div>
1375
+ <span className="inline-flex items-center rounded-full px-3 py-1 text-xs font-medium bg-[#1E9D6D]/10 text-[#1E9D6D] border border-[#1E9D6D]/30">
1376
+ Active
1377
+ </span>
1378
+ </div>
1379
+
1380
+ <div className="grid grid-cols-2 gap-4">
1381
+ <div>
1382
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-1 uppercase tracking-wider">Commission Rate</p>
1383
+ <p className="text-2xl font-bold text-[#1E9D6D]">17%</p>
1384
+ </div>
1385
+ <div>
1386
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-1 uppercase tracking-wider">Contract Term</p>
1387
+ <p className="text-sm font-semibold text-[#0D1117] dark:text-white">Mar 2025 - Feb 2027</p>
1388
+ </div>
1389
+ </div>
1390
+
1391
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-lg p-4 space-y-3">
1392
+ <div>
1393
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-1 uppercase tracking-wider">Attrition Method</p>
1394
+ <p className="text-sm font-semibold text-[#0D1117] dark:text-white">Per Night (80% threshold)</p>
1395
+ </div>
1396
+ <div>
1397
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-1 uppercase tracking-wider">Resale Credit Policy</p>
1398
+ <p className="text-sm font-semibold text-[#0D1117] dark:text-white">Partial Credit (50%)</p>
1399
+ </div>
1400
+ </div>
1401
+
1402
+ <div className="pt-2">
1403
+ <button
1404
+ onClick={() => toast.info("Opening full contract PDF...")}
1405
+ className="w-full inline-flex items-center justify-center gap-2 px-4 py-3 bg-[#F3F3F4] dark:bg-[#616368]/20 hover:bg-[#7DCBD9]/10 dark:hover:bg-[#7DCBD9]/10 text-[#0D1117] dark:text-white font-semibold rounded-lg border border-[#B0B1B3]/30 dark:border-[#616368]/50 hover:border-[#7DCBD9] transition-colors text-sm"
1406
+ >
1407
+ <DocumentArrowDownIcon className="h-4 w-4" />
1408
+ Download Full Contract
1409
+ </button>
1410
+ </div>
1411
+ </div>
1412
+ </div>
1413
+ </div>
1414
+ )}
1415
+
1416
+ {/* Section */}
1417
+ <div className="pt-8 space-y-2">
1418
+ <h2 className="text-3xl font-bold text-[#0D1117] dark:text-white tracking-tight">
1419
+ What's been happening
1420
+ </h2>
1421
+ <p className="text-lg text-[#616368] dark:text-[#B0B1B3]">
1422
+ A quick timeline of recent updates
1423
+ </p>
1424
+ </div>
1425
+
1426
+ {/* Activity Feed */}
1427
+ {isLoading ? (
1428
+ <CardSkeleton lines={6} />
1429
+ ) : (
1430
+ <ActivityCard
1431
+ title="Recent updates"
1432
+ actions={myActivity.map((a) => ({
1433
+ id: a.id,
1434
+ status:
1435
+ a.status === "alert"
1436
+ ? "error"
1437
+ : a.status === "warning"
1438
+ ? "pending"
1439
+ : a.status === "success"
1440
+ ? "complete"
1441
+ : "working",
1442
+ title: a.title,
1443
+ subtitle: a.description,
1444
+ timestamp: a.timestamp,
1445
+ }))}
1446
+ />
1447
+ )}
1448
+
1449
+ {/* Help Section - Enhanced */}
1450
+ <div className="bg-gradient-to-br from-white to-[#F3F3F4] dark:from-[#0D1117] dark:to-[#1a1f2e] rounded-2xl p-12 border border-[#B0B1B3]/20 dark:border-[#616368]/30 shadow-lg">
1451
+ <div className="text-center max-w-2xl mx-auto space-y-6">
1452
+ <h3 className="text-3xl font-bold text-[#0D1117] dark:text-white tracking-tight">
1453
+ Questions? We're here to help
1454
+ </h3>
1455
+ <p className="text-[#616368] dark:text-[#B0B1B3] text-xl leading-relaxed">
1456
+ Our partner team is standing by if you need anything — from billing questions to contract details.
1457
+ </p>
1458
+ <div className="flex gap-4 justify-center flex-wrap pt-2">
1459
+ <Modal>
1460
+ <button className="group px-8 py-4 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-xl transition-colors duration-200 hover:shadow-xl">
1461
+ Get in touch
1462
+ </button>
1463
+ <Modal.Backdrop>
1464
+ <Modal.Container>
1465
+ <Modal.Dialog className="max-w-2xl">
1466
+ <Modal.CloseTrigger />
1467
+ <Modal.Header>
1468
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1469
+ Contact Support
1470
+ </Modal.Heading>
1471
+ </Modal.Header>
1472
+ <Modal.Body className="space-y-6">
1473
+ <p className="text-[#616368] dark:text-[#B0B1B3]">
1474
+ Our partner support team is here to help with billing questions, contract details, or any other issues you may have.
1475
+ </p>
1476
+
1477
+ {/* Contact Options */}
1478
+ <div className="space-y-4">
1479
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-6 hover:border hover:border-[#7DCBD9] transition-colors cursor-pointer">
1480
+ <div className="flex items-start gap-4">
1481
+ <div className="bg-[#7DCBD9]/20 rounded-lg p-3">
1482
+ <svg className="h-6 w-6 text-[#7DCBD9]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1483
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
1484
+ </svg>
1485
+ </div>
1486
+ <div>
1487
+ <h4 className="font-semibold text-[#0D1117] dark:text-white mb-1">Email Support</h4>
1488
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">
1489
+ Get help via email - we typically respond within 2-4 hours
1490
+ </p>
1491
+ <a href="mailto:partners@engine.com" className="text-sm font-medium text-[#7DCBD9] hover:underline">
1492
+ partners@engine.com
1493
+ </a>
1494
+ </div>
1495
+ </div>
1496
+ </div>
1497
+
1498
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-6 hover:border hover:border-[#7DCBD9] transition-colors cursor-pointer">
1499
+ <div className="flex items-start gap-4">
1500
+ <div className="bg-[#1E9D6D]/20 rounded-lg p-3">
1501
+ <svg className="h-6 w-6 text-[#1E9D6D]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1502
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
1503
+ </svg>
1504
+ </div>
1505
+ <div>
1506
+ <h4 className="font-semibold text-[#0D1117] dark:text-white mb-1">Phone Support</h4>
1507
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">
1508
+ Speak with a partner specialist directly
1509
+ </p>
1510
+ <a href="tel:+18005551234" className="text-sm font-medium text-[#1E9D6D] hover:underline">
1511
+ 1-800-555-1234
1512
+ </a>
1513
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mt-1">
1514
+ Mon-Fri, 8am-6pm EST
1515
+ </p>
1516
+ </div>
1517
+ </div>
1518
+ </div>
1519
+ </div>
1520
+ </Modal.Body>
1521
+ <Modal.Footer>
1522
+ <div className="flex gap-3 justify-end">
1523
+ <Modal.CloseTrigger className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors">
1524
+ Close
1525
+ </Modal.CloseTrigger>
1526
+ </div>
1527
+ </Modal.Footer>
1528
+ </Modal.Dialog>
1529
+ </Modal.Container>
1530
+ </Modal.Backdrop>
1531
+ </Modal>
1532
+
1533
+ <button
1534
+ onClick={() => toast.info("Opening help documentation...")}
1535
+ className="group px-8 py-4 bg-transparent hover:bg-[#F3F3F4] dark:hover:bg-[#616368]/20 text-[#0D1117] dark:text-white font-semibold rounded-xl border-2 border-[#B0B1B3]/30 dark:border-[#616368]/50 hover:border-[#7DCBD9] transition-colors duration-200"
1536
+ >
1537
+ Browse help docs
1538
+ </button>
1539
+ </div>
1540
+ </div>
1541
+ </div>
1542
+
1543
+ {/* Footer */}
1544
+ <div className="text-center py-8 text-sm text-[#B0B1B3]">
1545
+ <p>© 2026 Engine · Welcome to modern travel management</p>
1546
+ </div>
1547
+ </div>
1548
+
1549
+ {/* Properties Modal */}
1550
+ <Modal isOpen={isPropertiesModalOpen} onOpenChange={setIsPropertiesModalOpen}>
1551
+ <Modal.Backdrop>
1552
+ <Modal.Container>
1553
+ <Modal.Dialog className="max-w-4xl">
1554
+ <Modal.CloseTrigger />
1555
+ <Modal.Header>
1556
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1557
+ Your Properties
1558
+ </Modal.Heading>
1559
+ </Modal.Header>
1560
+ <Modal.Body className="space-y-4">
1561
+ <p className="text-[#616368] dark:text-[#B0B1B3] mb-4">
1562
+ {myProperties} active properties in your portfolio
1563
+ </p>
1564
+ <div className="space-y-3">
1565
+ {revenueTrendByProperty.properties?.map((prop, idx) => (
1566
+ <div
1567
+ key={idx}
1568
+ className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-5 border border-[#B0B1B3]/20 dark:border-[#616368]/30 hover:border-[#7DCBD9] transition-colors"
1569
+ >
1570
+ <div className="flex items-start justify-between gap-4">
1571
+ <div className="flex items-start gap-3 flex-1">
1572
+ <div className="mt-1">
1573
+ <BuildingOfficeIcon className="h-5 w-5 text-[#7DCBD9]" />
1574
+ </div>
1575
+ <div>
1576
+ <h4 className="font-semibold text-[#0D1117] dark:text-white mb-1">
1577
+ {prop.name}
1578
+ </h4>
1579
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3]">
1580
+ Latest revenue: ${prop.values[prop.values.length - 1].toLocaleString()}
1581
+ </p>
1582
+ </div>
1583
+ </div>
1584
+ <span
1585
+ className="inline-block h-3 w-3 rounded-full flex-shrink-0 mt-2"
1586
+ style={{ backgroundColor: prop.color }}
1587
+ />
1588
+ </div>
1589
+ </div>
1590
+ ))}
1591
+ </div>
1592
+ </Modal.Body>
1593
+ <Modal.Footer>
1594
+ <Modal.CloseTrigger asChild>
1595
+ <button className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors">
1596
+ Close
1597
+ </button>
1598
+ </Modal.CloseTrigger>
1599
+ </Modal.Footer>
1600
+ </Modal.Dialog>
1601
+ </Modal.Container>
1602
+ </Modal.Backdrop>
1603
+ </Modal>
1604
+
1605
+ {/* Revenue Modal */}
1606
+ <Modal isOpen={isRevenueModalOpen} onOpenChange={setIsRevenueModalOpen}>
1607
+ <Modal.Backdrop>
1608
+ <Modal.Container>
1609
+ <Modal.Dialog className="max-w-4xl">
1610
+ <Modal.CloseTrigger />
1611
+ <Modal.Header>
1612
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1613
+ Revenue Breakdown
1614
+ </Modal.Heading>
1615
+ </Modal.Header>
1616
+ <Modal.Body className="space-y-4">
1617
+ <div className="bg-gradient-to-br from-[#1E9D6D]/10 to-[#7DCBD9]/10 rounded-xl p-6 mb-4">
1618
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">Total Revenue</p>
1619
+ <p className="text-4xl font-bold text-[#0D1117] dark:text-white">
1620
+ ${myRevenue.toLocaleString()}
1621
+ </p>
1622
+ </div>
1623
+ <h3 className="font-semibold text-[#0D1117] dark:text-white mb-3">By Property</h3>
1624
+ <div className="space-y-3">
1625
+ {revenueTrendByProperty.properties?.map((prop, idx) => {
1626
+ const totalRevenue = prop.values.reduce((sum, val) => sum + val, 0);
1627
+ const percentage = ((totalRevenue / myRevenue) * 100).toFixed(1);
1628
+ return (
1629
+ <div
1630
+ key={idx}
1631
+ className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-5 border border-[#B0B1B3]/20 dark:border-[#616368]/30"
1632
+ >
1633
+ <div className="flex items-center justify-between mb-3">
1634
+ <div className="flex items-center gap-3">
1635
+ <span
1636
+ className="inline-block h-3 w-3 rounded-full"
1637
+ style={{ backgroundColor: prop.color }}
1638
+ />
1639
+ <span className="font-semibold text-[#0D1117] dark:text-white">
1640
+ {prop.name}
1641
+ </span>
1642
+ </div>
1643
+ <span className="text-lg font-bold text-[#0D1117] dark:text-white">
1644
+ ${totalRevenue.toLocaleString()}
1645
+ </span>
1646
+ </div>
1647
+ <div className="flex items-center gap-3">
1648
+ <div className="flex-1 bg-[#B0B1B3]/20 rounded-full h-2">
1649
+ <div
1650
+ className="h-2 rounded-full"
1651
+ style={{
1652
+ width: `${percentage}%`,
1653
+ backgroundColor: prop.color,
1654
+ }}
1655
+ />
1656
+ </div>
1657
+ <span className="text-sm text-[#616368] dark:text-[#B0B1B3] w-12 text-right">
1658
+ {percentage}%
1659
+ </span>
1660
+ </div>
1661
+ </div>
1662
+ );
1663
+ })}
1664
+ </div>
1665
+ </Modal.Body>
1666
+ <Modal.Footer>
1667
+ <button
1668
+ onClick={() => setIsRevenueModalOpen(false)}
1669
+ className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors"
1670
+ >
1671
+ Close
1672
+ </button>
1673
+ </Modal.Footer>
1674
+ </Modal.Dialog>
1675
+ </Modal.Container>
1676
+ </Modal.Backdrop>
1677
+ </Modal>
1678
+
1679
+ {/* Reservations Modal */}
1680
+ <Modal isOpen={isReservationsModalOpen} onOpenChange={setIsReservationsModalOpen}>
1681
+ <Modal.Backdrop>
1682
+ <Modal.Container>
1683
+ <Modal.Dialog className="max-w-4xl">
1684
+ <Modal.CloseTrigger />
1685
+ <Modal.Header>
1686
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1687
+ Reservations
1688
+ </Modal.Heading>
1689
+ </Modal.Header>
1690
+ <Modal.Body>
1691
+ <p className="text-[#616368] dark:text-[#B0B1B3] mb-4">
1692
+ {myReservations} total reservations through Engine
1693
+ </p>
1694
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-8 text-center">
1695
+ <ClockIcon className="h-12 w-12 text-[#157DE5] mx-auto mb-3" />
1696
+ <p className="text-[#616368] dark:text-[#B0B1B3]">
1697
+ Detailed reservation data is available in your full property management dashboard
1698
+ </p>
1699
+ </div>
1700
+ </Modal.Body>
1701
+ <Modal.Footer>
1702
+ <button
1703
+ onClick={() => setIsReservationsModalOpen(false)}
1704
+ className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors"
1705
+ >
1706
+ Close
1707
+ </button>
1708
+ </Modal.Footer>
1709
+ </Modal.Dialog>
1710
+ </Modal.Container>
1711
+ </Modal.Backdrop>
1712
+ </Modal>
1713
+
1714
+ {/* Disputes Modal */}
1715
+ <Modal isOpen={isDisputesModalOpen} onOpenChange={setIsDisputesModalOpen}>
1716
+ <Modal.Backdrop>
1717
+ <Modal.Container>
1718
+ <Modal.Dialog className="max-w-4xl">
1719
+ <Modal.CloseTrigger />
1720
+ <Modal.Header>
1721
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1722
+ Items to Review
1723
+ </Modal.Heading>
1724
+ </Modal.Header>
1725
+ <Modal.Body className="space-y-4">
1726
+ <p className="text-[#616368] dark:text-[#B0B1B3] mb-4">
1727
+ {myOpenDisputes} {myOpenDisputes === 1 ? 'item needs' : 'items need'} your attention
1728
+ </p>
1729
+ {myDisputes.length > 0 ? (
1730
+ <div className="space-y-3">
1731
+ {myDisputes.map((d) => (
1732
+ <div
1733
+ key={d.id}
1734
+ className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-5 border border-[#B0B1B3]/20 dark:border-[#616368]/30 hover:border-[#FFB200] transition-colors"
1735
+ >
1736
+ <div className="flex items-start justify-between gap-3 mb-2">
1737
+ <h4 className="font-semibold text-[#0D1117] dark:text-white flex-1">
1738
+ {d.title}
1739
+ </h4>
1740
+ <span
1741
+ className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium flex-shrink-0 ${
1742
+ d.status === "critical"
1743
+ ? "bg-[#FD4B23]/10 text-[#FD4B23] border border-[#FD4B23]/30"
1744
+ : d.status === "warning"
1745
+ ? "bg-[#FFB200]/10 text-[#FFB200] border border-[#FFB200]/30"
1746
+ : "bg-[#157DE5]/10 text-[#157DE5] border border-[#157DE5]/30"
1747
+ }`}
1748
+ >
1749
+ {d.badge}
1750
+ </span>
1751
+ </div>
1752
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-3">
1753
+ {d.description}
1754
+ </p>
1755
+ <div className="flex items-center justify-between">
1756
+ <span className="text-lg font-bold text-[#0D1117] dark:text-white">
1757
+ ${d.amount.toLocaleString()}
1758
+ </span>
1759
+ {d.agentHandled && (
1760
+ <span className="text-xs text-[#7DCBD9] flex items-center gap-1">
1761
+ <svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
1762
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
1763
+ </svg>
1764
+ Agent reviewed
1765
+ </span>
1766
+ )}
1767
+ </div>
1768
+ </div>
1769
+ ))}
1770
+ </div>
1771
+ ) : (
1772
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-8 text-center">
1773
+ <p className="text-[#616368] dark:text-[#B0B1B3]">No items need attention</p>
1774
+ </div>
1775
+ )}
1776
+ </Modal.Body>
1777
+ <Modal.Footer>
1778
+ <button
1779
+ onClick={() => setIsDisputesModalOpen(false)}
1780
+ className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors"
1781
+ >
1782
+ Close
1783
+ </button>
1784
+ </Modal.Footer>
1785
+ </Modal.Dialog>
1786
+ </Modal.Container>
1787
+ </Modal.Backdrop>
1788
+ </Modal>
1789
+
1790
+ {/* Penalty Modal */}
1791
+ <Modal isOpen={isPenaltyModalOpen} onOpenChange={setIsPenaltyModalOpen}>
1792
+ <Modal.Backdrop>
1793
+ <Modal.Container>
1794
+ <Modal.Dialog className="w-[95vw] max-w-4xl max-h-[90vh] overflow-y-auto">
1795
+ <Modal.CloseTrigger />
1796
+ <Modal.Header className="p-8 pb-4">
1797
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white pr-8">
1798
+ Penalty Review: {selectedPenalty?.name}
1799
+ </Modal.Heading>
1800
+ </Modal.Header>
1801
+ <Modal.Body className="space-y-6 px-8 py-4">
1802
+ {selectedPenalty && (
1803
+ <>
1804
+ {/* Property & Customer Info */}
1805
+ <div className="grid grid-cols-2 gap-4">
1806
+ <div>
1807
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-1">Property</p>
1808
+ <p className="font-semibold text-[#0D1117] dark:text-white">{selectedPenalty.property}</p>
1809
+ </div>
1810
+ <div>
1811
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-1">Customer</p>
1812
+ <p className="font-semibold text-[#0D1117] dark:text-white">{selectedPenalty.customer}</p>
1813
+ </div>
1814
+ </div>
1815
+
1816
+ {/* Booking Details */}
1817
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-6">
1818
+ <h4 className="font-semibold text-[#0D1117] dark:text-white mb-4">Booking Details</h4>
1819
+ <div className="grid grid-cols-3 gap-6">
1820
+ <div>
1821
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Original Block</p>
1822
+ <p className="text-xl font-bold text-[#0D1117] dark:text-white">{selectedPenalty.originalRoomBlock} rooms</p>
1823
+ </div>
1824
+ <div>
1825
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Rooms Used</p>
1826
+ <p className="text-xl font-bold text-[#1E9D6D]">{selectedPenalty.actualRoomsUsed} rooms</p>
1827
+ </div>
1828
+ <div>
1829
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Unused</p>
1830
+ <p className="text-xl font-bold text-[#FD4B23]">{selectedPenalty.unusedRooms} rooms</p>
1831
+ </div>
1832
+ </div>
1833
+ <div className="grid grid-cols-3 gap-6 mt-6 pt-6 border-t border-[#B0B1B3]/20 dark:border-[#616368]/30">
1834
+ <div>
1835
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Room Rate</p>
1836
+ <p className="text-xl font-bold text-[#0D1117] dark:text-white">${selectedPenalty.roomRate}/night</p>
1837
+ </div>
1838
+ <div>
1839
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Number of Nights</p>
1840
+ <p className="text-xl font-bold text-[#0D1117] dark:text-white">{selectedPenalty.numberOfNights}</p>
1841
+ </div>
1842
+ <div>
1843
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Rooms Resold</p>
1844
+ <p className="text-xl font-bold text-[#7DCBD9]">{selectedPenalty.roomsResold} rooms</p>
1845
+ </div>
1846
+ </div>
1847
+ </div>
1848
+
1849
+ {/* Calculation Method & Policy */}
1850
+ <div className="grid grid-cols-2 gap-6">
1851
+ <div>
1852
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">Calculation Method</p>
1853
+ <p className="text-lg font-semibold text-[#0D1117] dark:text-white">{selectedPenalty.method}</p>
1854
+ </div>
1855
+ <div>
1856
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">Resale Policy</p>
1857
+ <p className="text-lg font-semibold text-[#0D1117] dark:text-white">{selectedPenalty.resalePolicy}</p>
1858
+ </div>
1859
+ </div>
1860
+
1861
+ {/* Financial Details */}
1862
+ <div>
1863
+ <h4 className="font-semibold text-[#0D1117] dark:text-white mb-4">Financial Summary</h4>
1864
+ <div className="grid grid-cols-3 gap-6">
1865
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-lg p-5">
1866
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Calculated Penalty</p>
1867
+ <p className="text-2xl font-bold text-[#0D1117] dark:text-white">${selectedPenalty.penaltyCalculated.toLocaleString()}</p>
1868
+ </div>
1869
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-lg p-5">
1870
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Credit Applied</p>
1871
+ <p className="text-2xl font-bold text-[#1E9D6D]">${selectedPenalty.credit.toLocaleString()}</p>
1872
+ </div>
1873
+ <div className={`rounded-lg p-5 border ${selectedPenalty.isHero ? 'bg-[#FFB200]/10 border-[#FFB200]/30' : 'bg-[#F3F3F4] dark:bg-[#616368]/10 border-transparent'}`}>
1874
+ <p className="text-xs text-[#616368] dark:text-[#B0B1B3] mb-2 uppercase tracking-wider">Final Penalty</p>
1875
+ <p className={`text-2xl font-bold ${selectedPenalty.isHero ? 'text-[#FFB200]' : 'text-[#0D1117] dark:text-white'}`}>${selectedPenalty.penalty.toLocaleString()}</p>
1876
+ </div>
1877
+ </div>
1878
+ </div>
1879
+
1880
+ {/* Issue Description */}
1881
+ {selectedPenalty.isHero && (
1882
+ <div className="bg-[#FFB200]/10 border-l-4 border-[#FFB200] rounded-lg p-5">
1883
+ <p className="font-semibold text-[#0D1117] dark:text-white mb-3 flex items-center gap-2">
1884
+ <ExclamationTriangleIcon className="h-5 w-5 text-[#FFB200]" />
1885
+ Why this needs review:
1886
+ </p>
1887
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] leading-relaxed">
1888
+ Your contract specifies a {selectedPenalty.resalePolicy.toLowerCase()}. Based on {selectedPenalty.roomsResold} rooms resold at ${selectedPenalty.roomRate}/night for {selectedPenalty.numberOfNights} {selectedPenalty.numberOfNights === 1 ? 'night' : 'nights'},
1889
+ we'd expect to see a ${(selectedPenalty.roomsResold * selectedPenalty.roomRate * selectedPenalty.numberOfNights * 0.5).toLocaleString()} credit that doesn't appear to be fully applied.
1890
+ </p>
1891
+ </div>
1892
+ )}
1893
+
1894
+ {/* Status */}
1895
+ <div>
1896
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mb-2">Status</p>
1897
+ <span
1898
+ className={`inline-flex items-center rounded-full px-3 py-1 text-sm font-medium ${
1899
+ selectedPenalty.status === "Approved"
1900
+ ? "bg-[#1E9D6D]/10 text-[#1E9D6D] border border-[#1E9D6D]/30"
1901
+ : selectedPenalty.status === "Reviewed"
1902
+ ? "bg-[#157DE5]/10 text-[#157DE5] border border-[#157DE5]/30"
1903
+ : selectedPenalty.status === "Calculated"
1904
+ ? "bg-[#B0B1B3]/10 text-[#616368] border border-[#B0B1B3]/30"
1905
+ : "bg-[#FFB200]/10 text-[#FFB200] border border-[#FFB200]/30"
1906
+ }`}
1907
+ >
1908
+ {selectedPenalty.status}
1909
+ </span>
1910
+ </div>
1911
+ </>
1912
+ )}
1913
+ </Modal.Body>
1914
+ <Modal.Footer className="p-8 pt-4">
1915
+ <div className="flex gap-4 justify-end flex-wrap">
1916
+ <button
1917
+ onClick={() => setIsPenaltyModalOpen(false)}
1918
+ className="px-6 py-2.5 bg-transparent hover:bg-[#F3F3F4] dark:hover:bg-[#616368]/20 text-[#0D1117] dark:text-white font-semibold rounded-lg border border-[#B0B1B3]/30 dark:border-[#616368]/50 transition-colors"
1919
+ >
1920
+ Close
1921
+ </button>
1922
+ <button
1923
+ onClick={() => {
1924
+ toast.success("Dispute has been filed. Support team will follow up.");
1925
+ setIsPenaltyModalOpen(false);
1926
+ }}
1927
+ className="px-6 py-2.5 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors whitespace-nowrap"
1928
+ >
1929
+ File Dispute
1930
+ </button>
1931
+ </div>
1932
+ </Modal.Footer>
1933
+ </Modal.Dialog>
1934
+ </Modal.Container>
1935
+ </Modal.Backdrop>
1936
+ </Modal>
1937
+
1938
+ {/* Invoices Modal */}
1939
+ <Modal isOpen={isInvoicesModalOpen} onOpenChange={setIsInvoicesModalOpen}>
1940
+ <Modal.Backdrop>
1941
+ <Modal.Container>
1942
+ <Modal.Dialog className="max-w-4xl">
1943
+ <Modal.CloseTrigger />
1944
+ <Modal.Header>
1945
+ <Modal.Heading className="text-2xl font-bold text-[#0D1117] dark:text-white">
1946
+ Your Invoices
1947
+ </Modal.Heading>
1948
+ </Modal.Header>
1949
+ <Modal.Body className="space-y-4">
1950
+ <p className="text-[#616368] dark:text-[#B0B1B3] mb-4">
1951
+ {myPendingInvoices} {myPendingInvoices === 1 ? 'invoice' : 'invoices'} ready to pay
1952
+ </p>
1953
+ {myInvoices.length > 0 ? (
1954
+ <div className="space-y-3">
1955
+ {myInvoices.map((inv) => (
1956
+ <div
1957
+ key={inv.id}
1958
+ className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-5 border border-[#B0B1B3]/20 dark:border-[#616368]/30 hover:border-[#1E9D6D] transition-colors"
1959
+ >
1960
+ <div className="flex items-start justify-between gap-3 mb-2">
1961
+ <div>
1962
+ <h4 className="font-semibold text-[#0D1117] dark:text-white">
1963
+ {inv.title}
1964
+ </h4>
1965
+ <p className="text-sm text-[#616368] dark:text-[#B0B1B3] mt-1">
1966
+ {inv.description}
1967
+ </p>
1968
+ </div>
1969
+ <span
1970
+ className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium flex-shrink-0 ${
1971
+ inv.status === "critical"
1972
+ ? "bg-[#FD4B23]/10 text-[#FD4B23] border border-[#FD4B23]/30"
1973
+ : "bg-[#157DE5]/10 text-[#157DE5] border border-[#157DE5]/30"
1974
+ }`}
1975
+ >
1976
+ {inv.badge}
1977
+ </span>
1978
+ </div>
1979
+ <div className="flex items-center justify-between mt-3">
1980
+ <span className="text-lg font-bold text-[#0D1117] dark:text-white">
1981
+ ${inv.amount.toLocaleString()}
1982
+ </span>
1983
+ <span className="text-sm text-[#616368] dark:text-[#B0B1B3]">
1984
+ Due {inv.due}
1985
+ </span>
1986
+ </div>
1987
+ </div>
1988
+ ))}
1989
+ </div>
1990
+ ) : (
1991
+ <div className="bg-[#F3F3F4] dark:bg-[#616368]/10 rounded-xl p-8 text-center">
1992
+ <p className="text-[#616368] dark:text-[#B0B1B3]">No pending invoices</p>
1993
+ </div>
1994
+ )}
1995
+ </Modal.Body>
1996
+ <Modal.Footer>
1997
+ <button
1998
+ onClick={() => setIsInvoicesModalOpen(false)}
1999
+ className="px-6 py-2 bg-[#0D1117] dark:bg-white hover:bg-[#616368] dark:hover:bg-[#F3F3F4] text-white dark:text-[#0D1117] font-semibold rounded-lg transition-colors"
2000
+ >
2001
+ Close
2002
+ </button>
2003
+ </Modal.Footer>
2004
+ </Modal.Dialog>
2005
+ </Modal.Container>
2006
+ </Modal.Backdrop>
2007
+ </Modal>
2008
+ </div>
2009
+ );
2010
+ }