@pattern-stack/frontend-patterns 0.0.4 → 0.0.6

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/SalesPanel/SalesPanel.d.ts +19 -0
  2. package/dist/atoms/composed/SalesPanel/SalesPanel.d.ts.map +1 -0
  3. package/dist/atoms/composed/SalesPanel/index.d.ts +2 -0
  4. package/dist/atoms/composed/SalesPanel/index.d.ts.map +1 -0
  5. package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts +63 -0
  6. package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts.map +1 -0
  7. package/dist/atoms/composed/index.d.ts +1 -0
  8. package/dist/atoms/composed/index.d.ts.map +1 -1
  9. package/dist/atoms/types/entity-config.d.ts +117 -0
  10. package/dist/atoms/types/entity-config.d.ts.map +1 -0
  11. package/dist/atoms/types/index.d.ts +2 -0
  12. package/dist/atoms/types/index.d.ts.map +1 -1
  13. package/dist/atoms/types/navigation.d.ts +30 -0
  14. package/dist/atoms/types/navigation.d.ts.map +1 -0
  15. package/dist/atoms/ui/ErrorBoundary.d.ts +1 -1
  16. package/dist/atoms/ui/button.d.ts +1 -1
  17. package/dist/atoms/utils/icon-resolver.d.ts +72 -0
  18. package/dist/atoms/utils/icon-resolver.d.ts.map +1 -0
  19. package/dist/atoms/utils/metric-engine.d.ts +30 -0
  20. package/dist/atoms/utils/metric-engine.d.ts.map +1 -0
  21. package/dist/atoms/utils/utils.d.ts +2 -0
  22. package/dist/atoms/utils/utils.d.ts.map +1 -1
  23. package/dist/features/auth/components/ProtectedRoute.d.ts +1 -1
  24. package/dist/frontend-patterns.css +1 -1
  25. package/dist/index.es.js +402 -14
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/index.js +402 -14
  28. package/dist/index.js.map +1 -1
  29. package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts +16 -0
  30. package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts.map +1 -0
  31. package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts +2 -0
  32. package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts.map +1 -0
  33. package/dist/molecules/layout/NavigationContext.d.ts +15 -0
  34. package/dist/molecules/layout/NavigationContext.d.ts.map +1 -0
  35. package/dist/molecules/layout/Sidebar.d.ts.map +1 -1
  36. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +2 -0
  37. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -1
  38. package/dist/molecules/layout/index.d.ts +3 -0
  39. package/dist/molecules/layout/index.d.ts.map +1 -1
  40. package/dist/templates/factory.d.ts +2 -1
  41. package/dist/templates/factory.d.ts.map +1 -1
  42. package/dist/templates/index.d.ts.map +1 -1
  43. package/package.json +7 -3
  44. package/src/App.tsx +11 -1
  45. package/src/__tests__/atoms/composed/databadge.test.tsx +106 -0
  46. package/src/__tests__/atoms/composed/statcard.test.tsx +133 -0
  47. package/src/__tests__/atoms/utils/icon-resolver.test.tsx +140 -0
  48. package/src/atoms/composed/SalesPanel/SalesPanel.tsx +116 -0
  49. package/src/atoms/composed/SalesPanel/index.ts +1 -0
  50. package/src/atoms/composed/SalesPanel/mockSalesData.ts +151 -0
  51. package/src/atoms/composed/index.ts +1 -0
  52. package/src/atoms/types/entity-config.ts +127 -0
  53. package/src/atoms/types/index.ts +3 -1
  54. package/src/atoms/types/navigation.ts +43 -0
  55. package/src/atoms/utils/icon-resolver.tsx +54 -0
  56. package/src/atoms/utils/metric-engine.ts +236 -0
  57. package/src/atoms/utils/utils.ts +4 -2
  58. package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +42 -0
  59. package/src/molecules/layout/DashboardWithSidePanel/index.ts +1 -0
  60. package/src/molecules/layout/NavigationContext.tsx +63 -0
  61. package/src/molecules/layout/Sidebar.tsx +10 -23
  62. package/src/molecules/layout/SidebarButton/SidebarButton.tsx +32 -10
  63. package/src/molecules/layout/index.ts +4 -1
  64. package/src/organisms/entity/CategoryBreakdownPanel.tsx +427 -0
  65. package/src/organisms/entity/EntityListPanel.tsx +339 -0
  66. package/src/organisms/entity/MetricsOverviewPanel.tsx +236 -0
  67. package/src/organisms/entity/TrendAnalysisPanel.tsx +337 -0
  68. package/src/organisms/entity/index.ts +4 -0
  69. package/src/organisms/index.ts +5 -1
  70. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +77 -75
  71. package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +158 -0
  72. package/src/pages/AdminShowcase/index.tsx +2 -1
  73. package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +137 -0
  74. package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +117 -0
  75. package/src/pages/EntityShowcase/index.ts +2 -0
  76. package/src/pages/EntityTemplateExample.tsx +229 -0
  77. package/src/pages/TestEntityTemplate.tsx +40 -0
  78. package/src/pages/index.ts +2 -1
  79. package/src/templates/entity/EntityManagementTemplate.tsx +430 -0
  80. package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +277 -0
  81. package/src/templates/entity/configs/financial-config.ts +141 -0
  82. package/src/templates/entity/configs/index.ts +1 -0
  83. package/src/templates/entity/index.ts +3 -0
  84. package/src/templates/factory.tsx +14 -7
  85. package/src/templates/financial/FinancialDashboardTemplate.tsx +326 -0
  86. package/src/templates/index.ts +4 -0
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+
3
+ // Simple test component to verify the entity templates work
4
+ export const TestEntityTemplate: React.FC = () => {
5
+ const testData = [
6
+ {
7
+ id: '1',
8
+ amount: 1000,
9
+ category: 'Test',
10
+ date: '2024-01-01'
11
+ }
12
+ ];
13
+
14
+ const testConfig = {
15
+ entityType: 'transactional' as const,
16
+ display: {
17
+ title: 'Test Dashboard',
18
+ category: 1 as const
19
+ },
20
+ metrics: [
21
+ {
22
+ key: 'amount',
23
+ label: 'Total Amount',
24
+ type: 'currency' as const
25
+ }
26
+ ]
27
+ };
28
+
29
+ return (
30
+ <div className="p-8">
31
+ <h1 className="text-2xl font-bold mb-4">Entity Template Test</h1>
32
+ <div className="bg-muted p-4 rounded-lg">
33
+ <p>Config: {JSON.stringify(testConfig, null, 2)}</p>
34
+ <p>Data: {JSON.stringify(testData, null, 2)}</p>
35
+ </div>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export default TestEntityTemplate;
@@ -1,2 +1,3 @@
1
1
  export { ComponentShowcase } from './ComponentShowcase';
2
- export * from './AdminShowcase';
2
+ export * from './AdminShowcase';
3
+ export * from './EntityShowcase';
@@ -0,0 +1,430 @@
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
+ };