@pattern-stack/frontend-patterns 0.0.6 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/atoms/composed/index.d.ts +0 -1
  2. package/dist/atoms/composed/index.d.ts.map +1 -1
  3. package/dist/atoms/types/index.d.ts +0 -2
  4. package/dist/atoms/types/index.d.ts.map +1 -1
  5. package/dist/atoms/ui/ErrorBoundary.d.ts +1 -1
  6. package/dist/atoms/ui/button.d.ts +1 -1
  7. package/dist/atoms/utils/utils.d.ts +0 -2
  8. package/dist/atoms/utils/utils.d.ts.map +1 -1
  9. package/dist/features/auth/components/ProtectedRoute.d.ts +1 -1
  10. package/dist/frontend-patterns.css +1 -1
  11. package/dist/index.es.js +15 -403
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.js +14 -403
  14. package/dist/index.js.map +1 -1
  15. package/dist/molecules/layout/Sidebar.d.ts.map +1 -1
  16. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +0 -2
  17. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -1
  18. package/dist/molecules/layout/index.d.ts +0 -3
  19. package/dist/molecules/layout/index.d.ts.map +1 -1
  20. package/dist/templates/factory.d.ts +1 -2
  21. package/dist/templates/factory.d.ts.map +1 -1
  22. package/dist/templates/index.d.ts.map +1 -1
  23. package/package.json +3 -7
  24. package/src/App.tsx +1 -11
  25. package/src/atoms/composed/index.ts +0 -1
  26. package/src/atoms/types/index.ts +1 -3
  27. package/src/atoms/utils/utils.ts +2 -4
  28. package/src/molecules/layout/Sidebar.tsx +23 -10
  29. package/src/molecules/layout/SidebarButton/SidebarButton.tsx +10 -32
  30. package/src/molecules/layout/index.ts +1 -4
  31. package/src/organisms/index.ts +1 -5
  32. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +75 -77
  33. package/src/pages/AdminShowcase/index.tsx +1 -2
  34. package/src/pages/index.ts +1 -2
  35. package/src/templates/factory.tsx +7 -14
  36. package/src/templates/index.ts +0 -4
  37. package/dist/atoms/composed/SalesPanel/SalesPanel.d.ts +0 -19
  38. package/dist/atoms/composed/SalesPanel/SalesPanel.d.ts.map +0 -1
  39. package/dist/atoms/composed/SalesPanel/index.d.ts +0 -2
  40. package/dist/atoms/composed/SalesPanel/index.d.ts.map +0 -1
  41. package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts +0 -63
  42. package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts.map +0 -1
  43. package/dist/atoms/types/entity-config.d.ts +0 -117
  44. package/dist/atoms/types/entity-config.d.ts.map +0 -1
  45. package/dist/atoms/types/navigation.d.ts +0 -30
  46. package/dist/atoms/types/navigation.d.ts.map +0 -1
  47. package/dist/atoms/utils/icon-resolver.d.ts +0 -72
  48. package/dist/atoms/utils/icon-resolver.d.ts.map +0 -1
  49. package/dist/atoms/utils/metric-engine.d.ts +0 -30
  50. package/dist/atoms/utils/metric-engine.d.ts.map +0 -1
  51. package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts +0 -16
  52. package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts.map +0 -1
  53. package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts +0 -2
  54. package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts.map +0 -1
  55. package/dist/molecules/layout/NavigationContext.d.ts +0 -15
  56. package/dist/molecules/layout/NavigationContext.d.ts.map +0 -1
  57. package/src/__tests__/atoms/composed/databadge.test.tsx +0 -106
  58. package/src/__tests__/atoms/composed/statcard.test.tsx +0 -133
  59. package/src/__tests__/atoms/utils/icon-resolver.test.tsx +0 -140
  60. package/src/atoms/composed/SalesPanel/SalesPanel.tsx +0 -116
  61. package/src/atoms/composed/SalesPanel/index.ts +0 -1
  62. package/src/atoms/composed/SalesPanel/mockSalesData.ts +0 -151
  63. package/src/atoms/types/entity-config.ts +0 -127
  64. package/src/atoms/types/navigation.ts +0 -43
  65. package/src/atoms/utils/icon-resolver.tsx +0 -54
  66. package/src/atoms/utils/metric-engine.ts +0 -236
  67. package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +0 -42
  68. package/src/molecules/layout/DashboardWithSidePanel/index.ts +0 -1
  69. package/src/molecules/layout/NavigationContext.tsx +0 -63
  70. package/src/organisms/entity/CategoryBreakdownPanel.tsx +0 -427
  71. package/src/organisms/entity/EntityListPanel.tsx +0 -339
  72. package/src/organisms/entity/MetricsOverviewPanel.tsx +0 -236
  73. package/src/organisms/entity/TrendAnalysisPanel.tsx +0 -337
  74. package/src/organisms/entity/index.ts +0 -4
  75. package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +0 -158
  76. package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +0 -137
  77. package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +0 -117
  78. package/src/pages/EntityShowcase/index.ts +0 -2
  79. package/src/pages/EntityTemplateExample.tsx +0 -229
  80. package/src/pages/TestEntityTemplate.tsx +0 -40
  81. package/src/templates/entity/EntityManagementTemplate.tsx +0 -430
  82. package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +0 -277
  83. package/src/templates/entity/configs/financial-config.ts +0 -141
  84. package/src/templates/entity/configs/index.ts +0 -1
  85. package/src/templates/entity/index.ts +0 -3
  86. package/src/templates/financial/FinancialDashboardTemplate.tsx +0 -326
@@ -1,430 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { AppLayout } from '../../molecules/layout/AppLayout';
3
- import { PageTemplate } from '../../molecules/layout/PageTemplate';
4
- import { SectionHeader } from '../../molecules/layout/SectionHeader';
5
- import { EntityListPanel } from '../../organisms/entity';
6
- import { Modal } from '../../atoms/composed/Modal';
7
- import { Button } from '../../atoms/ui/button';
8
- import { Card } from '../../atoms/ui/card';
9
- import { Badge } from '../../atoms/ui/Badge';
10
- import type { EntityTemplateConfig, EntityData, ActionConfig } from '../../atoms/types';
11
- import { cn } from '../../atoms/utils/utils';
12
- import { Plus, Trash2, Filter, Download } from 'lucide-react';
13
-
14
- export interface EntityManagementTemplateProps<T extends EntityData> {
15
- config: EntityTemplateConfig<T>;
16
- data: T[];
17
- isLoading?: boolean;
18
- className?: string;
19
-
20
- // View configuration
21
- defaultView?: 'list' | 'grid' | 'cards';
22
- enableViews?: boolean;
23
- enableSearch?: boolean;
24
- enableFiltering?: boolean;
25
- enableBulkActions?: boolean;
26
- enableExport?: boolean;
27
- pageSize?: number;
28
-
29
- // CRUD operations
30
- onCreate?: (item: Partial<T>) => Promise<T> | T;
31
- onUpdate?: (id: string | number, item: Partial<T>) => Promise<T> | T;
32
- onDelete?: (id: string | number) => Promise<void> | void;
33
- onBulkDelete?: (ids: (string | number)[]) => Promise<void> | void;
34
- onView?: (item: T) => void;
35
-
36
- // Extension points - Render Props
37
- renderCreateForm?: (onSubmit: (item: Partial<T>) => void, onCancel: () => void) => React.ReactNode;
38
- renderEditForm?: (item: T, onSubmit: (item: Partial<T>) => void, onCancel: () => void) => React.ReactNode;
39
- renderDetailView?: (item: T, onEdit: () => void, onClose: () => void) => React.ReactNode;
40
- renderCustomActions?: (item?: T) => React.ReactNode;
41
- renderEmptyState?: () => React.ReactNode;
42
- renderFilterPanel?: (onFilter: (filters: Record<string, unknown>) => void) => React.ReactNode;
43
-
44
- // Extension points - Slots
45
- headerSlot?: React.ReactNode;
46
- footerSlot?: React.ReactNode;
47
- toolbarSlot?: React.ReactNode;
48
- listHeaderSlot?: React.ReactNode;
49
- listFooterSlot?: React.ReactNode;
50
-
51
- // Event handlers
52
- onRefresh?: () => void;
53
- onFilter?: (filters: Record<string, unknown>) => void;
54
- onExport?: (data: T[], format: 'csv' | 'json') => void;
55
- onSelectionChange?: (selectedItems: T[]) => void;
56
- }
57
-
58
- export const EntityManagementTemplate = <T extends EntityData>({
59
- config,
60
- data,
61
- isLoading = false,
62
- className,
63
- enableSearch = true,
64
- enableFiltering = true,
65
- enableBulkActions = true,
66
- enableExport = true,
67
- pageSize = 10,
68
- onCreate,
69
- onUpdate,
70
- onDelete,
71
- onBulkDelete,
72
- onView,
73
- renderCreateForm,
74
- renderEditForm,
75
- renderDetailView,
76
- renderCustomActions,
77
- renderEmptyState,
78
- renderFilterPanel,
79
- headerSlot,
80
- footerSlot,
81
- toolbarSlot,
82
- listHeaderSlot,
83
- listFooterSlot,
84
- onRefresh,
85
- onFilter,
86
- onExport
87
- }: EntityManagementTemplateProps<T>) => {
88
-
89
- const [showCreateModal, setShowCreateModal] = useState(false);
90
- const [showEditModal, setShowEditModal] = useState(false);
91
- const [showDetailModal, setShowDetailModal] = useState(false);
92
- const [showFilterPanel, setShowFilterPanel] = useState(false);
93
- const [selectedItem, setSelectedItem] = useState<T | null>(null);
94
- const [selectedItems, setSelectedItems] = useState<T[]>([]);
95
-
96
- const handleCreate = async (item: Partial<T>) => {
97
- if (onCreate) {
98
- try {
99
- await onCreate(item);
100
- setShowCreateModal(false);
101
- onRefresh?.();
102
- } catch (error) {
103
- console.error('Create failed:', error);
104
- }
105
- }
106
- };
107
-
108
- const handleUpdate = async (item: Partial<T>) => {
109
- if (onUpdate && selectedItem) {
110
- try {
111
- await onUpdate(selectedItem.id, item);
112
- setShowEditModal(false);
113
- setSelectedItem(null);
114
- onRefresh?.();
115
- } catch (error) {
116
- console.error('Update failed:', error);
117
- }
118
- }
119
- };
120
-
121
-
122
- const handleBulkDelete = async () => {
123
- if (onBulkDelete && selectedItems.length > 0 && confirm(`Are you sure you want to delete ${selectedItems.length} ${config.display.title.toLowerCase()}?`)) {
124
- try {
125
- const ids = selectedItems.map(item => item.id);
126
- await onBulkDelete(ids);
127
- setSelectedItems([]);
128
- onRefresh?.();
129
- } catch (error) {
130
- console.error('Bulk delete failed:', error);
131
- }
132
- }
133
- };
134
-
135
- const handleView = (item: T) => {
136
- if (onView) {
137
- onView(item);
138
- } else {
139
- setSelectedItem(item);
140
- setShowDetailModal(true);
141
- }
142
- };
143
-
144
- const handleEdit = (item: T) => {
145
- setSelectedItem(item);
146
- setShowEditModal(true);
147
- };
148
-
149
- const handleRowClick = (item: T) => {
150
- handleView(item);
151
- };
152
-
153
- const handleAction = (action: ActionConfig, selectedItems: T[]) => {
154
- action.onClick({ selectedItems, config });
155
- };
156
-
157
- const generateDefaultActions = (): ActionConfig[] => {
158
- const actions: ActionConfig[] = [];
159
-
160
- if (onCreate) {
161
- actions.push({
162
- label: `Add ${config.display.title.slice(0, -1)}`,
163
- type: 'primary',
164
- icon: Plus,
165
- onClick: () => setShowCreateModal(true)
166
- });
167
- }
168
-
169
- return actions;
170
- };
171
-
172
-
173
- const renderPageHeader = () => {
174
- const actions = [
175
- ...generateDefaultActions(),
176
- ...(config.actions || [])
177
- ];
178
-
179
- return (
180
- <SectionHeader
181
- title={config.display.title}
182
- description={config.display.description}
183
- category={config.display.category}
184
- actions={
185
- <div className="flex items-center gap-2">
186
- {renderCustomActions && renderCustomActions()}
187
-
188
- {enableFiltering && (
189
- <Button
190
- variant="outline"
191
- size="sm"
192
- onClick={() => setShowFilterPanel(!showFilterPanel)}
193
- className={cn(showFilterPanel && "bg-muted")}
194
- >
195
- <Filter className="w-4 h-4 mr-2" />
196
- Filters
197
- </Button>
198
- )}
199
-
200
- {enableExport && (
201
- <Button
202
- variant="outline"
203
- size="sm"
204
- onClick={() => onExport?.(selectedItems.length > 0 ? selectedItems : data, 'csv')}
205
- disabled={data.length === 0}
206
- >
207
- <Download className="w-4 h-4 mr-2" />
208
- Export
209
- </Button>
210
- )}
211
-
212
- {actions.map(action => (
213
- <Button
214
- key={action.label}
215
- variant={action.type === 'primary' ? 'default' : 'outline'}
216
- size="sm"
217
- onClick={() => action.onClick({ data, config })}
218
- >
219
- {action.icon && <action.icon className="w-4 h-4 mr-2" />}
220
- {action.label}
221
- </Button>
222
- ))}
223
- </div>
224
- }
225
- />
226
- );
227
- };
228
-
229
- const renderToolbar = () => {
230
- if (selectedItems.length === 0) return null;
231
-
232
- return (
233
- <div className="bg-muted/50 p-4 rounded-lg mb-4">
234
- <div className="flex items-center justify-between">
235
- <div className="flex items-center gap-2">
236
- <Badge variant="secondary">
237
- {selectedItems.length} selected
238
- </Badge>
239
- <Button
240
- variant="ghost"
241
- size="sm"
242
- onClick={() => setSelectedItems([])}
243
- >
244
- Clear selection
245
- </Button>
246
- </div>
247
-
248
- <div className="flex items-center gap-2">
249
- {onBulkDelete && (
250
- <Button
251
- variant="destructive"
252
- size="sm"
253
- onClick={handleBulkDelete}
254
- >
255
- <Trash2 className="w-4 h-4 mr-2" />
256
- Delete Selected
257
- </Button>
258
- )}
259
-
260
- {enableExport && (
261
- <Button
262
- variant="outline"
263
- size="sm"
264
- onClick={() => onExport?.(selectedItems, 'csv')}
265
- >
266
- <Download className="w-4 h-4 mr-2" />
267
- Export Selected
268
- </Button>
269
- )}
270
- </div>
271
- </div>
272
- </div>
273
- );
274
- };
275
-
276
- const renderFilterPanelSection = () => {
277
- if (!showFilterPanel) return null;
278
-
279
- return (
280
- <Card className="p-4 mb-4">
281
- {renderFilterPanel ? (
282
- renderFilterPanel(onFilter || (() => {}))
283
- ) : (
284
- <div className="text-center text-muted-foreground py-4">
285
- Filter functionality not implemented yet
286
- </div>
287
- )}
288
- </Card>
289
- );
290
- };
291
-
292
- const renderCreateModal = () => {
293
- if (!showCreateModal) return null;
294
-
295
- return (
296
- <Modal
297
- isOpen={showCreateModal}
298
- onClose={() => setShowCreateModal(false)}
299
- title={`Create ${config.display.title.slice(0, -1)}`}
300
- category={config.display.category}
301
- >
302
- {renderCreateForm ? (
303
- renderCreateForm(handleCreate, () => setShowCreateModal(false))
304
- ) : (
305
- <div className="p-6 text-center text-muted-foreground">
306
- Create form not implemented yet
307
- </div>
308
- )}
309
- </Modal>
310
- );
311
- };
312
-
313
- const renderEditModal = () => {
314
- if (!showEditModal || !selectedItem) return null;
315
-
316
- return (
317
- <Modal
318
- isOpen={showEditModal}
319
- onClose={() => {
320
- setShowEditModal(false);
321
- setSelectedItem(null);
322
- }}
323
- title={`Edit ${config.display.title.slice(0, -1)}`}
324
- category={config.display.category}
325
- >
326
- {renderEditForm ? (
327
- renderEditForm(
328
- selectedItem,
329
- handleUpdate,
330
- () => {
331
- setShowEditModal(false);
332
- setSelectedItem(null);
333
- }
334
- )
335
- ) : (
336
- <div className="p-6 text-center text-muted-foreground">
337
- Edit form not implemented yet
338
- </div>
339
- )}
340
- </Modal>
341
- );
342
- };
343
-
344
- const renderDetailModal = () => {
345
- if (!showDetailModal || !selectedItem) return null;
346
-
347
- return (
348
- <Modal
349
- isOpen={showDetailModal}
350
- onClose={() => {
351
- setShowDetailModal(false);
352
- setSelectedItem(null);
353
- }}
354
- title={`${config.display.title.slice(0, -1)} Details`}
355
- category={config.display.category}
356
- size="large"
357
- >
358
- {renderDetailView ? (
359
- renderDetailView(
360
- selectedItem,
361
- () => {
362
- setShowDetailModal(false);
363
- handleEdit(selectedItem);
364
- },
365
- () => {
366
- setShowDetailModal(false);
367
- setSelectedItem(null);
368
- }
369
- )
370
- ) : (
371
- <div className="p-6">
372
- <div className="space-y-4">
373
- {Object.entries(selectedItem).map(([key, value]) => (
374
- <div key={key} className="flex justify-between">
375
- <span className="font-medium capitalize">{key}:</span>
376
- <span>{value?.toString() || '-'}</span>
377
- </div>
378
- ))}
379
- </div>
380
- </div>
381
- )}
382
- </Modal>
383
- );
384
- };
385
-
386
- return (
387
- <AppLayout>
388
- <PageTemplate
389
- className={cn('container mx-auto px-4 py-6', className)}
390
- data-component-name="EntityManagementTemplate"
391
- >
392
- {headerSlot}
393
- {renderPageHeader()}
394
- {toolbarSlot}
395
- {renderFilterPanelSection()}
396
- {renderToolbar()}
397
-
398
- <div className="space-y-4">
399
- {listHeaderSlot}
400
-
401
- <EntityListPanel
402
- config={config}
403
- data={data}
404
- isLoading={isLoading}
405
- onRowClick={handleRowClick}
406
- onAction={handleAction}
407
- enableSelection={enableBulkActions}
408
- enableBulkActions={enableBulkActions}
409
- enableExport={enableExport}
410
- enableRefresh={!!onRefresh}
411
- showSearch={enableSearch}
412
- showPagination={true}
413
- pageSize={pageSize}
414
- renderEmptyState={renderEmptyState}
415
- onExport={onExport}
416
- onRefresh={onRefresh}
417
- />
418
-
419
- {listFooterSlot}
420
- </div>
421
-
422
- {renderCreateModal()}
423
- {renderEditModal()}
424
- {renderDetailModal()}
425
-
426
- {footerSlot}
427
- </PageTemplate>
428
- </AppLayout>
429
- );
430
- };
@@ -1,277 +0,0 @@
1
- import React from 'react';
2
- import { AppLayout } from '../../molecules/layout/AppLayout';
3
- import { PageTemplate } from '../../molecules/layout/PageTemplate';
4
- import { SectionHeader } from '../../molecules/layout/SectionHeader';
5
- import {
6
- MetricsOverviewPanel,
7
- MetricsOverviewWithInsightsPanel,
8
- TrendAnalysisPanel,
9
- CategoryBreakdownPanel
10
- } from '../../organisms/entity';
11
- import type { EntityTemplateConfig, EntityData, MetricValue, ActionConfig, MetricConfig } from '../../atoms/types';
12
- import { cn } from '../../atoms/utils/utils';
13
-
14
- export interface EntityPerformanceDashboardTemplateProps<T extends EntityData> {
15
- config: EntityTemplateConfig<T>;
16
- data: T[];
17
- previousData?: T[];
18
- isLoading?: boolean;
19
- className?: string;
20
-
21
- // Layout configuration
22
- layout?: 'standard' | 'compact' | 'detailed';
23
- showInsights?: boolean;
24
- showTrends?: boolean;
25
- showCategories?: boolean;
26
-
27
- // Extension points - Render Props
28
- renderCustomActions?: () => React.ReactNode;
29
- renderAdditionalMetrics?: () => React.ReactNode;
30
- renderCustomTrendChart?: (config: EntityTemplateConfig<T>, data: T[]) => React.ReactNode;
31
- renderCustomCategoryView?: (config: EntityTemplateConfig<T>, data: T[]) => React.ReactNode;
32
-
33
- // Extension points - Slots
34
- headerSlot?: React.ReactNode;
35
- footerSlot?: React.ReactNode;
36
- metricsHeaderSlot?: React.ReactNode;
37
- metricsFooterSlot?: React.ReactNode;
38
- trendsHeaderSlot?: React.ReactNode;
39
- trendsFooterSlot?: React.ReactNode;
40
- categoriesHeaderSlot?: React.ReactNode;
41
- categoriesFooterSlot?: React.ReactNode;
42
-
43
- // Event handlers
44
- onMetricClick?: (metric: MetricConfig, value: MetricValue) => void;
45
- onCategoryClick?: (category: unknown) => void;
46
- onTrendPeriodChange?: (period: string) => void;
47
- onActionClick?: (action: ActionConfig, context: T[]) => void;
48
- onRefresh?: () => void;
49
- onExport?: (type: 'metrics' | 'trends' | 'categories', data: unknown) => void;
50
- }
51
-
52
- export const EntityPerformanceDashboardTemplate = <T extends EntityData>({
53
- config,
54
- data,
55
- previousData,
56
- isLoading = false,
57
- className,
58
- layout = 'standard',
59
- showInsights = true,
60
- showTrends = true,
61
- showCategories = true,
62
- renderCustomActions,
63
- renderAdditionalMetrics,
64
- renderCustomTrendChart,
65
- renderCustomCategoryView,
66
- headerSlot,
67
- footerSlot,
68
- metricsHeaderSlot,
69
- metricsFooterSlot,
70
- trendsHeaderSlot,
71
- trendsFooterSlot,
72
- categoriesHeaderSlot,
73
- categoriesFooterSlot,
74
- onMetricClick,
75
- onCategoryClick,
76
- onTrendPeriodChange,
77
- onExport
78
- }: EntityPerformanceDashboardTemplateProps<T>) => {
79
-
80
- const getLayoutClasses = () => {
81
- switch (layout) {
82
- case 'compact':
83
- return 'space-y-4';
84
- case 'detailed':
85
- return 'space-y-8';
86
- case 'standard':
87
- default:
88
- return 'space-y-6';
89
- }
90
- };
91
-
92
- const renderPageHeader = () => {
93
- return (
94
- <SectionHeader
95
- title={config.display.title}
96
- description={config.display.description}
97
- category={config.display.category}
98
- actions={renderCustomActions ? renderCustomActions() : undefined}
99
- />
100
- );
101
- };
102
-
103
- const renderMetricsSection = () => {
104
- if (!config.metrics.length) return null;
105
-
106
- const MetricsComponent = showInsights ? MetricsOverviewWithInsightsPanel : MetricsOverviewPanel;
107
-
108
- return (
109
- <section className="space-y-4">
110
- {metricsHeaderSlot}
111
- <MetricsComponent
112
- metrics={config.metrics}
113
- data={data}
114
- previousData={previousData}
115
- isLoading={isLoading}
116
- onMetricClick={onMetricClick}
117
- category={config.display.category}
118
- columns={layout === 'compact' ? 2 : layout === 'detailed' ? 4 : 3}
119
- headerSlot={renderAdditionalMetrics ? renderAdditionalMetrics() : undefined}
120
- />
121
- {metricsFooterSlot}
122
- </section>
123
- );
124
- };
125
-
126
- const renderTrendsSection = () => {
127
- if (!showTrends || !config.temporal || !config.metrics.length) return null;
128
-
129
- if (renderCustomTrendChart) {
130
- const customChart = renderCustomTrendChart(config, data);
131
- if (customChart) {
132
- return (
133
- <section className="space-y-4">
134
- {trendsHeaderSlot}
135
- {customChart}
136
- {trendsFooterSlot}
137
- </section>
138
- );
139
- }
140
- }
141
-
142
- return (
143
- <section className="space-y-4">
144
- {trendsHeaderSlot}
145
- <TrendAnalysisPanel
146
- metrics={config.metrics}
147
- data={data}
148
- temporal={config.temporal}
149
- isLoading={isLoading}
150
- category={config.display.category}
151
- enablePeriodSelection={true}
152
- enableChartTypeSelection={layout === 'detailed'}
153
- showComparisons={layout !== 'compact'}
154
- onPeriodChange={onTrendPeriodChange}
155
- onExport={(chartData, metric) => onExport?.('trends', { chartData, metric })}
156
- />
157
- {trendsFooterSlot}
158
- </section>
159
- );
160
- };
161
-
162
- const renderCategoriesSection = () => {
163
- if (!showCategories || !config.categories || !data.length) return null;
164
-
165
- // Find a suitable value field from metrics or use first numeric field
166
- const valueField = config.metrics.find(m => m.type === 'currency' || m.type === 'count')?.key ||
167
- Object.keys(data[0]).find(key => typeof data[0][key] === 'number') ||
168
- 'value';
169
-
170
- if (renderCustomCategoryView) {
171
- const customView = renderCustomCategoryView(config, data);
172
- if (customView) {
173
- return (
174
- <section className="space-y-4">
175
- {categoriesHeaderSlot}
176
- {customView}
177
- {categoriesFooterSlot}
178
- </section>
179
- );
180
- }
181
- }
182
-
183
- return (
184
- <section className="space-y-4">
185
- {categoriesHeaderSlot}
186
- <CategoryBreakdownPanel
187
- data={data}
188
- categoryConfig={config.categories}
189
- valueField={valueField}
190
- isLoading={isLoading}
191
- category={config.display.category}
192
- title={`${config.display.title} by ${config.categories.defaultGroupBy}`}
193
- defaultView={layout === 'compact' ? 'list' : 'both'}
194
- enableDrillDown={config.categories.enableDrillDown}
195
- onCategoryClick={onCategoryClick}
196
- onExport={(categoryData) => onExport?.('categories', categoryData)}
197
- />
198
- {categoriesFooterSlot}
199
- </section>
200
- );
201
- };
202
-
203
- const renderLayoutContent = () => {
204
- const sections = [
205
- renderMetricsSection(),
206
- renderTrendsSection(),
207
- renderCategoriesSection()
208
- ].filter(Boolean);
209
-
210
- if (layout === 'detailed' && sections.length > 1) {
211
- // For detailed layout, use a grid for trends and categories
212
- const [metricsSection, ...otherSections] = sections;
213
-
214
- return (
215
- <div className={getLayoutClasses()}>
216
- {metricsSection}
217
-
218
- {otherSections.length > 0 && (
219
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
220
- {otherSections}
221
- </div>
222
- )}
223
- </div>
224
- );
225
- }
226
-
227
- return (
228
- <div className={getLayoutClasses()}>
229
- {sections}
230
- </div>
231
- );
232
- };
233
-
234
- return (
235
- <AppLayout>
236
- <PageTemplate
237
- className={cn('container mx-auto px-4 py-6', className)}
238
- data-component-name="EntityPerformanceDashboardTemplate"
239
- >
240
- {headerSlot}
241
- {renderPageHeader()}
242
- {renderLayoutContent()}
243
- {footerSlot}
244
- </PageTemplate>
245
- </AppLayout>
246
- );
247
- };
248
-
249
- // Enhanced version with real-time updates
250
- export interface EntityPerformanceDashboardTemplateWithRealTimeProps<T extends EntityData>
251
- extends EntityPerformanceDashboardTemplateProps<T> {
252
- enableRealTime?: boolean;
253
- refreshInterval?: number;
254
- onDataUpdate?: (newData: T[]) => void;
255
- }
256
-
257
- export const EntityPerformanceDashboardTemplateWithRealTime = <T extends EntityData>({
258
- enableRealTime = false,
259
- refreshInterval = 30000, // 30 seconds
260
- onDataUpdate,
261
- ...props
262
- }: EntityPerformanceDashboardTemplateWithRealTimeProps<T>) => {
263
- React.useEffect(() => {
264
- if (!enableRealTime) return;
265
-
266
- const interval = setInterval(() => {
267
- if (onDataUpdate) {
268
- // This would typically fetch new data from an API
269
- onDataUpdate(props.data);
270
- }
271
- }, refreshInterval);
272
-
273
- return () => clearInterval(interval);
274
- }, [enableRealTime, refreshInterval, onDataUpdate, props.data]);
275
-
276
- return <EntityPerformanceDashboardTemplate {...props} />;
277
- };