@stackcraftmx/calcita 0.23.0 → 0.26.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 (403) hide show
  1. package/README.md +127 -22
  2. package/cli/projectFiles.js +5 -3
  3. package/cli/templates/project/README.md.tpl +28 -0
  4. package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
  5. package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
  7. package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
  8. package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
  9. package/cli/templates/project/src/core/index.js.tpl +1 -0
  10. package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
  11. package/cli/templates/project/vite.config.js.tpl +27 -1
  12. package/dist/DataTable/AuditTrailTable.js +5 -3
  13. package/dist/DataTable/AuditTrailTable.js.map +1 -1
  14. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  16. package/dist/DataTable/EmptyState.js +2 -2
  17. package/dist/DataTable/EmptyState.js.map +1 -1
  18. package/dist/DataTable/FilterChips.js +2 -2
  19. package/dist/DataTable/FilterChips.js.map +1 -1
  20. package/dist/DataTable/GoToPage.js +4 -3
  21. package/dist/DataTable/GoToPage.js.map +1 -1
  22. package/dist/DataTable/ServerTable.js +153 -83
  23. package/dist/DataTable/ServerTable.js.map +1 -1
  24. package/dist/DataTable/rowSelection.js +61 -0
  25. package/dist/DataTable/rowSelection.js.map +1 -0
  26. package/dist/access/PermissionMatrix.js +3 -2
  27. package/dist/access/PermissionMatrix.js.map +1 -1
  28. package/dist/account/AccountSecuritySection.js +2 -2
  29. package/dist/account/AccountSecuritySection.js.map +1 -1
  30. package/dist/account/ForgotPasswordScreen.js +5 -4
  31. package/dist/account/ForgotPasswordScreen.js.map +1 -1
  32. package/dist/account/ProfileScreen.js +39 -13
  33. package/dist/account/ProfileScreen.js.map +1 -1
  34. package/dist/account/ResetPasswordScreen.js +3 -2
  35. package/dist/account/ResetPasswordScreen.js.map +1 -1
  36. package/dist/account/SignInScreen.js +7 -6
  37. package/dist/account/SignInScreen.js.map +1 -1
  38. package/dist/account/accountConnector.js.map +1 -1
  39. package/dist/account/restAccountConnector.js +2 -1
  40. package/dist/account/restAccountConnector.js.map +1 -1
  41. package/dist/adapters/reactRouterRoutes.js +7 -6
  42. package/dist/adapters/reactRouterRoutes.js.map +1 -1
  43. package/dist/api/createApiClient.js +40 -5
  44. package/dist/api/createApiClient.js.map +1 -1
  45. package/dist/appShell/AccountMenu.js +15 -23
  46. package/dist/appShell/AccountMenu.js.map +1 -1
  47. package/dist/appShell/AppLayout.js +42 -15
  48. package/dist/appShell/AppLayout.js.map +1 -1
  49. package/dist/appShell/AppearanceSettings.js +29 -6
  50. package/dist/appShell/AppearanceSettings.js.map +1 -1
  51. package/dist/appShell/AuthLayout.js +2 -0
  52. package/dist/appShell/AuthLayout.js.map +1 -1
  53. package/dist/appShell/BarActionButton.js +3 -3
  54. package/dist/appShell/BarActionButton.js.map +1 -1
  55. package/dist/appShell/BarActions.js +19 -8
  56. package/dist/appShell/BarActions.js.map +1 -1
  57. package/dist/appShell/BarCapsule.js +19 -4
  58. package/dist/appShell/BarCapsule.js.map +1 -1
  59. package/dist/appShell/BarSearch.js +92 -0
  60. package/dist/appShell/BarSearch.js.map +1 -0
  61. package/dist/appShell/HeaderTools.js +4 -2
  62. package/dist/appShell/HeaderTools.js.map +1 -1
  63. package/dist/appShell/MenuLinks.js +36 -0
  64. package/dist/appShell/MenuLinks.js.map +1 -0
  65. package/dist/appShell/MobileTabBar.js +6 -4
  66. package/dist/appShell/MobileTabBar.js.map +1 -1
  67. package/dist/appShell/SettingsMenu.js +10 -5
  68. package/dist/appShell/SettingsMenu.js.map +1 -1
  69. package/dist/appShell/SideMenu.js +5 -3
  70. package/dist/appShell/SideMenu.js.map +1 -1
  71. package/dist/appShell/SideMenuEntry.js +3 -3
  72. package/dist/appShell/SideMenuEntry.js.map +1 -1
  73. package/dist/appShell/SubmenuActionsMenu.js +3 -4
  74. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  75. package/dist/appShell/TopMenu.js +5 -6
  76. package/dist/appShell/TopMenu.js.map +1 -1
  77. package/dist/appShell/Wordmark.js +11 -0
  78. package/dist/appShell/Wordmark.js.map +1 -0
  79. package/dist/appShell/menuItemText.js +4 -0
  80. package/dist/appShell/menuItemText.js.map +1 -1
  81. package/dist/appShell/shellSizes.js +2 -0
  82. package/dist/appShell/shellSizes.js.map +1 -1
  83. package/dist/auth/PasswordChecklist.js +2 -2
  84. package/dist/auth/PasswordChecklist.js.map +1 -1
  85. package/dist/calendar/EventCalendar.js +16 -5
  86. package/dist/calendar/EventCalendar.js.map +1 -1
  87. package/dist/calendar/EventDetails.js +74 -0
  88. package/dist/calendar/EventDetails.js.map +1 -0
  89. package/dist/calendar/calendarEvents.js +31 -0
  90. package/dist/calendar/calendarEvents.js.map +1 -0
  91. package/dist/calendar.js +8 -1
  92. package/dist/calendar.js.map +1 -1
  93. package/dist/charts.js +10 -1
  94. package/dist/charts.js.map +1 -1
  95. package/dist/datePickers/DateInput.js +3 -2
  96. package/dist/datePickers/DateInput.js.map +1 -1
  97. package/dist/downloads/DownloadManager.js +14 -20
  98. package/dist/downloads/DownloadManager.js.map +1 -1
  99. package/dist/downloads/downloadQueue.js +4 -2
  100. package/dist/downloads/downloadQueue.js.map +1 -1
  101. package/dist/downloads/downloadsMachine.js +4 -1
  102. package/dist/downloads/downloadsMachine.js.map +1 -1
  103. package/dist/downloads/fileTypes.js +22 -0
  104. package/dist/downloads/fileTypes.js.map +1 -0
  105. package/dist/feedback/ConfirmDialog.js +3 -2
  106. package/dist/feedback/ConfirmDialog.js.map +1 -1
  107. package/dist/feedback/ModalWaiting.js +2 -2
  108. package/dist/feedback/ModalWaiting.js.map +1 -1
  109. package/dist/feedback/NewBuildNotice.js +2 -2
  110. package/dist/feedback/NewBuildNotice.js.map +1 -1
  111. package/dist/filepond/FileDropzone.js +5 -3
  112. package/dist/filepond/FileDropzone.js.map +1 -1
  113. package/dist/formatting.js +171 -143
  114. package/dist/formatting.js.map +1 -1
  115. package/dist/forms/FormWizard.js +18 -45
  116. package/dist/forms/FormWizard.js.map +1 -1
  117. package/dist/forms/auth/ChangePasswordForm.js +5 -4
  118. package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
  119. package/dist/forms/auth/ForgotPasswordForm.js +2 -2
  120. package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
  121. package/dist/forms/auth/SignInForm.js +2 -2
  122. package/dist/forms/auth/SignInForm.js.map +1 -1
  123. package/dist/forms/security/SecondStepForm.js +2 -2
  124. package/dist/forms/security/SecondStepForm.js.map +1 -1
  125. package/dist/forms/security/TwoStepVerificationCard.js +8 -6
  126. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  127. package/dist/hookForm/FormWizard.js +59 -0
  128. package/dist/hookForm/FormWizard.js.map +1 -0
  129. package/dist/inbox/InboxLayout.js +44 -8
  130. package/dist/inbox/InboxLayout.js.map +1 -1
  131. package/dist/inbox/MessageList.js +71 -21
  132. package/dist/inbox/MessageList.js.map +1 -1
  133. package/dist/inbox/MessageView.js +80 -29
  134. package/dist/inbox/MessageView.js.map +1 -1
  135. package/dist/inbox/inboxDates.js +4 -3
  136. package/dist/inbox/inboxDates.js.map +1 -1
  137. package/dist/index.js +163 -136
  138. package/dist/index.js.map +1 -1
  139. package/dist/inputs/CatalogInput.js +8 -4
  140. package/dist/inputs/CatalogInput.js.map +1 -1
  141. package/dist/inputs/ColorInput.js +3 -2
  142. package/dist/inputs/ColorInput.js.map +1 -1
  143. package/dist/inputs/FormSection.js +3 -3
  144. package/dist/inputs/FormSection.js.map +1 -1
  145. package/dist/inputs/NumberInput.js +3 -2
  146. package/dist/inputs/NumberInput.js.map +1 -1
  147. package/dist/inputs/SelectInput.js +29 -11
  148. package/dist/inputs/SelectInput.js.map +1 -1
  149. package/dist/inputs/TextInput.js +6 -2
  150. package/dist/inputs/TextInput.js.map +1 -1
  151. package/dist/inputs/WizardLayout.js +54 -0
  152. package/dist/inputs/WizardLayout.js.map +1 -0
  153. package/dist/kanban/KanbanBoard.js +239 -14
  154. package/dist/kanban/KanbanBoard.js.map +1 -1
  155. package/dist/kanban/kanbanCards.js +19 -0
  156. package/dist/kanban/kanbanCards.js.map +1 -0
  157. package/dist/kanban.js +7 -1
  158. package/dist/kanban.js.map +1 -1
  159. package/dist/locales/en.js +74 -1
  160. package/dist/locales/en.js.map +1 -1
  161. package/dist/locales/es.js +74 -1
  162. package/dist/locales/es.js.map +1 -1
  163. package/dist/maps/MapView.js +28 -8
  164. package/dist/maps/MapView.js.map +1 -1
  165. package/dist/navigation/appMenu.js +13 -1
  166. package/dist/navigation/appMenu.js.map +1 -1
  167. package/dist/observability/logger.js +18 -0
  168. package/dist/observability/logger.js.map +1 -1
  169. package/dist/observability/usePublishedBuildWatch.js +4 -3
  170. package/dist/observability/usePublishedBuildWatch.js.map +1 -1
  171. package/dist/pwa/InstallAppSetting.js +6 -5
  172. package/dist/pwa/InstallAppSetting.js.map +1 -1
  173. package/dist/pwa/InstallInstructionsDialog.js +2 -2
  174. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  175. package/dist/pwa/InstallPromptBanner.js +15 -9
  176. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  177. package/dist/pwa/useInstallPrompt.js +2 -1
  178. package/dist/pwa/useInstallPrompt.js.map +1 -1
  179. package/dist/react-hook-form.js +6 -4
  180. package/dist/react-hook-form.js.map +1 -1
  181. package/dist/security/ActivityLog.js +4 -3
  182. package/dist/security/ActivityLog.js.map +1 -1
  183. package/dist/security/RecoveryCodesDialog.js +4 -3
  184. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  185. package/dist/security/SessionsCard.js +8 -6
  186. package/dist/security/SessionsCard.js.map +1 -1
  187. package/dist/table.js +2 -0
  188. package/dist/table.js.map +1 -1
  189. package/dist/theme/components/MuiAppBar.js +6 -5
  190. package/dist/theme/components/MuiAppBar.js.map +1 -1
  191. package/dist/theme/components/MuiDialog.js +13 -0
  192. package/dist/theme/components/MuiDialog.js.map +1 -0
  193. package/dist/theme/components/glassComponents.js +32 -31
  194. package/dist/theme/components/glassComponents.js.map +1 -1
  195. package/dist/theme/components/minimalComponents.js +8 -4
  196. package/dist/theme/components/minimalComponents.js.map +1 -1
  197. package/dist/theme/createToolkitTheme.js.map +1 -1
  198. package/dist/theme/glassSurface.js +36 -38
  199. package/dist/theme/glassSurface.js.map +1 -1
  200. package/dist/theme/lightDarkStyles.js +48 -0
  201. package/dist/theme/lightDarkStyles.js.map +1 -0
  202. package/dist/theme/toolkitComponents.js +2 -0
  203. package/dist/theme/toolkitComponents.js.map +1 -1
  204. package/dist/toolkit/ComponentTexts.js +13 -0
  205. package/dist/toolkit/ComponentTexts.js.map +1 -0
  206. package/dist/toolkit/ToolkitProvider.js +36 -28
  207. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  208. package/dist/toolkit/browserNavigation.js +19 -20
  209. package/dist/toolkit/browserNavigation.js.map +1 -1
  210. package/dist/toolkit/componentTextsContext.js +15 -0
  211. package/dist/toolkit/componentTextsContext.js.map +1 -0
  212. package/dist/toolkit/hashNavigation.js +40 -0
  213. package/dist/toolkit/hashNavigation.js.map +1 -0
  214. package/dist/toolkit/navigationPaths.js +49 -0
  215. package/dist/toolkit/navigationPaths.js.map +1 -0
  216. package/dist/toolkit/toolkitSettings.js +24 -4
  217. package/dist/toolkit/toolkitSettings.js.map +1 -1
  218. package/dist/toolkit/useFormatters.js +12 -0
  219. package/dist/toolkit/useFormatters.js.map +1 -0
  220. package/dist/toolkit/useToolkitTranslation.js +11 -7
  221. package/dist/toolkit/useToolkitTranslation.js.map +1 -1
  222. package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
  223. package/dist/types/DataTable/EmptyState.d.ts +7 -2
  224. package/dist/types/DataTable/FilterChips.d.ts +3 -1
  225. package/dist/types/DataTable/GoToPage.d.ts +7 -2
  226. package/dist/types/DataTable/SelectionBar.d.ts +32 -0
  227. package/dist/types/DataTable/ServerTable.d.ts +64 -3
  228. package/dist/types/DataTable/index.d.ts +1 -1
  229. package/dist/types/DataTable/rowSelection.d.ts +113 -0
  230. package/dist/types/access/PermissionMatrix.d.ts +3 -1
  231. package/dist/types/account/AccountSecuritySection.d.ts +3 -1
  232. package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
  233. package/dist/types/account/ProfileScreen.d.ts +31 -1
  234. package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
  235. package/dist/types/account/SignInScreen.d.ts +3 -1
  236. package/dist/types/account/accountConnector.d.ts +7 -2
  237. package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
  238. package/dist/types/api/createApiClient.d.ts +46 -1
  239. package/dist/types/appShell/AppLayout.d.ts +15 -1
  240. package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
  241. package/dist/types/appShell/BarActionButton.d.ts +2 -2
  242. package/dist/types/appShell/BarActions.d.ts +6 -1
  243. package/dist/types/appShell/BarCapsule.d.ts +8 -0
  244. package/dist/types/appShell/BarSearch.d.ts +24 -0
  245. package/dist/types/appShell/MenuLinks.d.ts +17 -0
  246. package/dist/types/appShell/MobileTabBar.d.ts +4 -1
  247. package/dist/types/appShell/SettingsMenu.d.ts +3 -1
  248. package/dist/types/appShell/SideMenu.d.ts +3 -1
  249. package/dist/types/appShell/Wordmark.d.ts +6 -0
  250. package/dist/types/appShell/menuItemText.d.ts +16 -0
  251. package/dist/types/appShell/shellSizes.d.ts +1 -0
  252. package/dist/types/auth/PasswordChecklist.d.ts +3 -1
  253. package/dist/types/calendar/EventCalendar.d.ts +18 -1
  254. package/dist/types/calendar/EventDetails.d.ts +36 -0
  255. package/dist/types/calendar/calendarEvents.d.ts +27 -0
  256. package/dist/types/calendar.d.ts +2 -0
  257. package/dist/types/charts.d.ts +4 -0
  258. package/dist/types/datePickers/DateInput.d.ts +3 -1
  259. package/dist/types/downloads/DownloadManager.d.ts +7 -1
  260. package/dist/types/downloads/downloadQueue.d.ts +8 -3
  261. package/dist/types/downloads/fileTypes.d.ts +39 -0
  262. package/dist/types/downloads/index.d.ts +1 -0
  263. package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
  264. package/dist/types/feedback/ModalWaiting.d.ts +7 -2
  265. package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
  266. package/dist/types/filepond/FileDropzone.d.ts +3 -1
  267. package/dist/types/formatting.d.ts +45 -90
  268. package/dist/types/forms/FormWizard.d.ts +3 -1
  269. package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
  270. package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
  271. package/dist/types/forms/auth/SignInForm.d.ts +3 -1
  272. package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
  273. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
  274. package/dist/types/hookForm/FormWizard.d.ts +22 -0
  275. package/dist/types/inbox/InboxLayout.d.ts +14 -2
  276. package/dist/types/inbox/MessageList.d.ts +30 -5
  277. package/dist/types/inbox/MessageView.d.ts +35 -3
  278. package/dist/types/inbox/inboxDates.d.ts +6 -1
  279. package/dist/types/index.d.ts +15 -8
  280. package/dist/types/inputs/CatalogInput.d.ts +9 -5
  281. package/dist/types/inputs/ColorInput.d.ts +3 -1
  282. package/dist/types/inputs/FormSection.d.ts +2 -2
  283. package/dist/types/inputs/NumberInput.d.ts +3 -1
  284. package/dist/types/inputs/SelectInput.d.ts +9 -5
  285. package/dist/types/inputs/TextInput.d.ts +3 -1
  286. package/dist/types/inputs/WizardLayout.d.ts +34 -0
  287. package/dist/types/kanban/KanbanBoard.d.ts +61 -6
  288. package/dist/types/kanban/kanbanCards.d.ts +22 -0
  289. package/dist/types/kanban.d.ts +1 -0
  290. package/dist/types/locales/en.d.ts +77 -0
  291. package/dist/types/locales/es.d.ts +77 -0
  292. package/dist/types/maps/MapView.d.ts +12 -3
  293. package/dist/types/navigation/appMenu.d.ts +8 -0
  294. package/dist/types/observability/logger.d.ts +13 -0
  295. package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
  296. package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
  297. package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
  298. package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
  299. package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
  300. package/dist/types/react-hook-form.d.ts +1 -0
  301. package/dist/types/security/ActivityLog.d.ts +3 -1
  302. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
  303. package/dist/types/security/SessionsCard.d.ts +3 -1
  304. package/dist/types/table.d.ts +3 -1
  305. package/dist/types/theme/components/MuiAppBar.d.ts +3 -1
  306. package/dist/types/theme/components/MuiDialog.d.ts +9 -0
  307. package/dist/types/theme/components/glassComponents.d.ts +5 -0
  308. package/dist/types/theme/lightDarkStyles.d.ts +8 -0
  309. package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
  310. package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
  311. package/dist/types/toolkit/browserNavigation.d.ts +23 -28
  312. package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
  313. package/dist/types/toolkit/hashNavigation.d.ts +12 -0
  314. package/dist/types/toolkit/navigationPaths.d.ts +43 -0
  315. package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
  316. package/dist/types/toolkit/useFormatters.d.ts +6 -0
  317. package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
  318. package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
  319. package/dist/types/ui/actions/CopyButton.d.ts +3 -1
  320. package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
  321. package/dist/types/ui/actions/SecretField.d.ts +5 -1
  322. package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
  323. package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
  324. package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
  325. package/dist/types/ui/index.d.ts +1 -1
  326. package/dist/types/ui/labels/tagOf.d.ts +13 -0
  327. package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
  328. package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
  329. package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
  330. package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
  331. package/dist/types/ui/layout/PageHeading.d.ts +3 -1
  332. package/dist/types/ui/metrics/BarList.d.ts +7 -1
  333. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
  334. package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
  335. package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
  336. package/dist/types/ui/metrics/LineChart.d.ts +41 -0
  337. package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
  338. package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
  339. package/dist/types/ui/metrics/chartParts.d.ts +112 -0
  340. package/dist/types/ui/navigation/BackButton.d.ts +3 -1
  341. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
  342. package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
  343. package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
  344. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
  345. package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
  346. package/dist/types/upload/FileDropzone.d.ts +3 -1
  347. package/dist/ui/actions/ActionsMenu.js +51 -0
  348. package/dist/ui/actions/ActionsMenu.js.map +1 -0
  349. package/dist/ui/actions/CopyButton.js +2 -2
  350. package/dist/ui/actions/CopyButton.js.map +1 -1
  351. package/dist/ui/actions/DownloadMenu.js +11 -12
  352. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  353. package/dist/ui/actions/SecretField.js +8 -4
  354. package/dist/ui/actions/SecretField.js.map +1 -1
  355. package/dist/ui/actions/SelectionActions.js +142 -0
  356. package/dist/ui/actions/SelectionActions.js.map +1 -0
  357. package/dist/ui/filters/DateRangeFilter.js +4 -3
  358. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  359. package/dist/ui/filters/QuickFilters.js +2 -2
  360. package/dist/ui/filters/QuickFilters.js.map +1 -1
  361. package/dist/ui/labels/tagOf.js +7 -0
  362. package/dist/ui/labels/tagOf.js.map +1 -0
  363. package/dist/ui/layout/CollapsibleSection.js +6 -2
  364. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  365. package/dist/ui/layout/ContentDialog.js +2 -2
  366. package/dist/ui/layout/ContentDialog.js.map +1 -1
  367. package/dist/ui/layout/ErrorPage.js +21 -7
  368. package/dist/ui/layout/ErrorPage.js.map +1 -1
  369. package/dist/ui/layout/FullPageLoader.js +2 -2
  370. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  371. package/dist/ui/layout/PageHeading.js +2 -2
  372. package/dist/ui/layout/PageHeading.js.map +1 -1
  373. package/dist/ui/metrics/BarList.js +18 -13
  374. package/dist/ui/metrics/BarList.js.map +1 -1
  375. package/dist/ui/metrics/CalendarHeatmap.js +11 -6
  376. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  377. package/dist/ui/metrics/ChartTooltip.js +1 -1
  378. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  379. package/dist/ui/metrics/ColumnChart.js +59 -63
  380. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  381. package/dist/ui/metrics/LineChart.js +117 -0
  382. package/dist/ui/metrics/LineChart.js.map +1 -0
  383. package/dist/ui/metrics/MetricCard.js +41 -8
  384. package/dist/ui/metrics/MetricCard.js.map +1 -1
  385. package/dist/ui/metrics/TableToggle.js +4 -2
  386. package/dist/ui/metrics/TableToggle.js.map +1 -1
  387. package/dist/ui/metrics/chartParts.js +93 -0
  388. package/dist/ui/metrics/chartParts.js.map +1 -0
  389. package/dist/ui/navigation/BackButton.js +2 -2
  390. package/dist/ui/navigation/BackButton.js.map +1 -1
  391. package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
  392. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  393. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  394. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  395. package/dist/ui/navigation/LanguageToggle.js +40 -19
  396. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  397. package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
  398. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  399. package/dist/ui/timeline/GroupedTimeline.js +10 -8
  400. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  401. package/dist/upload/FileDropzone.js +5 -3
  402. package/dist/upload/FileDropzone.js.map +1 -1
  403. package/package.json +16 -7
package/README.md CHANGED
@@ -4,10 +4,16 @@
4
4
  transparente famoso por doblar la luz, y eso es lo que define al vidrio líquido: su lente.
5
5
 
6
6
  Arranca un proyecto sin copiar y volver a probar lo de siempre: el marco de la app con menú y permisos, tabla de servidor,
7
- diálogos de confirmación, avisos, descargas, formularios, formatos de número y fecha, errores RFC 9457, reportes en Excel
8
- y el tema con vidrio líquido, interpretado componente por componente según la guía de Apple.
7
+ diálogos de confirmación, avisos, descargas, formularios, formatos de número y fecha, errores RFC 9457, reportes en Excel,
8
+ la cuenta completa, mapa, calendario, tablero kanban, bandeja tipo correo y el tema con vidrio líquido, interpretado
9
+ componente por componente según la guía de Apple.
9
10
 
10
- JavaScript con sus tipos generados: se usa igual desde un proyecto en TypeScript.
11
+ JavaScript con sus tipos generados: se usa igual desde un proyecto en TypeScript. Sirve para un proyecto nuevo y también
12
+ para meterlo por pedazos en un sistema que ya existe (ver «En un sistema que ya existe»).
13
+
14
+ > **Versiones:** Calcita está hecha y probada con **MUI 7** (Material 7.3, MUI X 8), React 19 y react-router 7. MUI 9 ya
15
+ > existe, pero cambia piezas que Calcita usa: todavía no está soportado. Con MUI 9 en el proyecto, quédese en MUI 7 por
16
+ > ahora.
11
17
 
12
18
  ## Arrancar un proyecto con el CLI
13
19
 
@@ -47,9 +53,11 @@ El CLI nunca pisa un archivo que ya existe. Sus plantillas son los archivos tal
47
53
 
48
54
  ```bash
49
55
  pnpm add @stackcraftmx/calcita @mui/material @mui/icons-material @emotion/react @emotion/styled xstate @xstate/react sonner
56
+ pnpm add formik yup # solo si usa /forms o /account
50
57
  ```
51
58
 
52
- Con npm es igual, con `npm install` en lugar de `pnpm add`.
59
+ Con npm es igual, con `npm install` en lugar de `pnpm add`. Desde la 0.25, formik y yup los pone el proyecto: Calcita usa
60
+ la versión que ya tenga, no se casa con una.
53
61
 
54
62
  ```jsx
55
63
  import {ThemeProvider, createTheme} from '@mui/material/styles';
@@ -73,7 +81,8 @@ Sin nada más, todo sale en español, con la zona horaria del navegador, montos
73
81
 
74
82
  | Prop | Para qué | Por omisión |
75
83
  |---|---|---|
76
- | `language`, `onLanguageChange` | idioma activo (`es` o `en`) y a quién avisar cuando se cambia | `es` |
84
+ | `language`, `onLanguageChange` | idioma activo y a quién avisar cuando se cambia | `es` |
85
+ | `languages` | los idiomas que ofrece la app, con su nombre y su locale (`{code: 'pt', label: 'Português', locale: 'pt-BR'}`); sus traducciones van en `texts` | español e inglés |
77
86
  | `texts` | cambiar textos del paquete, por idioma: `{es: {common: {actions: {back: 'Volver'}}}}` | los del paquete |
78
87
  | `translate` | un traductor propio, por ejemplo i18next | ninguno |
79
88
  | `timeZone` | zona en que se pintan las horas | la del navegador |
@@ -82,6 +91,12 @@ Sin nada más, todo sale en español, con la zona horaria del navegador, montos
82
91
  | `navigation` | el router: `{useNavigate, useLocation, Link}` | la del navegador |
83
92
  | `canAccess` | quién ve qué (menú, rutas, botones): la regla del proyecto, normalmente `createAccessChecker(...)` | solo lo que no tiene regla |
84
93
  | `buildId` | la compilación del front que corre en la pestaña, para el aviso de versión nueva | ninguna (sin aviso) |
94
+ | `fileTypes` | tipos de archivo propios para las descargas (su ícono y su color), además de Excel, CSV y PDF | los de la casa |
95
+
96
+ Un `ToolkitProvider` dentro de otro (una parte de la app en otro idioma, una vista previa) toma del de afuera lo que no le
97
+ digan, y lo de adentro usa el suyo: el más cercano. No le cambia nada al resto de la app. Dentro de un componente,
98
+ `useFormatters()` da los formatos (`formatDate`, `formatCurrency`…) con el idioma, la zona y la moneda del proveedor más
99
+ cercano; los sueltos usan los del proveedor de la raíz.
85
100
 
86
101
  Con react-router y i18next:
87
102
 
@@ -141,7 +156,7 @@ export default defineConfig({
141
156
  });
142
157
 
143
158
  // en la raíz de la app, una vez (en desarrollo no hay archivo: sin url no hace nada)
144
- usePublishedBuildWatch({url: import.meta.env.PROD ? '/version.json' : null});
159
+ usePublishedBuildWatch({url: import.meta.env.PROD ? `${import.meta.env.BASE_URL}version.json` : null, buildId: import.meta.env.VITE_BUILD_ID});
145
160
  // opciones: checkEveryMinutes (5), reloadAfterHiddenMinutes (15; null = nunca recarga sola), onReload
146
161
  ```
147
162
 
@@ -153,12 +168,12 @@ Cada puerta pide solo sus librerías; un proyecto instala lo que usa.
153
168
 
154
169
  | Importar de | Trae | Pide además |
155
170
  |---|---|---|
156
- | `@stackcraftmx/calcita` | proveedor, diálogos, avisos, descargas, encabezados, filtros, chips, métricas, formatos, fechas, errores RFC 9457, la petición de la casa (`createApiClient`), la recuperación de versión vieja, la seguridad de la cuenta (códigos de recuperación, sesiones, bitácora) y la bandeja tipo correo (`InboxLayout`, `MessageList`, `MessageView`) | — |
171
+ | `@stackcraftmx/calcita` | proveedor, marco de la app, diálogos, avisos, descargas, encabezados, filtros, chips, métricas, formatos (`useFormatters`), fechas, errores RFC 9457, la petición de la casa (`createApiClient`), la recuperación de versión vieja, la navegación sin router (`createBrowserNavigation`, `createHashNavigation`), la búsqueda de la barra (`BarSearch`), el menú «⋯» (`ActionsMenu`), las acciones de lo elegido (`SelectionActions`), la seguridad de la cuenta (códigos de recuperación, sesiones, bitácora) y la bandeja tipo correo (`InboxLayout`, `MessageList` con selección, `MessageView` con la conversación) | — |
157
172
  | `@stackcraftmx/calcita/table` | `ServerTable`: paginada, filtrada y ordenada en el servidor, con su estado en la URL | `@mui/x-data-grid`, `nuqs` |
158
- | `@stackcraftmx/calcita/forms` | campos de formik (texto, lista, número con miles, catálogo, color), formulario por pasos, aviso de arriba, errores del servidor pegados a cada campo; `Formik`, `Form` y `Yup` vienen con el paquete | — |
159
- | `@stackcraftmx/calcita/react-hook-form` | los mismos campos con los mismos nombres, conectados a react-hook-form, y `yupResolver` | `react-hook-form`, `@hookform/resolvers` |
173
+ | `@stackcraftmx/calcita/forms` | campos de formik (texto, lista de una o varias opciones, número con miles, catálogo, color), formulario por pasos, aviso de arriba, errores del servidor pegados a cada campo | `formik`, `yup` |
174
+ | `@stackcraftmx/calcita/react-hook-form` | los mismos campos con los mismos nombres, conectados a react-hook-form, su formulario por pasos y `yupResolver` | `react-hook-form`, `@hookform/resolvers` (y `yup` si valida con yup) |
160
175
  | `@stackcraftmx/calcita/date-pickers` | la fecha con el DatePicker de MUI X (`DateInput`, `FormDateField`); viaja `AAAA-MM-DD` | `@mui/x-date-pickers` 8, `dayjs` |
161
- | `@stackcraftmx/calcita/charts` | barras y columnas con vista de tabla | `recharts` |
176
+ | `@stackcraftmx/calcita/charts` | barras, columnas y líneas (una serie o varias), cada una con su vista de tabla, y `ChartTooltip` para las propias | `recharts` |
162
177
  | `@stackcraftmx/calcita/timeline` | línea de tiempo por grupos | `@mui/lab` |
163
178
  | `@stackcraftmx/calcita/excel` | reporte en Excel (Resumen y Datos) y CSV | `exceljs`, que se le pasa al llamarlo |
164
179
  | `@stackcraftmx/calcita/react-router` | el adaptador de navegación | `react-router` |
@@ -168,11 +183,11 @@ Cada puerta pide solo sus librerías; un proyecto instala lo que usa.
168
183
  | `@stackcraftmx/calcita/next` | la navegación de Next.js (App Router) para ToolkitProvider; los componentes ya vienen marcados con `'use client'` y los puros sirven en el servidor | `next` |
169
184
  | `@stackcraftmx/calcita/money` | `Money`: dinero exacto en centavos enteros, con suma, resta, multiplicación, división, potencia, raíz y su formato con la moneda | — |
170
185
  | `@stackcraftmx/calcita/passkeys` | llaves de acceso (passkeys): crearlas y firmar con ellas, con los errores del navegador en palabras de la persona | `@simplewebauthn/browser` 14 |
171
- | `@stackcraftmx/calcita/account` | la cuenta completa: `SignInScreen` (con el segundo paso), `ForgotPasswordScreen`, `ResetPasswordScreen`, `ProfileScreen`, `AccountSecuritySection`, `AccountProvider` y `createRestAccountConnector` | `@simplewebauthn/browser` 14 |
186
+ | `@stackcraftmx/calcita/account` | la cuenta completa: `SignInScreen` (con el segundo paso), `ForgotPasswordScreen`, `ResetPasswordScreen`, `ProfileScreen` (con campos y pestañas propias), `AccountSecuritySection`, `AccountProvider` y `createRestAccountConnector` | `@simplewebauthn/browser` 14, `formik`, `yup` |
172
187
  | `@stackcraftmx/calcita/account-demo` | `createDemoAccountConnector`: la cuenta en el navegador, sin backend (y los códigos TOTP) | — |
173
188
  | `@stackcraftmx/calcita/maps` | `MapView`: mapa con marcadores del color del tema, el elegido con su globo, se acomoda solo; en oscuro, mosaicos oscuros | `leaflet`, `react-leaflet` 5 |
174
- | `@stackcraftmx/calcita/calendar` | `EventCalendar`: mes, semana, día y lista, con la barra del framework; arrastrar cambia la cita y regresa si el servidor dice que no | `@fullcalendar/react`, `core`, `daygrid`, `timegrid`, `list`, `interaction` 6.1 |
175
- | `@stackcraftmx/calcita/kanban` | `KanbanBoard`: columnas con tope y tarjetas que se arrastran con el ratón, el dedo o el teclado; `moveCard` para ver la movida al instante | `@hello-pangea/dnd` 18 |
189
+ | `@stackcraftmx/calcita/calendar` | `EventCalendar`: mes, semana, día y lista, con la barra del framework; arrastrar cambia la cita y regresa si el servidor dice que no. `EventDetails`: el detalle de una cita (lugar, liga de la reunión, invitados con su respuesta, recordatorio, notas) | `@fullcalendar/react`, `core`, `daygrid`, `timegrid`, `list`, `interaction` 6.1 |
190
+ | `@stackcraftmx/calcita/kanban` | `KanbanBoard`: columnas con tope (y que se encogen, como un congelador) y tarjetas de producto (etiquetas, responsables, fecha, pendientes, terminar) que se arrastran con el ratón, el dedo o el teclado; `moveCard` para ver la movida al instante | `@hello-pangea/dnd` 18 |
176
191
 
177
192
  ## El tema
178
193
 
@@ -187,7 +202,7 @@ const theme = createToolkitTheme({colorSchemes: {light: {palette: {primary: {mai
187
202
  const theme = createToolkitTheme({components: {MuiCard: {…}}}); // se SUMA a los ajustes del framework
188
203
  ```
189
204
 
190
- El vidrio es la versión en CSS del Liquid Glass de Apple, poco transparente (80 %). Va en lo que flota sobre el
205
+ El vidrio es la versión en CSS del Liquid Glass de Apple (60 % de opacidad por omisión). Va en lo que flota sobre el
191
206
  contenido y en los controles, como pide su guía:
192
207
 
193
208
  - la barra y el menú lateral, que en escritorio flotan a 8 px de la orilla;
@@ -198,8 +213,10 @@ contenido y en los controles, como pide su guía:
198
213
  - un lienzo con manchas tenues de color, para que el vidrio tenga algo que desenfocar.
199
214
 
200
215
  Las tarjetas del contenido se quedan sólidas, para que el texto largo se lea, salvo que el proyecto las pida de vidrio.
201
- Todo se pinta sólido si la persona pidió en su equipo «Reducir transparencia» o «Aumentar contraste». No trae refracción
202
- (la lupa que deforma lo de atrás): en la web solo sale con filtros que hoy funcionan nada más en Chrome.
216
+ Todo se pinta sólido si la persona pidió en su equipo «Reducir transparencia» o «Aumentar contraste». La refracción (el
217
+ lente que dobla lo de atrás en la orilla del vidrio) sale en Chrome y sus parientes (Edge, Brave, Arc) con
218
+ `GlassLensFilter`, que `AppLayout` y `AuthLayout` ya ponen; Safari y Firefox no pintan ese filtro y ahí el vidrio se queda
219
+ esmerilado.
203
220
 
204
221
  ```js
205
222
  const theme = createToolkitTheme({glass: {opacity: 0.7}}); // más transparente (0 a 1)
@@ -265,6 +282,9 @@ nombre; las demás, en «Más».
265
282
  Claro u oscuro tiene tres opciones en el menú de la cuenta: Claro, Oscuro y Automático, que sigue lo que diga el equipo y
266
283
  es lo de omisión. El botón de la barra fija claro u oscuro; Automático es la forma de volver a seguir al equipo.
267
284
 
285
+ Una marca que es una palabra larga va en `wordmark` (a 24 px de alto, en lugar del logo cuadrado y el nombre). Una entrada
286
+ del menú puede llevar a otro sistema (`href`) y traer su ícono ya pintado (`icon`) en lugar del componente (`Icon`).
287
+
268
288
  El menú no se escribe en una lista central: cada módulo trae su `menu.js` y `composeMenu` los junta, los ordena y deja
269
289
  solo los que la sesión puede ver (también dentro de los submenús).
270
290
 
@@ -296,10 +316,95 @@ const menuItems = composeMenu(import.meta.glob('@/modules/*/menu.js', {eager: tr
296
316
  En cada pantalla, `PageHeading` pone el título, la descripción y los botones, y arma las migas solo desde el menú
297
317
  (Inicio › Sección › Pantalla). `PageHeader` es el encabezado centrado de una portada y `DetailHeader` el de un registro.
298
318
 
299
- La misma regla protege las rutas (`withAccess(access, routes)` de `/react-router`: quien no puede ve «sin permiso») y
319
+ La misma regla protege las rutas (`withAccess(access, routes)` de `/react-router`: quien no puede ve «sin permiso», o la
320
+ página propia que se pase en `{fallback}`) y
300
321
  los botones (`<Can access={…}>`, `useCan`). El framework no sabe de roles: RBAC por permisos o roles y ABAC por políticas
301
322
  con nombre o reglas escritas ahí mismo, como cada proyecto las necesite. Lo que se pinta no protege nada: el backend
302
- vuelve a revisar cada petición. `ErrorPage` trae las pantallas de 404, 403 y 500.
323
+ vuelve a revisar cada petición. `ErrorPage` trae las pantallas de 404, 403 y 500, y se extiende sin tocarla: botones
324
+ propios junto a los de la casa (`extraActions`) o en su lugar (`actions`), algo debajo del texto (`children`), otro ícono y
325
+ `onGoHome`.
326
+
327
+ El marco avisa cuando la persona encoge el menú (`onMenuCollapse`), abre el menú del teléfono (`onMobileMenuChange`) o elige
328
+ sus pestañas (`onMobileTabsChange`), para guardar esas preferencias en el servidor del proyecto si se quiere; sus textos se
329
+ cambian con `texts`.
330
+
331
+ ## Eventos y textos de cada componente
332
+
333
+ Cada componente de la casa avisa hacia afuera todo lo que pasa adentro (`onSelect`, `onChange`, `onViewChange`,
334
+ `onMenuCollapse`, `onCardComplete`, `onDatesChange`…) y nunca llama un servidor propio: el proyecto decide qué hacer. Así se
335
+ mide, se guarda una preferencia en el servidor del proyecto o se conecta un sistema viejo sin tocar Calcita.
336
+
337
+ Cada texto también se cambia desde afuera: el componente recibe `texts` con la forma de su namespace en los textos del
338
+ paquete (`src/locales/es.js`), y `ComponentTexts` los cambia para todo lo que va adentro (un módulo con su propio
339
+ vocabulario):
340
+
341
+ ```jsx
342
+ <ServerTable texts={{count: {records_one: '{{total}} pedido', records_other: '{{total}} pedidos'}}} … />
343
+
344
+ <ComponentTexts namespace="table" texts={{emptyState: {title: 'Sin pedidos'}}}>
345
+ <OrdersModule />
346
+ </ComponentTexts>
347
+ ```
348
+
349
+ ## En un sistema que ya existe
350
+
351
+ Calcita entra por pedazos en un sistema hecho en PHP, JSP o .NET, sin reescribirlo: cada pedazo es una «isla» de React en
352
+ un `<div>` de la página vieja, con su propio `ToolkitProvider`.
353
+
354
+ - **Sin router:** `createHashNavigation()` pone las pantallas después del `#` (`/app.php#/ventas`): cambiar de pantalla no
355
+ recarga, atrás y adelante funcionan y recargar no le pide nada nuevo al servidor viejo. Con la app bajo un prefijo y sin
356
+ router, `createBrowserNavigation({basePath: '/portal'})`.
357
+ - **Su tema de MUI tal cual:** Calcita lee los colores del tema, con o sin variables CSS y con el prefijo que traiga
358
+ (`cssVarPrefix`).
359
+ - **Varias islas, cada una con lo suyo:** dos islas pueden ir en idiomas y monedas distintas; `useFormatters()` escribe con
360
+ los de su isla.
361
+ - **Un backend que contesta a su manera:** `createApiClient({toProblem, mapResponse})` convierte sus errores a RFC 9457
362
+ (Laravel manda `{message, errors}`) y desenvuelve sus respuestas (`{data: …}`); `onError` recibe cada error en un lugar.
363
+ Una subida con avance (`uploadWithProgress`) regresa la respuesta tal cual: `apiErrorOf(response)` da el mismo `ApiError`.
364
+ - **Su marca y su menú:** un logo ancho (`wordmark`) en lugar del cuadrado y el nombre; entradas del menú con liga a otro
365
+ sistema (`href`) o con un ícono ya pintado (`icon`, por ejemplo de iconify); y las ligas de la cuenta (`accountMenuItems`)
366
+ salen también sin sesión, detrás del engrane («Regresar al sistema»).
367
+ - **Su `sx` gana:** lo que el proyecto pinte con `sx` les gana a los ajustes de Calcita en claro y en oscuro.
368
+ - **Su registro:** `addLogSink` manda cada anotación también a su reporte (Sentry, Datadog, su endpoint).
369
+ - **Si parte el código en archivos** (`manualChunks`): Calcita en el suyo. Junto con lo demás de `node_modules` arma un
370
+ círculo con MUI y la app truena al abrir («reading 'isValidElementType'»). Su FilePond se queda fuera para seguir bajando
371
+ solo donde se usa:
372
+
373
+ ```js
374
+ if (id.includes('@stackcraftmx/calcita') && !id.includes('/filepond')) return 'calcita';
375
+ ```
376
+
377
+ ```jsx
378
+ const hashNavigation = createHashNavigation(); // una vez, fuera de los componentes
379
+
380
+ document.querySelectorAll('[data-calcita="sales"]').forEach((element) => {
381
+ createRoot(element).render(
382
+ <ThemeProvider theme={legacyTheme}>
383
+ <ToolkitProvider language={element.lang || 'es'} navigation={hashNavigation}>
384
+ <SalesOfTheDay onOpen={() => element.dispatchEvent(new CustomEvent('calcita:open-sales', {bubbles: true}))} />
385
+ </ToolkitProvider>
386
+ </ThemeProvider>,
387
+ );
388
+ });
389
+ ```
390
+
391
+ ## Detrás de un proxy con prefijo
392
+
393
+ Para publicar la app en una ruta y no en un subdominio (`miempresa.com/ventas/`), el prefijo se da una vez al construir,
394
+ `ASSET_URL=/ventas`, la misma línea del `.env` del servidor. El proyecto que arma `calcita new` ya lo trae:
395
+
396
+ 1. `vite.config.js`: `base` con el prefijo; Vite se lo pone a sus archivos y a las rutas de `index.html`;
397
+ 2. el router con el mismo prefijo (`createBrowserRouter(routes, {basename})`): las pantallas siguen escribiendo sus rutas
398
+ desde la raíz;
399
+ 3. lo de `public/` y `version.json` con `import.meta.env.BASE_URL` (una dirección escrita desde la raíz se rompe detrás del
400
+ proxy) y el manifiesto con rutas relativas;
401
+ 4. el servidor: cada dirección bajo el prefijo que no sea un archivo regresa su `index.html`.
402
+
403
+ ```nginx
404
+ location /ventas/ {
405
+ try_files $uri $uri/ /ventas/index.html;
406
+ }
407
+ ```
303
408
 
304
409
  ## La petición de la casa
305
410
 
@@ -488,7 +593,7 @@ menú, diccionarios) no lleva marca, así que también corre en los componentes
488
593
  ## Formularios
489
594
 
490
595
  Cada campo tiene su vista suelta en la puerta principal (`TextInput`, `SelectInput`, `NumberInput`, `CatalogInput`,
491
- `ColorInput`; sirven sin formulario) y su conexión: a formik en `/forms` y a react-hook-form en `/react-hook-form`, con
596
+ `ColorInput`; sirven sin formulario; `SelectInput` y `CatalogInput` aceptan varias opciones con `multiple`) y su conexión: a formik en `/forms` y a react-hook-form en `/react-hook-form`, con
492
597
  los mismos nombres. Cambiar de librería es cambiar el import.
493
598
 
494
599
  ```jsx
@@ -507,9 +612,9 @@ import {Form, Formik, FormNumberField, FormProblemAlert, FormTextField, SubmitBu
507
612
  </Formik>
508
613
  ```
509
614
 
510
- formik y yup vienen con el paquete: se importan de `@stackcraftmx/calcita/forms` y no de `formik` / `yup` directo (con
511
- otra versión en el proyecto, los campos no encontrarían el formulario). `FormWizard` parte un formulario en pasos, con
512
- pasos opcionales; los ejemplos traen uno completo.
615
+ formik y yup los instala el proyecto (`pnpm add formik yup`); `Formik`, `Form` y `Yup` también se pueden importar de
616
+ `@stackcraftmx/calcita/forms`, que es la misma copia. `FormWizard` parte un formulario en pasos, con pasos opcionales (hay
617
+ uno para formik y otro para react-hook-form); los ejemplos traen los dos.
513
618
 
514
619
  Para que el formulario se vea como los de Apple (Ajustes, la hoja «Nuevo evento» de Calendario), los campos van dentro de
515
620
  una `FormSection`: cada uno se vuelve un renglón, con la etiqueta a la izquierda y el valor a la derecha, sin la caja de
@@ -5,10 +5,12 @@
5
5
  import {versionsOf} from './packageVersions.js';
6
6
  import {primaryColorOf, templateFlagsOf} from './projectAnswers.js';
7
7
 
8
- // lo que pide toda app con Calcita: React, MUI, el router, los avisos y las máquinas de estados que usa su puerta principal
8
+ // lo que pide toda app con Calcita: React, MUI, el router, los avisos y las máquinas de estados que usa su puerta principal, y
9
+ // formik y yup (los mensajes de validación en español salen de /forms en main.jsx, y la cuenta usa formik). Desde la 0.25 los
10
+ // pone el proyecto: Calcita ya no los trae adentro
9
11
  const BASE_DEPENDENCIES = Object.freeze([
10
- '@emotion/react', '@emotion/styled', '@mui/icons-material', '@mui/material', '@xstate/react', 'react', 'react-dom', 'react-router',
11
- 'sonner', 'xstate',
12
+ '@emotion/react', '@emotion/styled', '@mui/icons-material', '@mui/material', '@xstate/react', 'formik', 'react', 'react-dom',
13
+ 'react-router', 'sonner', 'xstate', 'yup',
12
14
  ]);
13
15
  const HOOK_FORM_DEPENDENCIES = Object.freeze(['@hookform/resolvers', 'react-hook-form']);
14
16
  // la cuenta: las llaves de acceso (las pide @stackcraftmx/calcita/account) y el QR de la demostración
@@ -42,6 +42,34 @@ src/
42
42
  validaciones) ya vienen.
43
43
  - **Imports con el alias `@/`** (= `src/`), declarado en `vite.config.js` y en `jsconfig.json`.
44
44
 
45
+ ## Publicar una versión nueva
46
+
47
+ `pnpm build` deja la app en `dist/`, con `version.json` (la versión de esa compilación). La app publicada lo vuelve a leer al
48
+ abrirla y cada tanto: si ya hay otra, se recarga sola cuando estuvo fuera 15 minutos o más (una pestaña de ayer, la app
49
+ anclada al inicio del iPhone) y, si no, enseña el aviso «Versión nueva» con su botón. Para que funcione, el servidor no
50
+ debe guardar `index.html` ni `version.json` (`Cache-Control: no-cache`); los archivos de `dist/assets/` sí, un año.
51
+
52
+ ## Detrás de un proxy con prefijo
53
+
54
+ Para publicarla en una ruta y no en un subdominio (`miempresa.com/ventas/`), el prefijo se da una vez al construir:
55
+
56
+ ```bash
57
+ ASSET_URL=/ventas pnpm build
58
+ ```
59
+
60
+ Vite le pone el prefijo a sus archivos y a las rutas de `index.html`, el router lo quita al leer la dirección y las pantallas
61
+ siguen escribiendo sus rutas desde la raíz (`/contacts`). Lo de `public/` se pide con `publicUrlOf('favicon.svg')`
62
+ (`src/core/publicPaths.js`), nunca escrito desde la raíz. En el servidor, cada dirección bajo el prefijo que no sea un archivo
63
+ regresa su `index.html`:
64
+
65
+ ```nginx
66
+ location /ventas/ {
67
+ try_files $uri $uri/ /ventas/index.html;
68
+ }
69
+ ```
70
+
71
+ Con otro prefijo se vuelve a construir.
72
+
45
73
  ## Crecer la app con el CLI
46
74
 
47
75
  ```bash
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "__APP_TITLE__",
3
3
  "short_name": "__APP_TITLE__",
4
- "id": "/",
5
- "start_url": "/",
6
- "scope": "/",
4
+ "id": "./",
5
+ "start_url": "./",
6
+ "scope": "./",
7
7
  "display": "standalone",
8
8
  "background_color": "#FFFFFF",
9
9
  "theme_color": "__PRIMARY_COLOR__",
10
10
  "lang": "es-MX",
11
11
  "dir": "ltr",
12
12
  "icons": [
13
- {"src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any"},
14
- {"src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any"},
15
- {"src": "/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable"}
13
+ {"src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any"},
14
+ {"src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any"},
15
+ {"src": "icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable"}
16
16
  ]
17
17
  }
@@ -1,5 +1,7 @@
1
1
  // La raíz de la app: el ÚNICO lugar donde se conecta el framework. ToolkitProvider recibe el idioma, los textos, la zona, la
2
- // moneda y el router; los avisos (Toaster) se montan aquí una vez, para todas las pantallas.
2
+ // moneda y el router; los avisos (Toaster) se montan aquí una vez, para todas las pantallas. Aquí también se vigila si ya se
3
+ // publicó otra versión (usePublishedBuildWatch): una pestaña abierta desde ayer, o la app anclada al inicio del iPhone, se
4
+ // actualiza sola si estuvo fuera 15 minutos o más; si no, sale el aviso «Versión nueva» con su botón.
3
5
  //#if auth
4
6
  //
5
7
  // AccountProvider carga la sesión con el conector de la cuenta (core/accountConnector.js) y le pasa a ToolkitProvider la regla
@@ -17,6 +19,7 @@ import {
17
19
  //#endif
18
20
  Toaster,
19
21
  ToolkitProvider,
22
+ usePublishedBuildWatch,
20
23
  } from '@stackcraftmx/calcita';
21
24
  //#if auth
22
25
  import {AccountProvider} from '@stackcraftmx/calcita/account';
@@ -26,7 +29,7 @@ import {translateWithI18next} from '@stackcraftmx/calcita/i18next';
26
29
  //#endif
27
30
  import {reactRouterNavigation} from '@stackcraftmx/calcita/react-router';
28
31
 
29
- import {APP_LANGUAGES, APP_NAME, PRODUCT_CURRENCY, PRODUCT_TIME_ZONE} from '@/app/appSettings.js';
32
+ import {APP_BUILD_ID, APP_LANGUAGES, APP_NAME, BUILD_VERSION_URL, PRODUCT_CURRENCY, PRODUCT_TIME_ZONE} from '@/app/appSettings.js';
30
33
  //#if !i18next
31
34
  import {APP_TEXTS} from '@/app/appTexts.js';
32
35
  //#endif
@@ -44,6 +47,7 @@ const translate = translateWithI18next(i18n);
44
47
 
45
48
  export const AppProviders = () => {
46
49
  const [language, setLanguage] = useState(APP_LANGUAGES.spanish);
50
+ usePublishedBuildWatch({url: BUILD_VERSION_URL, buildId: APP_BUILD_ID});
47
51
 
48
52
  // el idioma de la página, para el lector de pantalla y el corrector del navegador
49
53
  useEffect(() => {
@@ -70,7 +74,7 @@ export const AppProviders = () => {
70
74
  onLanguageChange={changeLanguage} translate={translate}
71
75
  //#endif
72
76
  timeZone={PRODUCT_TIME_ZONE} currency={PRODUCT_CURRENCY} appName={APP_NAME} navigation={reactRouterNavigation}
73
- canAccess={canAccess}>
77
+ buildId={APP_BUILD_ID} canAccess={canAccess}>
74
78
  <Outlet />
75
79
  <Toaster />
76
80
  //#if pwa
@@ -90,7 +94,8 @@ export const AppProviders = () => {
90
94
  //#if i18next
91
95
  onLanguageChange={changeLanguage} translate={translate}
92
96
  //#endif
93
- timeZone={PRODUCT_TIME_ZONE} currency={PRODUCT_CURRENCY} appName={APP_NAME} navigation={reactRouterNavigation}>
97
+ timeZone={PRODUCT_TIME_ZONE} currency={PRODUCT_CURRENCY} appName={APP_NAME} navigation={reactRouterNavigation}
98
+ buildId={APP_BUILD_ID}>
94
99
  <Outlet />
95
100
  <Toaster />
96
101
  //#if pwa
@@ -12,7 +12,7 @@ import {RouteErrorScreen, withAccess, withChunkRecovery} from '@stackcraftmx/cal
12
12
 
13
13
  import {AppProviders} from '@/app/AppProviders.jsx';
14
14
  import {AppShell} from '@/app/AppShell.jsx';
15
- import {APP_PATHS} from '@/core';
15
+ import {APP_PATHS, ROUTER_BASENAME} from '@/core';
16
16
 
17
17
  const ROUTE_MODULES = import.meta.glob('@/modules/*/routes.js', {eager: true});
18
18
  const MENU_MODULES = import.meta.glob('@/modules/*/menu.js', {eager: true});
@@ -46,4 +46,6 @@ export const appRouter = createBrowserRouter([
46
46
  },
47
47
  ]),
48
48
  },
49
- ]);
49
+ // detrás de un proxy con prefijo (ASSET_URL), el router lo quita al leer la dirección y lo pone en cada liga: las pantallas
50
+ // siguen escribiendo sus rutas desde la raíz ('/contacts')
51
+ ], {basename: ROUTER_BASENAME});
@@ -1,4 +1,5 @@
1
1
  // Lo que la app le pasa a ToolkitProvider y que no cambia mientras corre.
2
+ import {publicUrlOf} from '@/core';
2
3
 
3
4
  /** El nombre de la app: en la barra y en la pestaña, después del título de cada pantalla («Contactos | __APP_TITLE__»). */
4
5
  export const APP_NAME = '__APP_TITLE__';
@@ -9,5 +10,18 @@ export const PRODUCT_TIME_ZONE = 'America/Mexico_City';
9
10
  /** La moneda de los montos (ISO 4217). */
10
11
  export const PRODUCT_CURRENCY = 'MXN';
11
12
 
13
+ /**
14
+ * La versión del front que corre en esta pestaña: la pone el build (VITE_BUILD_ID, de vite.config.js, la misma que escribe
15
+ * en version.json). En desarrollo no hay build: queda vacía y no se compara nada.
16
+ */
17
+ export const APP_BUILD_ID = import.meta.env.VITE_BUILD_ID ?? '';
18
+
19
+ /**
20
+ * Dónde está el archivo con la versión publicada: la app lo vuelve a leer al abrirla y cada tanto (usePublishedBuildWatch).
21
+ * Solo en lo construido, con el prefijo si la app vive detrás de un proxy. OJO: el servidor no debe guardarlo (Cache-Control:
22
+ * no-cache, como el index.html).
23
+ */
24
+ export const BUILD_VERSION_URL = import.meta.env.PROD ? publicUrlOf('version.json') : null;
25
+
12
26
  /** Los idiomas que ofrece la app; el primero es el de arranque. */
13
27
  export const APP_LANGUAGES = Object.freeze({spanish: 'es', english: 'en'});
@@ -4,9 +4,11 @@
4
4
  // Genérico, sin estado: function (regla de la casa).
5
5
  import Box from '@mui/material/Box';
6
6
 
7
+ import {publicUrlOf} from '@/core/publicPaths.js';
8
+
7
9
  // el tamaño que piden la barra y la tarjeta de entrar
8
10
  const LOGO_SIZE_PIXELS = 28;
9
11
 
10
12
  export function AppLogo() {
11
- return <Box component="img" src="/favicon.svg" alt="" sx={{width: LOGO_SIZE_PIXELS, height: LOGO_SIZE_PIXELS}} />;
13
+ return <Box component="img" src={publicUrlOf('favicon.svg')} alt="" sx={{width: LOGO_SIZE_PIXELS, height: LOGO_SIZE_PIXELS}} />;
12
14
  }
@@ -2,6 +2,7 @@
2
2
  // ruta. Core no importa módulos ni app/: si algo lo usan dos módulos, sube aquí.
3
3
  export {APP_PATHS} from '@/core/appPaths.js';
4
4
  export {AppLogo} from '@/core/AppLogo.jsx';
5
+ export {ROUTER_BASENAME, publicUrlOf} from '@/core/publicPaths.js';
5
6
  export {apiClient} from '@/core/apiClient.js';
6
7
  //#if auth
7
8
  export {DEMO_USER, USES_DEMO_ACCOUNT, accountConnector} from '@/core/accountConnector.js';
@@ -0,0 +1,24 @@
1
+ // Dónde vive la app: en la raíz o detrás de un proxy con prefijo (miempresa.com/ventas/, sin subdominio). El prefijo se da UNA
2
+ // vez, al construir, con ASSET_URL=/ventas (vite.config.js), la misma línea que lleva el .env del servidor. Vite lo deja en
3
+ // import.meta.env.BASE_URL ('/' o '/ventas/') y de aquí lo toman el router (basename), las imágenes de public/ y version.json.
4
+ //
5
+ // OJO: una dirección escrita a mano desde la raíz ('/favicon.svg', '/version.json') funciona en la raíz y se rompe detrás del
6
+ // proxy: se arma con publicUrlOf.
7
+ //
8
+ // Funciones puras, no renderizan: function (regla de la casa).
9
+
10
+ // en las pruebas de node no hay import.meta.env (es de Vite): ahí, la raíz
11
+ const APP_BASE_URL = import.meta.env?.BASE_URL ?? '/';
12
+
13
+ /** El prefijo como lo pide el router: '/ventas' (sin la diagonal del final) o '/'. */
14
+ export const ROUTER_BASENAME = APP_BASE_URL.replace(/\/$/, '') || '/';
15
+
16
+ /**
17
+ * La dirección de un archivo de public/ con el prefijo: 'favicon.svg' → '/ventas/favicon.svg'.
18
+ *
19
+ * @param {string} path la ruta dentro de public/, sin la diagonal del inicio
20
+ * @returns {string}
21
+ */
22
+ export function publicUrlOf(path) {
23
+ return `${APP_BASE_URL}${path}`;
24
+ }
@@ -17,8 +17,34 @@ const SINGLE_COPY_PACKAGES = [
17
17
  // de los ~790 kB del archivo principal (medido con el proyecto recién creado). Cada pantalla va aparte (rutas lazy)
18
18
  const LARGEST_CHUNK_KILOBYTES = 1000;
19
19
 
20
+ // Detrás de un proxy con prefijo (miempresa.com/ventas/, sin subdominio): UNA variable al construir, ASSET_URL=/ventas, la
21
+ // misma línea que lleva el .env del servidor. Vite pide sus archivos con el prefijo, se lo pone a las rutas de index.html y lo
22
+ // deja en import.meta.env.BASE_URL, de donde lo toman el router, las imágenes y version.json (src/core/publicPaths.js). Sin
23
+ // ella, todo en la raíz. Con otro prefijo hay que volver a construir.
24
+ const basePathOf = (assetUrl) => {
25
+ const prefix = (assetUrl ?? '').trim().replace(/^\/+|\/+$/g, '');
26
+ return prefix ? `/${prefix}/` : '/';
27
+ };
28
+
29
+ // La versión de esta compilación: va dentro de la app (import.meta.env.VITE_BUILD_ID → ToolkitProvider buildId) y en
30
+ // dist/version.json, que la app publicada vuelve a leer al abrirla para saber si ya hay otra (usePublishedBuildWatch). Se
31
+ // puede fijar desde afuera (VITE_BUILD_ID=… al construir, p. ej. el commit); si no, la hora en que se construyó
32
+ const BUILD_ID = process.env.VITE_BUILD_ID || new Date().toISOString();
33
+
34
+ function buildVersionPlugin() {
35
+ return {
36
+ name: 'build-version',
37
+ apply: 'build',
38
+ generateBundle() {
39
+ this.emitFile({type: 'asset', fileName: 'version.json', source: JSON.stringify({buildId: BUILD_ID})});
40
+ },
41
+ };
42
+ }
43
+
20
44
  export default defineConfig({
21
- plugins: [react()],
45
+ base: basePathOf(process.env.ASSET_URL),
46
+ plugins: [react(), buildVersionPlugin()],
47
+ define: {'import.meta.env.VITE_BUILD_ID': JSON.stringify(BUILD_ID)},
22
48
  resolve: {
23
49
  alias: {'@': fileURLToPath(new URL('./src', import.meta.url))},
24
50
  dedupe: SINGLE_COPY_PACKAGES,
@@ -4,7 +4,7 @@ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
4
4
  import ServerTable from "./ServerTable.js";
5
5
  import { FILTER_KINDS } from "./useTableState.js";
6
6
  import { labelOf, lookup } from "../dictionaries/dictionaries.js";
7
- import { formatDateTime } from "../formatting.js";
7
+ import { useFormatters } from "../toolkit/useFormatters.js";
8
8
  import StatusChip from "../ui/labels/StatusChip.js";
9
9
  const AUDIT_FILTER_DEFAULTS = { event: [] };
10
10
  const AUDIT_FILTER_KINDS = { event: FILTER_KINDS.multiSelect };
@@ -17,9 +17,11 @@ function AuditTrailTable({
17
17
  ariaLabel,
18
18
  maxGridHeight = null,
19
19
  refreshToken,
20
- eventSuffixOf = null
20
+ eventSuffixOf = null,
21
+ texts
21
22
  }) {
22
- const { t } = useToolkitTranslation(["table", "dictionaries"]);
23
+ const { t } = useToolkitTranslation(["table", "dictionaries"], texts);
24
+ const { formatDateTime } = useFormatters();
23
25
  const columns = [
24
26
  {
25
27
  field: "occurred_at",
@@ -1 +1 @@
1
- {"version":3,"file":"AuditTrailTable.js","sources":["../../src/DataTable/AuditTrailTable.jsx"],"sourcesContent":["// Tabla de historial de un renglón: sus eventos, paginados y filtrados EN EL SERVIDOR (regla de tablas).\n//\n// La usan AuditTrailDialog (historial en un diálogo desde una lista) y los detalles con historial a la vista (p. ej. el\n// detalle de una cuenta). Recibe QUÉ consultar (fetchPage) y CÓMO nombrar los eventos (diccionarios) por props: core no\n// conoce el API.\n//\n// Filtros y página en memoria (sin prefijo de URL): el historial vive dentro de otra pantalla y escribir en la barra de\n// direcciones ensuciaría la URL de esa pantalla.\n//\n// Contrato de fetchPage: el de ServerTable ({filters: {event: [...]}, sortOrders, page, pageSize}) →\n// {rows: [{id, event, result, occurred_at, actor: {id, name}|null, ...}], total, facets: {event: [{value, total}]}}.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\nimport ServerTable from '@/DataTable/ServerTable.jsx';\nimport {FILTER_KINDS} from '@/DataTable/useTableState.js';\nimport {labelOf, lookup} from '@/dictionaries/dictionaries.js';\nimport {formatDateTime} from '@/formatting.js';\nimport StatusChip from '@/ui/labels/StatusChip.jsx';\n\n// OJO: constantes de módulo; objetos nuevos en cada render reinician el estado de la tabla\nconst AUDIT_FILTER_DEFAULTS = {event: []};\nconst AUDIT_FILTER_KINDS = {event: FILTER_KINDS.multiSelect};\n\n/**\n * @param {object} props\n * @param {string} props.subjectKey identifica el renglón (cambia la consulta al cambiar de renglón); '' no consulta\n * @param {(tableRequest: object, signal: AbortSignal) => Promise<{rows: Array, total: number, facets?: object}>} props.fetchPage\n * @param {Record<string, object>} props.eventDictionary\n * @param {Record<string, object>} [props.resultDictionary]\n * @param {Array<object>} [props.extraColumns] columnas del DataGrid propias del módulo (archivo, origen, motivo...)\n * @param {(row: any) => string|null} [props.eventSuffixOf] texto que va después del evento con « · » (p. ej. para\n * quién era la descarga); null = solo el evento\n * @param {string} props.ariaLabel\n * @param {number} [props.maxGridHeight]\n * @param {unknown} [props.refreshToken] cambia para volver a consultar (después de una acción sobre el mismo renglón)\n */\nexport default function AuditTrailTable({\n subjectKey, fetchPage, eventDictionary, resultDictionary, extraColumns = [], ariaLabel, maxGridHeight = null, refreshToken,\n eventSuffixOf = null,\n}) {\n const {t} = useToolkitTranslation(['table', 'dictionaries']);\n\n const columns = [\n {\n field: 'occurred_at', headerName: t('table:audit.columns.occurredAt'), width: 170, sortable: false,\n valueFormatter: (value) => (value ? formatDateTime(value) : '—'),\n },\n {\n field: 'event', headerName: t('table:audit.columns.event'), flex: 1, minWidth: 230, sortable: false,\n renderCell: ({row}) => <StatusChip variant=\"chip\" dictionary={eventDictionary} value={row.event} suffix={eventSuffixOf?.(row) ?? undefined} />,\n },\n ...(resultDictionary ? [{\n field: 'result', headerName: t('table:audit.columns.result'), width: 170, sortable: false,\n renderCell: ({row}) => <StatusChip dictionary={resultDictionary} value={row.result} />,\n }] : []),\n {\n field: 'actor', headerName: t('table:audit.columns.actor'), width: 190, sortable: false,\n // los eventos que escribe un proceso automático o la propia persona no tienen actor: se dice quién fue en palabras\n valueGetter: (value) => value?.name ?? t('table:audit.systemActor'),\n },\n ...extraColumns,\n ];\n\n // OJO: key={subjectKey}. Sin prefijo, los filtros viven en memoria dentro de ServerTable; si el diálogo o la página\n // siguen montados al pasar a otro renglón, el filtro de evento del renglón anterior se quedaría puesto\n return (\n <ServerTable\n key={subjectKey || 'without-subject'}\n queryKey={subjectKey ? `audit:${subjectKey}` : ''}\n enabled={Boolean(subjectKey)}\n fetchPage={fetchPage}\n refreshToken={refreshToken}\n columns={columns}\n filterDefaults={AUDIT_FILTER_DEFAULTS}\n filterKinds={AUDIT_FILTER_KINDS}\n searchField={null}\n chipConfig={[]}\n quickFilters={[{\n field: 'event',\n facetKey: 'event',\n caption: t('table:audit.eventFilterCaption'),\n renderLabel: (eventValue) => labelOf(lookup(eventDictionary, eventValue), t),\n }]}\n countLabel={(total) => t('table:audit.count', {count: total})}\n emptyLabel={t('table:audit.empty')}\n defaultPageSize={10}\n pageSizeOptions={[10, 25, 50]}\n maxGridHeight={maxGridHeight}\n ariaLabel={ariaLabel}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAsBA,MAAM,wBAAwB,EAAC,OAAO,GAAC;AACvC,MAAM,qBAAqB,EAAC,OAAO,aAAa,YAAA;AAehD,SAAwB,gBAAgB;AAAA,EACpC;AAAA,EAAY;AAAA,EAAW;AAAA,EAAiB;AAAA,EAAkB,eAAe,CAAA;AAAA,EAAI;AAAA,EAAW,gBAAgB;AAAA,EAAM;AAAA,EAC9G,gBAAgB;AACpB,GAAG;AACC,QAAM,EAAC,EAAA,IAAK,sBAAsB,CAAC,SAAS,cAAc,CAAC;AAE3D,QAAM,UAAU;AAAA,IACZ;AAAA,MACI,OAAO;AAAA,MAAe,YAAY,EAAE,gCAAgC;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA,MAC7F,gBAAgB,CAAC,UAAW,QAAQ,eAAe,KAAK,IAAI;AAAA,IAAA;AAAA,IAEhE;AAAA,MACI,OAAO;AAAA,MAAS,YAAY,EAAE,2BAA2B;AAAA,MAAG,MAAM;AAAA,MAAG,UAAU;AAAA,MAAK,UAAU;AAAA,MAC9F,YAAY,CAAC,EAAC,UAAS,oBAAC,cAAW,SAAQ,QAAO,YAAY,iBAAiB,OAAO,IAAI,OAAO,QAAQ,gBAAgB,GAAG,KAAK,OAAA,CAAW;AAAA,IAAA;AAAA,IAEhJ,GAAI,mBAAmB,CAAC;AAAA,MACpB,OAAO;AAAA,MAAU,YAAY,EAAE,4BAA4B;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA,MACpF,YAAY,CAAC,EAAC,IAAA,MAAS,oBAAC,YAAA,EAAW,YAAY,kBAAkB,OAAO,IAAI,OAAA,CAAQ;AAAA,IAAA,CACvF,IAAI,CAAA;AAAA,IACL;AAAA,MACI,OAAO;AAAA,MAAS,YAAY,EAAE,2BAA2B;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA;AAAA,MAElF,aAAa,CAAC,UAAU,OAAO,QAAQ,EAAE,yBAAyB;AAAA,IAAA;AAAA,IAEtE,GAAG;AAAA,EAAA;AAKP,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAEG,UAAU,aAAa,SAAS,UAAU,KAAK;AAAA,MAC/C,SAAS,QAAQ,UAAU;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,YAAY,CAAA;AAAA,MACZ,cAAc,CAAC;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,EAAE,gCAAgC;AAAA,QAC3C,aAAa,CAAC,eAAe,QAAQ,OAAO,iBAAiB,UAAU,GAAG,CAAC;AAAA,MAAA,CAC9E;AAAA,MACD,YAAY,CAAC,UAAU,EAAE,qBAAqB,EAAC,OAAO,OAAM;AAAA,MAC5D,YAAY,EAAE,mBAAmB;AAAA,MACjC,iBAAiB;AAAA,MACjB,iBAAiB,CAAC,IAAI,IAAI,EAAE;AAAA,MAC5B;AAAA,MACA;AAAA,IAAA;AAAA,IArBK,cAAc;AAAA,EAAA;AAwB/B;"}
1
+ {"version":3,"file":"AuditTrailTable.js","sources":["../../src/DataTable/AuditTrailTable.jsx"],"sourcesContent":["// Tabla de historial de un renglón: sus eventos, paginados y filtrados EN EL SERVIDOR (regla de tablas).\n//\n// La usan AuditTrailDialog (historial en un diálogo desde una lista) y los detalles con historial a la vista (p. ej. el\n// detalle de una cuenta). Recibe QUÉ consultar (fetchPage) y CÓMO nombrar los eventos (diccionarios) por props: core no\n// conoce el API.\n//\n// Filtros y página en memoria (sin prefijo de URL): el historial vive dentro de otra pantalla y escribir en la barra de\n// direcciones ensuciaría la URL de esa pantalla.\n//\n// Contrato de fetchPage: el de ServerTable ({filters: {event: [...]}, sortOrders, page, pageSize}) →\n// {rows: [{id, event, result, occurred_at, actor: {id, name}|null, ...}], total, facets: {event: [{value, total}]}}.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\nimport ServerTable from '@/DataTable/ServerTable.jsx';\nimport {FILTER_KINDS} from '@/DataTable/useTableState.js';\nimport {labelOf, lookup} from '@/dictionaries/dictionaries.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport StatusChip from '@/ui/labels/StatusChip.jsx';\n\n// OJO: constantes de módulo; objetos nuevos en cada render reinician el estado de la tabla\nconst AUDIT_FILTER_DEFAULTS = {event: []};\nconst AUDIT_FILTER_KINDS = {event: FILTER_KINDS.multiSelect};\n\n/**\n * @param {object} props\n * @param {string} props.subjectKey identifica el renglón (cambia la consulta al cambiar de renglón); '' no consulta\n * @param {(tableRequest: object, signal: AbortSignal) => Promise<{rows: Array, total: number, facets?: object}>} props.fetchPage\n * @param {Record<string, object>} props.eventDictionary\n * @param {Record<string, object>} [props.resultDictionary]\n * @param {Array<object>} [props.extraColumns] columnas del DataGrid propias del módulo (archivo, origen, motivo...)\n * @param {(row: any) => string|null} [props.eventSuffixOf] texto que va después del evento con « · » (p. ej. para\n * quién era la descarga); null = solo el evento\n * @param {string} props.ariaLabel\n * @param {number} [props.maxGridHeight]\n * @param {unknown} [props.refreshToken] cambia para volver a consultar (después de una acción sobre el mismo renglón)\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function AuditTrailTable({\n subjectKey, fetchPage, eventDictionary, resultDictionary, extraColumns = [], ariaLabel, maxGridHeight = null, refreshToken,\n eventSuffixOf = null, texts,\n}) {\n const {t} = useToolkitTranslation(['table', 'dictionaries'], texts);\n const {formatDateTime} = useFormatters();\n\n const columns = [\n {\n field: 'occurred_at', headerName: t('table:audit.columns.occurredAt'), width: 170, sortable: false,\n valueFormatter: (value) => (value ? formatDateTime(value) : '—'),\n },\n {\n field: 'event', headerName: t('table:audit.columns.event'), flex: 1, minWidth: 230, sortable: false,\n renderCell: ({row}) => <StatusChip variant=\"chip\" dictionary={eventDictionary} value={row.event} suffix={eventSuffixOf?.(row) ?? undefined} />,\n },\n ...(resultDictionary ? [{\n field: 'result', headerName: t('table:audit.columns.result'), width: 170, sortable: false,\n renderCell: ({row}) => <StatusChip dictionary={resultDictionary} value={row.result} />,\n }] : []),\n {\n field: 'actor', headerName: t('table:audit.columns.actor'), width: 190, sortable: false,\n // los eventos que escribe un proceso automático o la propia persona no tienen actor: se dice quién fue en palabras\n valueGetter: (value) => value?.name ?? t('table:audit.systemActor'),\n },\n ...extraColumns,\n ];\n\n // OJO: key={subjectKey}. Sin prefijo, los filtros viven en memoria dentro de ServerTable; si el diálogo o la página\n // siguen montados al pasar a otro renglón, el filtro de evento del renglón anterior se quedaría puesto\n return (\n <ServerTable\n key={subjectKey || 'without-subject'}\n queryKey={subjectKey ? `audit:${subjectKey}` : ''}\n enabled={Boolean(subjectKey)}\n fetchPage={fetchPage}\n refreshToken={refreshToken}\n columns={columns}\n filterDefaults={AUDIT_FILTER_DEFAULTS}\n filterKinds={AUDIT_FILTER_KINDS}\n searchField={null}\n chipConfig={[]}\n quickFilters={[{\n field: 'event',\n facetKey: 'event',\n caption: t('table:audit.eventFilterCaption'),\n renderLabel: (eventValue) => labelOf(lookup(eventDictionary, eventValue), t),\n }]}\n countLabel={(total) => t('table:audit.count', {count: total})}\n emptyLabel={t('table:audit.empty')}\n defaultPageSize={10}\n pageSizeOptions={[10, 25, 50]}\n maxGridHeight={maxGridHeight}\n ariaLabel={ariaLabel}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAsBA,MAAM,wBAAwB,EAAC,OAAO,GAAC;AACvC,MAAM,qBAAqB,EAAC,OAAO,aAAa,YAAA;AAgBhD,SAAwB,gBAAgB;AAAA,EACpC;AAAA,EAAY;AAAA,EAAW;AAAA,EAAiB;AAAA,EAAkB,eAAe,CAAA;AAAA,EAAI;AAAA,EAAW,gBAAgB;AAAA,EAAM;AAAA,EAC9G,gBAAgB;AAAA,EAAM;AAC1B,GAAG;AACC,QAAM,EAAC,MAAK,sBAAsB,CAAC,SAAS,cAAc,GAAG,KAAK;AAClE,QAAM,EAAC,eAAA,IAAkB,cAAA;AAEzB,QAAM,UAAU;AAAA,IACZ;AAAA,MACI,OAAO;AAAA,MAAe,YAAY,EAAE,gCAAgC;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA,MAC7F,gBAAgB,CAAC,UAAW,QAAQ,eAAe,KAAK,IAAI;AAAA,IAAA;AAAA,IAEhE;AAAA,MACI,OAAO;AAAA,MAAS,YAAY,EAAE,2BAA2B;AAAA,MAAG,MAAM;AAAA,MAAG,UAAU;AAAA,MAAK,UAAU;AAAA,MAC9F,YAAY,CAAC,EAAC,UAAS,oBAAC,cAAW,SAAQ,QAAO,YAAY,iBAAiB,OAAO,IAAI,OAAO,QAAQ,gBAAgB,GAAG,KAAK,OAAA,CAAW;AAAA,IAAA;AAAA,IAEhJ,GAAI,mBAAmB,CAAC;AAAA,MACpB,OAAO;AAAA,MAAU,YAAY,EAAE,4BAA4B;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA,MACpF,YAAY,CAAC,EAAC,IAAA,MAAS,oBAAC,YAAA,EAAW,YAAY,kBAAkB,OAAO,IAAI,OAAA,CAAQ;AAAA,IAAA,CACvF,IAAI,CAAA;AAAA,IACL;AAAA,MACI,OAAO;AAAA,MAAS,YAAY,EAAE,2BAA2B;AAAA,MAAG,OAAO;AAAA,MAAK,UAAU;AAAA;AAAA,MAElF,aAAa,CAAC,UAAU,OAAO,QAAQ,EAAE,yBAAyB;AAAA,IAAA;AAAA,IAEtE,GAAG;AAAA,EAAA;AAKP,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAEG,UAAU,aAAa,SAAS,UAAU,KAAK;AAAA,MAC/C,SAAS,QAAQ,UAAU;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,YAAY,CAAA;AAAA,MACZ,cAAc,CAAC;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,EAAE,gCAAgC;AAAA,QAC3C,aAAa,CAAC,eAAe,QAAQ,OAAO,iBAAiB,UAAU,GAAG,CAAC;AAAA,MAAA,CAC9E;AAAA,MACD,YAAY,CAAC,UAAU,EAAE,qBAAqB,EAAC,OAAO,OAAM;AAAA,MAC5D,YAAY,EAAE,mBAAmB;AAAA,MACjC,iBAAiB;AAAA,MACjB,iBAAiB,CAAC,IAAI,IAAI,EAAE;AAAA,MAC5B;AAAA,MACA;AAAA,IAAA;AAAA,IArBK,cAAc;AAAA,EAAA;AAwB/B;"}
@@ -7,10 +7,11 @@ import FormControlLabel from "@mui/material/FormControlLabel";
7
7
  import Stack from "@mui/material/Stack";
8
8
  import TextField from "@mui/material/TextField";
9
9
  import Typography from "@mui/material/Typography";
10
- import { formatNumber } from "../../formatting.js";
10
+ import { useFormatters } from "../../toolkit/useFormatters.js";
11
11
  const SEARCHABLE_FROM_OPTIONS = 8;
12
12
  function MultiSelectColumnFilter({ value = [], onChange, options = [], renderOption }) {
13
13
  const { t } = useToolkitTranslation("table");
14
+ const { formatNumber } = useFormatters();
14
15
  const [searchText, setSearchText] = useState("");
15
16
  const toggleOption = (optionValue) => onChange(value.includes(optionValue) ? value.filter((currentValue) => currentValue !== optionValue) : [...value, optionValue]);
16
17
  const isSearchable = options.length >= SEARCHABLE_FROM_OPTIONS;
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelectColumnFilter.js","sources":["../../../src/DataTable/ColumnFilters/MultiSelectColumnFilter.jsx"],"sourcesContent":["// Filtro de opciones de una columna.\n//\n// Dos cosas lo separan de un multi-select cualquiera:\n// - las opciones NO salen de las filas visibles: salen de las FACETAS que el servidor calcula sobre el resultado\n// COMPLETO (ofrecer solo lo que aparece en las primeras 25 filas convierte el filtro en adorno);\n// - cada opción trae su conteo, que es lo que deja decidir si vale la pena filtrar.\n//\n// Desde ocho opciones aparece una caja para buscar entre ellas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {formatNumber} from '@/formatting.js';\n\nconst SEARCHABLE_FROM_OPTIONS = 8;\n\n/**\n * @param {object} props\n * @param {string[]} [props.value]\n * @param {(value: string[]) => void} props.onChange\n * @param {Array<{value: string, total: number}>} [props.options] faceta del servidor\n * @param {(value: string) => import('react').ReactNode} [props.renderOption] cómo se lee el valor (diccionario)\n */\nexport default function MultiSelectColumnFilter({value = [], onChange, options = [], renderOption}) {\n const {t} = useToolkitTranslation('table');\n const [searchText, setSearchText] = useState('');\n\n const toggleOption = (optionValue) => onChange(value.includes(optionValue)\n ? value.filter((currentValue) => currentValue !== optionValue)\n : [...value, optionValue]);\n\n const isSearchable = options.length >= SEARCHABLE_FROM_OPTIONS;\n const visibleOptions = isSearchable && searchText\n ? options.filter((option) => String(option.value).toLowerCase().includes(searchText.toLowerCase()))\n : options;\n\n return (\n <Stack spacing={1}>\n {isSearchable && (\n <TextField size=\"small\" autoFocus fullWidth placeholder={t('options.searchPlaceholder')} value={searchText}\n onChange={(event) => setSearchText(event.target.value)} />\n )}\n <Stack sx={{maxHeight: 300, overflowY: 'auto'}}>\n {options.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.empty')}</Typography>\n )}\n {options.length > 0 && visibleOptions.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.noMatch', {text: searchText})}</Typography>\n )}\n {visibleOptions.map((option) => (\n <FormControlLabel\n key={option.value}\n control={<Checkbox size=\"small\" checked={value.includes(option.value)} onChange={() => toggleOption(option.value)} />}\n label={(\n <Stack direction=\"row\" spacing={0.75} sx={{alignItems: 'center'}}>\n {renderOption ? renderOption(option.value) : <Typography variant=\"body2\">{option.value}</Typography>}\n <Typography variant=\"caption\" color=\"text.secondary\">{formatNumber(option.total)}</Typography>\n </Stack>\n )}\n />\n ))}\n </Stack>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAoBA,MAAM,0BAA0B;AAShC,SAAwB,wBAAwB,EAAC,QAAQ,CAAA,GAAI,UAAU,UAAU,CAAA,GAAI,gBAAe;AAChG,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,eAAe,CAAC,gBAAgB,SAAS,MAAM,SAAS,WAAW,IACnE,MAAM,OAAO,CAAC,iBAAiB,iBAAiB,WAAW,IAC3D,CAAC,GAAG,OAAO,WAAW,CAAC;AAE7B,QAAM,eAAe,QAAQ,UAAU;AACvC,QAAM,iBAAiB,gBAAgB,aACjC,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,KAAK,EAAE,cAAc,SAAS,WAAW,YAAA,CAAa,CAAC,IAChG;AAEN,SACI,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,IAAA,gBACG;AAAA,MAAC;AAAA,MAAA;AAAA,QAAU,MAAK;AAAA,QAAQ,WAAS;AAAA,QAAC,WAAS;AAAA,QAAC,aAAa,EAAE,2BAA2B;AAAA,QAAG,OAAO;AAAA,QACrF,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,SAAM,IAAI,EAAC,WAAW,KAAK,WAAW,UAClC,UAAA;AAAA,MAAA,QAAQ,WAAW,KAChB,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,MAE5E,QAAQ,SAAS,KAAK,eAAe,WAAW,yBAC5C,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,mBAAmB,EAAC,MAAM,WAAA,CAAW,GAAE;AAAA,MAElG,eAAe,IAAI,CAAC,WACjB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEG,SAAS,oBAAC,UAAA,EAAS,MAAK,SAAQ,SAAS,MAAM,SAAS,OAAO,KAAK,GAAG,UAAU,MAAM,aAAa,OAAO,KAAK,GAAG;AAAA,UACnH,OACI,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,MAAM,IAAI,EAAC,YAAY,SAAA,GAClD,UAAA;AAAA,YAAA,eAAe,aAAa,OAAO,KAAK,wBAAK,YAAA,EAAW,SAAQ,SAAS,UAAA,OAAO,MAAA,CAAM;AAAA,YACvF,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,aAAa,OAAO,KAAK,EAAA,CAAE;AAAA,UAAA,EAAA,CACrF;AAAA,QAAA;AAAA,QANC,OAAO;AAAA,MAAA,CASnB;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"MultiSelectColumnFilter.js","sources":["../../../src/DataTable/ColumnFilters/MultiSelectColumnFilter.jsx"],"sourcesContent":["// Filtro de opciones de una columna.\n//\n// Dos cosas lo separan de un multi-select cualquiera:\n// - las opciones NO salen de las filas visibles: salen de las FACETAS que el servidor calcula sobre el resultado\n// COMPLETO (ofrecer solo lo que aparece en las primeras 25 filas convierte el filtro en adorno);\n// - cada opción trae su conteo, que es lo que deja decidir si vale la pena filtrar.\n//\n// Desde ocho opciones aparece una caja para buscar entre ellas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport TextField from '@mui/material/TextField';\nimport Typography from '@mui/material/Typography';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\n\nconst SEARCHABLE_FROM_OPTIONS = 8;\n\n/**\n * @param {object} props\n * @param {string[]} [props.value]\n * @param {(value: string[]) => void} props.onChange\n * @param {Array<{value: string, total: number}>} [props.options] faceta del servidor\n * @param {(value: string) => import('react').ReactNode} [props.renderOption] cómo se lee el valor (diccionario)\n */\nexport default function MultiSelectColumnFilter({value = [], onChange, options = [], renderOption}) {\n const {t} = useToolkitTranslation('table');\n const {formatNumber} = useFormatters();\n const [searchText, setSearchText] = useState('');\n\n const toggleOption = (optionValue) => onChange(value.includes(optionValue)\n ? value.filter((currentValue) => currentValue !== optionValue)\n : [...value, optionValue]);\n\n const isSearchable = options.length >= SEARCHABLE_FROM_OPTIONS;\n const visibleOptions = isSearchable && searchText\n ? options.filter((option) => String(option.value).toLowerCase().includes(searchText.toLowerCase()))\n : options;\n\n return (\n <Stack spacing={1}>\n {isSearchable && (\n <TextField size=\"small\" autoFocus fullWidth placeholder={t('options.searchPlaceholder')} value={searchText}\n onChange={(event) => setSearchText(event.target.value)} />\n )}\n <Stack sx={{maxHeight: 300, overflowY: 'auto'}}>\n {options.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.empty')}</Typography>\n )}\n {options.length > 0 && visibleOptions.length === 0 && (\n <Typography variant=\"caption\" color=\"text.secondary\">{t('options.noMatch', {text: searchText})}</Typography>\n )}\n {visibleOptions.map((option) => (\n <FormControlLabel\n key={option.value}\n control={<Checkbox size=\"small\" checked={value.includes(option.value)} onChange={() => toggleOption(option.value)} />}\n label={(\n <Stack direction=\"row\" spacing={0.75} sx={{alignItems: 'center'}}>\n {renderOption ? renderOption(option.value) : <Typography variant=\"body2\">{option.value}</Typography>}\n <Typography variant=\"caption\" color=\"text.secondary\">{formatNumber(option.total)}</Typography>\n </Stack>\n )}\n />\n ))}\n </Stack>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAoBA,MAAM,0BAA0B;AAShC,SAAwB,wBAAwB,EAAC,QAAQ,CAAA,GAAI,UAAU,UAAU,CAAA,GAAI,gBAAe;AAChG,QAAM,EAAC,EAAA,IAAK,sBAAsB,OAAO;AACzC,QAAM,EAAC,aAAA,IAAgB,cAAA;AACvB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,eAAe,CAAC,gBAAgB,SAAS,MAAM,SAAS,WAAW,IACnE,MAAM,OAAO,CAAC,iBAAiB,iBAAiB,WAAW,IAC3D,CAAC,GAAG,OAAO,WAAW,CAAC;AAE7B,QAAM,eAAe,QAAQ,UAAU;AACvC,QAAM,iBAAiB,gBAAgB,aACjC,QAAQ,OAAO,CAAC,WAAW,OAAO,OAAO,KAAK,EAAE,cAAc,SAAS,WAAW,YAAA,CAAa,CAAC,IAChG;AAEN,SACI,qBAAC,OAAA,EAAM,SAAS,GACX,UAAA;AAAA,IAAA,gBACG;AAAA,MAAC;AAAA,MAAA;AAAA,QAAU,MAAK;AAAA,QAAQ,WAAS;AAAA,QAAC,WAAS;AAAA,QAAC,aAAa,EAAE,2BAA2B;AAAA,QAAG,OAAO;AAAA,QACrF,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,IAEpE,qBAAC,SAAM,IAAI,EAAC,WAAW,KAAK,WAAW,UAClC,UAAA;AAAA,MAAA,QAAQ,WAAW,KAChB,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,MAE5E,QAAQ,SAAS,KAAK,eAAe,WAAW,yBAC5C,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,EAAE,mBAAmB,EAAC,MAAM,WAAA,CAAW,GAAE;AAAA,MAElG,eAAe,IAAI,CAAC,WACjB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEG,SAAS,oBAAC,UAAA,EAAS,MAAK,SAAQ,SAAS,MAAM,SAAS,OAAO,KAAK,GAAG,UAAU,MAAM,aAAa,OAAO,KAAK,GAAG;AAAA,UACnH,OACI,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,MAAM,IAAI,EAAC,YAAY,SAAA,GAClD,UAAA;AAAA,YAAA,eAAe,aAAa,OAAO,KAAK,wBAAK,YAAA,EAAW,SAAQ,SAAS,UAAA,OAAO,MAAA,CAAM;AAAA,YACvF,oBAAC,cAAW,SAAQ,WAAU,OAAM,kBAAkB,UAAA,aAAa,OAAO,KAAK,EAAA,CAAE;AAAA,UAAA,EAAA,CACrF;AAAA,QAAA;AAAA,QANC,OAAO;AAAA,MAAA,CASnB;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}