@wakastellar/ui 1.0.12 → 2.1.0

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 (436) hide show
  1. package/README.md +163 -193
  2. package/dist/charts.cjs.js +1 -0
  3. package/dist/charts.es.js +16 -0
  4. package/dist/cli/commands/add.d.ts +7 -0
  5. package/dist/cli/commands/init.d.ts +6 -0
  6. package/dist/cli/commands/list.d.ts +5 -0
  7. package/dist/cli/commands/search.d.ts +1 -0
  8. package/dist/cli/index.cjs +4844 -0
  9. package/dist/cli/index.d.ts +1 -0
  10. package/dist/cli/utils/config.d.ts +29 -0
  11. package/dist/cli/utils/logger.d.ts +20 -0
  12. package/dist/cli/utils/registry.d.ts +23 -0
  13. package/dist/cn-B-fTneHh.js +1 -0
  14. package/dist/cn-DzRe1GWm.mjs +21 -0
  15. package/dist/components/index.d.ts +122 -0
  16. package/dist/components/waka-3d-pie-chart/index.d.ts +67 -0
  17. package/dist/components/waka-achievement-unlock/index.d.ts +83 -0
  18. package/dist/components/waka-activity-feed/index.d.ts +78 -0
  19. package/dist/components/waka-address-autocomplete/index.d.ts +124 -0
  20. package/dist/components/waka-alert-stack/index.d.ts +58 -0
  21. package/dist/components/waka-allocation-matrix/index.d.ts +193 -0
  22. package/dist/components/waka-approval-chain/index.d.ts +43 -0
  23. package/dist/components/waka-audit-log/index.d.ts +142 -0
  24. package/dist/components/waka-badge-showcase/index.d.ts +51 -0
  25. package/dist/components/waka-biometric-prompt/index.d.ts +84 -0
  26. package/dist/components/waka-bottom-sheet/index.d.ts +61 -0
  27. package/dist/components/waka-breadcrumb-path/index.d.ts +46 -0
  28. package/dist/components/waka-budget-burn/index.d.ts +154 -0
  29. package/dist/components/waka-capacity-planner/index.d.ts +132 -0
  30. package/dist/components/waka-cart-summary/index.d.ts +154 -0
  31. package/dist/components/waka-challenge-timer/index.d.ts +86 -0
  32. package/dist/components/waka-chat-bubble/index.d.ts +127 -0
  33. package/dist/components/waka-checklist/index.d.ts +123 -0
  34. package/dist/components/waka-checkout-stepper/index.d.ts +154 -0
  35. package/dist/components/waka-cohort-table/index.d.ts +130 -0
  36. package/dist/components/waka-combo-counter/index.d.ts +53 -0
  37. package/dist/components/waka-command-bar/index.d.ts +45 -0
  38. package/dist/components/waka-compare-period/index.d.ts +122 -0
  39. package/dist/components/waka-connection-matrix/index.d.ts +117 -0
  40. package/dist/components/waka-contribution-graph/index.d.ts +34 -0
  41. package/dist/components/waka-cost-breakdown/index.d.ts +50 -0
  42. package/dist/components/waka-coupon-input/index.d.ts +105 -0
  43. package/dist/components/waka-credit-card-input/index.d.ts +95 -0
  44. package/dist/components/waka-daily-reward/index.d.ts +76 -0
  45. package/dist/components/waka-deployment-lane/index.d.ts +43 -0
  46. package/dist/components/waka-device-trust/index.d.ts +95 -0
  47. package/dist/components/waka-dock/index.d.ts +44 -0
  48. package/dist/components/waka-empty-state/index.d.ts +85 -0
  49. package/dist/components/waka-error-shake/index.d.ts +49 -0
  50. package/dist/components/waka-feature-announcement/index.d.ts +112 -0
  51. package/dist/components/waka-floating-nav/index.d.ts +51 -0
  52. package/dist/components/waka-flow-diagram/index.d.ts +71 -0
  53. package/dist/components/waka-funnel-chart/index.d.ts +108 -0
  54. package/dist/components/waka-glow-card/index.d.ts +32 -0
  55. package/dist/components/waka-goal-progress/index.d.ts +139 -0
  56. package/dist/components/waka-haptic-button/index.d.ts +45 -0
  57. package/dist/components/waka-health-pulse/index.d.ts +28 -0
  58. package/dist/components/waka-heatmap/index.d.ts +135 -0
  59. package/dist/components/waka-hotspot/index.d.ts +106 -0
  60. package/dist/components/waka-incident-timeline/index.d.ts +38 -0
  61. package/dist/components/waka-invoice-preview/index.d.ts +137 -0
  62. package/dist/components/waka-kpi-dashboard/index.d.ts +80 -0
  63. package/dist/components/waka-leaderboard/index.d.ts +85 -0
  64. package/dist/components/waka-level-progress/index.d.ts +89 -0
  65. package/dist/components/waka-liquid-button/index.d.ts +41 -0
  66. package/dist/components/waka-loading-orbit/index.d.ts +90 -0
  67. package/dist/components/waka-loot-box/index.d.ts +87 -0
  68. package/dist/components/waka-magic-link/index.d.ts +34 -0
  69. package/dist/components/waka-magnetic-button/index.d.ts +56 -0
  70. package/dist/components/waka-mention-input/index.d.ts +106 -0
  71. package/dist/components/waka-metric-sparkline/index.d.ts +46 -0
  72. package/dist/components/waka-milestone-road/index.d.ts +91 -0
  73. package/dist/components/waka-morph-button/index.d.ts +62 -0
  74. package/dist/components/waka-network-topology/index.d.ts +35 -0
  75. package/dist/components/waka-orbital-menu/index.d.ts +61 -0
  76. package/dist/components/waka-order-tracker/index.d.ts +121 -0
  77. package/dist/components/waka-password-strength/index.d.ts +98 -0
  78. package/dist/components/waka-payment-method-picker/index.d.ts +88 -0
  79. package/dist/components/waka-permission-matrix/index.d.ts +197 -0
  80. package/dist/components/waka-phone-input/index.d.ts +93 -0
  81. package/dist/components/waka-pipeline-view/index.d.ts +49 -0
  82. package/dist/components/waka-player-card/index.d.ts +36 -0
  83. package/dist/components/waka-points-popup/index.d.ts +75 -0
  84. package/dist/components/waka-power-up/index.d.ts +103 -0
  85. package/dist/components/waka-presence-indicator/index.d.ts +188 -0
  86. package/dist/components/waka-pricing-table/index.d.ts +77 -0
  87. package/dist/components/waka-product-card/index.d.ts +81 -0
  88. package/dist/components/waka-progress-onboarding/index.d.ts +97 -0
  89. package/dist/components/waka-pull-to-refresh/index.d.ts +45 -0
  90. package/dist/components/waka-quest-card/index.d.ts +110 -0
  91. package/dist/components/waka-quota-bar/index.d.ts +100 -0
  92. package/dist/components/waka-radar-score/index.d.ts +95 -0
  93. package/dist/components/waka-rank-badge/index.d.ts +58 -0
  94. package/dist/components/waka-rating-input/index.d.ts +110 -0
  95. package/dist/components/waka-reaction-picker/index.d.ts +77 -0
  96. package/dist/components/waka-region-map/index.d.ts +27 -0
  97. package/dist/components/waka-resource-gauge/index.d.ts +78 -0
  98. package/dist/components/waka-resource-pool/index.d.ts +81 -0
  99. package/dist/components/waka-rollback-slider/index.d.ts +79 -0
  100. package/dist/components/waka-sankey-diagram/index.d.ts +120 -0
  101. package/dist/components/waka-schedule-picker/index.d.ts +100 -0
  102. package/dist/components/waka-scratch-card/index.d.ts +87 -0
  103. package/dist/components/waka-season-pass/index.d.ts +65 -0
  104. package/dist/components/waka-security-score/index.d.ts +124 -0
  105. package/dist/components/waka-server-rack/index.d.ts +44 -0
  106. package/dist/components/waka-session-manager/index.d.ts +116 -0
  107. package/dist/components/waka-signature-pad/index.d.ts +87 -0
  108. package/dist/components/waka-skeleton-wave/index.d.ts +79 -0
  109. package/dist/components/waka-skill-tree/index.d.ts +78 -0
  110. package/dist/components/waka-sla-tracker/index.d.ts +65 -0
  111. package/dist/components/waka-slider-range/index.d.ts +88 -0
  112. package/dist/components/waka-spin-wheel/index.d.ts +51 -0
  113. package/dist/components/waka-spotlight/index.d.ts +47 -0
  114. package/dist/components/waka-stats-hexagon/index.d.ts +149 -0
  115. package/dist/components/waka-status-matrix/index.d.ts +38 -0
  116. package/dist/components/waka-streak-counter/index.d.ts +27 -0
  117. package/dist/components/waka-success-explosion/index.d.ts +51 -0
  118. package/dist/components/waka-swipe-card/index.d.ts +64 -0
  119. package/dist/components/waka-tabs-morph/index.d.ts +66 -0
  120. package/dist/components/waka-tag-input/index.d.ts +134 -0
  121. package/dist/components/waka-team-banner/index.d.ts +122 -0
  122. package/dist/components/waka-terminal-output/index.d.ts +48 -0
  123. package/dist/components/waka-thread-view/index.d.ts +101 -0
  124. package/dist/components/waka-tilt-card/index.d.ts +36 -0
  125. package/dist/components/waka-tooltip-tour/index.d.ts +118 -0
  126. package/dist/components/waka-tour-guide/index.d.ts +122 -0
  127. package/dist/components/waka-tournament-bracket/index.d.ts +101 -0
  128. package/dist/components/waka-treemap-chart/index.d.ts +104 -0
  129. package/dist/components/waka-two-factor-setup/index.d.ts +93 -0
  130. package/dist/components/waka-typewriter/index.d.ts +98 -0
  131. package/dist/components/waka-typing-indicator/index.d.ts +64 -0
  132. package/dist/components/waka-versus-card/index.d.ts +117 -0
  133. package/dist/components/waka-video-call/index.d.ts +170 -0
  134. package/dist/components/waka-voice-message/index.d.ts +117 -0
  135. package/dist/components/waka-welcome-modal/index.d.ts +120 -0
  136. package/dist/components/waka-xp-bar/index.d.ts +54 -0
  137. package/dist/export.cjs.js +1 -0
  138. package/dist/export.d.ts +3 -1
  139. package/dist/export.es.js +5 -0
  140. package/dist/index-B9GTFkji.js +1 -0
  141. package/dist/index-c0jcWyEL.mjs +466 -0
  142. package/dist/index.cjs.js +2530 -22
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.es.js +72081 -19512
  145. package/dist/rich-text.cjs.js +1 -0
  146. package/dist/rich-text.es.js +4 -0
  147. package/dist/types-BOWIoR7j.mjs +1111 -0
  148. package/dist/types-D2yCJ91P.js +1 -0
  149. package/dist/useDataTableImport-D8R2HQl6.mjs +229 -0
  150. package/dist/useDataTableImport-S_hhA5Wo.js +9 -0
  151. package/package.json +70 -22
  152. package/src/blocks/activity-timeline/index.tsx +586 -0
  153. package/src/blocks/calendar-view/index.tsx +756 -0
  154. package/src/blocks/chat/index.tsx +1018 -0
  155. package/src/blocks/chat/widget.tsx +504 -0
  156. package/src/blocks/dashboard/index.tsx +522 -0
  157. package/src/blocks/empty-states/index.tsx +452 -0
  158. package/src/blocks/error-pages/index.tsx +426 -0
  159. package/src/blocks/faq/index.tsx +479 -0
  160. package/src/blocks/file-manager/index.tsx +890 -0
  161. package/src/blocks/footer/index.tsx +133 -0
  162. package/src/blocks/header/index.tsx +357 -0
  163. package/src/blocks/headtab/index.tsx +139 -0
  164. package/src/blocks/i18n-editor/index.tsx +1016 -0
  165. package/src/blocks/index.ts +80 -0
  166. package/src/blocks/kanban-board/index.tsx +779 -0
  167. package/src/blocks/landing/index.tsx +677 -0
  168. package/src/blocks/language-selector/index.tsx +88 -0
  169. package/src/blocks/layout/index.tsx +159 -0
  170. package/src/blocks/login/index.tsx +339 -0
  171. package/src/blocks/login/types.ts +131 -0
  172. package/src/blocks/pricing/index.tsx +564 -0
  173. package/src/blocks/profile/index.tsx +746 -0
  174. package/src/blocks/settings/index.tsx +558 -0
  175. package/src/blocks/sidebar/index.tsx +713 -0
  176. package/src/blocks/theme-creator-block/index.tsx +835 -0
  177. package/src/blocks/user-management/index.tsx +1037 -0
  178. package/src/blocks/wizard/index.tsx +719 -0
  179. package/src/components/DataTable/DataTable.tsx +406 -0
  180. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  181. package/src/components/DataTable/DataTableBody.tsx +216 -0
  182. package/src/components/DataTable/DataTableCell.tsx +172 -0
  183. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  184. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  185. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  186. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  187. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  188. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  189. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  190. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  191. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  192. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  193. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  194. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  195. package/src/components/DataTable/README.md +446 -0
  196. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  197. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  198. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  199. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  200. package/src/components/DataTable/formatters/index.ts +335 -0
  201. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  202. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  203. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  204. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  205. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  206. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  207. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  208. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  209. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  210. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  211. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  212. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  213. package/src/components/DataTable/index.ts +81 -0
  214. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  215. package/src/components/DataTable/templates/index.tsx +803 -0
  216. package/src/components/DataTable/types.ts +504 -0
  217. package/src/components/DataTable/utils.ts +164 -0
  218. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  219. package/src/components/accordion/index.tsx +61 -0
  220. package/src/components/alert/index.tsx +61 -0
  221. package/src/components/alert-dialog/index.tsx +146 -0
  222. package/src/components/aspect-ratio/index.tsx +12 -0
  223. package/src/components/avatar/index.tsx +54 -0
  224. package/src/components/badge/Badge.stories.tsx +64 -0
  225. package/src/components/badge/index.tsx +38 -0
  226. package/src/components/button/Button.stories.tsx +173 -0
  227. package/src/components/button/index.tsx +56 -0
  228. package/src/components/calendar/index.tsx +73 -0
  229. package/src/components/card/index.tsx +78 -0
  230. package/src/components/checkbox/index.tsx +34 -0
  231. package/src/components/code/index.tsx +229 -0
  232. package/src/components/collapsible/index.tsx +16 -0
  233. package/src/components/command/index.tsx +162 -0
  234. package/src/components/context-menu/index.tsx +204 -0
  235. package/src/components/dialog/index.tsx +126 -0
  236. package/src/components/dropdown-menu/index.tsx +204 -0
  237. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  238. package/src/components/error-boundary/index.ts +7 -0
  239. package/src/components/form/index.tsx +183 -0
  240. package/src/components/hover-card/index.tsx +33 -0
  241. package/src/components/index.ts +368 -0
  242. package/src/components/input/Input.stories.tsx +100 -0
  243. package/src/components/input/index.tsx +27 -0
  244. package/src/components/input-otp/index.tsx +277 -0
  245. package/src/components/label/index.tsx +30 -0
  246. package/src/components/language-selector/index.tsx +341 -0
  247. package/src/components/menubar/index.tsx +240 -0
  248. package/src/components/navigation-menu/index.tsx +134 -0
  249. package/src/components/popover/index.tsx +35 -0
  250. package/src/components/progress/index.tsx +32 -0
  251. package/src/components/radio-group/index.tsx +48 -0
  252. package/src/components/scroll-area/index.tsx +52 -0
  253. package/src/components/select/index.tsx +164 -0
  254. package/src/components/separator/index.tsx +35 -0
  255. package/src/components/sheet/index.tsx +147 -0
  256. package/src/components/skeleton/index.tsx +22 -0
  257. package/src/components/slider/index.tsx +32 -0
  258. package/src/components/switch/index.tsx +33 -0
  259. package/src/components/table/index.tsx +117 -0
  260. package/src/components/tabs/index.tsx +59 -0
  261. package/src/components/textarea/index.tsx +30 -0
  262. package/src/components/theme-selector/index.tsx +327 -0
  263. package/src/components/toast/index.tsx +133 -0
  264. package/src/components/toaster/index.tsx +34 -0
  265. package/src/components/toggle/index.tsx +49 -0
  266. package/src/components/tooltip/index.tsx +34 -0
  267. package/src/components/typography/index.tsx +276 -0
  268. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  269. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  270. package/src/components/waka-activity-feed/index.tsx +686 -0
  271. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  272. package/src/components/waka-admincrumb/index.tsx +349 -0
  273. package/src/components/waka-alert-stack/index.tsx +827 -0
  274. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  275. package/src/components/waka-approval-chain/index.tsx +766 -0
  276. package/src/components/waka-audit-log/index.tsx +1475 -0
  277. package/src/components/waka-autocomplete/index.tsx +358 -0
  278. package/src/components/waka-badge-showcase/index.tsx +704 -0
  279. package/src/components/waka-barcode/index.tsx +260 -0
  280. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  281. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  282. package/src/components/waka-breadcrumb/index.tsx +376 -0
  283. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  284. package/src/components/waka-budget-burn/index.tsx +1234 -0
  285. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  286. package/src/components/waka-carousel/index.tsx +893 -0
  287. package/src/components/waka-cart-summary/index.tsx +1055 -0
  288. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  289. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  290. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  291. package/src/components/waka-charts/WakaChart.tsx +124 -0
  292. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  293. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  294. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  295. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  296. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  297. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  298. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  299. package/src/components/waka-charts/index.ts +90 -0
  300. package/src/components/waka-charts/types.ts +330 -0
  301. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  302. package/src/components/waka-checklist/index.tsx +1067 -0
  303. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  304. package/src/components/waka-cohort-table/index.tsx +1011 -0
  305. package/src/components/waka-color-picker/index.tsx +447 -0
  306. package/src/components/waka-combo-counter/index.tsx +864 -0
  307. package/src/components/waka-combobox/index.tsx +497 -0
  308. package/src/components/waka-command-bar/index.tsx +403 -0
  309. package/src/components/waka-compare-period/index.tsx +1230 -0
  310. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  311. package/src/components/waka-contribution-graph/index.tsx +552 -0
  312. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  313. package/src/components/waka-coupon-input/index.tsx +592 -0
  314. package/src/components/waka-credit-card-input/index.tsx +982 -0
  315. package/src/components/waka-daily-reward/index.tsx +762 -0
  316. package/src/components/waka-date-range-picker/index.tsx +378 -0
  317. package/src/components/waka-datetime-picker/index.tsx +793 -0
  318. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  319. package/src/components/waka-deployment-lane/index.tsx +673 -0
  320. package/src/components/waka-device-trust/index.tsx +1259 -0
  321. package/src/components/waka-dock/index.tsx +285 -0
  322. package/src/components/waka-drawer/index.tsx +319 -0
  323. package/src/components/waka-empty-state/index.tsx +545 -0
  324. package/src/components/waka-error-shake/index.tsx +398 -0
  325. package/src/components/waka-feature-announcement/index.tsx +991 -0
  326. package/src/components/waka-file-upload/index.tsx +437 -0
  327. package/src/components/waka-floating-nav/index.tsx +413 -0
  328. package/src/components/waka-flow-diagram/index.tsx +508 -0
  329. package/src/components/waka-funnel-chart/index.tsx +823 -0
  330. package/src/components/waka-glow-card/index.tsx +246 -0
  331. package/src/components/waka-goal-progress/index.tsx +1025 -0
  332. package/src/components/waka-haptic-button/index.tsx +388 -0
  333. package/src/components/waka-health-pulse/index.tsx +451 -0
  334. package/src/components/waka-heatmap/index.tsx +1026 -0
  335. package/src/components/waka-hotspot/index.tsx +682 -0
  336. package/src/components/waka-image/index.tsx +373 -0
  337. package/src/components/waka-incident-timeline/index.tsx +686 -0
  338. package/src/components/waka-invoice-preview/index.tsx +829 -0
  339. package/src/components/waka-kanban/index.tsx +646 -0
  340. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  341. package/src/components/waka-leaderboard/index.tsx +746 -0
  342. package/src/components/waka-level-progress/index.tsx +665 -0
  343. package/src/components/waka-liquid-button/index.tsx +520 -0
  344. package/src/components/waka-loading-orbit/index.tsx +478 -0
  345. package/src/components/waka-loot-box/index.tsx +1091 -0
  346. package/src/components/waka-magic-link/index.tsx +321 -0
  347. package/src/components/waka-magnetic-button/index.tsx +567 -0
  348. package/src/components/waka-mention-input/index.tsx +953 -0
  349. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  350. package/src/components/waka-milestone-road/index.tsx +1064 -0
  351. package/src/components/waka-modal/index.tsx +374 -0
  352. package/src/components/waka-morph-button/index.tsx +495 -0
  353. package/src/components/waka-network-topology/index.tsx +801 -0
  354. package/src/components/waka-notifications/index.tsx +414 -0
  355. package/src/components/waka-number-input/index.tsx +373 -0
  356. package/src/components/waka-orbital-menu/index.tsx +445 -0
  357. package/src/components/waka-order-tracker/index.tsx +1041 -0
  358. package/src/components/waka-pagination/index.tsx +393 -0
  359. package/src/components/waka-password-strength/index.tsx +824 -0
  360. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  361. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  362. package/src/components/waka-phone-input/index.tsx +801 -0
  363. package/src/components/waka-pipeline-view/index.tsx +604 -0
  364. package/src/components/waka-player-card/index.tsx +691 -0
  365. package/src/components/waka-points-popup/index.tsx +366 -0
  366. package/src/components/waka-power-up/index.tsx +1155 -0
  367. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  368. package/src/components/waka-pricing-table/index.tsx +755 -0
  369. package/src/components/waka-product-card/index.tsx +786 -0
  370. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  371. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  372. package/src/components/waka-qrcode/index.tsx +232 -0
  373. package/src/components/waka-quest-card/index.tsx +1275 -0
  374. package/src/components/waka-quota-bar/index.tsx +693 -0
  375. package/src/components/waka-radar-score/index.tsx +512 -0
  376. package/src/components/waka-rank-badge/index.tsx +813 -0
  377. package/src/components/waka-rating-input/index.tsx +560 -0
  378. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  379. package/src/components/waka-region-map/index.tsx +730 -0
  380. package/src/components/waka-resource-gauge/index.tsx +654 -0
  381. package/src/components/waka-resource-pool/index.tsx +1035 -0
  382. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  383. package/src/components/waka-rollback-slider/index.tsx +891 -0
  384. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  385. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  386. package/src/components/waka-scratch-card/index.tsx +914 -0
  387. package/src/components/waka-season-pass/index.tsx +886 -0
  388. package/src/components/waka-security-score/index.tsx +1126 -0
  389. package/src/components/waka-segmented-control/index.tsx +238 -0
  390. package/src/components/waka-server-rack/index.tsx +764 -0
  391. package/src/components/waka-session-manager/index.tsx +815 -0
  392. package/src/components/waka-signature-pad/index.tsx +744 -0
  393. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  394. package/src/components/waka-skill-tree/index.tsx +1031 -0
  395. package/src/components/waka-sla-tracker/index.tsx +798 -0
  396. package/src/components/waka-slider-range/index.tsx +765 -0
  397. package/src/components/waka-spin-wheel/index.tsx +671 -0
  398. package/src/components/waka-spinner/index.tsx +284 -0
  399. package/src/components/waka-spotlight/index.tsx +410 -0
  400. package/src/components/waka-stat/index.tsx +428 -0
  401. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  402. package/src/components/waka-status-matrix/index.tsx +565 -0
  403. package/src/components/waka-stepper/index.tsx +489 -0
  404. package/src/components/waka-streak-counter/index.tsx +334 -0
  405. package/src/components/waka-success-explosion/index.tsx +453 -0
  406. package/src/components/waka-swipe-card/index.tsx +574 -0
  407. package/src/components/waka-tabs-morph/index.tsx +509 -0
  408. package/src/components/waka-tag-input/index.tsx +877 -0
  409. package/src/components/waka-team-banner/index.tsx +1183 -0
  410. package/src/components/waka-terminal-output/index.tsx +836 -0
  411. package/src/components/waka-theme-creator/index.tsx +762 -0
  412. package/src/components/waka-theme-manager/index.tsx +654 -0
  413. package/src/components/waka-thread-view/index.tsx +874 -0
  414. package/src/components/waka-tilt-card/index.tsx +250 -0
  415. package/src/components/waka-time-picker/index.tsx +479 -0
  416. package/src/components/waka-timeline/index.tsx +385 -0
  417. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  418. package/src/components/waka-tour-guide/index.tsx +920 -0
  419. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  420. package/src/components/waka-tree/index.tsx +557 -0
  421. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  422. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  423. package/src/components/waka-typewriter/index.tsx +566 -0
  424. package/src/components/waka-typing-indicator/index.tsx +649 -0
  425. package/src/components/waka-versus-card/index.tsx +1026 -0
  426. package/src/components/waka-video/index.tsx +557 -0
  427. package/src/components/waka-video-call/index.tsx +1087 -0
  428. package/src/components/waka-virtual-list/index.tsx +327 -0
  429. package/src/components/waka-voice-message/index.tsx +1019 -0
  430. package/src/components/waka-welcome-modal/index.tsx +790 -0
  431. package/src/components/waka-xp-bar/index.tsx +799 -0
  432. package/src/styles/base.css +108 -0
  433. package/src/styles/code-highlight.css +82 -86
  434. package/src/styles/globals-v3.css +9 -0
  435. package/src/styles/globals.css +57 -74
  436. package/src/styles/tailwind.preset.js +69 -0
@@ -0,0 +1,1278 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import {
6
+ AlertTriangle,
7
+ ArrowUpDown,
8
+ ChevronDown,
9
+ ChevronUp,
10
+ Filter,
11
+ GripHorizontal,
12
+ Percent,
13
+ Search,
14
+ X,
15
+ } from "lucide-react"
16
+
17
+ // ============================================================================
18
+ // Types
19
+ // ============================================================================
20
+
21
+ export interface Resource {
22
+ /** Unique identifier for the resource */
23
+ id: string
24
+ /** Display name */
25
+ name: string
26
+ /** Optional category for grouping */
27
+ category?: string
28
+ /** Maximum capacity (default: 100) */
29
+ capacity?: number
30
+ /** Custom color for the resource row */
31
+ color?: string
32
+ }
33
+
34
+ export interface Consumer {
35
+ /** Unique identifier for the consumer/project */
36
+ id: string
37
+ /** Display name */
38
+ name: string
39
+ /** Optional priority level */
40
+ priority?: "low" | "medium" | "high" | "critical"
41
+ /** Custom color for the consumer column */
42
+ color?: string
43
+ }
44
+
45
+ export interface Allocation {
46
+ /** Resource ID */
47
+ resourceId: string
48
+ /** Consumer ID */
49
+ consumerId: string
50
+ /** Allocation percentage (0-100+) */
51
+ value: number
52
+ }
53
+
54
+ export type SortField = "name" | "total" | "allocation"
55
+ export type SortDirection = "asc" | "desc"
56
+ export type FilterMode = "all" | "over-allocated" | "under-allocated" | "unallocated"
57
+
58
+ export interface AllocationMatrixState {
59
+ /** Current allocations */
60
+ allocations: Map<string, number>
61
+ /** Selected cell for editing */
62
+ selectedCell: { resourceId: string; consumerId: string } | null
63
+ /** Row sort configuration */
64
+ rowSort: { field: SortField; direction: SortDirection }
65
+ /** Column sort configuration */
66
+ columnSort: { field: SortField; direction: SortDirection }
67
+ /** Filter mode */
68
+ filterMode: FilterMode
69
+ /** Search query */
70
+ searchQuery: string
71
+ /** Dragging state */
72
+ isDragging: boolean
73
+ /** Drag start value */
74
+ dragStartValue: number
75
+ }
76
+
77
+ export interface AllocationSummary {
78
+ /** Total allocations across all cells */
79
+ totalAllocations: number
80
+ /** Average allocation per cell */
81
+ averageAllocation: number
82
+ /** Number of over-allocated resources (>100%) */
83
+ overAllocatedCount: number
84
+ /** Number of under-allocated resources (<100%) */
85
+ underAllocatedCount: number
86
+ /** Number of fully allocated resources (=100%) */
87
+ fullyAllocatedCount: number
88
+ /** Number of unallocated resources (0%) */
89
+ unallocatedCount: number
90
+ /** Utilization efficiency percentage */
91
+ utilizationEfficiency: number
92
+ }
93
+
94
+ export interface WakaAllocationMatrixProps {
95
+ /** Resources (rows) */
96
+ resources: Resource[]
97
+ /** Consumers/Projects (columns) */
98
+ consumers: Consumer[]
99
+ /** Initial allocations */
100
+ allocations?: Allocation[]
101
+ /** Enable editing */
102
+ editable?: boolean
103
+ /** Enable drag to adjust allocations */
104
+ enableDrag?: boolean
105
+ /** Show row totals */
106
+ showRowTotals?: boolean
107
+ /** Show column totals */
108
+ showColumnTotals?: boolean
109
+ /** Show summary statistics */
110
+ showSummary?: boolean
111
+ /** Show filter controls */
112
+ showFilters?: boolean
113
+ /** Show search */
114
+ showSearch?: boolean
115
+ /** Over-allocation threshold (default: 100) */
116
+ overAllocationThreshold?: number
117
+ /** Cell size */
118
+ cellSize?: "sm" | "md" | "lg"
119
+ /** Color scheme for intensity */
120
+ colorScheme?: "blue" | "green" | "purple" | "orange"
121
+ /** Callback when allocation changes */
122
+ onAllocationChange?: (resourceId: string, consumerId: string, value: number) => void
123
+ /** Callback when allocations are saved */
124
+ onSave?: (allocations: Allocation[]) => void
125
+ /** Custom value formatter */
126
+ formatValue?: (value: number) => string
127
+ /** Additional CSS classes */
128
+ className?: string
129
+ }
130
+
131
+ // ============================================================================
132
+ // Constants
133
+ // ============================================================================
134
+
135
+ const CELL_SIZE_CONFIG = {
136
+ sm: {
137
+ cell: "w-12 h-12",
138
+ header: "w-12 h-10",
139
+ text: "text-xs",
140
+ headerText: "text-xs",
141
+ padding: "p-1",
142
+ icon: "h-3 w-3",
143
+ },
144
+ md: {
145
+ cell: "w-16 h-16",
146
+ header: "w-16 h-12",
147
+ text: "text-sm",
148
+ headerText: "text-sm",
149
+ padding: "p-2",
150
+ icon: "h-4 w-4",
151
+ },
152
+ lg: {
153
+ cell: "w-20 h-20",
154
+ header: "w-20 h-14",
155
+ text: "text-base",
156
+ headerText: "text-base",
157
+ padding: "p-3",
158
+ icon: "h-5 w-5",
159
+ },
160
+ }
161
+
162
+ const COLOR_SCHEMES = {
163
+ blue: {
164
+ light: ["bg-blue-50", "bg-blue-100", "bg-blue-200", "bg-blue-300", "bg-blue-400", "bg-blue-500"],
165
+ dark: ["dark:bg-blue-950", "dark:bg-blue-900", "dark:bg-blue-800", "dark:bg-blue-700", "dark:bg-blue-600", "dark:bg-blue-500"],
166
+ text: ["text-blue-900", "text-blue-900", "text-blue-900", "text-white", "text-white", "text-white"],
167
+ darkText: ["dark:text-blue-100", "dark:text-blue-100", "dark:text-blue-100", "dark:text-white", "dark:text-white", "dark:text-white"],
168
+ },
169
+ green: {
170
+ light: ["bg-green-50", "bg-green-100", "bg-green-200", "bg-green-300", "bg-green-400", "bg-green-500"],
171
+ dark: ["dark:bg-green-950", "dark:bg-green-900", "dark:bg-green-800", "dark:bg-green-700", "dark:bg-green-600", "dark:bg-green-500"],
172
+ text: ["text-green-900", "text-green-900", "text-green-900", "text-white", "text-white", "text-white"],
173
+ darkText: ["dark:text-green-100", "dark:text-green-100", "dark:text-green-100", "dark:text-white", "dark:text-white", "dark:text-white"],
174
+ },
175
+ purple: {
176
+ light: ["bg-purple-50", "bg-purple-100", "bg-purple-200", "bg-purple-300", "bg-purple-400", "bg-purple-500"],
177
+ dark: ["dark:bg-purple-950", "dark:bg-purple-900", "dark:bg-purple-800", "dark:bg-purple-700", "dark:bg-purple-600", "dark:bg-purple-500"],
178
+ text: ["text-purple-900", "text-purple-900", "text-purple-900", "text-white", "text-white", "text-white"],
179
+ darkText: ["dark:text-purple-100", "dark:text-purple-100", "dark:text-purple-100", "dark:text-white", "dark:text-white", "dark:text-white"],
180
+ },
181
+ orange: {
182
+ light: ["bg-orange-50", "bg-orange-100", "bg-orange-200", "bg-orange-300", "bg-orange-400", "bg-orange-500"],
183
+ dark: ["dark:bg-orange-950", "dark:bg-orange-900", "dark:bg-orange-800", "dark:bg-orange-700", "dark:bg-orange-600", "dark:bg-orange-500"],
184
+ text: ["text-orange-900", "text-orange-900", "text-orange-900", "text-white", "text-white", "text-white"],
185
+ darkText: ["dark:text-orange-100", "dark:text-orange-100", "dark:text-orange-100", "dark:text-white", "dark:text-white", "dark:text-white"],
186
+ },
187
+ }
188
+
189
+ const PRIORITY_COLORS = {
190
+ low: "bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400",
191
+ medium: "bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400",
192
+ high: "bg-orange-100 dark:bg-orange-900 text-orange-600 dark:text-orange-400",
193
+ critical: "bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-400",
194
+ }
195
+
196
+ // ============================================================================
197
+ // Helper Functions
198
+ // ============================================================================
199
+
200
+ function getAllocationKey(resourceId: string, consumerId: string): string {
201
+ return `${resourceId}:${consumerId}`
202
+ }
203
+
204
+ function getIntensityLevel(value: number, maxValue: number = 100): number {
205
+ if (value === 0) return 0
206
+ const percentage = (value / maxValue) * 100
207
+ if (percentage <= 20) return 1
208
+ if (percentage <= 40) return 2
209
+ if (percentage <= 60) return 3
210
+ if (percentage <= 80) return 4
211
+ return 5
212
+ }
213
+
214
+ function calculateRowTotal(
215
+ resourceId: string,
216
+ consumers: Consumer[],
217
+ allocations: Map<string, number>
218
+ ): number {
219
+ return consumers.reduce((sum, consumer) => {
220
+ const key = getAllocationKey(resourceId, consumer.id)
221
+ return sum + (allocations.get(key) || 0)
222
+ }, 0)
223
+ }
224
+
225
+ function calculateColumnTotal(
226
+ consumerId: string,
227
+ resources: Resource[],
228
+ allocations: Map<string, number>
229
+ ): number {
230
+ return resources.reduce((sum, resource) => {
231
+ const key = getAllocationKey(resource.id, consumerId)
232
+ return sum + (allocations.get(key) || 0)
233
+ }, 0)
234
+ }
235
+
236
+ function calculateSummary(
237
+ resources: Resource[],
238
+ consumers: Consumer[],
239
+ allocations: Map<string, number>,
240
+ overAllocationThreshold: number
241
+ ): AllocationSummary {
242
+ let totalAllocations = 0
243
+ let overAllocatedCount = 0
244
+ let underAllocatedCount = 0
245
+ let fullyAllocatedCount = 0
246
+ let unallocatedCount = 0
247
+
248
+ resources.forEach((resource) => {
249
+ const rowTotal = calculateRowTotal(resource.id, consumers, allocations)
250
+ totalAllocations += rowTotal
251
+ const capacity = resource.capacity || overAllocationThreshold
252
+
253
+ if (rowTotal === 0) {
254
+ unallocatedCount++
255
+ } else if (rowTotal > capacity) {
256
+ overAllocatedCount++
257
+ } else if (rowTotal === capacity) {
258
+ fullyAllocatedCount++
259
+ } else {
260
+ underAllocatedCount++
261
+ }
262
+ })
263
+
264
+ const totalCells = resources.length * consumers.length
265
+ const averageAllocation = totalCells > 0 ? totalAllocations / totalCells : 0
266
+ const maxPossibleAllocation = resources.reduce(
267
+ (sum, r) => sum + (r.capacity || overAllocationThreshold),
268
+ 0
269
+ )
270
+ const utilizationEfficiency =
271
+ maxPossibleAllocation > 0
272
+ ? Math.min(100, (totalAllocations / maxPossibleAllocation) * 100)
273
+ : 0
274
+
275
+ return {
276
+ totalAllocations,
277
+ averageAllocation,
278
+ overAllocatedCount,
279
+ underAllocatedCount,
280
+ fullyAllocatedCount,
281
+ unallocatedCount,
282
+ utilizationEfficiency,
283
+ }
284
+ }
285
+
286
+ // ============================================================================
287
+ // Custom Hook: useAllocationMatrix
288
+ // ============================================================================
289
+
290
+ export interface UseAllocationMatrixOptions {
291
+ /** Initial resources */
292
+ resources: Resource[]
293
+ /** Initial consumers */
294
+ consumers: Consumer[]
295
+ /** Initial allocations */
296
+ initialAllocations?: Allocation[]
297
+ /** Over-allocation threshold */
298
+ overAllocationThreshold?: number
299
+ /** Callback when allocation changes */
300
+ onAllocationChange?: (resourceId: string, consumerId: string, value: number) => void
301
+ }
302
+
303
+ export interface UseAllocationMatrixReturn {
304
+ /** Current allocations map */
305
+ allocations: Map<string, number>
306
+ /** Set allocation for a cell */
307
+ setAllocation: (resourceId: string, consumerId: string, value: number) => void
308
+ /** Get allocation for a cell */
309
+ getAllocation: (resourceId: string, consumerId: string) => number
310
+ /** Get row total */
311
+ getRowTotal: (resourceId: string) => number
312
+ /** Get column total */
313
+ getColumnTotal: (consumerId: string) => number
314
+ /** Check if resource is over-allocated */
315
+ isOverAllocated: (resourceId: string) => boolean
316
+ /** Get summary statistics */
317
+ getSummary: () => AllocationSummary
318
+ /** Clear all allocations */
319
+ clearAll: () => void
320
+ /** Reset to initial allocations */
321
+ reset: () => void
322
+ /** Export allocations as array */
323
+ exportAllocations: () => Allocation[]
324
+ /** Import allocations from array */
325
+ importAllocations: (allocations: Allocation[]) => void
326
+ /** Selected cell */
327
+ selectedCell: { resourceId: string; consumerId: string } | null
328
+ /** Set selected cell */
329
+ setSelectedCell: (cell: { resourceId: string; consumerId: string } | null) => void
330
+ /** Row sort */
331
+ rowSort: { field: SortField; direction: SortDirection }
332
+ /** Set row sort */
333
+ setRowSort: (sort: { field: SortField; direction: SortDirection }) => void
334
+ /** Column sort */
335
+ columnSort: { field: SortField; direction: SortDirection }
336
+ /** Set column sort */
337
+ setColumnSort: (sort: { field: SortField; direction: SortDirection }) => void
338
+ /** Filter mode */
339
+ filterMode: FilterMode
340
+ /** Set filter mode */
341
+ setFilterMode: (mode: FilterMode) => void
342
+ /** Search query */
343
+ searchQuery: string
344
+ /** Set search query */
345
+ setSearchQuery: (query: string) => void
346
+ /** Sorted and filtered resources */
347
+ filteredResources: Resource[]
348
+ /** Sorted consumers */
349
+ sortedConsumers: Consumer[]
350
+ }
351
+
352
+ export function useAllocationMatrix({
353
+ resources,
354
+ consumers,
355
+ initialAllocations = [],
356
+ overAllocationThreshold = 100,
357
+ onAllocationChange,
358
+ }: UseAllocationMatrixOptions): UseAllocationMatrixReturn {
359
+ // Initialize allocations map
360
+ const [allocations, setAllocations] = React.useState<Map<string, number>>(() => {
361
+ const map = new Map<string, number>()
362
+ initialAllocations.forEach((alloc) => {
363
+ const key = getAllocationKey(alloc.resourceId, alloc.consumerId)
364
+ map.set(key, alloc.value)
365
+ })
366
+ return map
367
+ })
368
+
369
+ const [selectedCell, setSelectedCell] = React.useState<{
370
+ resourceId: string
371
+ consumerId: string
372
+ } | null>(null)
373
+
374
+ const [rowSort, setRowSort] = React.useState<{
375
+ field: SortField
376
+ direction: SortDirection
377
+ }>({ field: "name", direction: "asc" })
378
+
379
+ const [columnSort, setColumnSort] = React.useState<{
380
+ field: SortField
381
+ direction: SortDirection
382
+ }>({ field: "name", direction: "asc" })
383
+
384
+ const [filterMode, setFilterMode] = React.useState<FilterMode>("all")
385
+ const [searchQuery, setSearchQuery] = React.useState("")
386
+
387
+ // Store initial allocations for reset
388
+ const initialAllocationsRef = React.useRef(initialAllocations)
389
+
390
+ const setAllocation = React.useCallback(
391
+ (resourceId: string, consumerId: string, value: number) => {
392
+ const key = getAllocationKey(resourceId, consumerId)
393
+ setAllocations((prev) => {
394
+ const next = new Map(prev)
395
+ if (value === 0) {
396
+ next.delete(key)
397
+ } else {
398
+ next.set(key, Math.max(0, value))
399
+ }
400
+ return next
401
+ })
402
+ onAllocationChange?.(resourceId, consumerId, value)
403
+ },
404
+ [onAllocationChange]
405
+ )
406
+
407
+ const getAllocation = React.useCallback(
408
+ (resourceId: string, consumerId: string): number => {
409
+ const key = getAllocationKey(resourceId, consumerId)
410
+ return allocations.get(key) || 0
411
+ },
412
+ [allocations]
413
+ )
414
+
415
+ const getRowTotal = React.useCallback(
416
+ (resourceId: string): number => {
417
+ return calculateRowTotal(resourceId, consumers, allocations)
418
+ },
419
+ [consumers, allocations]
420
+ )
421
+
422
+ const getColumnTotal = React.useCallback(
423
+ (consumerId: string): number => {
424
+ return calculateColumnTotal(consumerId, resources, allocations)
425
+ },
426
+ [resources, allocations]
427
+ )
428
+
429
+ const isOverAllocated = React.useCallback(
430
+ (resourceId: string): boolean => {
431
+ const resource = resources.find((r) => r.id === resourceId)
432
+ const capacity = resource?.capacity || overAllocationThreshold
433
+ return getRowTotal(resourceId) > capacity
434
+ },
435
+ [resources, getRowTotal, overAllocationThreshold]
436
+ )
437
+
438
+ const getSummary = React.useCallback((): AllocationSummary => {
439
+ return calculateSummary(resources, consumers, allocations, overAllocationThreshold)
440
+ }, [resources, consumers, allocations, overAllocationThreshold])
441
+
442
+ const clearAll = React.useCallback(() => {
443
+ setAllocations(new Map())
444
+ }, [])
445
+
446
+ const reset = React.useCallback(() => {
447
+ const map = new Map<string, number>()
448
+ initialAllocationsRef.current.forEach((alloc) => {
449
+ const key = getAllocationKey(alloc.resourceId, alloc.consumerId)
450
+ map.set(key, alloc.value)
451
+ })
452
+ setAllocations(map)
453
+ }, [])
454
+
455
+ const exportAllocations = React.useCallback((): Allocation[] => {
456
+ const result: Allocation[] = []
457
+ allocations.forEach((value, key) => {
458
+ const [resourceId, consumerId] = key.split(":")
459
+ result.push({ resourceId, consumerId, value })
460
+ })
461
+ return result
462
+ }, [allocations])
463
+
464
+ const importAllocations = React.useCallback((newAllocations: Allocation[]) => {
465
+ const map = new Map<string, number>()
466
+ newAllocations.forEach((alloc) => {
467
+ const key = getAllocationKey(alloc.resourceId, alloc.consumerId)
468
+ map.set(key, alloc.value)
469
+ })
470
+ setAllocations(map)
471
+ }, [])
472
+
473
+ // Sorted and filtered resources
474
+ const filteredResources = React.useMemo(() => {
475
+ let filtered = [...resources]
476
+
477
+ // Apply search filter
478
+ if (searchQuery) {
479
+ const query = searchQuery.toLowerCase()
480
+ filtered = filtered.filter(
481
+ (r) =>
482
+ r.name.toLowerCase().includes(query) ||
483
+ r.category?.toLowerCase().includes(query)
484
+ )
485
+ }
486
+
487
+ // Apply allocation filter
488
+ switch (filterMode) {
489
+ case "over-allocated":
490
+ filtered = filtered.filter((r) => isOverAllocated(r.id))
491
+ break
492
+ case "under-allocated":
493
+ filtered = filtered.filter((r) => {
494
+ const total = getRowTotal(r.id)
495
+ const capacity = r.capacity || overAllocationThreshold
496
+ return total > 0 && total < capacity
497
+ })
498
+ break
499
+ case "unallocated":
500
+ filtered = filtered.filter((r) => getRowTotal(r.id) === 0)
501
+ break
502
+ }
503
+
504
+ // Apply sorting
505
+ filtered.sort((a, b) => {
506
+ let comparison = 0
507
+ switch (rowSort.field) {
508
+ case "name":
509
+ comparison = a.name.localeCompare(b.name)
510
+ break
511
+ case "total":
512
+ comparison = getRowTotal(a.id) - getRowTotal(b.id)
513
+ break
514
+ }
515
+ return rowSort.direction === "asc" ? comparison : -comparison
516
+ })
517
+
518
+ return filtered
519
+ }, [
520
+ resources,
521
+ searchQuery,
522
+ filterMode,
523
+ rowSort,
524
+ isOverAllocated,
525
+ getRowTotal,
526
+ overAllocationThreshold,
527
+ ])
528
+
529
+ // Sorted consumers
530
+ const sortedConsumers = React.useMemo(() => {
531
+ const sorted = [...consumers]
532
+ sorted.sort((a, b) => {
533
+ let comparison = 0
534
+ switch (columnSort.field) {
535
+ case "name":
536
+ comparison = a.name.localeCompare(b.name)
537
+ break
538
+ case "total":
539
+ comparison = getColumnTotal(a.id) - getColumnTotal(b.id)
540
+ break
541
+ }
542
+ return columnSort.direction === "asc" ? comparison : -comparison
543
+ })
544
+ return sorted
545
+ }, [consumers, columnSort, getColumnTotal])
546
+
547
+ return {
548
+ allocations,
549
+ setAllocation,
550
+ getAllocation,
551
+ getRowTotal,
552
+ getColumnTotal,
553
+ isOverAllocated,
554
+ getSummary,
555
+ clearAll,
556
+ reset,
557
+ exportAllocations,
558
+ importAllocations,
559
+ selectedCell,
560
+ setSelectedCell,
561
+ rowSort,
562
+ setRowSort,
563
+ columnSort,
564
+ setColumnSort,
565
+ filterMode,
566
+ setFilterMode,
567
+ searchQuery,
568
+ setSearchQuery,
569
+ filteredResources,
570
+ sortedConsumers,
571
+ }
572
+ }
573
+
574
+ // ============================================================================
575
+ // Sub-Components
576
+ // ============================================================================
577
+
578
+ interface CellProps {
579
+ resourceId: string
580
+ consumerId: string
581
+ value: number
582
+ isSelected: boolean
583
+ isEditable: boolean
584
+ enableDrag: boolean
585
+ isOverAllocated: boolean
586
+ colorScheme: keyof typeof COLOR_SCHEMES
587
+ size: "sm" | "md" | "lg"
588
+ onSelect: () => void
589
+ onChange: (value: number) => void
590
+ onDragStart: (value: number) => void
591
+ onDrag: (deltaY: number) => void
592
+ onDragEnd: () => void
593
+ }
594
+
595
+ function AllocationCell({
596
+ value,
597
+ isSelected,
598
+ isEditable,
599
+ enableDrag,
600
+ isOverAllocated,
601
+ colorScheme,
602
+ size,
603
+ onSelect,
604
+ onChange,
605
+ onDragStart,
606
+ onDrag,
607
+ onDragEnd,
608
+ }: CellProps) {
609
+ const [editValue, setEditValue] = React.useState(value.toString())
610
+ const [isDragging, setIsDragging] = React.useState(false)
611
+ const dragStartY = React.useRef(0)
612
+ const inputRef = React.useRef<HTMLInputElement>(null)
613
+
614
+ const config = CELL_SIZE_CONFIG[size]
615
+ const colors = COLOR_SCHEMES[colorScheme]
616
+ const level = getIntensityLevel(value)
617
+
618
+ React.useEffect(() => {
619
+ setEditValue(value.toString())
620
+ }, [value])
621
+
622
+ React.useEffect(() => {
623
+ if (isSelected && inputRef.current) {
624
+ inputRef.current.focus()
625
+ inputRef.current.select()
626
+ }
627
+ }, [isSelected])
628
+
629
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
630
+ if (e.key === "Enter") {
631
+ const newValue = Math.max(0, parseInt(editValue) || 0)
632
+ onChange(newValue)
633
+ onSelect()
634
+ } else if (e.key === "Escape") {
635
+ setEditValue(value.toString())
636
+ onSelect()
637
+ }
638
+ }
639
+
640
+ const handleBlur = () => {
641
+ const newValue = Math.max(0, parseInt(editValue) || 0)
642
+ if (newValue !== value) {
643
+ onChange(newValue)
644
+ }
645
+ }
646
+
647
+ const handleMouseDown = (e: React.MouseEvent) => {
648
+ if (!enableDrag || !isEditable) return
649
+ setIsDragging(true)
650
+ dragStartY.current = e.clientY
651
+ onDragStart(value)
652
+ }
653
+
654
+ const handleMouseMove = React.useCallback(
655
+ (e: MouseEvent) => {
656
+ if (!isDragging) return
657
+ const deltaY = dragStartY.current - e.clientY
658
+ onDrag(deltaY)
659
+ },
660
+ [isDragging, onDrag]
661
+ )
662
+
663
+ const handleMouseUp = React.useCallback(() => {
664
+ if (!isDragging) return
665
+ setIsDragging(false)
666
+ onDragEnd()
667
+ }, [isDragging, onDragEnd])
668
+
669
+ React.useEffect(() => {
670
+ if (isDragging) {
671
+ window.addEventListener("mousemove", handleMouseMove)
672
+ window.addEventListener("mouseup", handleMouseUp)
673
+ return () => {
674
+ window.removeEventListener("mousemove", handleMouseMove)
675
+ window.removeEventListener("mouseup", handleMouseUp)
676
+ }
677
+ }
678
+ }, [isDragging, handleMouseMove, handleMouseUp])
679
+
680
+ return (
681
+ <div
682
+ className={cn(
683
+ "relative flex items-center justify-center border border-border/50 transition-all duration-200",
684
+ config.cell,
685
+ config.padding,
686
+ colors.light[level],
687
+ colors.dark[level],
688
+ colors.text[level],
689
+ colors.darkText[level],
690
+ isEditable && "cursor-pointer hover:ring-2 hover:ring-primary/50",
691
+ isSelected && "ring-2 ring-primary z-10",
692
+ isDragging && "cursor-ns-resize",
693
+ isOverAllocated && value > 0 && "ring-2 ring-red-500 ring-inset"
694
+ )}
695
+ onClick={isEditable && !isSelected ? onSelect : undefined}
696
+ onMouseDown={handleMouseDown}
697
+ >
698
+ {isSelected && isEditable ? (
699
+ <input
700
+ ref={inputRef}
701
+ type="number"
702
+ min="0"
703
+ max="200"
704
+ value={editValue}
705
+ onChange={(e) => setEditValue(e.target.value)}
706
+ onKeyDown={handleKeyDown}
707
+ onBlur={handleBlur}
708
+ className={cn(
709
+ "w-full h-full text-center bg-transparent outline-none",
710
+ config.text,
711
+ "font-medium tabular-nums",
712
+ "[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
713
+ )}
714
+ />
715
+ ) : (
716
+ <span className={cn(config.text, "font-medium tabular-nums select-none")}>
717
+ {value > 0 ? `${value}%` : "-"}
718
+ </span>
719
+ )}
720
+ {enableDrag && isEditable && !isSelected && (
721
+ <div className="absolute bottom-0.5 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-50 transition-opacity">
722
+ <GripHorizontal className={cn(config.icon, "text-current")} />
723
+ </div>
724
+ )}
725
+ </div>
726
+ )
727
+ }
728
+
729
+ interface HeaderCellProps {
730
+ label: string
731
+ subtitle?: string
732
+ priority?: "low" | "medium" | "high" | "critical"
733
+ isColumn?: boolean
734
+ isSortable?: boolean
735
+ sortDirection?: SortDirection | null
736
+ size: "sm" | "md" | "lg"
737
+ onSort?: () => void
738
+ }
739
+
740
+ function HeaderCell({
741
+ label,
742
+ subtitle,
743
+ priority,
744
+ isColumn,
745
+ isSortable,
746
+ sortDirection,
747
+ size,
748
+ onSort,
749
+ }: HeaderCellProps) {
750
+ const config = CELL_SIZE_CONFIG[size]
751
+
752
+ return (
753
+ <div
754
+ className={cn(
755
+ "flex flex-col items-center justify-center bg-muted/50 border border-border/50",
756
+ config.header,
757
+ config.padding,
758
+ isColumn && "min-w-max",
759
+ isSortable && "cursor-pointer hover:bg-muted transition-colors"
760
+ )}
761
+ onClick={onSort}
762
+ >
763
+ <div className="flex items-center gap-1">
764
+ <span
765
+ className={cn(
766
+ config.headerText,
767
+ "font-medium truncate max-w-full",
768
+ isColumn && "writing-mode-vertical-rl rotate-180"
769
+ )}
770
+ style={isColumn ? { writingMode: "vertical-rl" } : undefined}
771
+ title={label}
772
+ >
773
+ {label}
774
+ </span>
775
+ {isSortable && sortDirection && (
776
+ sortDirection === "asc" ? (
777
+ <ChevronUp className={config.icon} />
778
+ ) : (
779
+ <ChevronDown className={config.icon} />
780
+ )
781
+ )}
782
+ </div>
783
+ {subtitle && (
784
+ <span className={cn("text-muted-foreground", size === "sm" ? "text-[10px]" : "text-xs")}>
785
+ {subtitle}
786
+ </span>
787
+ )}
788
+ {priority && (
789
+ <span
790
+ className={cn(
791
+ "mt-0.5 px-1 rounded text-[10px] font-medium",
792
+ PRIORITY_COLORS[priority]
793
+ )}
794
+ >
795
+ {priority}
796
+ </span>
797
+ )}
798
+ </div>
799
+ )
800
+ }
801
+
802
+ interface TotalCellProps {
803
+ value: number
804
+ isOverAllocated?: boolean
805
+ maxValue?: number
806
+ size: "sm" | "md" | "lg"
807
+ isRow?: boolean
808
+ }
809
+
810
+ function TotalCell({ value, isOverAllocated, maxValue = 100, size, isRow }: TotalCellProps) {
811
+ const config = CELL_SIZE_CONFIG[size]
812
+ const percentage = (value / maxValue) * 100
813
+
814
+ return (
815
+ <div
816
+ className={cn(
817
+ "flex flex-col items-center justify-center border border-border/50",
818
+ isRow ? "bg-muted/30" : "bg-muted/50",
819
+ config.cell,
820
+ config.padding,
821
+ isOverAllocated && "bg-red-100 dark:bg-red-900/30"
822
+ )}
823
+ >
824
+ <span
825
+ className={cn(
826
+ config.text,
827
+ "font-bold tabular-nums",
828
+ isOverAllocated ? "text-red-600 dark:text-red-400" : "text-foreground"
829
+ )}
830
+ >
831
+ {value}%
832
+ </span>
833
+ {isRow && (
834
+ <div className="w-full mt-1 h-1 bg-muted rounded-full overflow-hidden">
835
+ <div
836
+ className={cn(
837
+ "h-full rounded-full transition-all duration-300",
838
+ isOverAllocated
839
+ ? "bg-red-500"
840
+ : percentage >= 80
841
+ ? "bg-yellow-500"
842
+ : "bg-green-500"
843
+ )}
844
+ style={{ width: `${Math.min(percentage, 100)}%` }}
845
+ />
846
+ </div>
847
+ )}
848
+ {isOverAllocated && (
849
+ <AlertTriangle className={cn(config.icon, "text-red-500 mt-0.5")} />
850
+ )}
851
+ </div>
852
+ )
853
+ }
854
+
855
+ interface SummaryCardProps {
856
+ summary: AllocationSummary
857
+ size: "sm" | "md" | "lg"
858
+ }
859
+
860
+ function SummaryCard({ summary, size }: SummaryCardProps) {
861
+ const config = CELL_SIZE_CONFIG[size]
862
+
863
+ const stats = [
864
+ { label: "Total Allocated", value: `${summary.totalAllocations.toFixed(0)}%`, color: "text-foreground" },
865
+ { label: "Avg per Cell", value: `${summary.averageAllocation.toFixed(1)}%`, color: "text-foreground" },
866
+ { label: "Utilization", value: `${summary.utilizationEfficiency.toFixed(1)}%`, color: "text-blue-600" },
867
+ { label: "Over-allocated", value: summary.overAllocatedCount.toString(), color: summary.overAllocatedCount > 0 ? "text-red-600" : "text-green-600" },
868
+ { label: "Fully Allocated", value: summary.fullyAllocatedCount.toString(), color: "text-green-600" },
869
+ { label: "Under-allocated", value: summary.underAllocatedCount.toString(), color: "text-yellow-600" },
870
+ { label: "Unallocated", value: summary.unallocatedCount.toString(), color: "text-muted-foreground" },
871
+ ]
872
+
873
+ return (
874
+ <div className="flex flex-wrap gap-4 p-4 bg-muted/30 rounded-lg border border-border">
875
+ {stats.map((stat) => (
876
+ <div key={stat.label} className="flex flex-col">
877
+ <span className={cn("text-muted-foreground", config.headerText)}>{stat.label}</span>
878
+ <span className={cn("font-bold tabular-nums", config.text, stat.color)}>
879
+ {stat.value}
880
+ </span>
881
+ </div>
882
+ ))}
883
+ </div>
884
+ )
885
+ }
886
+
887
+ // ============================================================================
888
+ // Main Component
889
+ // ============================================================================
890
+
891
+ export function WakaAllocationMatrix({
892
+ resources,
893
+ consumers,
894
+ allocations: initialAllocations = [],
895
+ editable = true,
896
+ enableDrag = true,
897
+ showRowTotals = true,
898
+ showColumnTotals = true,
899
+ showSummary = true,
900
+ showFilters = true,
901
+ showSearch = true,
902
+ overAllocationThreshold = 100,
903
+ cellSize = "md",
904
+ colorScheme = "blue",
905
+ onAllocationChange,
906
+ onSave,
907
+ formatValue,
908
+ className,
909
+ }: WakaAllocationMatrixProps) {
910
+ const matrix = useAllocationMatrix({
911
+ resources,
912
+ consumers,
913
+ initialAllocations,
914
+ overAllocationThreshold,
915
+ onAllocationChange,
916
+ })
917
+
918
+ const [dragStartValue, setDragStartValue] = React.useState(0)
919
+ const [currentDragCell, setCurrentDragCell] = React.useState<{
920
+ resourceId: string
921
+ consumerId: string
922
+ } | null>(null)
923
+
924
+ const config = CELL_SIZE_CONFIG[cellSize]
925
+
926
+ const handleDragStart = (resourceId: string, consumerId: string, value: number) => {
927
+ setDragStartValue(value)
928
+ setCurrentDragCell({ resourceId, consumerId })
929
+ }
930
+
931
+ const handleDrag = (resourceId: string, consumerId: string, deltaY: number) => {
932
+ if (!currentDragCell) return
933
+ // Every 5 pixels of drag = 1% change
934
+ const newValue = Math.max(0, Math.min(200, dragStartValue + Math.round(deltaY / 5)))
935
+ matrix.setAllocation(resourceId, consumerId, newValue)
936
+ }
937
+
938
+ const handleDragEnd = () => {
939
+ setCurrentDragCell(null)
940
+ }
941
+
942
+ const handleSave = () => {
943
+ onSave?.(matrix.exportAllocations())
944
+ }
945
+
946
+ const toggleRowSort = () => {
947
+ matrix.setRowSort({
948
+ field: matrix.rowSort.field === "name" ? "total" : "name",
949
+ direction:
950
+ matrix.rowSort.field === matrix.rowSort.field && matrix.rowSort.direction === "asc"
951
+ ? "desc"
952
+ : "asc",
953
+ })
954
+ }
955
+
956
+ const toggleColumnSort = () => {
957
+ matrix.setColumnSort({
958
+ field: matrix.columnSort.field === "name" ? "total" : "name",
959
+ direction:
960
+ matrix.columnSort.field === matrix.columnSort.field && matrix.columnSort.direction === "asc"
961
+ ? "desc"
962
+ : "asc",
963
+ })
964
+ }
965
+
966
+ const summary = matrix.getSummary()
967
+
968
+ return (
969
+ <div className={cn("space-y-4", className)}>
970
+ {/* Controls */}
971
+ {(showFilters || showSearch) && (
972
+ <div className="flex flex-wrap items-center gap-4">
973
+ {/* Search */}
974
+ {showSearch && (
975
+ <div className="relative">
976
+ <Search className={cn(config.icon, "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground")} />
977
+ <input
978
+ type="text"
979
+ placeholder="Search resources..."
980
+ value={matrix.searchQuery}
981
+ onChange={(e) => matrix.setSearchQuery(e.target.value)}
982
+ className={cn(
983
+ "pl-10 pr-4 py-2 border border-border rounded-lg bg-background",
984
+ config.text,
985
+ "focus:outline-none focus:ring-2 focus:ring-primary/50"
986
+ )}
987
+ />
988
+ {matrix.searchQuery && (
989
+ <button
990
+ onClick={() => matrix.setSearchQuery("")}
991
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
992
+ >
993
+ <X className={config.icon} />
994
+ </button>
995
+ )}
996
+ </div>
997
+ )}
998
+
999
+ {/* Filters */}
1000
+ {showFilters && (
1001
+ <div className="flex items-center gap-2">
1002
+ <Filter className={cn(config.icon, "text-muted-foreground")} />
1003
+ <div className="flex rounded-lg border border-border overflow-hidden">
1004
+ {(["all", "over-allocated", "under-allocated", "unallocated"] as FilterMode[]).map(
1005
+ (mode) => (
1006
+ <button
1007
+ key={mode}
1008
+ onClick={() => matrix.setFilterMode(mode)}
1009
+ className={cn(
1010
+ "px-3 py-1.5 transition-colors",
1011
+ config.text,
1012
+ matrix.filterMode === mode
1013
+ ? "bg-primary text-primary-foreground"
1014
+ : "bg-background hover:bg-muted"
1015
+ )}
1016
+ >
1017
+ {mode === "all"
1018
+ ? "All"
1019
+ : mode === "over-allocated"
1020
+ ? "Over"
1021
+ : mode === "under-allocated"
1022
+ ? "Under"
1023
+ : "None"}
1024
+ </button>
1025
+ )
1026
+ )}
1027
+ </div>
1028
+ </div>
1029
+ )}
1030
+
1031
+ {/* Sort controls */}
1032
+ <div className="flex items-center gap-2 ml-auto">
1033
+ <button
1034
+ onClick={toggleRowSort}
1035
+ className={cn(
1036
+ "flex items-center gap-1 px-3 py-1.5 border border-border rounded-lg",
1037
+ "bg-background hover:bg-muted transition-colors",
1038
+ config.text
1039
+ )}
1040
+ >
1041
+ <ArrowUpDown className={config.icon} />
1042
+ Rows: {matrix.rowSort.field}
1043
+ </button>
1044
+ <button
1045
+ onClick={toggleColumnSort}
1046
+ className={cn(
1047
+ "flex items-center gap-1 px-3 py-1.5 border border-border rounded-lg",
1048
+ "bg-background hover:bg-muted transition-colors",
1049
+ config.text
1050
+ )}
1051
+ >
1052
+ <ArrowUpDown className={config.icon} />
1053
+ Columns: {matrix.columnSort.field}
1054
+ </button>
1055
+ </div>
1056
+ </div>
1057
+ )}
1058
+
1059
+ {/* Summary Statistics */}
1060
+ {showSummary && <SummaryCard summary={summary} size={cellSize} />}
1061
+
1062
+ {/* Matrix Grid */}
1063
+ <div className="overflow-auto">
1064
+ <div className="inline-block min-w-full">
1065
+ <div className="flex">
1066
+ {/* Corner cell */}
1067
+ <div
1068
+ className={cn(
1069
+ "flex items-center justify-center bg-muted/50 border border-border/50 sticky left-0 z-20",
1070
+ config.header,
1071
+ config.padding
1072
+ )}
1073
+ >
1074
+ <Percent className={cn(config.icon, "text-muted-foreground")} />
1075
+ </div>
1076
+
1077
+ {/* Column headers */}
1078
+ {matrix.sortedConsumers.map((consumer) => (
1079
+ <HeaderCell
1080
+ key={consumer.id}
1081
+ label={consumer.name}
1082
+ priority={consumer.priority}
1083
+ isColumn
1084
+ isSortable
1085
+ sortDirection={
1086
+ matrix.columnSort.field === "name"
1087
+ ? matrix.columnSort.direction
1088
+ : null
1089
+ }
1090
+ size={cellSize}
1091
+ onSort={toggleColumnSort}
1092
+ />
1093
+ ))}
1094
+
1095
+ {/* Row totals header */}
1096
+ {showRowTotals && (
1097
+ <div
1098
+ className={cn(
1099
+ "flex items-center justify-center bg-muted/50 border border-border/50",
1100
+ config.header,
1101
+ config.padding
1102
+ )}
1103
+ >
1104
+ <span className={cn(config.headerText, "font-bold")}>Total</span>
1105
+ </div>
1106
+ )}
1107
+ </div>
1108
+
1109
+ {/* Rows */}
1110
+ {matrix.filteredResources.map((resource) => {
1111
+ const rowTotal = matrix.getRowTotal(resource.id)
1112
+ const capacity = resource.capacity || overAllocationThreshold
1113
+ const isOverAllocated = rowTotal > capacity
1114
+
1115
+ return (
1116
+ <div key={resource.id} className="flex group">
1117
+ {/* Row header */}
1118
+ <HeaderCell
1119
+ label={resource.name}
1120
+ subtitle={resource.category}
1121
+ isSortable
1122
+ sortDirection={
1123
+ matrix.rowSort.field === "name" ? matrix.rowSort.direction : null
1124
+ }
1125
+ size={cellSize}
1126
+ onSort={toggleRowSort}
1127
+ />
1128
+
1129
+ {/* Cells */}
1130
+ {matrix.sortedConsumers.map((consumer) => {
1131
+ const value = matrix.getAllocation(resource.id, consumer.id)
1132
+ const isSelected =
1133
+ matrix.selectedCell?.resourceId === resource.id &&
1134
+ matrix.selectedCell?.consumerId === consumer.id
1135
+
1136
+ return (
1137
+ <AllocationCell
1138
+ key={`${resource.id}-${consumer.id}`}
1139
+ resourceId={resource.id}
1140
+ consumerId={consumer.id}
1141
+ value={value}
1142
+ isSelected={isSelected}
1143
+ isEditable={editable}
1144
+ enableDrag={enableDrag}
1145
+ isOverAllocated={isOverAllocated}
1146
+ colorScheme={colorScheme}
1147
+ size={cellSize}
1148
+ onSelect={() =>
1149
+ matrix.setSelectedCell(
1150
+ isSelected ? null : { resourceId: resource.id, consumerId: consumer.id }
1151
+ )
1152
+ }
1153
+ onChange={(newValue) =>
1154
+ matrix.setAllocation(resource.id, consumer.id, newValue)
1155
+ }
1156
+ onDragStart={(v) => handleDragStart(resource.id, consumer.id, v)}
1157
+ onDrag={(deltaY) => handleDrag(resource.id, consumer.id, deltaY)}
1158
+ onDragEnd={handleDragEnd}
1159
+ />
1160
+ )
1161
+ })}
1162
+
1163
+ {/* Row total */}
1164
+ {showRowTotals && (
1165
+ <TotalCell
1166
+ value={rowTotal}
1167
+ isOverAllocated={isOverAllocated}
1168
+ maxValue={capacity}
1169
+ size={cellSize}
1170
+ isRow
1171
+ />
1172
+ )}
1173
+ </div>
1174
+ )
1175
+ })}
1176
+
1177
+ {/* Column totals row */}
1178
+ {showColumnTotals && (
1179
+ <div className="flex">
1180
+ {/* Total label */}
1181
+ <div
1182
+ className={cn(
1183
+ "flex items-center justify-center bg-muted/50 border border-border/50",
1184
+ config.header,
1185
+ config.padding
1186
+ )}
1187
+ >
1188
+ <span className={cn(config.headerText, "font-bold")}>Total</span>
1189
+ </div>
1190
+
1191
+ {/* Column totals */}
1192
+ {matrix.sortedConsumers.map((consumer) => (
1193
+ <TotalCell
1194
+ key={`total-${consumer.id}`}
1195
+ value={matrix.getColumnTotal(consumer.id)}
1196
+ size={cellSize}
1197
+ />
1198
+ ))}
1199
+
1200
+ {/* Grand total */}
1201
+ {showRowTotals && (
1202
+ <div
1203
+ className={cn(
1204
+ "flex items-center justify-center bg-muted border border-border/50 font-bold",
1205
+ config.cell,
1206
+ config.padding,
1207
+ config.text
1208
+ )}
1209
+ >
1210
+ {summary.totalAllocations.toFixed(0)}%
1211
+ </div>
1212
+ )}
1213
+ </div>
1214
+ )}
1215
+ </div>
1216
+ </div>
1217
+
1218
+ {/* Actions */}
1219
+ {editable && onSave && (
1220
+ <div className="flex justify-end gap-2">
1221
+ <button
1222
+ onClick={matrix.reset}
1223
+ className={cn(
1224
+ "px-4 py-2 border border-border rounded-lg",
1225
+ "bg-background hover:bg-muted transition-colors",
1226
+ config.text
1227
+ )}
1228
+ >
1229
+ Reset
1230
+ </button>
1231
+ <button
1232
+ onClick={matrix.clearAll}
1233
+ className={cn(
1234
+ "px-4 py-2 border border-border rounded-lg",
1235
+ "bg-background hover:bg-muted transition-colors",
1236
+ config.text
1237
+ )}
1238
+ >
1239
+ Clear All
1240
+ </button>
1241
+ <button
1242
+ onClick={handleSave}
1243
+ className={cn(
1244
+ "px-4 py-2 rounded-lg",
1245
+ "bg-primary text-primary-foreground hover:bg-primary/90 transition-colors",
1246
+ config.text
1247
+ )}
1248
+ >
1249
+ Save Allocations
1250
+ </button>
1251
+ </div>
1252
+ )}
1253
+
1254
+ {/* CSS Animations */}
1255
+ <style>{`
1256
+ @keyframes allocation-pulse {
1257
+ 0%, 100% { opacity: 1; }
1258
+ 50% { opacity: 0.7; }
1259
+ }
1260
+
1261
+ .allocation-cell-editing {
1262
+ animation: allocation-pulse 1.5s ease-in-out infinite;
1263
+ }
1264
+
1265
+ @keyframes over-allocation-warning {
1266
+ 0%, 100% { box-shadow: inset 0 0 0 2px rgba(239, 68, 68, 0.5); }
1267
+ 50% { box-shadow: inset 0 0 0 2px rgba(239, 68, 68, 1); }
1268
+ }
1269
+
1270
+ .over-allocated-cell {
1271
+ animation: over-allocation-warning 2s ease-in-out infinite;
1272
+ }
1273
+ `}</style>
1274
+ </div>
1275
+ )
1276
+ }
1277
+
1278
+ export default WakaAllocationMatrix