@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,427 +0,0 @@
1
- import React, { useMemo, useState } from 'react';
2
- import { Chart } from '../../atoms/composed/Chart';
3
- import type { ChartDataPoint } from '../../atoms/composed/Chart';
4
- import { Card } from '../../atoms/ui/card';
5
- import { Button } from '../../atoms/ui/button';
6
- import { Badge } from '../../atoms/ui/Badge';
7
- import { DataBadge } from '../../atoms/composed/DataBadge';
8
- import { MetricCalculationEngine } from '../../atoms/utils/metric-engine';
9
- import type { CategoryConfig, EntityData, CategoryBreakdown } from '../../atoms/types';
10
- import { cn } from '../../atoms/utils/utils';
11
- import {
12
- ChevronRight,
13
- ChevronDown,
14
- PieChart,
15
- BarChart3,
16
- List,
17
- Download,
18
- Filter
19
- } from 'lucide-react';
20
-
21
- export interface CategoryBreakdownPanelProps {
22
- data: EntityData[];
23
- categoryConfig: CategoryConfig;
24
- valueField: string;
25
- isLoading?: boolean;
26
- className?: string;
27
- category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
28
-
29
- // Display configuration
30
- title?: string;
31
- subtitle?: string;
32
- defaultView?: 'chart' | 'list' | 'both';
33
- defaultChartType?: 'pie' | 'bar';
34
- maxCategories?: number;
35
- showPercentages?: boolean;
36
- enableDrillDown?: boolean;
37
-
38
- // Extension points
39
- renderHeader?: () => React.ReactNode;
40
- renderFooter?: () => React.ReactNode;
41
- renderCustomCategory?: (category: CategoryBreakdown, index: number) => React.ReactNode;
42
- renderDrillDownContent?: (category: string, subcategories: CategoryBreakdown[]) => React.ReactNode;
43
- headerSlot?: React.ReactNode;
44
- footerSlot?: React.ReactNode;
45
-
46
- // Event handlers
47
- onCategoryClick?: (category: CategoryBreakdown) => void;
48
- onDrillDown?: (category: string, level: number) => void;
49
- onExport?: (data: CategoryBreakdown[]) => void;
50
- }
51
-
52
- export const CategoryBreakdownPanel: React.FC<CategoryBreakdownPanelProps> = ({
53
- data,
54
- categoryConfig,
55
- valueField,
56
- isLoading = false,
57
- className,
58
- category,
59
- title = 'Category Breakdown',
60
- subtitle,
61
- defaultView = 'both',
62
- defaultChartType = 'pie',
63
- maxCategories = 8,
64
- showPercentages = true,
65
- enableDrillDown = true,
66
- renderHeader,
67
- renderFooter,
68
- renderCustomCategory,
69
- renderDrillDownContent,
70
- headerSlot,
71
- footerSlot,
72
- onCategoryClick,
73
- onDrillDown,
74
- onExport
75
- }) => {
76
- const [currentView, setCurrentView] = useState<'chart' | 'list' | 'both'>(defaultView);
77
- const [chartType, setChartType] = useState<'pie' | 'bar'>(defaultChartType);
78
- const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
79
- const [drillDownLevel, setDrillDownLevel] = useState(0);
80
- const [drillDownPath, setDrillDownPath] = useState<string[]>([]);
81
-
82
- // Calculate category breakdown
83
- const categoryBreakdown = useMemo(() => {
84
- if (!data.length) return [];
85
-
86
- const currentCategoryField = categoryConfig?.hierarchy?.[drillDownLevel] || categoryConfig?.defaultGroupBy || 'category';
87
-
88
- return MetricCalculationEngine.calculateCategoryBreakdown(
89
- data,
90
- currentCategoryField,
91
- valueField,
92
- maxCategories
93
- );
94
- }, [data, categoryConfig, valueField, maxCategories, drillDownLevel]);
95
-
96
- // Convert to chart data
97
- const chartData = useMemo((): ChartDataPoint[] => {
98
- return categoryBreakdown.map((item, index) => ({
99
- label: item.category,
100
- value: item.value,
101
- category: ((index % 8) + 1) as 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
102
- }));
103
- }, [categoryBreakdown]);
104
-
105
- // Calculate total value
106
- const totalValue = useMemo(() => {
107
- return categoryBreakdown.reduce((sum, item) => sum + item.value, 0);
108
- }, [categoryBreakdown]);
109
-
110
- const handleCategoryClick = (categoryItem: CategoryBreakdown) => {
111
- onCategoryClick?.(categoryItem);
112
-
113
- if (enableDrillDown && categoryConfig?.enableDrillDown && drillDownLevel < (categoryConfig?.hierarchy?.length || 0) - 1) {
114
- setDrillDownLevel(prev => prev + 1);
115
- setDrillDownPath(prev => [...prev, categoryItem.category]);
116
- onDrillDown?.(categoryItem.category, drillDownLevel + 1);
117
- }
118
- };
119
-
120
- const handleDrillUp = (level: number) => {
121
- if (level < drillDownLevel) {
122
- setDrillDownLevel(level);
123
- setDrillDownPath(prev => prev.slice(0, level));
124
- onDrillDown?.(level > 0 ? drillDownPath[level - 1] : '', level);
125
- }
126
- };
127
-
128
- const toggleCategoryExpansion = (categoryName: string) => {
129
- setExpandedCategories(prev => {
130
- const newSet = new Set(prev);
131
- if (newSet.has(categoryName)) {
132
- newSet.delete(categoryName);
133
- } else {
134
- newSet.add(categoryName);
135
- }
136
- return newSet;
137
- });
138
- };
139
-
140
- const handleExport = () => {
141
- if (onExport) {
142
- onExport(categoryBreakdown);
143
- } else {
144
- // Default CSV export
145
- const csvContent = [
146
- 'Category,Value,Percentage',
147
- ...categoryBreakdown.map(item =>
148
- `${item.category},${item.value},${item.percentage.toFixed(2)}%`
149
- )
150
- ].join('\n');
151
-
152
- const blob = new Blob([csvContent], { type: 'text/csv' });
153
- const url = URL.createObjectURL(blob);
154
- const a = document.createElement('a');
155
- a.href = url;
156
- a.download = `category-breakdown-${Date.now()}.csv`;
157
- a.click();
158
- URL.revokeObjectURL(url);
159
- }
160
- };
161
-
162
- const renderControls = () => {
163
- return (
164
- <div className="flex items-center justify-between mb-4">
165
- <div className="flex items-center gap-4">
166
- <div className="flex items-center gap-2">
167
- <PieChart className="w-4 h-4 text-muted-foreground" />
168
- <h3 className="text-lg font-semibold text-foreground">{title}</h3>
169
- </div>
170
-
171
- {subtitle && (
172
- <Badge variant="outline" className="text-xs">
173
- {subtitle}
174
- </Badge>
175
- )}
176
-
177
- {totalValue > 0 && (
178
- <Badge variant="outline">
179
- Total: {new Intl.NumberFormat().format(totalValue)}
180
- </Badge>
181
- )}
182
- </div>
183
-
184
- <div className="flex items-center gap-2">
185
- {/* View toggle */}
186
- <div className="flex items-center border rounded">
187
- <Button
188
- variant={currentView === 'chart' ? 'default' : 'ghost'}
189
- size="sm"
190
- onClick={() => setCurrentView('chart')}
191
- >
192
- <PieChart className="w-4 h-4" />
193
- </Button>
194
- <Button
195
- variant={currentView === 'list' ? 'default' : 'ghost'}
196
- size="sm"
197
- onClick={() => setCurrentView('list')}
198
- >
199
- <List className="w-4 h-4" />
200
- </Button>
201
- <Button
202
- variant={currentView === 'both' ? 'default' : 'ghost'}
203
- size="sm"
204
- onClick={() => setCurrentView('both')}
205
- >
206
- Both
207
- </Button>
208
- </div>
209
-
210
- {/* Chart type toggle */}
211
- {(currentView === 'chart' || currentView === 'both') && (
212
- <div className="flex items-center border rounded">
213
- <Button
214
- variant={chartType === 'pie' ? 'default' : 'ghost'}
215
- size="sm"
216
- onClick={() => setChartType('pie')}
217
- >
218
- <PieChart className="w-4 h-4" />
219
- </Button>
220
- <Button
221
- variant={chartType === 'bar' ? 'default' : 'ghost'}
222
- size="sm"
223
- onClick={() => setChartType('bar')}
224
- >
225
- <BarChart3 className="w-4 h-4" />
226
- </Button>
227
- </div>
228
- )}
229
-
230
- <Button
231
- variant="outline"
232
- size="sm"
233
- onClick={handleExport}
234
- disabled={categoryBreakdown.length === 0}
235
- >
236
- <Download className="w-4 h-4" />
237
- </Button>
238
- </div>
239
- </div>
240
- );
241
- };
242
-
243
- const renderBreadcrumb = () => {
244
- if (drillDownLevel === 0) return null;
245
-
246
- return (
247
- <div className="flex items-center gap-2 mb-4 text-sm text-muted-foreground">
248
- <Button
249
- variant="ghost"
250
- size="sm"
251
- onClick={() => handleDrillUp(0)}
252
- className="p-0 h-auto font-normal"
253
- >
254
- All
255
- </Button>
256
- {drillDownPath.map((segment, index) => (
257
- <React.Fragment key={index}>
258
- <ChevronRight className="w-3 h-3" />
259
- <Button
260
- variant="ghost"
261
- size="sm"
262
- onClick={() => handleDrillUp(index + 1)}
263
- className="p-0 h-auto font-normal"
264
- >
265
- {segment}
266
- </Button>
267
- </React.Fragment>
268
- ))}
269
- </div>
270
- );
271
- };
272
-
273
- const renderChart = () => {
274
- if (currentView === 'list') return null;
275
-
276
- return (
277
- <div className={cn(currentView === 'both' && 'mb-6')}>
278
- <Chart
279
- title=""
280
- data={chartData}
281
- type={chartType}
282
- category={category}
283
- showLegend={true}
284
- height="medium"
285
- isLoading={isLoading}
286
- noWrapper={true}
287
- onClick={() => {}}
288
- />
289
- </div>
290
- );
291
- };
292
-
293
- const renderCategoryList = () => {
294
- if (currentView === 'chart') return null;
295
-
296
- return (
297
- <div className="space-y-2">
298
- {categoryBreakdown.map((item, index) => {
299
- if (renderCustomCategory) {
300
- const customContent = renderCustomCategory(item, index);
301
- if (customContent) return customContent;
302
- }
303
-
304
- const canExpand = enableDrillDown && categoryConfig.enableDrillDown && 'subcategories' in item && item.subcategories && Array.isArray(item.subcategories) && item.subcategories.length > 0;
305
- const isExpanded = expandedCategories.has(item.category);
306
-
307
- return (
308
- <div key={item.category} className="border rounded-lg">
309
- <div
310
- className={cn(
311
- "flex items-center justify-between p-3 cursor-pointer hover:bg-muted/50",
312
- canExpand && "cursor-pointer"
313
- )}
314
- onClick={() => handleCategoryClick(item)}
315
- >
316
- <div className="flex items-center gap-3">
317
- {canExpand && (
318
- <Button
319
- variant="ghost"
320
- size="sm"
321
- className="p-0 w-4 h-4"
322
- onClick={(e) => {
323
- e.stopPropagation();
324
- toggleCategoryExpansion(item.category);
325
- }}
326
- >
327
- {isExpanded ? (
328
- <ChevronDown className="w-3 h-3" />
329
- ) : (
330
- <ChevronRight className="w-3 h-3" />
331
- )}
332
- </Button>
333
- )}
334
-
335
- <DataBadge
336
- variant="category"
337
- category={((index % 8) + 1) as 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8}
338
- size="sm"
339
- >
340
- {item.category}
341
- </DataBadge>
342
- </div>
343
-
344
- <div className="flex items-center gap-3">
345
- <div className="text-right">
346
- <div className="font-semibold">
347
- {new Intl.NumberFormat().format(item.value)}
348
- </div>
349
- {showPercentages && (
350
- <div className="text-xs text-muted-foreground">
351
- {item.percentage.toFixed(1)}%
352
- </div>
353
- )}
354
- </div>
355
-
356
- {/* Progress bar */}
357
- <div className="w-20 h-2 bg-muted rounded-full overflow-hidden">
358
- <div
359
- className={cn(
360
- "h-full transition-all duration-300",
361
- `bg-category-${((index % 8) + 1)}`
362
- )}
363
- style={{ width: `${item.percentage}%` }}
364
- />
365
- </div>
366
- </div>
367
- </div>
368
-
369
- {/* Subcategories */}
370
- {canExpand && isExpanded && 'subcategories' in item && item.subcategories && (
371
- <div className="border-t bg-muted/20">
372
- {renderDrillDownContent ? (
373
- renderDrillDownContent(item.category, item.subcategories)
374
- ) : (
375
- <div className="p-3 space-y-1">
376
- {Array.isArray(item.subcategories) && item.subcategories.map((subItem: CategoryBreakdown) => (
377
- <div key={subItem.category} className="flex items-center justify-between text-sm pl-4">
378
- <span className="text-muted-foreground">{subItem.category}</span>
379
- <span className="font-medium">{new Intl.NumberFormat().format(subItem.value)}</span>
380
- </div>
381
- ))}
382
- </div>
383
- )}
384
- </div>
385
- )}
386
- </div>
387
- );
388
- })}
389
- </div>
390
- );
391
- };
392
-
393
- const renderEmptyState = () => {
394
- if (categoryBreakdown.length > 0) return null;
395
-
396
- return (
397
- <div className="text-center py-12">
398
- <Filter className="w-8 h-8 text-muted-foreground mx-auto mb-4" />
399
- <div className="text-muted-foreground">
400
- No category data available
401
- </div>
402
- </div>
403
- );
404
- };
405
-
406
- return (
407
- <Card className={cn('p-6', className)} category={category} data-component-name="CategoryBreakdownPanel">
408
- {headerSlot}
409
- {renderHeader && renderHeader()}
410
-
411
- {renderControls()}
412
- {renderBreadcrumb()}
413
-
414
- {categoryBreakdown.length === 0 ? (
415
- renderEmptyState()
416
- ) : (
417
- <>
418
- {renderChart()}
419
- {renderCategoryList()}
420
- </>
421
- )}
422
-
423
- {renderFooter && renderFooter()}
424
- {footerSlot}
425
- </Card>
426
- );
427
- };