@pattern-stack/frontend-patterns 0.0.3 → 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 (406) hide show
  1. package/dist/frontend-patterns.css +1 -1
  2. package/dist/index.es.js +1918 -3
  3. package/dist/index.es.js.map +1 -1
  4. package/dist/index.js +1917 -1
  5. package/dist/index.js.map +1 -1
  6. package/package.json +10 -5
  7. package/src/App.css +42 -0
  8. package/src/App.tsx +64 -0
  9. package/src/__tests__/README.md +221 -0
  10. package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
  11. package/src/__tests__/atoms/ui/button.test.tsx +68 -0
  12. package/src/__tests__/atoms/utils/simple.test.ts +18 -0
  13. package/src/__tests__/atoms/utils/utils.test.ts +77 -0
  14. package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
  15. package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
  16. package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
  17. package/src/__tests__/setup.ts +51 -0
  18. package/src/__tests__/utils.tsx +123 -0
  19. package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
  20. package/{dist/atoms/composed/Accordion/index.d.ts → src/atoms/composed/Accordion/index.ts} +1 -2
  21. package/src/atoms/composed/Alert/Alert.tsx +132 -0
  22. package/src/atoms/composed/Alert/index.ts +1 -0
  23. package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
  24. package/src/atoms/composed/Breadcrumb/index.ts +1 -0
  25. package/src/atoms/composed/Chart/Chart.tsx +425 -0
  26. package/{dist/atoms/composed/Chart/index.d.ts → src/atoms/composed/Chart/index.ts} +1 -2
  27. package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
  28. package/{dist/atoms/composed/ColorSwatch/index.d.ts → src/atoms/composed/ColorSwatch/index.ts} +1 -2
  29. package/src/atoms/composed/DarkModeToggle.tsx +66 -0
  30. package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
  31. package/src/atoms/composed/DataBadge/index.ts +1 -0
  32. package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
  33. package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
  34. package/src/atoms/composed/DataTable/index.ts +2 -0
  35. package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
  36. package/src/atoms/composed/DateTimePicker/index.ts +2 -0
  37. package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
  38. package/src/atoms/composed/DetailedCard/index.ts +2 -0
  39. package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
  40. package/src/atoms/composed/EmptyState/index.ts +1 -0
  41. package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
  42. package/{dist/atoms/composed/FileUpload/index.d.ts → src/atoms/composed/FileUpload/index.ts} +1 -2
  43. package/src/atoms/composed/FormField/FormField.tsx +92 -0
  44. package/src/atoms/composed/FormField/index.ts +1 -0
  45. package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
  46. package/src/atoms/composed/GlobalSearch/index.ts +1 -0
  47. package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
  48. package/src/atoms/composed/IconBadge/index.ts +2 -0
  49. package/src/atoms/composed/Modal/Modal.tsx +223 -0
  50. package/src/atoms/composed/Modal/index.ts +2 -0
  51. package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
  52. package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
  53. package/{dist/atoms/composed/ProgressBar/index.d.ts → src/atoms/composed/ProgressBar/index.ts} +1 -2
  54. package/src/atoms/composed/SalesPanel/SalesPanel.tsx +116 -0
  55. package/src/atoms/composed/SalesPanel/index.ts +1 -0
  56. package/src/atoms/composed/SalesPanel/mockSalesData.ts +151 -0
  57. package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
  58. package/src/atoms/composed/StatCard/index.ts +1 -0
  59. package/src/atoms/composed/StyleGuide.tsx +717 -0
  60. package/src/atoms/composed/Toast/Toast.tsx +219 -0
  61. package/{dist/atoms/composed/Toast/index.d.ts → src/atoms/composed/Toast/index.ts} +1 -2
  62. package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
  63. package/src/atoms/composed/Tooltip/index.ts +1 -0
  64. package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
  65. package/src/atoms/composed/UserAvatar/index.ts +1 -0
  66. package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
  67. package/src/atoms/composed/UserMenu/index.ts +1 -0
  68. package/{dist/atoms/composed/index.d.ts → src/atoms/composed/index.ts} +7 -2
  69. package/src/atoms/hooks/useApi.ts +80 -0
  70. package/src/atoms/hooks/useHealth.ts +17 -0
  71. package/{dist/atoms/index.d.ts → src/atoms/index.ts} +6 -2
  72. package/src/atoms/services/api/client.ts +134 -0
  73. package/src/atoms/services/auth-service.ts +248 -0
  74. package/src/atoms/services/health.ts +15 -0
  75. package/{dist/atoms/services/index.d.ts → src/atoms/services/index.ts} +1 -2
  76. package/src/atoms/shared/config/constants.ts +17 -0
  77. package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
  78. package/src/atoms/shared/config/environment.ts +10 -0
  79. package/src/atoms/shared/index.ts +4 -0
  80. package/src/atoms/shared/styles/color-palettes.css +566 -0
  81. package/src/atoms/types/auth.ts +62 -0
  82. package/src/atoms/types/entity-config.ts +127 -0
  83. package/{dist/atoms/types/generated.d.ts → src/atoms/types/generated.ts} +1 -1
  84. package/{dist/atoms/types/index.d.ts → src/atoms/types/index.ts} +2 -1
  85. package/{dist/atoms/types/loading.d.ts → src/atoms/types/loading.ts} +10 -8
  86. package/src/atoms/ui/Badge.tsx +30 -0
  87. package/src/atoms/ui/ErrorBoundary.tsx +59 -0
  88. package/src/atoms/ui/Select.tsx +53 -0
  89. package/src/atoms/ui/Switch.tsx +42 -0
  90. package/src/atoms/ui/Tabs.tsx +118 -0
  91. package/src/atoms/ui/avatar.tsx +48 -0
  92. package/src/atoms/ui/button.tsx +70 -0
  93. package/src/atoms/ui/card.tsx +76 -0
  94. package/src/atoms/ui/dropdown-menu.tsx +199 -0
  95. package/{dist/atoms/ui/index.d.ts → src/atoms/ui/index.ts} +27 -3
  96. package/src/atoms/ui/input.tsx +23 -0
  97. package/src/atoms/ui/label.tsx +23 -0
  98. package/src/atoms/ui/skeleton.tsx +13 -0
  99. package/src/atoms/ui/spinner.tsx +49 -0
  100. package/src/atoms/ui/table.tsx +116 -0
  101. package/src/atoms/utils/animations.ts +135 -0
  102. package/src/atoms/utils/metric-engine.ts +236 -0
  103. package/src/atoms/utils/tooltip-helpers.ts +140 -0
  104. package/src/atoms/utils/utils.ts +10 -0
  105. package/src/features/auth/components/LoginForm.tsx +168 -0
  106. package/src/features/auth/components/LogoutButton.tsx +19 -0
  107. package/src/features/auth/components/ProtectedRoute.tsx +60 -0
  108. package/{dist/features/auth/components/index.d.ts → src/features/auth/components/index.ts} +1 -1
  109. package/src/features/auth/hooks/index.ts +2 -0
  110. package/src/features/auth/hooks/useAuth.tsx +205 -0
  111. package/src/features/auth/hooks/usePermissions.ts +35 -0
  112. package/src/features/auth/index.ts +2 -0
  113. package/src/features/index.ts +2 -0
  114. package/src/index.css +704 -0
  115. package/{dist/index.d.ts → src/index.ts} +5 -2
  116. package/src/main.tsx +48 -0
  117. package/src/molecules/.gitkeep +0 -0
  118. package/src/molecules/forms/FormGroup.tsx +75 -0
  119. package/src/molecules/forms/SearchInput.tsx +259 -0
  120. package/src/molecules/forms/index.ts +4 -0
  121. package/src/molecules/index.ts +4 -0
  122. package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
  123. package/src/molecules/layout/AppHeader/index.ts +1 -0
  124. package/src/molecules/layout/AppLayout.tsx +29 -0
  125. package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +42 -0
  126. package/src/molecules/layout/DashboardWithSidePanel/index.ts +1 -0
  127. package/src/molecules/layout/PageTemplate.tsx +87 -0
  128. package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
  129. package/{dist/molecules/layout/SectionHeader/index.d.ts → src/molecules/layout/SectionHeader/index.ts} +1 -2
  130. package/src/molecules/layout/ShowcaseSection.tsx +57 -0
  131. package/src/molecules/layout/Sidebar.tsx +152 -0
  132. package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
  133. package/src/molecules/layout/SidebarButton/index.ts +1 -0
  134. package/src/molecules/layout/SidebarContext.tsx +31 -0
  135. package/{dist/molecules/layout/index.d.ts → src/molecules/layout/index.ts} +2 -2
  136. package/src/molecules/navigation/NavMenu.tsx +188 -0
  137. package/src/molecules/navigation/Pagination.tsx +172 -0
  138. package/src/molecules/navigation/index.ts +4 -0
  139. package/src/organisms/entity/CategoryBreakdownPanel.tsx +427 -0
  140. package/src/organisms/entity/EntityListPanel.tsx +339 -0
  141. package/src/organisms/entity/MetricsOverviewPanel.tsx +236 -0
  142. package/src/organisms/entity/TrendAnalysisPanel.tsx +337 -0
  143. package/src/organisms/entity/index.ts +4 -0
  144. package/src/organisms/index.ts +8 -0
  145. package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
  146. package/src/organisms/showcase/index.ts +1 -0
  147. package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
  148. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +173 -0
  149. package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
  150. package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +158 -0
  151. package/src/pages/AdminShowcase/index.tsx +4 -0
  152. package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
  153. package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
  154. package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
  155. package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
  156. package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
  157. package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
  158. package/src/pages/ComponentShowcase/index.tsx +188 -0
  159. package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +137 -0
  160. package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +117 -0
  161. package/src/pages/EntityShowcase/index.ts +2 -0
  162. package/src/pages/EntityTemplateExample.tsx +229 -0
  163. package/src/pages/TestEntityTemplate.tsx +40 -0
  164. package/src/pages/index.ts +3 -0
  165. package/src/templates/AuthTemplate.tsx +216 -0
  166. package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
  167. package/src/templates/DashboardTemplate.tsx +232 -0
  168. package/src/templates/DataTemplate.tsx +319 -0
  169. package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
  170. package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
  171. package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
  172. package/src/templates/admin/index.ts +29 -0
  173. package/src/templates/entity/EntityManagementTemplate.tsx +430 -0
  174. package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +277 -0
  175. package/src/templates/entity/configs/financial-config.ts +141 -0
  176. package/src/templates/entity/configs/index.ts +1 -0
  177. package/src/templates/entity/index.ts +3 -0
  178. package/src/templates/factory.tsx +169 -0
  179. package/src/templates/financial/FinancialDashboardTemplate.tsx +326 -0
  180. package/src/templates/index.ts +40 -0
  181. package/src/vite-env.d.ts +1 -0
  182. package/dist/atoms/composed/Accordion/Accordion.d.ts +0 -20
  183. package/dist/atoms/composed/Accordion/Accordion.d.ts.map +0 -1
  184. package/dist/atoms/composed/Accordion/index.d.ts.map +0 -1
  185. package/dist/atoms/composed/Alert/Alert.d.ts +0 -25
  186. package/dist/atoms/composed/Alert/Alert.d.ts.map +0 -1
  187. package/dist/atoms/composed/Alert/index.d.ts +0 -2
  188. package/dist/atoms/composed/Alert/index.d.ts.map +0 -1
  189. package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts +0 -17
  190. package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +0 -1
  191. package/dist/atoms/composed/Breadcrumb/index.d.ts +0 -2
  192. package/dist/atoms/composed/Breadcrumb/index.d.ts.map +0 -1
  193. package/dist/atoms/composed/Chart/Chart.d.ts +0 -37
  194. package/dist/atoms/composed/Chart/Chart.d.ts.map +0 -1
  195. package/dist/atoms/composed/Chart/index.d.ts.map +0 -1
  196. package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts +0 -19
  197. package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +0 -1
  198. package/dist/atoms/composed/ColorSwatch/index.d.ts.map +0 -1
  199. package/dist/atoms/composed/DarkModeToggle.d.ts +0 -4
  200. package/dist/atoms/composed/DarkModeToggle.d.ts.map +0 -1
  201. package/dist/atoms/composed/DataBadge/DataBadge.d.ts +0 -13
  202. package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +0 -1
  203. package/dist/atoms/composed/DataBadge/index.d.ts +0 -2
  204. package/dist/atoms/composed/DataBadge/index.d.ts.map +0 -1
  205. package/dist/atoms/composed/DataTable/DataTable.d.ts +0 -28
  206. package/dist/atoms/composed/DataTable/DataTable.d.ts.map +0 -1
  207. package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts +0 -10
  208. package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +0 -1
  209. package/dist/atoms/composed/DataTable/index.d.ts +0 -3
  210. package/dist/atoms/composed/DataTable/index.d.ts.map +0 -1
  211. package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts +0 -45
  212. package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +0 -1
  213. package/dist/atoms/composed/DateTimePicker/index.d.ts +0 -3
  214. package/dist/atoms/composed/DateTimePicker/index.d.ts.map +0 -1
  215. package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts +0 -30
  216. package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +0 -1
  217. package/dist/atoms/composed/DetailedCard/index.d.ts +0 -3
  218. package/dist/atoms/composed/DetailedCard/index.d.ts.map +0 -1
  219. package/dist/atoms/composed/EmptyState/EmptyState.d.ts +0 -18
  220. package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +0 -1
  221. package/dist/atoms/composed/EmptyState/index.d.ts +0 -2
  222. package/dist/atoms/composed/EmptyState/index.d.ts.map +0 -1
  223. package/dist/atoms/composed/FileUpload/FileUpload.d.ts +0 -46
  224. package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +0 -1
  225. package/dist/atoms/composed/FileUpload/index.d.ts.map +0 -1
  226. package/dist/atoms/composed/FormField/FormField.d.ts +0 -23
  227. package/dist/atoms/composed/FormField/FormField.d.ts.map +0 -1
  228. package/dist/atoms/composed/FormField/index.d.ts +0 -2
  229. package/dist/atoms/composed/FormField/index.d.ts.map +0 -1
  230. package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts +0 -8
  231. package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +0 -1
  232. package/dist/atoms/composed/GlobalSearch/index.d.ts +0 -2
  233. package/dist/atoms/composed/GlobalSearch/index.d.ts.map +0 -1
  234. package/dist/atoms/composed/IconBadge/IconBadge.d.ts +0 -16
  235. package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +0 -1
  236. package/dist/atoms/composed/IconBadge/index.d.ts +0 -3
  237. package/dist/atoms/composed/IconBadge/index.d.ts.map +0 -1
  238. package/dist/atoms/composed/Modal/Modal.d.ts +0 -18
  239. package/dist/atoms/composed/Modal/Modal.d.ts.map +0 -1
  240. package/dist/atoms/composed/Modal/index.d.ts +0 -3
  241. package/dist/atoms/composed/Modal/index.d.ts.map +0 -1
  242. package/dist/atoms/composed/PaletteSwitcher.d.ts +0 -7
  243. package/dist/atoms/composed/PaletteSwitcher.d.ts.map +0 -1
  244. package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts +0 -25
  245. package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +0 -1
  246. package/dist/atoms/composed/ProgressBar/index.d.ts.map +0 -1
  247. package/dist/atoms/composed/StatCard/StatCard.d.ts +0 -21
  248. package/dist/atoms/composed/StatCard/StatCard.d.ts.map +0 -1
  249. package/dist/atoms/composed/StatCard/index.d.ts +0 -2
  250. package/dist/atoms/composed/StatCard/index.d.ts.map +0 -1
  251. package/dist/atoms/composed/StyleGuide.d.ts +0 -3
  252. package/dist/atoms/composed/StyleGuide.d.ts.map +0 -1
  253. package/dist/atoms/composed/Toast/Toast.d.ts +0 -40
  254. package/dist/atoms/composed/Toast/Toast.d.ts.map +0 -1
  255. package/dist/atoms/composed/Toast/index.d.ts.map +0 -1
  256. package/dist/atoms/composed/Tooltip/Tooltip.d.ts +0 -16
  257. package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +0 -1
  258. package/dist/atoms/composed/Tooltip/index.d.ts +0 -2
  259. package/dist/atoms/composed/Tooltip/index.d.ts.map +0 -1
  260. package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts +0 -8
  261. package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +0 -1
  262. package/dist/atoms/composed/UserAvatar/index.d.ts +0 -2
  263. package/dist/atoms/composed/UserAvatar/index.d.ts.map +0 -1
  264. package/dist/atoms/composed/UserMenu/UserMenu.d.ts +0 -8
  265. package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +0 -1
  266. package/dist/atoms/composed/UserMenu/index.d.ts +0 -2
  267. package/dist/atoms/composed/UserMenu/index.d.ts.map +0 -1
  268. package/dist/atoms/composed/index.d.ts.map +0 -1
  269. package/dist/atoms/hooks/useApi.d.ts +0 -25
  270. package/dist/atoms/hooks/useApi.d.ts.map +0 -1
  271. package/dist/atoms/hooks/useHealth.d.ts +0 -19
  272. package/dist/atoms/hooks/useHealth.d.ts.map +0 -1
  273. package/dist/atoms/index.d.ts.map +0 -1
  274. package/dist/atoms/services/api/client.d.ts +0 -20
  275. package/dist/atoms/services/api/client.d.ts.map +0 -1
  276. package/dist/atoms/services/auth-service.d.ts +0 -24
  277. package/dist/atoms/services/auth-service.d.ts.map +0 -1
  278. package/dist/atoms/services/health.d.ts +0 -7
  279. package/dist/atoms/services/health.d.ts.map +0 -1
  280. package/dist/atoms/services/index.d.ts.map +0 -1
  281. package/dist/atoms/shared/config/constants.d.ts +0 -15
  282. package/dist/atoms/shared/config/constants.d.ts.map +0 -1
  283. package/dist/atoms/shared/config/dashboard-sizes.d.ts +0 -83
  284. package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +0 -1
  285. package/dist/atoms/shared/config/environment.d.ts +0 -10
  286. package/dist/atoms/shared/config/environment.d.ts.map +0 -1
  287. package/dist/atoms/shared/index.d.ts +0 -4
  288. package/dist/atoms/shared/index.d.ts.map +0 -1
  289. package/dist/atoms/types/auth.d.ts +0 -56
  290. package/dist/atoms/types/auth.d.ts.map +0 -1
  291. package/dist/atoms/types/generated.d.ts.map +0 -1
  292. package/dist/atoms/types/index.d.ts.map +0 -1
  293. package/dist/atoms/types/loading.d.ts.map +0 -1
  294. package/dist/atoms/ui/Badge.d.ts +0 -10
  295. package/dist/atoms/ui/Badge.d.ts.map +0 -1
  296. package/dist/atoms/ui/ErrorBoundary.d.ts +0 -18
  297. package/dist/atoms/ui/ErrorBoundary.d.ts.map +0 -1
  298. package/dist/atoms/ui/Select.d.ts +0 -28
  299. package/dist/atoms/ui/Select.d.ts.map +0 -1
  300. package/dist/atoms/ui/Switch.d.ts +0 -9
  301. package/dist/atoms/ui/Switch.d.ts.map +0 -1
  302. package/dist/atoms/ui/Tabs.d.ts +0 -30
  303. package/dist/atoms/ui/Tabs.d.ts.map +0 -1
  304. package/dist/atoms/ui/avatar.d.ts +0 -7
  305. package/dist/atoms/ui/avatar.d.ts.map +0 -1
  306. package/dist/atoms/ui/button.d.ts +0 -14
  307. package/dist/atoms/ui/button.d.ts.map +0 -1
  308. package/dist/atoms/ui/card.d.ts +0 -12
  309. package/dist/atoms/ui/card.d.ts.map +0 -1
  310. package/dist/atoms/ui/dropdown-menu.d.ts +0 -28
  311. package/dist/atoms/ui/dropdown-menu.d.ts.map +0 -1
  312. package/dist/atoms/ui/index.d.ts.map +0 -1
  313. package/dist/atoms/ui/input.d.ts +0 -5
  314. package/dist/atoms/ui/input.d.ts.map +0 -1
  315. package/dist/atoms/ui/label.d.ts +0 -6
  316. package/dist/atoms/ui/label.d.ts.map +0 -1
  317. package/dist/atoms/ui/skeleton.d.ts +0 -3
  318. package/dist/atoms/ui/skeleton.d.ts.map +0 -1
  319. package/dist/atoms/ui/spinner.d.ts +0 -14
  320. package/dist/atoms/ui/spinner.d.ts.map +0 -1
  321. package/dist/atoms/ui/table.d.ts +0 -11
  322. package/dist/atoms/ui/table.d.ts.map +0 -1
  323. package/dist/atoms/utils/animations.d.ts +0 -65
  324. package/dist/atoms/utils/animations.d.ts.map +0 -1
  325. package/dist/atoms/utils/tooltip-helpers.d.ts +0 -71
  326. package/dist/atoms/utils/tooltip-helpers.d.ts.map +0 -1
  327. package/dist/atoms/utils/utils.d.ts +0 -4
  328. package/dist/atoms/utils/utils.d.ts.map +0 -1
  329. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  330. package/dist/features/auth/components/LoginForm.d.ts.map +0 -1
  331. package/dist/features/auth/components/LogoutButton.d.ts +0 -2
  332. package/dist/features/auth/components/LogoutButton.d.ts.map +0 -1
  333. package/dist/features/auth/components/ProtectedRoute.d.ts +0 -10
  334. package/dist/features/auth/components/ProtectedRoute.d.ts.map +0 -1
  335. package/dist/features/auth/components/index.d.ts.map +0 -1
  336. package/dist/features/auth/hooks/index.d.ts +0 -3
  337. package/dist/features/auth/hooks/index.d.ts.map +0 -1
  338. package/dist/features/auth/hooks/useAuth.d.ts +0 -10
  339. package/dist/features/auth/hooks/useAuth.d.ts.map +0 -1
  340. package/dist/features/auth/hooks/usePermissions.d.ts +0 -13
  341. package/dist/features/auth/hooks/usePermissions.d.ts.map +0 -1
  342. package/dist/features/auth/index.d.ts +0 -3
  343. package/dist/features/auth/index.d.ts.map +0 -1
  344. package/dist/features/index.d.ts +0 -2
  345. package/dist/features/index.d.ts.map +0 -1
  346. package/dist/index.d.ts.map +0 -1
  347. package/dist/molecules/forms/FormGroup.d.ts +0 -17
  348. package/dist/molecules/forms/FormGroup.d.ts.map +0 -1
  349. package/dist/molecules/forms/SearchInput.d.ts +0 -36
  350. package/dist/molecules/forms/SearchInput.d.ts.map +0 -1
  351. package/dist/molecules/forms/index.d.ts +0 -3
  352. package/dist/molecules/forms/index.d.ts.map +0 -1
  353. package/dist/molecules/index.d.ts +0 -4
  354. package/dist/molecules/index.d.ts.map +0 -1
  355. package/dist/molecules/layout/AppHeader/AppHeader.d.ts +0 -7
  356. package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +0 -1
  357. package/dist/molecules/layout/AppHeader/index.d.ts +0 -2
  358. package/dist/molecules/layout/AppHeader/index.d.ts.map +0 -1
  359. package/dist/molecules/layout/AppLayout.d.ts +0 -2
  360. package/dist/molecules/layout/AppLayout.d.ts.map +0 -1
  361. package/dist/molecules/layout/PageTemplate.d.ts +0 -19
  362. package/dist/molecules/layout/PageTemplate.d.ts.map +0 -1
  363. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +0 -24
  364. package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +0 -1
  365. package/dist/molecules/layout/SectionHeader/index.d.ts.map +0 -1
  366. package/dist/molecules/layout/ShowcaseSection.d.ts +0 -22
  367. package/dist/molecules/layout/ShowcaseSection.d.ts.map +0 -1
  368. package/dist/molecules/layout/Sidebar.d.ts +0 -6
  369. package/dist/molecules/layout/Sidebar.d.ts.map +0 -1
  370. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +0 -13
  371. package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +0 -1
  372. package/dist/molecules/layout/SidebarButton/index.d.ts +0 -2
  373. package/dist/molecules/layout/SidebarButton/index.d.ts.map +0 -1
  374. package/dist/molecules/layout/SidebarContext.d.ts +0 -12
  375. package/dist/molecules/layout/SidebarContext.d.ts.map +0 -1
  376. package/dist/molecules/layout/index.d.ts.map +0 -1
  377. package/dist/molecules/navigation/NavMenu.d.ts +0 -20
  378. package/dist/molecules/navigation/NavMenu.d.ts.map +0 -1
  379. package/dist/molecules/navigation/Pagination.d.ts +0 -14
  380. package/dist/molecules/navigation/Pagination.d.ts.map +0 -1
  381. package/dist/molecules/navigation/index.d.ts +0 -3
  382. package/dist/molecules/navigation/index.d.ts.map +0 -1
  383. package/dist/organisms/index.d.ts +0 -2
  384. package/dist/organisms/index.d.ts.map +0 -1
  385. package/dist/organisms/showcase/ComponentShowcasePage.d.ts +0 -3
  386. package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +0 -1
  387. package/dist/templates/AuthTemplate.d.ts +0 -68
  388. package/dist/templates/AuthTemplate.d.ts.map +0 -1
  389. package/dist/templates/ComponentShowcaseTemplate.d.ts +0 -53
  390. package/dist/templates/ComponentShowcaseTemplate.d.ts.map +0 -1
  391. package/dist/templates/DashboardTemplate.d.ts +0 -62
  392. package/dist/templates/DashboardTemplate.d.ts.map +0 -1
  393. package/dist/templates/DataTemplate.d.ts +0 -78
  394. package/dist/templates/DataTemplate.d.ts.map +0 -1
  395. package/dist/templates/admin/AdminCRUDTemplate.d.ts +0 -105
  396. package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +0 -1
  397. package/dist/templates/admin/AdminDashboardTemplate.d.ts +0 -89
  398. package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +0 -1
  399. package/dist/templates/admin/AdminDetailTemplate.d.ts +0 -132
  400. package/dist/templates/admin/AdminDetailTemplate.d.ts.map +0 -1
  401. package/dist/templates/admin/index.d.ts +0 -4
  402. package/dist/templates/admin/index.d.ts.map +0 -1
  403. package/dist/templates/factory.d.ts +0 -28
  404. package/dist/templates/factory.d.ts.map +0 -1
  405. package/dist/templates/index.d.ts +0 -7
  406. package/dist/templates/index.d.ts.map +0 -1
@@ -0,0 +1,339 @@
1
+ import React, { useMemo } from 'react';
2
+ import { DataTable } from '../../atoms/composed/DataTable';
3
+ import type { Column } from '../../atoms/composed/DataTable';
4
+ import { Button } from '../../atoms/ui/button';
5
+ import { Badge } from '../../atoms/ui/Badge';
6
+ import { Card } from '../../atoms/ui/card';
7
+ import type { EntityData, EntityTemplateConfig, ActionConfig } from '../../atoms/types';
8
+ import { cn } from '../../atoms/utils/utils';
9
+ import { Download, Filter, Plus, RefreshCw } from 'lucide-react';
10
+
11
+ export interface EntityListPanelProps<T extends EntityData> {
12
+ config: EntityTemplateConfig<T>;
13
+ data: T[];
14
+ isLoading?: boolean;
15
+ onRowClick?: (item: T) => void;
16
+ onAction?: (action: ActionConfig, selectedItems: T[]) => void;
17
+ className?: string;
18
+
19
+ // Table configuration
20
+ columns?: Column<T>[];
21
+ showSearch?: boolean;
22
+ showPagination?: boolean;
23
+ pageSize?: number;
24
+ searchPlaceholder?: string;
25
+
26
+ // Business logic features
27
+ enableSelection?: boolean;
28
+ enableBulkActions?: boolean;
29
+ enableExport?: boolean;
30
+ enableFiltering?: boolean;
31
+ enableRefresh?: boolean;
32
+
33
+ // Extension points
34
+ renderToolbar?: () => React.ReactNode;
35
+ renderBulkActions?: (selectedItems: T[]) => React.ReactNode;
36
+ renderCustomColumn?: (column: Column<T>, item: T) => React.ReactNode;
37
+ renderEmptyState?: () => React.ReactNode;
38
+ headerSlot?: React.ReactNode;
39
+ footerSlot?: React.ReactNode;
40
+
41
+ // Event handlers
42
+ onExport?: (data: T[]) => void;
43
+ onRefresh?: () => void;
44
+ onFilter?: (filters: Record<string, unknown>) => void;
45
+ }
46
+
47
+ export const EntityListPanel = <T extends EntityData>({
48
+ config,
49
+ data,
50
+ isLoading = false,
51
+ onRowClick,
52
+ onAction,
53
+ className,
54
+ columns: customColumns,
55
+ showSearch = true,
56
+ showPagination = true,
57
+ pageSize = 10,
58
+ searchPlaceholder,
59
+ enableSelection = false,
60
+ enableBulkActions = false,
61
+ enableExport = true,
62
+ enableFiltering = false,
63
+ enableRefresh = true,
64
+ renderToolbar,
65
+ renderBulkActions,
66
+ renderCustomColumn,
67
+ renderEmptyState,
68
+ headerSlot,
69
+ footerSlot,
70
+ onExport,
71
+ onRefresh
72
+ }: EntityListPanelProps<T>) => {
73
+ const [selectedItems, setSelectedItems] = React.useState<T[]>([]);
74
+ const [showFilters, setShowFilters] = React.useState(false);
75
+
76
+ // Generate columns based on config if not provided
77
+ const tableColumns = useMemo(() => {
78
+ if (customColumns) return customColumns;
79
+
80
+ const generatedColumns: Column<T>[] = [];
81
+
82
+ // Add selection column if enabled
83
+ if (enableSelection) {
84
+ generatedColumns.push({
85
+ key: '__selection',
86
+ header: (
87
+ <input
88
+ type="checkbox"
89
+ checked={selectedItems.length === data.length && data.length > 0}
90
+ onChange={(e) => {
91
+ if (e.target.checked) {
92
+ setSelectedItems([...data]);
93
+ } else {
94
+ setSelectedItems([]);
95
+ }
96
+ }}
97
+ />
98
+ ),
99
+ cell: (item: T) => (
100
+ <input
101
+ type="checkbox"
102
+ checked={selectedItems.some(selected => selected.id === item.id)}
103
+ onChange={(e) => {
104
+ if (e.target.checked) {
105
+ setSelectedItems(prev => [...prev, item]);
106
+ } else {
107
+ setSelectedItems(prev => prev.filter(selected => selected.id !== item.id));
108
+ }
109
+ }}
110
+ />
111
+ ),
112
+ sortable: false,
113
+ width: '50px'
114
+ });
115
+ }
116
+
117
+ // Auto-generate columns from first data item
118
+ if (data.length > 0) {
119
+ const sampleItem = data[0];
120
+ Object.keys(sampleItem).forEach(key => {
121
+ if (key === 'id') return; // Skip ID column usually
122
+
123
+ const isStatus = key.toLowerCase().includes('status');
124
+ const isCategory = key.toLowerCase().includes('category') || key.toLowerCase().includes('type');
125
+ const isDate = key.toLowerCase().includes('date') || key.toLowerCase().includes('time');
126
+ const isAmount = key.toLowerCase().includes('amount') || key.toLowerCase().includes('price') || key.toLowerCase().includes('cost');
127
+
128
+ generatedColumns.push({
129
+ key,
130
+ header: key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'),
131
+ type: isStatus ? 'status' : isCategory ? 'category' : 'default',
132
+ sortable: true,
133
+ cell: renderCustomColumn ? (item: T) => {
134
+ const column = { key, header: '', type: isStatus ? 'status' : isCategory ? 'category' : 'default' } as Column<T>;
135
+ const customContent = renderCustomColumn(column, item);
136
+ if (customContent) return customContent;
137
+
138
+ // Default rendering with smart formatting
139
+ const value = item[key];
140
+ if (isDate && value) {
141
+ return new Date(value as string).toLocaleDateString();
142
+ }
143
+ if (isAmount && typeof value === 'number') {
144
+ return new Intl.NumberFormat('en-US', {
145
+ style: 'currency',
146
+ currency: 'USD'
147
+ }).format(value);
148
+ }
149
+ return value?.toString() || '-';
150
+ } : undefined
151
+ });
152
+ });
153
+ }
154
+
155
+ return generatedColumns;
156
+ }, [customColumns, data, enableSelection, selectedItems, renderCustomColumn]);
157
+
158
+ const handleExport = () => {
159
+ if (onExport) {
160
+ onExport(selectedItems.length > 0 ? selectedItems : data);
161
+ } else {
162
+ // Default CSV export
163
+ const headers = tableColumns.filter(col => col.key !== '__selection').map(col => col.key);
164
+ const csvContent = [
165
+ headers.join(','),
166
+ ...(selectedItems.length > 0 ? selectedItems : data).map(item =>
167
+ headers.map(header => item[header] || '').join(',')
168
+ )
169
+ ].join('\n');
170
+
171
+ const blob = new Blob([csvContent], { type: 'text/csv' });
172
+ const url = URL.createObjectURL(blob);
173
+ const a = document.createElement('a');
174
+ a.href = url;
175
+ a.download = `${config.display.title.toLowerCase().replace(/\s+/g, '-')}-export.csv`;
176
+ a.click();
177
+ URL.revokeObjectURL(url);
178
+ }
179
+ };
180
+
181
+ const renderToolbarSection = () => {
182
+ return (
183
+ <div className="flex items-center justify-between gap-4 mb-4">
184
+ <div className="flex items-center gap-2">
185
+ <h3 className="text-lg font-semibold text-foreground">
186
+ {config.display.title}
187
+ </h3>
188
+ {config.display.description && (
189
+ <Badge variant="outline" className="text-xs">
190
+ {data.length} items
191
+ </Badge>
192
+ )}
193
+ </div>
194
+
195
+ <div className="flex items-center gap-2">
196
+ {renderToolbar && renderToolbar()}
197
+
198
+ {enableFiltering && (
199
+ <Button
200
+ variant="outline"
201
+ size="sm"
202
+ onClick={() => setShowFilters(!showFilters)}
203
+ className={cn(showFilters && "bg-muted")}
204
+ >
205
+ <Filter className="w-4 h-4 mr-2" />
206
+ Filters
207
+ </Button>
208
+ )}
209
+
210
+ {enableRefresh && (
211
+ <Button
212
+ variant="outline"
213
+ size="sm"
214
+ onClick={onRefresh}
215
+ disabled={isLoading}
216
+ >
217
+ <RefreshCw className={cn("w-4 h-4 mr-2", isLoading && "animate-spin")} />
218
+ Refresh
219
+ </Button>
220
+ )}
221
+
222
+ {enableExport && (
223
+ <Button
224
+ variant="outline"
225
+ size="sm"
226
+ onClick={handleExport}
227
+ disabled={data.length === 0}
228
+ >
229
+ <Download className="w-4 h-4 mr-2" />
230
+ Export
231
+ </Button>
232
+ )}
233
+
234
+ {config.actions?.filter(action => action.type === 'primary').map(action => (
235
+ <Button
236
+ key={action.label}
237
+ size="sm"
238
+ onClick={() => onAction?.(action, [])}
239
+ disabled={isLoading}
240
+ >
241
+ {action.icon && <action.icon className="w-4 h-4 mr-2" />}
242
+ {action.label}
243
+ </Button>
244
+ ))}
245
+ </div>
246
+ </div>
247
+ );
248
+ };
249
+
250
+ const renderBulkActionsSection = () => {
251
+ if (!enableBulkActions || selectedItems.length === 0) return null;
252
+
253
+ return (
254
+ <div className="flex items-center justify-between bg-muted/50 p-3 rounded-md mb-4">
255
+ <div className="flex items-center gap-2">
256
+ <span className="text-sm text-muted-foreground">
257
+ {selectedItems.length} items selected
258
+ </span>
259
+ <Button
260
+ variant="ghost"
261
+ size="sm"
262
+ onClick={() => setSelectedItems([])}
263
+ >
264
+ Clear selection
265
+ </Button>
266
+ </div>
267
+
268
+ <div className="flex items-center gap-2">
269
+ {renderBulkActions && renderBulkActions(selectedItems)}
270
+
271
+ {config.actions?.filter(action => action.type !== 'primary').map(action => (
272
+ <Button
273
+ key={action.label}
274
+ variant={action.type === 'danger' ? 'destructive' : 'outline'}
275
+ size="sm"
276
+ onClick={() => onAction?.(action, selectedItems)}
277
+ >
278
+ {action.icon && <action.icon className="w-4 h-4 mr-2" />}
279
+ {action.label}
280
+ </Button>
281
+ ))}
282
+ </div>
283
+ </div>
284
+ );
285
+ };
286
+
287
+ const renderEmptyStateSection = () => {
288
+ if (data.length > 0) return null;
289
+
290
+ if (renderEmptyState) {
291
+ return renderEmptyState();
292
+ }
293
+
294
+ return (
295
+ <div className="text-center py-12">
296
+ <div className="text-muted-foreground mb-4">
297
+ No {config.display.title.toLowerCase()} found
298
+ </div>
299
+ {config.actions?.find(action => action.type === 'primary') && (
300
+ <Button onClick={() => {
301
+ const primaryAction = config.actions?.find(action => action.type === 'primary');
302
+ if (primaryAction) onAction?.(primaryAction, []);
303
+ }}>
304
+ <Plus className="w-4 h-4 mr-2" />
305
+ Add {config.display.title.slice(0, -1)}
306
+ </Button>
307
+ )}
308
+ </div>
309
+ );
310
+ };
311
+
312
+ return (
313
+ <Card className={cn('p-6', className)} category={config.display.category} data-component-name="EntityListPanel">
314
+ {headerSlot}
315
+
316
+ {renderToolbarSection()}
317
+ {renderBulkActionsSection()}
318
+
319
+ {data.length === 0 ? (
320
+ renderEmptyStateSection()
321
+ ) : (
322
+ <DataTable
323
+ data={data}
324
+ columns={tableColumns}
325
+ isLoading={isLoading}
326
+ showSearch={showSearch}
327
+ showPagination={showPagination}
328
+ pageSize={pageSize}
329
+ searchPlaceholder={searchPlaceholder || `Search ${config.display.title.toLowerCase()}...`}
330
+ onRowClick={onRowClick}
331
+ hover={!!onRowClick}
332
+ emptyMessage={`No ${config.display.title.toLowerCase()} found`}
333
+ />
334
+ )}
335
+
336
+ {footerSlot}
337
+ </Card>
338
+ );
339
+ };
@@ -0,0 +1,236 @@
1
+ import React from 'react';
2
+ import { StatCard } from '../../atoms/composed/StatCard';
3
+ import { MetricCalculationEngine } from '../../atoms/utils/metric-engine';
4
+ import type { MetricConfig, EntityData, MetricValue } from '../../atoms/types';
5
+ import { cn } from '../../atoms/utils/utils';
6
+
7
+ export interface MetricsOverviewPanelProps {
8
+ metrics: MetricConfig[];
9
+ data: EntityData[];
10
+ previousData?: EntityData[];
11
+ isLoading?: boolean;
12
+ onMetricClick?: (metric: MetricConfig, value: MetricValue) => void;
13
+ className?: string;
14
+ layout?: 'grid' | 'horizontal' | 'vertical';
15
+ columns?: 2 | 3 | 4;
16
+ category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
17
+
18
+ // Extension points
19
+ renderCustomMetric?: (metric: MetricConfig, value: MetricValue, index: number) => React.ReactNode;
20
+ renderHeader?: () => React.ReactNode;
21
+ renderFooter?: () => React.ReactNode;
22
+ headerSlot?: React.ReactNode;
23
+ footerSlot?: React.ReactNode;
24
+ }
25
+
26
+ export const MetricsOverviewPanel: React.FC<MetricsOverviewPanelProps> = ({
27
+ metrics,
28
+ data,
29
+ previousData,
30
+ isLoading = false,
31
+ onMetricClick,
32
+ className,
33
+ layout = 'grid',
34
+ columns = 4,
35
+ category,
36
+ renderCustomMetric,
37
+ renderHeader,
38
+ renderFooter,
39
+ headerSlot,
40
+ footerSlot
41
+ }) => {
42
+ const calculatedMetrics = React.useMemo(() => {
43
+ if (isLoading || !data?.length || !metrics?.length) return {};
44
+
45
+ return metrics.reduce((acc, metric) => {
46
+ acc[metric.key] = MetricCalculationEngine.calculateMetric(
47
+ metric,
48
+ data,
49
+ previousData
50
+ );
51
+ return acc;
52
+ }, {} as Record<string, MetricValue>);
53
+ }, [metrics, data, previousData, isLoading]);
54
+
55
+ const getLayoutClasses = () => {
56
+ switch (layout) {
57
+ case 'horizontal':
58
+ return 'flex flex-wrap gap-4';
59
+ case 'vertical':
60
+ return 'flex flex-col gap-4';
61
+ case 'grid':
62
+ default:
63
+ return cn(
64
+ 'grid gap-4',
65
+ {
66
+ 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4': columns === 4,
67
+ 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3': columns === 3,
68
+ 'grid-cols-1 sm:grid-cols-2': columns === 2
69
+ }
70
+ );
71
+ }
72
+ };
73
+
74
+ const renderMetricCard = (metric: MetricConfig, index: number) => {
75
+ if (renderCustomMetric) {
76
+ const customContent = renderCustomMetric(metric, calculatedMetrics[metric.key], index);
77
+ if (customContent) return customContent;
78
+ }
79
+
80
+ const metricValue = calculatedMetrics[metric.key];
81
+
82
+ if (isLoading || !metricValue) {
83
+ return (
84
+ <StatCard
85
+ key={metric.key}
86
+ title={metric.label}
87
+ value=""
88
+ isLoading={true}
89
+ category={category}
90
+ icon={metric.icon ? <metric.icon /> : undefined}
91
+ />
92
+ );
93
+ }
94
+
95
+ const trend = metricValue.trend !== 'neutral' && metricValue.previous
96
+ ? {
97
+ value: Number(((metricValue.current - metricValue.previous) / Math.abs(metricValue.previous) * 100).toFixed(1)),
98
+ label: 'vs last period'
99
+ }
100
+ : undefined;
101
+
102
+ const formatTargetValue = (value: number) => {
103
+ switch (metric.type) {
104
+ case 'currency':
105
+ return new Intl.NumberFormat('en-US', {
106
+ style: 'currency',
107
+ currency: 'USD'
108
+ }).format(value);
109
+ case 'percentage':
110
+ return `${value}%`;
111
+ default:
112
+ return value.toString();
113
+ }
114
+ };
115
+
116
+ const subtitle = metricValue.target
117
+ ? `Target: ${formatTargetValue(metricValue.target)}`
118
+ : undefined;
119
+
120
+ return (
121
+ <StatCard
122
+ key={metric.key}
123
+ title={metric.label}
124
+ value={metricValue.formattedValue}
125
+ subtitle={subtitle}
126
+ trend={trend}
127
+ category={category}
128
+ icon={metric.icon ? <metric.icon /> : undefined}
129
+ onClick={onMetricClick ? () => onMetricClick(metric, metricValue) : undefined}
130
+ valueTooltip={`Current: ${metricValue.formattedValue}${metricValue.previous ? ` | Previous: ${formatTargetValue(metricValue.previous)}` : ''}`}
131
+ iconTooltip={`View ${metric.label} details`}
132
+ />
133
+ );
134
+ };
135
+
136
+ return (
137
+ <div className={cn('space-y-4', className)} data-component-name="MetricsOverviewPanel">
138
+ {/* Header Extension Point */}
139
+ {headerSlot}
140
+ {renderHeader && renderHeader()}
141
+
142
+ {/* Metrics Grid */}
143
+ <div className={getLayoutClasses()}>
144
+ {metrics.map((metric, index) => renderMetricCard(metric, index))}
145
+ </div>
146
+
147
+ {/* Footer Extension Point */}
148
+ {renderFooter && renderFooter()}
149
+ {footerSlot}
150
+ </div>
151
+ );
152
+ };
153
+
154
+ // Enhanced version with insights
155
+ export interface MetricsOverviewWithInsightsPanelProps extends MetricsOverviewPanelProps {
156
+ showInsights?: boolean;
157
+ insightThresholds?: Record<string, { warning: number; critical: number }>;
158
+ renderInsight?: (insight: { type: 'positive' | 'negative' | 'neutral'; message: string; value?: number }, index: number) => React.ReactNode;
159
+ }
160
+
161
+ export const MetricsOverviewWithInsightsPanel: React.FC<MetricsOverviewWithInsightsPanelProps> = ({
162
+ showInsights = true,
163
+ insightThresholds,
164
+ renderInsight,
165
+ ...props
166
+ }) => {
167
+ // Ensure metrics is always an array
168
+ const safeMetrics = props.metrics || [];
169
+ const safeData = props.data || [];
170
+
171
+ const calculatedMetrics = React.useMemo(() => {
172
+ if (props.isLoading || !safeData.length || !safeMetrics.length) return {};
173
+
174
+ return safeMetrics.reduce((acc, metric) => {
175
+ acc[metric.key] = MetricCalculationEngine.calculateMetric(
176
+ metric,
177
+ safeData,
178
+ props.previousData
179
+ );
180
+ return acc;
181
+ }, {} as Record<string, MetricValue>);
182
+ }, [safeMetrics, safeData, props.previousData, props.isLoading]);
183
+
184
+ const insights = React.useMemo(() => {
185
+ if (!showInsights || props.isLoading || Object.keys(calculatedMetrics).length === 0) {
186
+ return [];
187
+ }
188
+
189
+ return MetricCalculationEngine.detectInsights(calculatedMetrics, insightThresholds);
190
+ }, [calculatedMetrics, showInsights, insightThresholds, props.isLoading]);
191
+
192
+ const renderInsightsSection = () => {
193
+ if (!showInsights || insights.length === 0) return null;
194
+
195
+ return (
196
+ <div className="space-y-2">
197
+ <h3 className="text-sm font-semibold text-foreground">Key Insights</h3>
198
+ <div className="space-y-1">
199
+ {insights.map((insight, index) => {
200
+ if (renderInsight) {
201
+ const customInsight = renderInsight(insight, index);
202
+ if (customInsight) return customInsight;
203
+ }
204
+
205
+ const colorClass = {
206
+ positive: 'text-status-success',
207
+ negative: 'text-status-error',
208
+ neutral: 'text-muted-foreground'
209
+ }[insight.type];
210
+
211
+ return (
212
+ <div
213
+ key={index}
214
+ className={cn('text-sm', colorClass)}
215
+ >
216
+ • {insight.message}
217
+ </div>
218
+ );
219
+ })}
220
+ </div>
221
+ </div>
222
+ );
223
+ };
224
+
225
+ return (
226
+ <MetricsOverviewPanel
227
+ {...props}
228
+ footerSlot={
229
+ <>
230
+ {renderInsightsSection()}
231
+ {props.footerSlot}
232
+ </>
233
+ }
234
+ />
235
+ );
236
+ };