@stackcraftmx/calcita 0.23.0 → 0.25.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.
- package/README.md +111 -22
- package/cli/projectFiles.js +5 -3
- package/cli/templates/project/README.md.tpl +28 -0
- package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
- package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
- package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
- package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
- package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
- package/cli/templates/project/src/core/index.js.tpl +1 -0
- package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
- package/cli/templates/project/vite.config.js.tpl +27 -1
- package/dist/DataTable/AuditTrailTable.js +5 -3
- package/dist/DataTable/AuditTrailTable.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/EmptyState.js +2 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +2 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +4 -3
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +153 -83
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/rowSelection.js +61 -0
- package/dist/DataTable/rowSelection.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountSecuritySection.js +2 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/ForgotPasswordScreen.js +5 -4
- package/dist/account/ForgotPasswordScreen.js.map +1 -1
- package/dist/account/ProfileScreen.js +39 -13
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/ResetPasswordScreen.js +3 -2
- package/dist/account/ResetPasswordScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +7 -6
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/account/accountConnector.js.map +1 -1
- package/dist/account/restAccountConnector.js +2 -1
- package/dist/account/restAccountConnector.js.map +1 -1
- package/dist/adapters/reactRouterRoutes.js +7 -6
- package/dist/adapters/reactRouterRoutes.js.map +1 -1
- package/dist/api/createApiClient.js +35 -5
- package/dist/api/createApiClient.js.map +1 -1
- package/dist/appShell/AppLayout.js +27 -7
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +29 -6
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +2 -0
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +3 -3
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +19 -8
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +19 -4
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +92 -0
- package/dist/appShell/BarSearch.js.map +1 -0
- package/dist/appShell/MobileTabBar.js +3 -1
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/shellSizes.js +2 -0
- package/dist/appShell/shellSizes.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +2 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +16 -5
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +74 -0
- package/dist/calendar/EventDetails.js.map +1 -0
- package/dist/calendar/calendarEvents.js +31 -0
- package/dist/calendar/calendarEvents.js.map +1 -0
- package/dist/calendar.js +8 -1
- package/dist/calendar.js.map +1 -1
- package/dist/charts.js +10 -1
- package/dist/charts.js.map +1 -1
- package/dist/datePickers/DateInput.js +3 -2
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/downloads/DownloadManager.js +14 -20
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/downloads/downloadQueue.js +4 -2
- package/dist/downloads/downloadQueue.js.map +1 -1
- package/dist/downloads/downloadsMachine.js +4 -1
- package/dist/downloads/downloadsMachine.js.map +1 -1
- package/dist/downloads/fileTypes.js +22 -0
- package/dist/downloads/fileTypes.js.map +1 -0
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +2 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +2 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +5 -3
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/formatting.js +171 -143
- package/dist/formatting.js.map +1 -1
- package/dist/forms/FormWizard.js +18 -45
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/auth/ChangePasswordForm.js +5 -4
- package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
- package/dist/forms/auth/ForgotPasswordForm.js +2 -2
- package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +2 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/security/SecondStepForm.js +2 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +8 -6
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/hookForm/FormWizard.js +59 -0
- package/dist/hookForm/FormWizard.js.map +1 -0
- package/dist/inbox/InboxLayout.js +44 -8
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +71 -21
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +80 -29
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/inbox/inboxDates.js +4 -3
- package/dist/inbox/inboxDates.js.map +1 -1
- package/dist/index.js +157 -133
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +8 -4
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +3 -2
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FormSection.js +3 -3
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/NumberInput.js +3 -2
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/SelectInput.js +29 -11
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +6 -2
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +54 -0
- package/dist/inputs/WizardLayout.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +239 -14
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/kanban/kanbanCards.js +19 -0
- package/dist/kanban/kanbanCards.js.map +1 -0
- package/dist/kanban.js +7 -1
- package/dist/kanban.js.map +1 -1
- package/dist/locales/en.js +74 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +74 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +28 -8
- package/dist/maps/MapView.js.map +1 -1
- package/dist/observability/usePublishedBuildWatch.js +4 -3
- package/dist/observability/usePublishedBuildWatch.js.map +1 -1
- package/dist/pwa/InstallAppSetting.js +6 -5
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +2 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +15 -9
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/pwa/useInstallPrompt.js +2 -1
- package/dist/pwa/useInstallPrompt.js.map +1 -1
- package/dist/react-hook-form.js +6 -4
- package/dist/react-hook-form.js.map +1 -1
- package/dist/security/ActivityLog.js +4 -3
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +4 -3
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +8 -6
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +2 -0
- package/dist/table.js.map +1 -1
- package/dist/theme/components/MuiDialog.js +13 -0
- package/dist/theme/components/MuiDialog.js.map +1 -0
- package/dist/theme/createToolkitTheme.js.map +1 -1
- package/dist/theme/toolkitComponents.js +2 -0
- package/dist/theme/toolkitComponents.js.map +1 -1
- package/dist/toolkit/ComponentTexts.js +13 -0
- package/dist/toolkit/ComponentTexts.js.map +1 -0
- package/dist/toolkit/ToolkitProvider.js +36 -28
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -20
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/componentTextsContext.js +15 -0
- package/dist/toolkit/componentTextsContext.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +40 -0
- package/dist/toolkit/hashNavigation.js.map +1 -0
- package/dist/toolkit/navigationPaths.js +49 -0
- package/dist/toolkit/navigationPaths.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +24 -4
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/toolkit/useFormatters.js +12 -0
- package/dist/toolkit/useFormatters.js.map +1 -0
- package/dist/toolkit/useToolkitTranslation.js +11 -7
- package/dist/toolkit/useToolkitTranslation.js.map +1 -1
- package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
- package/dist/types/DataTable/EmptyState.d.ts +7 -2
- package/dist/types/DataTable/FilterChips.d.ts +3 -1
- package/dist/types/DataTable/GoToPage.d.ts +7 -2
- package/dist/types/DataTable/SelectionBar.d.ts +32 -0
- package/dist/types/DataTable/ServerTable.d.ts +64 -3
- package/dist/types/DataTable/index.d.ts +1 -1
- package/dist/types/DataTable/rowSelection.d.ts +113 -0
- package/dist/types/access/PermissionMatrix.d.ts +3 -1
- package/dist/types/account/AccountSecuritySection.d.ts +3 -1
- package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
- package/dist/types/account/ProfileScreen.d.ts +31 -1
- package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
- package/dist/types/account/SignInScreen.d.ts +3 -1
- package/dist/types/account/accountConnector.d.ts +7 -2
- package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
- package/dist/types/api/createApiClient.d.ts +29 -1
- package/dist/types/appShell/AppLayout.d.ts +11 -1
- package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
- package/dist/types/appShell/BarActionButton.d.ts +2 -2
- package/dist/types/appShell/BarActions.d.ts +6 -1
- package/dist/types/appShell/BarCapsule.d.ts +8 -0
- package/dist/types/appShell/BarSearch.d.ts +24 -0
- package/dist/types/appShell/MobileTabBar.d.ts +4 -1
- package/dist/types/appShell/shellSizes.d.ts +1 -0
- package/dist/types/auth/PasswordChecklist.d.ts +3 -1
- package/dist/types/calendar/EventCalendar.d.ts +18 -1
- package/dist/types/calendar/EventDetails.d.ts +36 -0
- package/dist/types/calendar/calendarEvents.d.ts +27 -0
- package/dist/types/calendar.d.ts +2 -0
- package/dist/types/charts.d.ts +4 -0
- package/dist/types/datePickers/DateInput.d.ts +3 -1
- package/dist/types/downloads/DownloadManager.d.ts +7 -1
- package/dist/types/downloads/downloadQueue.d.ts +8 -3
- package/dist/types/downloads/fileTypes.d.ts +39 -0
- package/dist/types/downloads/index.d.ts +1 -0
- package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
- package/dist/types/feedback/ModalWaiting.d.ts +7 -2
- package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
- package/dist/types/filepond/FileDropzone.d.ts +3 -1
- package/dist/types/formatting.d.ts +45 -90
- package/dist/types/forms/FormWizard.d.ts +3 -1
- package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/SignInForm.d.ts +3 -1
- package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
- package/dist/types/hookForm/FormWizard.d.ts +22 -0
- package/dist/types/inbox/InboxLayout.d.ts +14 -2
- package/dist/types/inbox/MessageList.d.ts +30 -5
- package/dist/types/inbox/MessageView.d.ts +35 -3
- package/dist/types/inbox/inboxDates.d.ts +6 -1
- package/dist/types/index.d.ts +12 -5
- package/dist/types/inputs/CatalogInput.d.ts +9 -5
- package/dist/types/inputs/ColorInput.d.ts +3 -1
- package/dist/types/inputs/FormSection.d.ts +2 -2
- package/dist/types/inputs/NumberInput.d.ts +3 -1
- package/dist/types/inputs/SelectInput.d.ts +9 -5
- package/dist/types/inputs/TextInput.d.ts +3 -1
- package/dist/types/inputs/WizardLayout.d.ts +34 -0
- package/dist/types/kanban/KanbanBoard.d.ts +61 -6
- package/dist/types/kanban/kanbanCards.d.ts +22 -0
- package/dist/types/kanban.d.ts +1 -0
- package/dist/types/locales/en.d.ts +77 -0
- package/dist/types/locales/es.d.ts +77 -0
- package/dist/types/maps/MapView.d.ts +12 -3
- package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
- package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
- package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
- package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/security/ActivityLog.d.ts +3 -1
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
- package/dist/types/security/SessionsCard.d.ts +3 -1
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/components/MuiDialog.d.ts +9 -0
- package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
- package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
- package/dist/types/toolkit/browserNavigation.d.ts +23 -28
- package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
- package/dist/types/toolkit/hashNavigation.d.ts +12 -0
- package/dist/types/toolkit/navigationPaths.d.ts +43 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
- package/dist/types/toolkit/useFormatters.d.ts +6 -0
- package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
- package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
- package/dist/types/ui/actions/CopyButton.d.ts +3 -1
- package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
- package/dist/types/ui/actions/SecretField.d.ts +5 -1
- package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
- package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
- package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
- package/dist/types/ui/index.d.ts +1 -1
- package/dist/types/ui/labels/tagOf.d.ts +13 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
- package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
- package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
- package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
- package/dist/types/ui/layout/PageHeading.d.ts +3 -1
- package/dist/types/ui/metrics/BarList.d.ts +7 -1
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
- package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
- package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
- package/dist/types/ui/metrics/LineChart.d.ts +41 -0
- package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
- package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
- package/dist/types/ui/metrics/chartParts.d.ts +112 -0
- package/dist/types/ui/navigation/BackButton.d.ts +3 -1
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
- package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
- package/dist/types/upload/FileDropzone.d.ts +3 -1
- package/dist/ui/actions/ActionsMenu.js +51 -0
- package/dist/ui/actions/ActionsMenu.js.map +1 -0
- package/dist/ui/actions/CopyButton.js +2 -2
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +11 -12
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -4
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +142 -0
- package/dist/ui/actions/SelectionActions.js.map +1 -0
- package/dist/ui/filters/DateRangeFilter.js +4 -3
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +2 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/labels/tagOf.js +7 -0
- package/dist/ui/labels/tagOf.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +6 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +2 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +21 -7
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +2 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +2 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/metrics/BarList.js +18 -13
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +11 -6
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +1 -1
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +59 -63
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +117 -0
- package/dist/ui/metrics/LineChart.js.map +1 -0
- package/dist/ui/metrics/MetricCard.js +41 -8
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +4 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +93 -0
- package/dist/ui/metrics/chartParts.js.map +1 -0
- package/dist/ui/navigation/BackButton.js +2 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +3 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +40 -19
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +10 -8
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -3
- package/dist/upload/FileDropzone.js.map +1 -1
- package/package.json +14 -6
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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».
|
|
202
|
-
|
|
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)
|
|
@@ -296,10 +313,82 @@ const menuItems = composeMenu(import.meta.glob('@/modules/*/menu.js', {eager: tr
|
|
|
296
313
|
En cada pantalla, `PageHeading` pone el título, la descripción y los botones, y arma las migas solo desde el menú
|
|
297
314
|
(Inicio › Sección › Pantalla). `PageHeader` es el encabezado centrado de una portada y `DetailHeader` el de un registro.
|
|
298
315
|
|
|
299
|
-
La misma regla protege las rutas (`withAccess(access, routes)` de `/react-router`: quien no puede ve «sin permiso
|
|
316
|
+
La misma regla protege las rutas (`withAccess(access, routes)` de `/react-router`: quien no puede ve «sin permiso», o la
|
|
317
|
+
página propia que se pase en `{fallback}`) y
|
|
300
318
|
los botones (`<Can access={…}>`, `useCan`). El framework no sabe de roles: RBAC por permisos o roles y ABAC por políticas
|
|
301
319
|
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
|
|
320
|
+
vuelve a revisar cada petición. `ErrorPage` trae las pantallas de 404, 403 y 500, y se extiende sin tocarla: botones
|
|
321
|
+
propios junto a los de la casa (`extraActions`) o en su lugar (`actions`), algo debajo del texto (`children`), otro ícono y
|
|
322
|
+
`onGoHome`.
|
|
323
|
+
|
|
324
|
+
El marco avisa cuando la persona encoge el menú (`onMenuCollapse`), abre el menú del teléfono (`onMobileMenuChange`) o elige
|
|
325
|
+
sus pestañas (`onMobileTabsChange`), para guardar esas preferencias en el servidor del proyecto si se quiere; sus textos se
|
|
326
|
+
cambian con `texts`.
|
|
327
|
+
|
|
328
|
+
## Eventos y textos de cada componente
|
|
329
|
+
|
|
330
|
+
Cada componente de la casa avisa hacia afuera todo lo que pasa adentro (`onSelect`, `onChange`, `onViewChange`,
|
|
331
|
+
`onMenuCollapse`, `onCardComplete`, `onDatesChange`…) y nunca llama un servidor propio: el proyecto decide qué hacer. Así se
|
|
332
|
+
mide, se guarda una preferencia en el servidor del proyecto o se conecta un sistema viejo sin tocar Calcita.
|
|
333
|
+
|
|
334
|
+
Cada texto también se cambia desde afuera: el componente recibe `texts` con la forma de su namespace en los textos del
|
|
335
|
+
paquete (`src/locales/es.js`), y `ComponentTexts` los cambia para todo lo que va adentro (un módulo con su propio
|
|
336
|
+
vocabulario):
|
|
337
|
+
|
|
338
|
+
```jsx
|
|
339
|
+
<ServerTable texts={{count: {records_one: '{{total}} pedido', records_other: '{{total}} pedidos'}}} … />
|
|
340
|
+
|
|
341
|
+
<ComponentTexts namespace="table" texts={{emptyState: {title: 'Sin pedidos'}}}>
|
|
342
|
+
<OrdersModule />
|
|
343
|
+
</ComponentTexts>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## En un sistema que ya existe
|
|
347
|
+
|
|
348
|
+
Calcita entra por pedazos en un sistema hecho en PHP, JSP o .NET, sin reescribirlo: cada pedazo es una «isla» de React en
|
|
349
|
+
un `<div>` de la página vieja, con su propio `ToolkitProvider`.
|
|
350
|
+
|
|
351
|
+
- **Sin router:** `createHashNavigation()` pone las pantallas después del `#` (`/app.php#/ventas`): cambiar de pantalla no
|
|
352
|
+
recarga, atrás y adelante funcionan y recargar no le pide nada nuevo al servidor viejo. Con la app bajo un prefijo y sin
|
|
353
|
+
router, `createBrowserNavigation({basePath: '/portal'})`.
|
|
354
|
+
- **Su tema de MUI tal cual:** Calcita lee los colores del tema, con o sin variables CSS y con el prefijo que traiga
|
|
355
|
+
(`cssVarPrefix`).
|
|
356
|
+
- **Varias islas, cada una con lo suyo:** dos islas pueden ir en idiomas y monedas distintas; `useFormatters()` escribe con
|
|
357
|
+
los de su isla.
|
|
358
|
+
- **Un backend que contesta a su manera:** `createApiClient({toProblem, mapResponse})` convierte sus errores a RFC 9457
|
|
359
|
+
(Laravel manda `{message, errors}`) y desenvuelve sus respuestas (`{data: …}`); `onError` recibe cada error en un lugar.
|
|
360
|
+
|
|
361
|
+
```jsx
|
|
362
|
+
const hashNavigation = createHashNavigation(); // una vez, fuera de los componentes
|
|
363
|
+
|
|
364
|
+
document.querySelectorAll('[data-calcita="sales"]').forEach((element) => {
|
|
365
|
+
createRoot(element).render(
|
|
366
|
+
<ThemeProvider theme={legacyTheme}>
|
|
367
|
+
<ToolkitProvider language={element.lang || 'es'} navigation={hashNavigation}>
|
|
368
|
+
<SalesOfTheDay onOpen={() => element.dispatchEvent(new CustomEvent('calcita:open-sales', {bubbles: true}))} />
|
|
369
|
+
</ToolkitProvider>
|
|
370
|
+
</ThemeProvider>,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Detrás de un proxy con prefijo
|
|
376
|
+
|
|
377
|
+
Para publicar la app en una ruta y no en un subdominio (`miempresa.com/ventas/`), el prefijo se da una vez al construir,
|
|
378
|
+
`ASSET_URL=/ventas`, la misma línea del `.env` del servidor. El proyecto que arma `calcita new` ya lo trae:
|
|
379
|
+
|
|
380
|
+
1. `vite.config.js`: `base` con el prefijo; Vite se lo pone a sus archivos y a las rutas de `index.html`;
|
|
381
|
+
2. el router con el mismo prefijo (`createBrowserRouter(routes, {basename})`): las pantallas siguen escribiendo sus rutas
|
|
382
|
+
desde la raíz;
|
|
383
|
+
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
|
|
384
|
+
proxy) y el manifiesto con rutas relativas;
|
|
385
|
+
4. el servidor: cada dirección bajo el prefijo que no sea un archivo regresa su `index.html`.
|
|
386
|
+
|
|
387
|
+
```nginx
|
|
388
|
+
location /ventas/ {
|
|
389
|
+
try_files $uri $uri/ /ventas/index.html;
|
|
390
|
+
}
|
|
391
|
+
```
|
|
303
392
|
|
|
304
393
|
## La petición de la casa
|
|
305
394
|
|
|
@@ -488,7 +577,7 @@ menú, diccionarios) no lleva marca, así que también corre en los componentes
|
|
|
488
577
|
## Formularios
|
|
489
578
|
|
|
490
579
|
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
|
|
580
|
+
`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
581
|
los mismos nombres. Cambiar de librería es cambiar el import.
|
|
493
582
|
|
|
494
583
|
```jsx
|
|
@@ -507,9 +596,9 @@ import {Form, Formik, FormNumberField, FormProblemAlert, FormTextField, SubmitBu
|
|
|
507
596
|
</Formik>
|
|
508
597
|
```
|
|
509
598
|
|
|
510
|
-
formik y yup
|
|
511
|
-
|
|
512
|
-
|
|
599
|
+
formik y yup los instala el proyecto (`pnpm add formik yup`); `Formik`, `Form` y `Yup` también se pueden importar de
|
|
600
|
+
`@stackcraftmx/calcita/forms`, que es la misma copia. `FormWizard` parte un formulario en pasos, con pasos opcionales (hay
|
|
601
|
+
uno para formik y otro para react-hook-form); los ejemplos traen los dos.
|
|
513
602
|
|
|
514
603
|
Para que el formulario se vea como los de Apple (Ajustes, la hoja «Nuevo evento» de Calendario), los campos van dentro de
|
|
515
604
|
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
|
package/cli/projectFiles.js
CHANGED
|
@@ -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', '
|
|
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": "
|
|
14
|
-
{"src": "
|
|
15
|
-
{"src": "
|
|
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=
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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;"}
|
|
@@ -7,8 +7,8 @@ import Stack from "@mui/material/Stack";
|
|
|
7
7
|
import Typography from "@mui/material/Typography";
|
|
8
8
|
import FilterListOffIcon from "@mui/icons-material/FilterListOff";
|
|
9
9
|
import InboxOutlinedIcon from "@mui/icons-material/InboxOutlined";
|
|
10
|
-
function EmptyState({ filtered = false, label, onClearFilters }) {
|
|
11
|
-
const { t } = useToolkitTranslation("table");
|
|
10
|
+
function EmptyState({ filtered = false, label, onClearFilters, texts }) {
|
|
11
|
+
const { t } = useToolkitTranslation("table", texts);
|
|
12
12
|
const Icon = filtered ? FilterListOffIcon : InboxOutlinedIcon;
|
|
13
13
|
return /* @__PURE__ */ jsxs(Stack, { spacing: 1, sx: { height: "100%", px: 3, textAlign: "center", alignItems: "center", justifyContent: "center" }, children: [
|
|
14
14
|
/* @__PURE__ */ jsx(Box, { sx: { color: "text.disabled", display: "flex" }, children: /* @__PURE__ */ jsx(Icon, { fontSize: "large" }) }),
|