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