@kyro-cms/admin 0.12.3 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +64 -26
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +64 -26
- package/package.json +8 -2
- package/src/components/ActionBar.tsx +16 -16
- package/src/components/ApiHealth.tsx +522 -0
- package/src/components/ApiKeysManager.tsx +6 -4
- package/src/components/AuditLogsPage.tsx +4 -2
- package/src/components/AutoForm.tsx +10 -7
- package/src/components/BrandingHub.tsx +3 -1
- package/src/components/Dashboard.tsx +44 -37
- package/src/components/DashboardMetrics.tsx +168 -58
- package/src/components/DetailView.tsx +125 -50
- package/src/components/DeveloperCenter.tsx +5 -3
- package/src/components/GraphQLPlayground.tsx +10 -8
- package/src/components/ListView.tsx +66 -45
- package/src/components/LoginPage.tsx +21 -19
- package/src/components/MarketplaceManager.tsx +3 -1
- package/src/components/MediaGallery.tsx +12 -9
- package/src/components/PluginsManager.tsx +6 -4
- package/src/components/RestPlayground.tsx +8 -6
- package/src/components/SessionsManager.tsx +3 -1
- package/src/components/Sidebar.astro +180 -92
- package/src/components/UserManagement.tsx +8 -6
- package/src/components/UserMenu.tsx +19 -17
- package/src/components/VersionHistoryPanel.tsx +6 -4
- package/src/components/WebhookManager.tsx +143 -15
- package/src/components/autoform/AutoFormApiView.tsx +3 -1
- package/src/components/autoform/AutoFormEditView.tsx +5 -3
- package/src/components/autoform/AutoFormHeader.tsx +6 -6
- package/src/components/blocks/AccordionBlock.tsx +5 -3
- package/src/components/blocks/ArrayBlock.tsx +5 -3
- package/src/components/blocks/BlockWrapper.tsx +5 -3
- package/src/components/blocks/CodeBlock.tsx +4 -2
- package/src/components/blocks/FileBlock.tsx +5 -3
- package/src/components/blocks/HeroBlock.tsx +5 -3
- package/src/components/blocks/ImageBlock.tsx +6 -4
- package/src/components/blocks/ListBlock.tsx +5 -3
- package/src/components/blocks/ParagraphBlock.tsx +3 -1
- package/src/components/blocks/RelationshipBlock.tsx +5 -3
- package/src/components/blocks/RichTextBlock.tsx +5 -3
- package/src/components/blocks/VideoBlock.tsx +5 -3
- package/src/components/fields/AccordionField.tsx +10 -51
- package/src/components/fields/ArrayField.tsx +9 -7
- package/src/components/fields/ArrayLayout.tsx +3 -1
- package/src/components/fields/BlocksField.tsx +24 -75
- package/src/components/fields/ButtonField.tsx +3 -1
- package/src/components/fields/CardField.tsx +6 -4
- package/src/components/fields/GroupLayout.tsx +2 -49
- package/src/components/fields/HeadingField.tsx +3 -1
- package/src/components/fields/HeadingSubheadingField.tsx +4 -2
- package/src/components/fields/HeroField.tsx +8 -6
- package/src/components/fields/JSONField.tsx +9 -10
- package/src/components/fields/LinkField.tsx +4 -2
- package/src/components/fields/ListField.tsx +3 -1
- package/src/components/fields/MarkdownField.tsx +3 -17
- package/src/components/fields/RelationshipBlockField.tsx +7 -5
- package/src/components/fields/RelationshipField.tsx +3 -1
- package/src/components/fields/RichTextField.tsx +19 -16
- package/src/components/fields/SecretField.tsx +9 -69
- package/src/components/fields/SelectField.tsx +11 -6
- package/src/components/fields/TabsLayout.tsx +2 -48
- package/src/components/fields/TextField.tsx +9 -7
- package/src/components/fields/UploadField.tsx +3 -1
- package/src/components/fields/VideoField.tsx +6 -4
- package/src/components/ui/BlockDrawer.tsx +3 -1
- package/src/components/ui/CommandPalette.tsx +9 -7
- package/src/components/ui/CommandPaletteWrapper.tsx +3 -1
- package/src/components/ui/IconPickerModal.tsx +4 -2
- package/src/components/ui/PageHeader.tsx +48 -20
- package/src/components/users/UserDetail.tsx +3 -1
- package/src/components/users/UserForm.tsx +5 -3
- package/src/fields/examples/sample-field-2.tsx +3 -1
- package/src/fields/examples/sample-field.tsx +3 -1
- package/src/hooks/useAutoFormState.ts +19 -11
- package/src/integration.ts +21 -0
- package/src/layouts/AdminLayout.astro +252 -189
- package/src/layouts/AuthLayout.astro +52 -31
- package/src/lib/api.ts +7 -7
- package/src/lib/autoform-store.ts +4 -0
- package/src/lib/deep-equal.ts +3 -3
- package/src/lib/i18n.ts +44 -0
- package/src/lib/vite-shim-plugin.ts +3 -0
- package/src/locales/de.json +163 -0
- package/src/locales/en.json +163 -0
- package/src/locales/es.json +163 -0
- package/src/locales/fr.json +163 -0
- package/src/locales/pt.json +163 -0
- package/src/pages/403.astro +2 -1
- package/src/pages/[collection]/[id].astro +23 -10
- package/src/pages/[collection]/index.astro +48 -34
- package/src/pages/audit/index.astro +2 -1
- package/src/pages/graphql-explorer.astro +2 -1
- package/src/pages/graphql.astro +2 -1
- package/src/pages/health.astro +9 -0
- package/src/pages/index.astro +6 -5
- package/src/pages/keys.astro +2 -1
- package/src/pages/marketplace.astro +2 -1
- package/src/pages/media.astro +2 -1
- package/src/pages/plugins.astro +2 -1
- package/src/pages/rest-playground.astro +2 -1
- package/src/pages/roles/index.astro +16 -52
- package/src/pages/sessions.astro +2 -1
- package/src/pages/settings/[slug].astro +45 -30
- package/src/pages/users/[id].astro +1 -0
- package/src/pages/users/index.astro +2 -1
- package/src/pages/users/new.astro +2 -1
- package/src/pages/webhooks.astro +2 -1
- package/src/pages/index_ALT.astro +0 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { apiGet } from "../lib/api";
|
|
3
|
+
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
|
|
3
4
|
|
|
4
5
|
interface MetricsData {
|
|
5
6
|
totalDocuments: number;
|
|
@@ -70,15 +71,24 @@ function MetricCard({
|
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
export
|
|
74
|
+
export interface DashboardMetricsProps {
|
|
75
|
+
isEcommerce?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const DashboardMetrics: React.FC<DashboardMetricsProps> = ({ isEcommerce }) => {
|
|
74
79
|
const [data, setData] = useState<MetricsData | null>(null);
|
|
80
|
+
const [analyticsData, setAnalyticsData] = useState<any>(null);
|
|
75
81
|
const [loading, setLoading] = useState(true);
|
|
76
82
|
const [error, setError] = useState<string | null>(null);
|
|
77
83
|
|
|
78
84
|
useEffect(() => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
Promise.all([
|
|
86
|
+
apiGet<MetricsData>("/api/metrics", { autoToast: false }),
|
|
87
|
+
isEcommerce ? apiGet<any>("/api/analytics", { autoToast: false }).catch(() => null) : Promise.resolve(null)
|
|
88
|
+
])
|
|
89
|
+
.then(([metricsRes, analyticsRes]) => {
|
|
90
|
+
setData(metricsRes);
|
|
91
|
+
if (analyticsRes) setAnalyticsData(analyticsRes);
|
|
82
92
|
setError(null);
|
|
83
93
|
})
|
|
84
94
|
.catch((err) => {
|
|
@@ -86,14 +96,14 @@ export function DashboardMetrics() {
|
|
|
86
96
|
setError("Unable to load metrics");
|
|
87
97
|
})
|
|
88
98
|
.finally(() => setLoading(false));
|
|
89
|
-
}, []);
|
|
99
|
+
}, [isEcommerce]);
|
|
90
100
|
|
|
91
101
|
if (error) {
|
|
92
102
|
return (
|
|
93
|
-
<div className="surface-tile overflow-hidden">
|
|
103
|
+
<div className="surface-tile overflow-hidden mb-6">
|
|
94
104
|
<div className="p-6 border-b border-[var(--kyro-border)]">
|
|
95
105
|
<h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">
|
|
96
|
-
|
|
106
|
+
Metrics
|
|
97
107
|
</h2>
|
|
98
108
|
</div>
|
|
99
109
|
<div className="p-6">
|
|
@@ -108,21 +118,8 @@ export function DashboardMetrics() {
|
|
|
108
118
|
<div className="p-6 border-b border-[var(--kyro-border)]">
|
|
109
119
|
<div className="flex items-center justify-between">
|
|
110
120
|
<div>
|
|
111
|
-
<h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]
|
|
112
|
-
|
|
113
|
-
className="w-5 h-5 text-[var(--kyro-primary)]"
|
|
114
|
-
fill="none"
|
|
115
|
-
stroke="currentColor"
|
|
116
|
-
viewBox="0 0 24 24"
|
|
117
|
-
>
|
|
118
|
-
<path
|
|
119
|
-
strokeLinecap="round"
|
|
120
|
-
strokeLinejoin="round"
|
|
121
|
-
strokeWidth="2"
|
|
122
|
-
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
123
|
-
/>
|
|
124
|
-
</svg>
|
|
125
|
-
CMS Metrics
|
|
121
|
+
<h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">
|
|
122
|
+
Metrics
|
|
126
123
|
</h2>
|
|
127
124
|
<p className="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
|
|
128
125
|
Real-time content, performance, and system health overview
|
|
@@ -147,31 +144,86 @@ export function DashboardMetrics() {
|
|
|
147
144
|
<SkeletonCard />
|
|
148
145
|
</>
|
|
149
146
|
) : data ? (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
className="w-5 h-5"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
147
|
+
isEcommerce ? (
|
|
148
|
+
<>
|
|
149
|
+
<MetricCard
|
|
150
|
+
icon={
|
|
151
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
152
|
+
}
|
|
153
|
+
iconBg="rgba(34, 197, 94, 0.1)"
|
|
154
|
+
iconColor="#22c55e"
|
|
155
|
+
value={analyticsData?.totalRevenue
|
|
156
|
+
? new Intl.NumberFormat('en-US', { style: 'currency', currency: analyticsData.currencyCode || 'USD' }).format(analyticsData.totalRevenue)
|
|
157
|
+
: "$0.00"}
|
|
158
|
+
label="Sales"
|
|
159
|
+
subtext="Total revenue generated"
|
|
160
|
+
/>
|
|
161
|
+
<MetricCard
|
|
162
|
+
icon={
|
|
163
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /></svg>
|
|
164
|
+
}
|
|
165
|
+
iconBg="rgba(236, 72, 153, 0.1)"
|
|
166
|
+
iconColor="#ec4899"
|
|
167
|
+
value={data.collectionCounts?.['products'] || 0}
|
|
168
|
+
label="Products"
|
|
169
|
+
subtext="Active inventory items"
|
|
170
|
+
/>
|
|
171
|
+
<MetricCard
|
|
172
|
+
icon={
|
|
173
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" /></svg>
|
|
174
|
+
}
|
|
175
|
+
iconBg="rgba(16, 185, 129, 0.1)"
|
|
176
|
+
iconColor="#10b981"
|
|
177
|
+
value={data.collectionCounts?.['orders'] || 0}
|
|
178
|
+
label="Orders"
|
|
179
|
+
subtext="Total orders placed"
|
|
180
|
+
/>
|
|
181
|
+
<MetricCard
|
|
182
|
+
icon={
|
|
183
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
|
184
|
+
}
|
|
185
|
+
iconBg="rgba(59, 130, 246, 0.1)"
|
|
186
|
+
iconColor="#3b82f6"
|
|
187
|
+
value={data.collectionCounts?.['customers'] || 0}
|
|
188
|
+
label="Customers"
|
|
189
|
+
subtext="Registered shoppers"
|
|
190
|
+
/>
|
|
191
|
+
<MetricCard
|
|
192
|
+
icon={
|
|
193
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /></svg>
|
|
194
|
+
}
|
|
195
|
+
iconBg="rgba(245, 158, 11, 0.1)"
|
|
196
|
+
iconColor="#f59e0b"
|
|
197
|
+
value={data.collectionCounts?.['reviews'] || 0}
|
|
198
|
+
label="Reviews"
|
|
199
|
+
subtext="Customer feedback"
|
|
200
|
+
/>
|
|
201
|
+
</>
|
|
202
|
+
) : (
|
|
203
|
+
<>
|
|
204
|
+
<MetricCard
|
|
205
|
+
icon={
|
|
206
|
+
<svg
|
|
207
|
+
className="w-5 h-5"
|
|
208
|
+
fill="none"
|
|
209
|
+
stroke="currentColor"
|
|
210
|
+
viewBox="0 0 24 24"
|
|
211
|
+
>
|
|
212
|
+
<path
|
|
213
|
+
strokeLinecap="round"
|
|
214
|
+
strokeLinejoin="round"
|
|
215
|
+
strokeWidth="2"
|
|
216
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
217
|
+
/>
|
|
218
|
+
</svg>
|
|
219
|
+
}
|
|
220
|
+
iconBg="rgba(99, 102, 241, 0.1)"
|
|
221
|
+
iconColor="#6366f1"
|
|
222
|
+
value={data.totalDocuments || 0}
|
|
223
|
+
label="Documents"
|
|
224
|
+
subtext={`Across ${data.collections || 0} collection${data.collections !== 1 ? "s" : ""}`}
|
|
225
|
+
/>
|
|
173
226
|
|
|
174
|
-
{data.totalMedia !== undefined && (
|
|
175
227
|
<MetricCard
|
|
176
228
|
icon={
|
|
177
229
|
<svg
|
|
@@ -190,13 +242,11 @@ export function DashboardMetrics() {
|
|
|
190
242
|
}
|
|
191
243
|
iconBg="rgba(168, 85, 247, 0.1)"
|
|
192
244
|
iconColor="#a855f7"
|
|
193
|
-
value={data.totalMedia}
|
|
245
|
+
value={data.totalMedia || 0}
|
|
194
246
|
label="Media Files"
|
|
195
247
|
subtext="Images, videos & docs"
|
|
196
248
|
/>
|
|
197
|
-
)}
|
|
198
249
|
|
|
199
|
-
{data.totalUsers !== undefined && (
|
|
200
250
|
<MetricCard
|
|
201
251
|
icon={
|
|
202
252
|
<svg
|
|
@@ -215,13 +265,11 @@ export function DashboardMetrics() {
|
|
|
215
265
|
}
|
|
216
266
|
iconBg="rgba(14, 165, 233, 0.1)"
|
|
217
267
|
iconColor="#0ea5e9"
|
|
218
|
-
value={data.totalUsers}
|
|
268
|
+
value={data.totalUsers || 0}
|
|
219
269
|
label="Team Members"
|
|
220
270
|
subtext="Active user accounts"
|
|
221
271
|
/>
|
|
222
|
-
)}
|
|
223
272
|
|
|
224
|
-
{data.totalWebhooks !== undefined && (
|
|
225
273
|
<MetricCard
|
|
226
274
|
icon={
|
|
227
275
|
<svg
|
|
@@ -240,13 +288,11 @@ export function DashboardMetrics() {
|
|
|
240
288
|
}
|
|
241
289
|
iconBg="rgba(34, 197, 94, 0.1)"
|
|
242
290
|
iconColor="#22c55e"
|
|
243
|
-
value={data.totalWebhooks}
|
|
291
|
+
value={data.totalWebhooks || 0}
|
|
244
292
|
label="Webhooks"
|
|
245
293
|
subtext="Active integrations"
|
|
246
294
|
/>
|
|
247
|
-
)}
|
|
248
295
|
|
|
249
|
-
{data.totalApiKeys !== undefined && (
|
|
250
296
|
<MetricCard
|
|
251
297
|
icon={
|
|
252
298
|
<svg
|
|
@@ -265,12 +311,12 @@ export function DashboardMetrics() {
|
|
|
265
311
|
}
|
|
266
312
|
iconBg="rgba(245, 158, 11, 0.1)"
|
|
267
313
|
iconColor="#f59e0b"
|
|
268
|
-
value={data.totalApiKeys}
|
|
314
|
+
value={data.totalApiKeys || 0}
|
|
269
315
|
label="API Keys"
|
|
270
316
|
subtext="Developer access tokens"
|
|
271
317
|
/>
|
|
272
|
-
|
|
273
|
-
|
|
318
|
+
</>
|
|
319
|
+
)
|
|
274
320
|
) : null}
|
|
275
321
|
</div>
|
|
276
322
|
|
|
@@ -278,3 +324,67 @@ export function DashboardMetrics() {
|
|
|
278
324
|
</div>
|
|
279
325
|
);
|
|
280
326
|
}
|
|
327
|
+
|
|
328
|
+
export const RevenueChart: React.FC = () => {
|
|
329
|
+
const [data, setData] = useState<any>(null);
|
|
330
|
+
const [loading, setLoading] = useState(true);
|
|
331
|
+
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
apiGet<any>("/api/analytics", { autoToast: false })
|
|
334
|
+
.then(setData)
|
|
335
|
+
.catch(console.error)
|
|
336
|
+
.finally(() => setLoading(false));
|
|
337
|
+
}, []);
|
|
338
|
+
|
|
339
|
+
if (loading) return (
|
|
340
|
+
<div className="surface-tile mt-6 p-6 overflow-hidden">
|
|
341
|
+
<div className="h-64 animate-pulse bg-[var(--kyro-bg-secondary)] rounded-lg"></div>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
const hasChartData = data && data.chartData && data.chartData.length > 0;
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<div className="surface-tile mt-6 p-6 overflow-hidden">
|
|
348
|
+
<div className="mb-6 flex justify-between items-end">
|
|
349
|
+
<div>
|
|
350
|
+
<h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">Revenue Overview</h2>
|
|
351
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">Daily revenue and order volume</p>
|
|
352
|
+
</div>
|
|
353
|
+
<div className="text-right">
|
|
354
|
+
<h3 className="text-2xl font-bold text-[var(--kyro-primary)]">
|
|
355
|
+
{new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD' }).format(data?.totalRevenue || 0)}
|
|
356
|
+
</h3>
|
|
357
|
+
<p className="text-xs text-[var(--kyro-text-muted)]">{data?.totalOrders || 0} total orders</p>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
<div className="h-72 w-full">
|
|
361
|
+
{!hasChartData ? (
|
|
362
|
+
<div className="flex flex-col items-center justify-center w-full h-full text-[var(--kyro-text-secondary)] bg-[var(--kyro-bg-secondary)] rounded-xl border border-dashed border-[var(--kyro-border)]">
|
|
363
|
+
<svg className="w-10 h-10 mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>
|
|
364
|
+
<p className="text-sm font-medium">No chart data to display</p>
|
|
365
|
+
</div>
|
|
366
|
+
) : (
|
|
367
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
368
|
+
<AreaChart data={data.chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
|
369
|
+
<defs>
|
|
370
|
+
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
|
|
371
|
+
<stop offset="5%" stopColor="var(--kyro-primary)" stopOpacity={0.3}/>
|
|
372
|
+
<stop offset="95%" stopColor="var(--kyro-primary)" stopOpacity={0}/>
|
|
373
|
+
</linearGradient>
|
|
374
|
+
</defs>
|
|
375
|
+
<XAxis dataKey="date" stroke="var(--kyro-text-muted)" fontSize={12} tickLine={false} axisLine={false} />
|
|
376
|
+
<YAxis stroke="var(--kyro-text-muted)" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(value) => new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD', maximumFractionDigits: 0 }).format(value)} />
|
|
377
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="var(--kyro-border)" />
|
|
378
|
+
<Tooltip
|
|
379
|
+
contentStyle={{ backgroundColor: 'var(--kyro-bg-primary)', border: '1px solid var(--kyro-border)', borderRadius: '8px', color: 'var(--kyro-text-primary)' }}
|
|
380
|
+
itemStyle={{ color: 'var(--kyro-text-primary)' }}
|
|
381
|
+
formatter={(value: any) => [new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD' }).format(value), 'Revenue']}
|
|
382
|
+
/>
|
|
383
|
+
<Area type="monotone" dataKey="revenue" stroke="var(--kyro-primary)" fillOpacity={1} fill="url(#colorRevenue)" strokeWidth={2} />
|
|
384
|
+
</AreaChart>
|
|
385
|
+
</ResponsiveContainer>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}
|