@pattern-stack/frontend-patterns 0.0.4 → 0.0.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.
Files changed (283) hide show
  1. package/dist/frontend-patterns.css +1 -1
  2. package/dist/index.es.js +1917 -2
  3. package/dist/index.es.js.map +1 -1
  4. package/dist/index.js +1916 -1
  5. package/dist/index.js.map +1 -1
  6. package/package.json +6 -3
  7. package/src/App.tsx +11 -1
  8. package/src/atoms/composed/SalesPanel/SalesPanel.tsx +116 -0
  9. package/src/atoms/composed/SalesPanel/index.ts +1 -0
  10. package/src/atoms/composed/SalesPanel/mockSalesData.ts +151 -0
  11. package/src/atoms/composed/index.ts +1 -0
  12. package/src/atoms/types/entity-config.ts +127 -0
  13. package/src/atoms/types/index.ts +2 -1
  14. package/src/atoms/utils/metric-engine.ts +236 -0
  15. package/src/atoms/utils/utils.ts +2 -1
  16. package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +42 -0
  17. package/src/molecules/layout/DashboardWithSidePanel/index.ts +1 -0
  18. package/src/molecules/layout/Sidebar.tsx +10 -2
  19. package/src/molecules/layout/index.ts +1 -0
  20. package/src/organisms/entity/CategoryBreakdownPanel.tsx +427 -0
  21. package/src/organisms/entity/EntityListPanel.tsx +339 -0
  22. package/src/organisms/entity/MetricsOverviewPanel.tsx +236 -0
  23. package/src/organisms/entity/TrendAnalysisPanel.tsx +337 -0
  24. package/src/organisms/entity/index.ts +4 -0
  25. package/src/organisms/index.ts +4 -1
  26. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +77 -75
  27. package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +158 -0
  28. package/src/pages/AdminShowcase/index.tsx +2 -1
  29. package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +137 -0
  30. package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +117 -0
  31. package/src/pages/EntityShowcase/index.ts +2 -0
  32. package/src/pages/EntityTemplateExample.tsx +229 -0
  33. package/src/pages/TestEntityTemplate.tsx +40 -0
  34. package/src/pages/index.ts +2 -1
  35. package/src/templates/entity/EntityManagementTemplate.tsx +430 -0
  36. package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +277 -0
  37. package/src/templates/entity/configs/financial-config.ts +141 -0
  38. package/src/templates/entity/configs/index.ts +1 -0
  39. package/src/templates/entity/index.ts +3 -0
  40. package/src/templates/financial/FinancialDashboardTemplate.tsx +326 -0
  41. package/src/templates/index.ts +3 -0
  42. package/dist/atoms/composed/Accordion/Accordion.d.ts +0 -20
  43. package/dist/atoms/composed/Accordion/Accordion.d.ts.map +0 -1
  44. package/dist/atoms/composed/Accordion/index.d.ts +0 -2
  45. package/dist/atoms/composed/Accordion/index.d.ts.map +0 -1
  46. package/dist/atoms/composed/Alert/Alert.d.ts +0 -25
  47. package/dist/atoms/composed/Alert/Alert.d.ts.map +0 -1
  48. package/dist/atoms/composed/Alert/index.d.ts +0 -2
  49. package/dist/atoms/composed/Alert/index.d.ts.map +0 -1
  50. package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts +0 -17
  51. package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +0 -1
  52. package/dist/atoms/composed/Breadcrumb/index.d.ts +0 -2
  53. package/dist/atoms/composed/Breadcrumb/index.d.ts.map +0 -1
  54. package/dist/atoms/composed/Chart/Chart.d.ts +0 -37
  55. package/dist/atoms/composed/Chart/Chart.d.ts.map +0 -1
  56. package/dist/atoms/composed/Chart/index.d.ts +0 -3
  57. package/dist/atoms/composed/Chart/index.d.ts.map +0 -1
  58. package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts +0 -19
  59. package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +0 -1
  60. package/dist/atoms/composed/ColorSwatch/index.d.ts +0 -2
  61. package/dist/atoms/composed/ColorSwatch/index.d.ts.map +0 -1
  62. package/dist/atoms/composed/DarkModeToggle.d.ts +0 -4
  63. package/dist/atoms/composed/DarkModeToggle.d.ts.map +0 -1
  64. package/dist/atoms/composed/DataBadge/DataBadge.d.ts +0 -13
  65. package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +0 -1
  66. package/dist/atoms/composed/DataBadge/index.d.ts +0 -2
  67. package/dist/atoms/composed/DataBadge/index.d.ts.map +0 -1
  68. package/dist/atoms/composed/DataTable/DataTable.d.ts +0 -28
  69. package/dist/atoms/composed/DataTable/DataTable.d.ts.map +0 -1
  70. package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts +0 -10
  71. package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +0 -1
  72. package/dist/atoms/composed/DataTable/index.d.ts +0 -3
  73. package/dist/atoms/composed/DataTable/index.d.ts.map +0 -1
  74. package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts +0 -45
  75. package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +0 -1
  76. package/dist/atoms/composed/DateTimePicker/index.d.ts +0 -3
  77. package/dist/atoms/composed/DateTimePicker/index.d.ts.map +0 -1
  78. package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts +0 -30
  79. package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +0 -1
  80. package/dist/atoms/composed/DetailedCard/index.d.ts +0 -3
  81. package/dist/atoms/composed/DetailedCard/index.d.ts.map +0 -1
  82. package/dist/atoms/composed/EmptyState/EmptyState.d.ts +0 -18
  83. package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +0 -1
  84. package/dist/atoms/composed/EmptyState/index.d.ts +0 -2
  85. package/dist/atoms/composed/EmptyState/index.d.ts.map +0 -1
  86. package/dist/atoms/composed/FileUpload/FileUpload.d.ts +0 -46
  87. package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +0 -1
  88. package/dist/atoms/composed/FileUpload/index.d.ts +0 -3
  89. package/dist/atoms/composed/FileUpload/index.d.ts.map +0 -1
  90. package/dist/atoms/composed/FormField/FormField.d.ts +0 -23
  91. package/dist/atoms/composed/FormField/FormField.d.ts.map +0 -1
  92. package/dist/atoms/composed/FormField/index.d.ts +0 -2
  93. package/dist/atoms/composed/FormField/index.d.ts.map +0 -1
  94. package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts +0 -8
  95. package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +0 -1
  96. package/dist/atoms/composed/GlobalSearch/index.d.ts +0 -2
  97. package/dist/atoms/composed/GlobalSearch/index.d.ts.map +0 -1
  98. package/dist/atoms/composed/IconBadge/IconBadge.d.ts +0 -16
  99. package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +0 -1
  100. package/dist/atoms/composed/IconBadge/index.d.ts +0 -3
  101. package/dist/atoms/composed/IconBadge/index.d.ts.map +0 -1
  102. package/dist/atoms/composed/Modal/Modal.d.ts +0 -18
  103. package/dist/atoms/composed/Modal/Modal.d.ts.map +0 -1
  104. package/dist/atoms/composed/Modal/index.d.ts +0 -3
  105. package/dist/atoms/composed/Modal/index.d.ts.map +0 -1
  106. package/dist/atoms/composed/PaletteSwitcher.d.ts +0 -7
  107. package/dist/atoms/composed/PaletteSwitcher.d.ts.map +0 -1
  108. package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts +0 -25
  109. package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +0 -1
  110. package/dist/atoms/composed/ProgressBar/index.d.ts +0 -2
  111. package/dist/atoms/composed/ProgressBar/index.d.ts.map +0 -1
  112. package/dist/atoms/composed/StatCard/StatCard.d.ts +0 -21
  113. package/dist/atoms/composed/StatCard/StatCard.d.ts.map +0 -1
  114. package/dist/atoms/composed/StatCard/index.d.ts +0 -2
  115. package/dist/atoms/composed/StatCard/index.d.ts.map +0 -1
  116. package/dist/atoms/composed/StyleGuide.d.ts +0 -3
  117. package/dist/atoms/composed/StyleGuide.d.ts.map +0 -1
  118. package/dist/atoms/composed/Toast/Toast.d.ts +0 -40
  119. package/dist/atoms/composed/Toast/Toast.d.ts.map +0 -1
  120. package/dist/atoms/composed/Toast/index.d.ts +0 -2
  121. package/dist/atoms/composed/Toast/index.d.ts.map +0 -1
  122. package/dist/atoms/composed/Tooltip/Tooltip.d.ts +0 -16
  123. package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +0 -1
  124. package/dist/atoms/composed/Tooltip/index.d.ts +0 -2
  125. package/dist/atoms/composed/Tooltip/index.d.ts.map +0 -1
  126. package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts +0 -8
  127. package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +0 -1
  128. package/dist/atoms/composed/UserAvatar/index.d.ts +0 -2
  129. package/dist/atoms/composed/UserAvatar/index.d.ts.map +0 -1
  130. package/dist/atoms/composed/UserMenu/UserMenu.d.ts +0 -8
  131. package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +0 -1
  132. package/dist/atoms/composed/UserMenu/index.d.ts +0 -2
  133. package/dist/atoms/composed/UserMenu/index.d.ts.map +0 -1
  134. package/dist/atoms/composed/index.d.ts +0 -25
  135. package/dist/atoms/composed/index.d.ts.map +0 -1
  136. package/dist/atoms/hooks/useApi.d.ts +0 -25
  137. package/dist/atoms/hooks/useApi.d.ts.map +0 -1
  138. package/dist/atoms/hooks/useHealth.d.ts +0 -19
  139. package/dist/atoms/hooks/useHealth.d.ts.map +0 -1
  140. package/dist/atoms/index.d.ts +0 -9
  141. package/dist/atoms/index.d.ts.map +0 -1
  142. package/dist/atoms/services/api/client.d.ts +0 -20
  143. package/dist/atoms/services/api/client.d.ts.map +0 -1
  144. package/dist/atoms/services/auth-service.d.ts +0 -24
  145. package/dist/atoms/services/auth-service.d.ts.map +0 -1
  146. package/dist/atoms/services/health.d.ts +0 -7
  147. package/dist/atoms/services/health.d.ts.map +0 -1
  148. package/dist/atoms/services/index.d.ts +0 -4
  149. package/dist/atoms/services/index.d.ts.map +0 -1
  150. package/dist/atoms/shared/config/constants.d.ts +0 -15
  151. package/dist/atoms/shared/config/constants.d.ts.map +0 -1
  152. package/dist/atoms/shared/config/dashboard-sizes.d.ts +0 -83
  153. package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +0 -1
  154. package/dist/atoms/shared/config/environment.d.ts +0 -10
  155. package/dist/atoms/shared/config/environment.d.ts.map +0 -1
  156. package/dist/atoms/shared/index.d.ts +0 -4
  157. package/dist/atoms/shared/index.d.ts.map +0 -1
  158. package/dist/atoms/types/auth.d.ts +0 -56
  159. package/dist/atoms/types/auth.d.ts.map +0 -1
  160. package/dist/atoms/types/generated.d.ts +0 -1469
  161. package/dist/atoms/types/generated.d.ts.map +0 -1
  162. package/dist/atoms/types/index.d.ts +0 -4
  163. package/dist/atoms/types/index.d.ts.map +0 -1
  164. package/dist/atoms/types/loading.d.ts +0 -26
  165. package/dist/atoms/types/loading.d.ts.map +0 -1
  166. package/dist/atoms/ui/Badge.d.ts +0 -10
  167. package/dist/atoms/ui/Badge.d.ts.map +0 -1
  168. package/dist/atoms/ui/ErrorBoundary.d.ts +0 -18
  169. package/dist/atoms/ui/ErrorBoundary.d.ts.map +0 -1
  170. package/dist/atoms/ui/Select.d.ts +0 -28
  171. package/dist/atoms/ui/Select.d.ts.map +0 -1
  172. package/dist/atoms/ui/Switch.d.ts +0 -9
  173. package/dist/atoms/ui/Switch.d.ts.map +0 -1
  174. package/dist/atoms/ui/Tabs.d.ts +0 -30
  175. package/dist/atoms/ui/Tabs.d.ts.map +0 -1
  176. package/dist/atoms/ui/avatar.d.ts +0 -7
  177. package/dist/atoms/ui/avatar.d.ts.map +0 -1
  178. package/dist/atoms/ui/button.d.ts +0 -14
  179. package/dist/atoms/ui/button.d.ts.map +0 -1
  180. package/dist/atoms/ui/card.d.ts +0 -12
  181. package/dist/atoms/ui/card.d.ts.map +0 -1
  182. package/dist/atoms/ui/dropdown-menu.d.ts +0 -28
  183. package/dist/atoms/ui/dropdown-menu.d.ts.map +0 -1
  184. package/dist/atoms/ui/index.d.ts +0 -15
  185. package/dist/atoms/ui/index.d.ts.map +0 -1
  186. package/dist/atoms/ui/input.d.ts +0 -5
  187. package/dist/atoms/ui/input.d.ts.map +0 -1
  188. package/dist/atoms/ui/label.d.ts +0 -6
  189. package/dist/atoms/ui/label.d.ts.map +0 -1
  190. package/dist/atoms/ui/skeleton.d.ts +0 -3
  191. package/dist/atoms/ui/skeleton.d.ts.map +0 -1
  192. package/dist/atoms/ui/spinner.d.ts +0 -14
  193. package/dist/atoms/ui/spinner.d.ts.map +0 -1
  194. package/dist/atoms/ui/table.d.ts +0 -11
  195. package/dist/atoms/ui/table.d.ts.map +0 -1
  196. package/dist/atoms/utils/animations.d.ts +0 -65
  197. package/dist/atoms/utils/animations.d.ts.map +0 -1
  198. package/dist/atoms/utils/tooltip-helpers.d.ts +0 -71
  199. package/dist/atoms/utils/tooltip-helpers.d.ts.map +0 -1
  200. package/dist/atoms/utils/utils.d.ts +0 -4
  201. package/dist/atoms/utils/utils.d.ts.map +0 -1
  202. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  203. package/dist/features/auth/components/LoginForm.d.ts.map +0 -1
  204. package/dist/features/auth/components/LogoutButton.d.ts +0 -2
  205. package/dist/features/auth/components/LogoutButton.d.ts.map +0 -1
  206. package/dist/features/auth/components/ProtectedRoute.d.ts +0 -10
  207. package/dist/features/auth/components/ProtectedRoute.d.ts.map +0 -1
  208. package/dist/features/auth/components/index.d.ts +0 -4
  209. package/dist/features/auth/components/index.d.ts.map +0 -1
  210. package/dist/features/auth/hooks/index.d.ts +0 -3
  211. package/dist/features/auth/hooks/index.d.ts.map +0 -1
  212. package/dist/features/auth/hooks/useAuth.d.ts +0 -10
  213. package/dist/features/auth/hooks/useAuth.d.ts.map +0 -1
  214. package/dist/features/auth/hooks/usePermissions.d.ts +0 -13
  215. package/dist/features/auth/hooks/usePermissions.d.ts.map +0 -1
  216. package/dist/features/auth/index.d.ts +0 -3
  217. package/dist/features/auth/index.d.ts.map +0 -1
  218. package/dist/features/index.d.ts +0 -2
  219. package/dist/features/index.d.ts.map +0 -1
  220. package/dist/index.d.ts +0 -10
  221. package/dist/index.d.ts.map +0 -1
  222. package/dist/molecules/forms/FormGroup.d.ts +0 -17
  223. package/dist/molecules/forms/FormGroup.d.ts.map +0 -1
  224. package/dist/molecules/forms/SearchInput.d.ts +0 -36
  225. package/dist/molecules/forms/SearchInput.d.ts.map +0 -1
  226. package/dist/molecules/forms/index.d.ts +0 -3
  227. package/dist/molecules/forms/index.d.ts.map +0 -1
  228. package/dist/molecules/index.d.ts +0 -4
  229. package/dist/molecules/index.d.ts.map +0 -1
  230. package/dist/molecules/layout/AppHeader/AppHeader.d.ts +0 -7
  231. package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +0 -1
  232. package/dist/molecules/layout/AppHeader/index.d.ts +0 -2
  233. package/dist/molecules/layout/AppHeader/index.d.ts.map +0 -1
  234. package/dist/molecules/layout/AppLayout.d.ts +0 -2
  235. package/dist/molecules/layout/AppLayout.d.ts.map +0 -1
  236. package/dist/molecules/layout/PageTemplate.d.ts +0 -19
  237. package/dist/molecules/layout/PageTemplate.d.ts.map +0 -1
  238. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +0 -24
  239. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +0 -1
  240. package/dist/molecules/layout/SectionHeader/index.d.ts +0 -2
  241. package/dist/molecules/layout/SectionHeader/index.d.ts.map +0 -1
  242. package/dist/molecules/layout/ShowcaseSection.d.ts +0 -22
  243. package/dist/molecules/layout/ShowcaseSection.d.ts.map +0 -1
  244. package/dist/molecules/layout/Sidebar.d.ts +0 -6
  245. package/dist/molecules/layout/Sidebar.d.ts.map +0 -1
  246. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +0 -13
  247. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +0 -1
  248. package/dist/molecules/layout/SidebarButton/index.d.ts +0 -2
  249. package/dist/molecules/layout/SidebarButton/index.d.ts.map +0 -1
  250. package/dist/molecules/layout/SidebarContext.d.ts +0 -12
  251. package/dist/molecules/layout/SidebarContext.d.ts.map +0 -1
  252. package/dist/molecules/layout/index.d.ts +0 -8
  253. package/dist/molecules/layout/index.d.ts.map +0 -1
  254. package/dist/molecules/navigation/NavMenu.d.ts +0 -20
  255. package/dist/molecules/navigation/NavMenu.d.ts.map +0 -1
  256. package/dist/molecules/navigation/Pagination.d.ts +0 -14
  257. package/dist/molecules/navigation/Pagination.d.ts.map +0 -1
  258. package/dist/molecules/navigation/index.d.ts +0 -3
  259. package/dist/molecules/navigation/index.d.ts.map +0 -1
  260. package/dist/organisms/index.d.ts +0 -2
  261. package/dist/organisms/index.d.ts.map +0 -1
  262. package/dist/organisms/showcase/ComponentShowcasePage.d.ts +0 -3
  263. package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +0 -1
  264. package/dist/templates/AuthTemplate.d.ts +0 -68
  265. package/dist/templates/AuthTemplate.d.ts.map +0 -1
  266. package/dist/templates/ComponentShowcaseTemplate.d.ts +0 -53
  267. package/dist/templates/ComponentShowcaseTemplate.d.ts.map +0 -1
  268. package/dist/templates/DashboardTemplate.d.ts +0 -62
  269. package/dist/templates/DashboardTemplate.d.ts.map +0 -1
  270. package/dist/templates/DataTemplate.d.ts +0 -78
  271. package/dist/templates/DataTemplate.d.ts.map +0 -1
  272. package/dist/templates/admin/AdminCRUDTemplate.d.ts +0 -105
  273. package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +0 -1
  274. package/dist/templates/admin/AdminDashboardTemplate.d.ts +0 -89
  275. package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +0 -1
  276. package/dist/templates/admin/AdminDetailTemplate.d.ts +0 -132
  277. package/dist/templates/admin/AdminDetailTemplate.d.ts.map +0 -1
  278. package/dist/templates/admin/index.d.ts +0 -4
  279. package/dist/templates/admin/index.d.ts.map +0 -1
  280. package/dist/templates/factory.d.ts +0 -28
  281. package/dist/templates/factory.d.ts.map +0 -1
  282. package/dist/templates/index.d.ts +0 -7
  283. package/dist/templates/index.d.ts.map +0 -1
@@ -0,0 +1,427 @@
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
+ };