@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuickFilters.js","sources":["../../../src/ui/filters/QuickFilters.jsx"],"sourcesContent":["// Accesos rápidos: una tira de chips donde se elige UNO (p. ej. el estatus de un pedido).\n//\n// Es el complemento de FilterChips, no lo mismo:\n// - FilterChips dice qué filtros están PUESTOS y sirve para quitarlos;\n// - esto dice qué opciones HAY y sirve para saltar entre ellas de un toque.\n//\n// El conteo de cada chip sale de las facetas del servidor, contadas sobre el conjunto COMPLETO y sin este filtro\n// puesto: al elegir una, las demás siguen diciendo cuántas tienen.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport {alpha} from '@mui/material/styles';\nimport Chip from '@mui/material/Chip';\nimport Typography from '@mui/material/Typography';\n\nimport ScrollableRow from '@/ui/layout/ScrollableRow.jsx';\n\n\n// qué tan marcado va el velo del contador del filtro elegido\nconst ACTIVE_COUNT_VEIL_OPACITY = 0.18;\n\n/**\n * El velo del contador del filtro elegido: el color del texto base (negro en claro, blanco en oscuro) casi transparente.\n * Un tema con variables CSS trae ese color en theme.vars; el tema por omisión de MUI, en su modo.\n *\n * @param {object} theme tema de MUI\n * @returns {string} color CSS\n */\nfunction activeCountVeilOf(theme) {\n if (theme.vars) {\n return `rgba(${theme.vars.palette.common.onBackgroundChannel} / ${ACTIVE_COUNT_VEIL_OPACITY})`;\n }\n const baseTextColor = theme.palette.mode === 'dark' ? theme.palette.common.white : theme.palette.common.black;\n\n return alpha(baseTextColor, ACTIVE_COUNT_VEIL_OPACITY);\n}\n\n/** \"99+\" arriba de 99 y \"1.2k\" arriba de 999: el chip no se estira. */\nfunction compactCount(count) {\n if (count > 999) {\n return `${(count / 1000).toFixed(1)}k`;\n }\n if (count > 99) {\n return '99+';\n }\n return String(count);\n}\n\n/**\n * @param {object} props\n * @param {string} props.value opción activa ('' = todas)\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: import('react').ReactNode, count?: number}>} [props.options]\n * @param {import('react').ReactNode} [props.caption]\n * @param {string} [props.ariaLabel]\n * @param {import('react').ReactNode} [props.extra] al final de la tira (p. ej. un rango de fechas)\n */\nexport default function QuickFilters({value, onChange, options = [], caption, ariaLabel, extra}) {\n const {t} = useToolkitTranslation('table');\n const activeChip = useRef(null);\n const isFirstRender = useRef(true);\n\n // al cambiar la selección desde fuera (alguien limpió los filtros) el chip activo se trae a la vista; en el primer\n // render sin animación: una tira que se mueve sola al abrir la pantalla se siente rota\n useEffect(() => {\n if (!activeChip.current) {\n return;\n }\n const behavior = isFirstRender.current ? 'instant' : 'smooth';\n isFirstRender.current = false;\n activeChip.current.scrollIntoView({behavior, block: 'nearest', inline: 'center'});\n }, [value]);\n\n // guard clause: con una sola opción no hay entre qué saltar\n if (options.length <= 1) {\n return null;\n }\n\n return (\n <Box sx={{mb: 1}}>\n {caption && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{px: 0.25}}>{caption}</Typography>\n )}\n <ScrollableRow role=\"tablist\" aria-label={ariaLabel}>\n {options.map((option) => {\n const isActive = option.value === value;\n return (\n <Chip\n key={option.value || '__all__'}\n ref={isActive ? activeChip : null}\n role=\"tab\"\n aria-selected={isActive}\n clickable\n onClick={() => onChange(isActive ? '' : option.value)}\n // el elegido trae su ×: \"volver a Todas\" no se descubre solo\n onDelete={isActive && option.value ? () => onChange('') : undefined}\n variant={isActive ? 'filled' : 'outlined'}\n color={isActive ? 'primary' : 'default'}\n label={(\n <Box component=\"span\" sx={{display: 'inline-flex', alignItems: 'center', gap: 0.75}}>\n <span>{option.label}</span>\n {option.count !== null && option.count !== undefined && (\n <Box component=\"span\"\n aria-label={t('quickFilters.countAriaLabel', {count: option.count})}\n // velo del color del texto base (negro en claro, blanco en oscuro) y no rgba fijo\n sx={(theme) => ({\n px: 0.75, borderRadius: '6px', fontSize: 11, fontWeight: 700, minWidth: 18,\n textAlign: 'center', color: 'inherit',\n bgcolor: isActive ? activeCountVeilOf(theme) : 'action.selected',\n })}>\n {compactCount(option.count)}\n </Box>\n )}\n </Box>\n )}\n sx={{flex: '0 0 auto', height: 32}}\n />\n );\n })}\n {extra}\n </ScrollableRow>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAqBA,MAAM,4BAA4B;AASlC,SAAS,kBAAkB,OAAO;AAC9B,MAAI,MAAM,MAAM;AACZ,WAAO,QAAQ,MAAM,KAAK,QAAQ,OAAO,mBAAmB,MAAM,yBAAyB;AAAA,EAC/F;AACA,QAAM,gBAAgB,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAExG,SAAO,MAAM,eAAe,yBAAyB;AACzD;AAGA,SAAS,aAAa,OAAO;AACzB,MAAI,QAAQ,KAAK;AACb,WAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACvC;AACA,MAAI,QAAQ,IAAI;AACZ,WAAO;AAAA,EACX;AACA,SAAO,OAAO,KAAK;AACvB;
|
|
1
|
+
{"version":3,"file":"QuickFilters.js","sources":["../../../src/ui/filters/QuickFilters.jsx"],"sourcesContent":["// Accesos rápidos: una tira de chips donde se elige UNO (p. ej. el estatus de un pedido).\n//\n// Es el complemento de FilterChips, no lo mismo:\n// - FilterChips dice qué filtros están PUESTOS y sirve para quitarlos;\n// - esto dice qué opciones HAY y sirve para saltar entre ellas de un toque.\n//\n// El conteo de cada chip sale de las facetas del servidor, contadas sobre el conjunto COMPLETO y sin este filtro\n// puesto: al elegir una, las demás siguen diciendo cuántas tienen.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport {alpha} from '@mui/material/styles';\nimport Chip from '@mui/material/Chip';\nimport Typography from '@mui/material/Typography';\n\nimport ScrollableRow from '@/ui/layout/ScrollableRow.jsx';\n\n\n// qué tan marcado va el velo del contador del filtro elegido\nconst ACTIVE_COUNT_VEIL_OPACITY = 0.18;\n\n/**\n * El velo del contador del filtro elegido: el color del texto base (negro en claro, blanco en oscuro) casi transparente.\n * Un tema con variables CSS trae ese color en theme.vars; el tema por omisión de MUI, en su modo.\n *\n * @param {object} theme tema de MUI\n * @returns {string} color CSS\n */\nfunction activeCountVeilOf(theme) {\n if (theme.vars) {\n return `rgba(${theme.vars.palette.common.onBackgroundChannel} / ${ACTIVE_COUNT_VEIL_OPACITY})`;\n }\n const baseTextColor = theme.palette.mode === 'dark' ? theme.palette.common.white : theme.palette.common.black;\n\n return alpha(baseTextColor, ACTIVE_COUNT_VEIL_OPACITY);\n}\n\n/** \"99+\" arriba de 99 y \"1.2k\" arriba de 999: el chip no se estira. */\nfunction compactCount(count) {\n if (count > 999) {\n return `${(count / 1000).toFixed(1)}k`;\n }\n if (count > 99) {\n return '99+';\n }\n return String(count);\n}\n\n/**\n * @param {object} props\n * @param {string} props.value opción activa ('' = todas)\n * @param {(value: string) => void} props.onChange\n * @param {Array<{value: string, label: import('react').ReactNode, count?: number}>} [props.options]\n * @param {import('react').ReactNode} [props.caption]\n * @param {string} [props.ariaLabel]\n * @param {import('react').ReactNode} [props.extra] al final de la tira (p. ej. un rango de fechas)\n * @param {object} [props.texts] los textos que cambian, con la forma de `table` en los textos del paquete\n */\nexport default function QuickFilters({value, onChange, options = [], caption, ariaLabel, extra, texts}) {\n const {t} = useToolkitTranslation('table', texts);\n const activeChip = useRef(null);\n const isFirstRender = useRef(true);\n\n // al cambiar la selección desde fuera (alguien limpió los filtros) el chip activo se trae a la vista; en el primer\n // render sin animación: una tira que se mueve sola al abrir la pantalla se siente rota\n useEffect(() => {\n if (!activeChip.current) {\n return;\n }\n const behavior = isFirstRender.current ? 'instant' : 'smooth';\n isFirstRender.current = false;\n activeChip.current.scrollIntoView({behavior, block: 'nearest', inline: 'center'});\n }, [value]);\n\n // guard clause: con una sola opción no hay entre qué saltar\n if (options.length <= 1) {\n return null;\n }\n\n return (\n <Box sx={{mb: 1}}>\n {caption && (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{px: 0.25}}>{caption}</Typography>\n )}\n <ScrollableRow role=\"tablist\" aria-label={ariaLabel}>\n {options.map((option) => {\n const isActive = option.value === value;\n return (\n <Chip\n key={option.value || '__all__'}\n ref={isActive ? activeChip : null}\n role=\"tab\"\n aria-selected={isActive}\n clickable\n onClick={() => onChange(isActive ? '' : option.value)}\n // el elegido trae su ×: \"volver a Todas\" no se descubre solo\n onDelete={isActive && option.value ? () => onChange('') : undefined}\n variant={isActive ? 'filled' : 'outlined'}\n color={isActive ? 'primary' : 'default'}\n label={(\n <Box component=\"span\" sx={{display: 'inline-flex', alignItems: 'center', gap: 0.75}}>\n <span>{option.label}</span>\n {option.count !== null && option.count !== undefined && (\n <Box component=\"span\"\n aria-label={t('quickFilters.countAriaLabel', {count: option.count})}\n // velo del color del texto base (negro en claro, blanco en oscuro) y no rgba fijo\n sx={(theme) => ({\n px: 0.75, borderRadius: '6px', fontSize: 11, fontWeight: 700, minWidth: 18,\n textAlign: 'center', color: 'inherit',\n bgcolor: isActive ? activeCountVeilOf(theme) : 'action.selected',\n })}>\n {compactCount(option.count)}\n </Box>\n )}\n </Box>\n )}\n sx={{flex: '0 0 auto', height: 32}}\n />\n );\n })}\n {extra}\n </ScrollableRow>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAqBA,MAAM,4BAA4B;AASlC,SAAS,kBAAkB,OAAO;AAC9B,MAAI,MAAM,MAAM;AACZ,WAAO,QAAQ,MAAM,KAAK,QAAQ,OAAO,mBAAmB,MAAM,yBAAyB;AAAA,EAC/F;AACA,QAAM,gBAAgB,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAExG,SAAO,MAAM,eAAe,yBAAyB;AACzD;AAGA,SAAS,aAAa,OAAO;AACzB,MAAI,QAAQ,KAAK;AACb,WAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACvC;AACA,MAAI,QAAQ,IAAI;AACZ,WAAO;AAAA,EACX;AACA,SAAO,OAAO,KAAK;AACvB;AAYA,SAAwB,aAAa,EAAC,OAAO,UAAU,UAAU,IAAI,SAAS,WAAW,OAAO,SAAQ;AACpG,QAAM,EAAC,EAAA,IAAK,sBAAsB,SAAS,KAAK;AAChD,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,gBAAgB,OAAO,IAAI;AAIjC,YAAU,MAAM;AACZ,QAAI,CAAC,WAAW,SAAS;AACrB;AAAA,IACJ;AACA,UAAM,WAAW,cAAc,UAAU,YAAY;AACrD,kBAAc,UAAU;AACxB,eAAW,QAAQ,eAAe,EAAC,UAAU,OAAO,WAAW,QAAQ,UAAS;AAAA,EACpF,GAAG,CAAC,KAAK,CAAC;AAGV,MAAI,QAAQ,UAAU,GAAG;AACrB,WAAO;AAAA,EACX;AAEA,8BACK,KAAA,EAAI,IAAI,EAAC,IAAI,KACT,UAAA;AAAA,IAAA,WACG,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,IAAI,EAAC,IAAI,KAAA,GAAQ,UAAA,QAAA,CAAQ;AAAA,IAElF,qBAAC,eAAA,EAAc,MAAK,WAAU,cAAY,WACrC,UAAA;AAAA,MAAA,QAAQ,IAAI,CAAC,WAAW;AACrB,cAAM,WAAW,OAAO,UAAU;AAClC,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEG,KAAK,WAAW,aAAa;AAAA,YAC7B,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAS;AAAA,YACT,SAAS,MAAM,SAAS,WAAW,KAAK,OAAO,KAAK;AAAA,YAEpD,UAAU,YAAY,OAAO,QAAQ,MAAM,SAAS,EAAE,IAAI;AAAA,YAC1D,SAAS,WAAW,WAAW;AAAA,YAC/B,OAAO,WAAW,YAAY;AAAA,YAC9B,OACI,qBAAC,KAAA,EAAI,WAAU,QAAO,IAAI,EAAC,SAAS,eAAe,YAAY,UAAU,KAAK,QAC1E,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAM,iBAAO,MAAA,CAAM;AAAA,cACnB,OAAO,UAAU,QAAQ,OAAO,UAAU,UACvC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAAI,WAAU;AAAA,kBACV,cAAY,EAAE,+BAA+B,EAAC,OAAO,OAAO,OAAM;AAAA,kBAElE,IAAI,CAAC,WAAW;AAAA,oBACZ,IAAI;AAAA,oBAAM,cAAc;AAAA,oBAAO,UAAU;AAAA,oBAAI,YAAY;AAAA,oBAAK,UAAU;AAAA,oBACxE,WAAW;AAAA,oBAAU,OAAO;AAAA,oBAC5B,SAAS,WAAW,kBAAkB,KAAK,IAAI;AAAA,kBAAA;AAAA,kBAEnD,UAAA,aAAa,OAAO,KAAK;AAAA,gBAAA;AAAA,cAAA;AAAA,YAC9B,GAER;AAAA,YAEJ,IAAI,EAAC,MAAM,YAAY,QAAQ,GAAA;AAAA,UAAE;AAAA,UA3B5B,OAAO,SAAS;AAAA,QAAA;AAAA,MA8BjC,CAAC;AAAA,MACA;AAAA,IAAA,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tagOf.js","sources":["../../../src/ui/labels/tagOf.js"],"sourcesContent":["// Una etiqueta de color como la reciben las piezas de la casa (la tarjeta del tablero, el mensaje de la bandeja): texto solo\n// («Clientes») o con su color del tema ({label: 'Alta', color: 'error'}). Función pura: se prueba sin navegador.\n//\n// Funciones puras, no renderizan: function (regla de la casa).\n\n/**\n * Una etiqueta como la pintan la tarjeta y el mensaje: un texto suelto o {label, color} (un color del tema: error, warning, info…).\n *\n * @param {string|{label: string, color?: string}} tag\n * @returns {{label: string, color: string}}\n */\nexport function tagOf(tag) {\n return typeof tag === 'string' ? {label: tag, color: 'default'} : {label: tag.label, color: tag.color ?? 'default'};\n}\n"],"names":[],"mappings":"AAWO,SAAS,MAAM,KAAK;AACvB,SAAO,OAAO,QAAQ,WAAW,EAAC,OAAO,KAAK,OAAO,UAAS,IAAI,EAAC,OAAO,IAAI,OAAO,OAAO,IAAI,SAAS,UAAS;AACtH;"}
|
|
@@ -21,13 +21,17 @@ const HEADER_SX = {
|
|
|
21
21
|
"&:hover": { bgcolor: "action.hover" },
|
|
22
22
|
"& .MuiAccordionSummary-content": { my: 0.75, minWidth: 0 }
|
|
23
23
|
};
|
|
24
|
-
function CollapsibleSection({ collapsedTitle, expandedTitle, hint = "", defaultExpanded = false, children }) {
|
|
24
|
+
function CollapsibleSection({ collapsedTitle, expandedTitle, hint = "", defaultExpanded = false, onExpandedChange, children }) {
|
|
25
25
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
26
|
+
const changeExpanded = (nextIsExpanded) => {
|
|
27
|
+
setIsExpanded(nextIsExpanded);
|
|
28
|
+
onExpandedChange?.(nextIsExpanded);
|
|
29
|
+
};
|
|
26
30
|
return /* @__PURE__ */ jsx(Box, { sx: { minWidth: 0 }, children: /* @__PURE__ */ jsxs(
|
|
27
31
|
Accordion,
|
|
28
32
|
{
|
|
29
33
|
expanded: isExpanded,
|
|
30
|
-
onChange: (changeEvent, nextIsExpanded) =>
|
|
34
|
+
onChange: (changeEvent, nextIsExpanded) => changeExpanded(nextIsExpanded),
|
|
31
35
|
disableGutters: true,
|
|
32
36
|
elevation: 0,
|
|
33
37
|
square: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollapsibleSection.js","sources":["../../../src/ui/layout/CollapsibleSection.jsx"],"sourcesContent":["// Sección que se pliega: un encabezado que se toca y, debajo, el contenido, que se abre y se cierra.\n//\n// Para qué: en una página de detalle las cifras quedan a la vista y lo demás se guarda aquí; quien quiere más, lo abre.\n//\n// El encabezado se ve como lo que es, un botón a todo lo ancho: recuadro, la flecha ADELANTE del texto y otro fondo al pasar\n// el cursor. Su texto dice lo que va a pasar al tocarlo («Ver el detalle…» plegada, «Ocultar el detalle…» abierta) y,\n// plegada, un renglón más chico dice qué hay adentro: así nadie tiene que abrirla para saber si ahí está lo que busca.\n// No dice «dé clic»: en el teléfono no hay clic, y un tooltip tampoco se ve ahí.\n//\n// El contenido va sin fondo ni borde propios: adentro van tarjetas (SectionCard) y un recuadro alrededor se vería como\n// tarjeta dentro de tarjeta.\n//\n// OJO: el acordeón de MUI se quita el margen de arriba cuando está abierto y es el primero de su tipo entre sus hermanos.\n// Dentro de un Stack, que separa a sus hijos con margen, eso hacía brincar el encabezado 16 px al abrir y al cerrar. Por eso\n// va envuelto en una caja: el margen del Stack cae en la caja y el encabezado se queda quieto.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: sus textos llegan ya traducidos.\nimport {useState} from 'react';\nimport Accordion from '@mui/material/Accordion';\nimport AccordionDetails from '@mui/material/AccordionDetails';\nimport AccordionSummary from '@mui/material/AccordionSummary';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n\n// el mismo radio de las tarjetas (theme/components/surfaces.js): el encabezado se lee como una pieza más de la página\nconst CARD_BORDER_RADIUS_PIXELS = 10;\n\nconst HEADER_SX = {\n // la flecha adelante del texto: al final del renglón quedaba lejos de lo que se lee y pasaba inadvertida\n flexDirection: 'row-reverse', gap: 1, px: 1.5, minHeight: 44,\n border: 1, borderColor: 'divider', borderRadius: `${CARD_BORDER_RADIUS_PIXELS}px`, bgcolor: 'background.paper',\n '&:hover': {bgcolor: 'action.hover'},\n '& .MuiAccordionSummary-content': {my: 0.75, minWidth: 0},\n};\n\n/**\n * @param {object} props\n * @param {string} props.collapsedTitle lo que dice plegada, ya traducido («Ver el detalle de la validación»)\n * @param {string} props.expandedTitle lo que dice abierta («Ocultar el detalle de la validación»)\n * @param {string} [props.hint] plegada, qué hay adentro («Incluye resumen, motivos de rechazo y descargas.»)\n * @param {boolean} [props.defaultExpanded] si empieza abierta; por omisión empieza plegada\n * @param {import('react').ReactNode} props.children\n */\nexport default function CollapsibleSection({collapsedTitle, expandedTitle, hint = '', defaultExpanded = false, children}) {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n return (\n <Box sx={{minWidth: 0}}>\n <Accordion expanded={isExpanded} onChange={(changeEvent, nextIsExpanded) =>
|
|
1
|
+
{"version":3,"file":"CollapsibleSection.js","sources":["../../../src/ui/layout/CollapsibleSection.jsx"],"sourcesContent":["// Sección que se pliega: un encabezado que se toca y, debajo, el contenido, que se abre y se cierra.\n//\n// Para qué: en una página de detalle las cifras quedan a la vista y lo demás se guarda aquí; quien quiere más, lo abre.\n//\n// El encabezado se ve como lo que es, un botón a todo lo ancho: recuadro, la flecha ADELANTE del texto y otro fondo al pasar\n// el cursor. Su texto dice lo que va a pasar al tocarlo («Ver el detalle…» plegada, «Ocultar el detalle…» abierta) y,\n// plegada, un renglón más chico dice qué hay adentro: así nadie tiene que abrirla para saber si ahí está lo que busca.\n// No dice «dé clic»: en el teléfono no hay clic, y un tooltip tampoco se ve ahí.\n//\n// El contenido va sin fondo ni borde propios: adentro van tarjetas (SectionCard) y un recuadro alrededor se vería como\n// tarjeta dentro de tarjeta.\n//\n// OJO: el acordeón de MUI se quita el margen de arriba cuando está abierto y es el primero de su tipo entre sus hermanos.\n// Dentro de un Stack, que separa a sus hijos con margen, eso hacía brincar el encabezado 16 px al abrir y al cerrar. Por eso\n// va envuelto en una caja: el margen del Stack cae en la caja y el encabezado se queda quieto.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: sus textos llegan ya traducidos.\nimport {useState} from 'react';\nimport Accordion from '@mui/material/Accordion';\nimport AccordionDetails from '@mui/material/AccordionDetails';\nimport AccordionSummary from '@mui/material/AccordionSummary';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n\n// el mismo radio de las tarjetas (theme/components/surfaces.js): el encabezado se lee como una pieza más de la página\nconst CARD_BORDER_RADIUS_PIXELS = 10;\n\nconst HEADER_SX = {\n // la flecha adelante del texto: al final del renglón quedaba lejos de lo que se lee y pasaba inadvertida\n flexDirection: 'row-reverse', gap: 1, px: 1.5, minHeight: 44,\n border: 1, borderColor: 'divider', borderRadius: `${CARD_BORDER_RADIUS_PIXELS}px`, bgcolor: 'background.paper',\n '&:hover': {bgcolor: 'action.hover'},\n '& .MuiAccordionSummary-content': {my: 0.75, minWidth: 0},\n};\n\n/**\n * @param {object} props\n * @param {string} props.collapsedTitle lo que dice plegada, ya traducido («Ver el detalle de la validación»)\n * @param {string} props.expandedTitle lo que dice abierta («Ocultar el detalle de la validación»)\n * @param {string} [props.hint] plegada, qué hay adentro («Incluye resumen, motivos de rechazo y descargas.»)\n * @param {boolean} [props.defaultExpanded] si empieza abierta; por omisión empieza plegada\n * @param {(isExpanded: boolean) => void} [props.onExpandedChange] la persona la abrió o la plegó (por ejemplo, para pedir lo de\n * adentro hasta que se abre)\n * @param {import('react').ReactNode} props.children\n */\nexport default function CollapsibleSection({collapsedTitle, expandedTitle, hint = '', defaultExpanded = false, onExpandedChange, children}) {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n const changeExpanded = (nextIsExpanded) => {\n setIsExpanded(nextIsExpanded);\n onExpandedChange?.(nextIsExpanded);\n };\n\n return (\n <Box sx={{minWidth: 0}}>\n <Accordion expanded={isExpanded} onChange={(changeEvent, nextIsExpanded) => changeExpanded(nextIsExpanded)}\n disableGutters elevation={0} square sx={{bgcolor: 'transparent', '&::before': {display: 'none'}}}>\n {/* NOTA: el acordeón ya envuelve este renglón en un encabezado (h3); los textos van como span para no anidar dos */}\n <AccordionSummary expandIcon={<ExpandMoreIcon color=\"primary\" />} sx={HEADER_SX}>\n <Box sx={{minWidth: 0}}>\n <Typography variant=\"subtitle2\" component=\"span\" color=\"primary\" sx={{display: 'block'}}>\n {isExpanded ? expandedTitle : collapsedTitle}\n </Typography>\n {!isExpanded && hint && (\n <Typography variant=\"caption\" component=\"span\" color=\"text.secondary\" sx={{display: 'block'}}>{hint}</Typography>\n )}\n </Box>\n </AccordionSummary>\n <AccordionDetails sx={{p: 0, pt: 2}}>{children}</AccordionDetails>\n </Accordion>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AA0BA,MAAM,4BAA4B;AAElC,MAAM,YAAY;AAAA;AAAA,EAEd,eAAe;AAAA,EAAe,KAAK;AAAA,EAAG,IAAI;AAAA,EAAK,WAAW;AAAA,EAC1D,QAAQ;AAAA,EAAG,aAAa;AAAA,EAAW,cAAc,GAAG,yBAAyB;AAAA,EAAM,SAAS;AAAA,EAC5F,WAAW,EAAC,SAAS,eAAA;AAAA,EACrB,kCAAkC,EAAC,IAAI,MAAM,UAAU,EAAA;AAC3D;AAYA,SAAwB,mBAAmB,EAAC,gBAAgB,eAAe,OAAO,IAAI,kBAAkB,OAAO,kBAAkB,YAAW;AACxI,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,eAAe;AAC5D,QAAM,iBAAiB,CAAC,mBAAmB;AACvC,kBAAc,cAAc;AAC5B,uBAAmB,cAAc;AAAA,EACrC;AAEA,6BACK,KAAA,EAAI,IAAI,EAAC,UAAU,KAChB,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MAAU,UAAU;AAAA,MAAY,UAAU,CAAC,aAAa,mBAAmB,eAAe,cAAc;AAAA,MAC9F,gBAAc;AAAA,MAAC,WAAW;AAAA,MAAG,QAAM;AAAA,MAAC,IAAI,EAAC,SAAS,eAAe,aAAa,EAAC,SAAS,SAAM;AAAA,MAErG,UAAA;AAAA,QAAA,oBAAC,kBAAA,EAAiB,YAAY,oBAAC,gBAAA,EAAe,OAAM,UAAA,CAAU,GAAI,IAAI,WAClE,+BAAC,KAAA,EAAI,IAAI,EAAC,UAAU,KAChB,UAAA;AAAA,UAAA,oBAAC,YAAA,EAAW,SAAQ,aAAY,WAAU,QAAO,OAAM,WAAU,IAAI,EAAC,SAAS,QAAA,GAC1E,UAAA,aAAa,gBAAgB,gBAClC;AAAA,UACC,CAAC,cAAc,QACZ,oBAAC,YAAA,EAAW,SAAQ,WAAU,WAAU,QAAO,OAAM,kBAAiB,IAAI,EAAC,SAAS,QAAA,GAAW,UAAA,KAAA,CAAK;AAAA,QAAA,EAAA,CAE5G,EAAA,CACJ;AAAA,QACA,oBAAC,oBAAiB,IAAI,EAAC,GAAG,GAAG,IAAI,EAAA,GAAK,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,GAEvD;AAER;"}
|
|
@@ -49,8 +49,8 @@ function sheetSxOf(theme) {
|
|
|
49
49
|
function SlideUp(props) {
|
|
50
50
|
return /* @__PURE__ */ jsx(Slide, { direction: "up", ...props });
|
|
51
51
|
}
|
|
52
|
-
function ContentDialog({ open, onClose, title, children }) {
|
|
53
|
-
const { t } = useToolkitTranslation("common");
|
|
52
|
+
function ContentDialog({ open, onClose, title, children, texts }) {
|
|
53
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
54
54
|
const titleId = useId();
|
|
55
55
|
return /* @__PURE__ */ jsxs(
|
|
56
56
|
Dialog,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentDialog.js","sources":["../../../src/ui/layout/ContentDialog.jsx"],"sourcesContent":["// Diálogo de contenido: la «hoja de lectura» (la «page sheet» de Apple) para una vista grande que se lee, como un historial\n// con su tabla. Las confirmaciones y los formularios cortos siguen en su recuadro al centro (ConfirmDialog): esto es para\n// leer, no para contestar una pregunta.\n//\n// Cómo se ve, como en el iPad y en la Mac: una hoja SÓLIDA (para leer no va vidrio) con esquinas redondas, que sube desde\n// abajo; en escritorio centrada, de un ancho que se lee cómodo (SHEET_MAX_WIDTH_PIXELS) y debajo de la barra de arriba, que se\n// queda a la vista (atenuada) para no perder dónde se está; en el teléfono, separada de las orillas y casi de alto completo.\n// Antes ocupaba la pantalla de orilla a orilla: un rectángulo gris plano con renglones larguísimos (lo reportaron).\n//\n// Cómo se arma: un encabezado que no se mueve (el título y la X para cerrar) y un cuerpo que se desliza si no cabe.\n//\n// OJO: el alto de la barra superior no es fijo (con un aviso adentro mide más): se usa el que publica el layout\n// (APP_BAR_HEIGHT), no un número.\n//\n// Solo abre una vista: se cierra con la X, con Escape o con un clic afuera, sin confirmación.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: el título y el contenido llegan ya traducidos.\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Dialog from '@mui/material/Dialog';\nimport IconButton from '@mui/material/IconButton';\nimport Slide from '@mui/material/Slide';\nimport Typography from '@mui/material/Typography';\nimport CloseIcon from '@mui/icons-material/Close';\n\nimport {APP_BAR_HEIGHT} from '@/ui/layout/appBarHeight.js';\n\n// la hoja: lo ancho máximo en escritorio (un texto de 16 px se lee cómodo y cabe una tabla), su separación de las orillas y\n// la de su encabezado\nconst SHEET_MAX_WIDTH_PIXELS = 880;\nconst SHEET_GAP_PIXELS = 8;\nconst SHEET_PHONE_TOP_PIXELS = 24;\nconst SHEET_RADIUS_PIXELS = 20;\n\n/**\n * La hoja: fija a la ventana y centrada. Va como diálogo fullScreen (así el vidrio del tema la deja sólida y el fondo de\n * atrás no se desenfoca: la barra se sigue leyendo), con su tamaño y sus esquinas propios.\n * OJO: el alto del fullScreen de MUI es 100 %, que con top y bottom se saldría por abajo: va 'auto'.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction sheetSxOf(theme) {\n const palette = (theme.vars ?? theme).palette;\n return {\n position: 'fixed',\n m: 0,\n height: 'auto',\n maxHeight: 'none',\n borderRadius: `${SHEET_RADIUS_PIXELS}px`,\n // el que se desliza es el cuerpo, no la hoja: así el encabezado se queda quieto\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n backgroundColor: palette.background.paper,\n backgroundImage: 'none',\n boxShadow: `0 0 0 1px ${palette.divider}, 0 24px 64px rgba(0, 0, 0, 0.28)`,\n // en oscuro, un poco más clara que lo de atrás: lo que va al frente se aclara (HIG «Dark Mode»)\n ...theme.applyStyles('dark', {backgroundColor: `color-mix(in srgb, ${palette.background.paper}, white 6%)`}),\n // escritorio: centrada, debajo de la barra\n top: `calc(${APP_BAR_HEIGHT} + ${SHEET_GAP_PIXELS}px)`,\n bottom: SHEET_GAP_PIXELS,\n left: 0,\n right: 0,\n mx: 'auto',\n width: `min(${SHEET_MAX_WIDTH_PIXELS}px, calc(100% - ${SHEET_GAP_PIXELS * 4}px))`,\n maxWidth: 'none',\n // teléfono: separada de las orillas y casi de alto completo (la barra está abajo o es de MUI arriba: la hoja la tapa)\n [theme.breakpoints.down('md')]: {\n top: `calc(env(safe-area-inset-top) + ${SHEET_PHONE_TOP_PIXELS}px)`,\n bottom: `calc(env(safe-area-inset-bottom) + ${SHEET_GAP_PIXELS}px)`,\n width: `calc(100% - ${SHEET_GAP_PIXELS * 2}px)`,\n },\n };\n}\n\n/**\n * La animación de la hoja: entra desde abajo y sale por donde entró.\n *\n * @param {object} props lo que MUI le pasa a la animación de un diálogo (si está abierto, su contenido, sus tiempos)\n */\nfunction SlideUp(props) {\n return <Slide direction=\"up\" {...props} />;\n}\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {string} props.title texto ya traducido\n * @param {import('react').ReactNode} props.children\n */\nexport default function ContentDialog({open, onClose, title, children}) {\n const {t} = useToolkitTranslation('common');\n const titleId = useId();\n\n return (\n <Dialog open={open} onClose={onClose} aria-labelledby={titleId} fullScreen\n slots={{transition: SlideUp}} slotProps={{paper: {sx: sheetSxOf}}}>\n <Box sx={{\n display: 'flex', alignItems: 'center', gap: 2, flexShrink: 0, minHeight: 60, pl: {xs: 2.5, md: 4}, pr: {xs: 1.5, md: 2}, py: 1,\n borderBottom: 1, borderColor: 'divider',\n }}>\n <Typography id={titleId} variant=\"h6\" component=\"h2\" sx={{flex: 1, minWidth: 0}}>{title}</Typography>\n {/* la X de Apple: en un círculo gris tenue, no suelta */}\n <IconButton onClick={onClose} aria-label={t('actions.close')} size=\"small\"\n sx={{bgcolor: 'action.hover', '&:hover': {bgcolor: 'action.selected'}}}>\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n </Box>\n <Box sx={{flex: 1, minHeight: 0, overflow: 'auto', px: {xs: 2.5, md: 4}, py: {xs: 2, md: 3}}}>\n {children}\n </Box>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AA8BA,MAAM,yBAAyB;AAC/B,MAAM,mBAAmB;AACzB,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;AAU5B,SAAS,UAAU,OAAO;AACtB,QAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,SAAO;AAAA,IACH,UAAU;AAAA,IACV,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc,GAAG,mBAAmB;AAAA;AAAA,IAEpC,SAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAU;AAAA,IACV,iBAAiB,QAAQ,WAAW;AAAA,IACpC,iBAAiB;AAAA,IACjB,WAAW,aAAa,QAAQ,OAAO;AAAA;AAAA,IAEvC,GAAG,MAAM,YAAY,QAAQ,EAAC,iBAAiB,sBAAsB,QAAQ,WAAW,KAAK,cAAA,CAAc;AAAA;AAAA,IAE3G,KAAK,QAAQ,cAAc,MAAM,gBAAgB;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,OAAO,OAAO,sBAAsB,mBAAmB,mBAAmB,CAAC;AAAA,IAC3E,UAAU;AAAA;AAAA,IAEV,CAAC,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG;AAAA,MAC5B,KAAK,mCAAmC,sBAAsB;AAAA,MAC9D,QAAQ,sCAAsC,gBAAgB;AAAA,MAC9D,OAAO,eAAe,mBAAmB,CAAC;AAAA,IAAA;AAAA,EAC9C;AAER;AAOA,SAAS,QAAQ,OAAO;AACpB,SAAO,oBAAC,OAAA,EAAM,WAAU,MAAM,GAAG,OAAO;AAC5C;
|
|
1
|
+
{"version":3,"file":"ContentDialog.js","sources":["../../../src/ui/layout/ContentDialog.jsx"],"sourcesContent":["// Diálogo de contenido: la «hoja de lectura» (la «page sheet» de Apple) para una vista grande que se lee, como un historial\n// con su tabla. Las confirmaciones y los formularios cortos siguen en su recuadro al centro (ConfirmDialog): esto es para\n// leer, no para contestar una pregunta.\n//\n// Cómo se ve, como en el iPad y en la Mac: una hoja SÓLIDA (para leer no va vidrio) con esquinas redondas, que sube desde\n// abajo; en escritorio centrada, de un ancho que se lee cómodo (SHEET_MAX_WIDTH_PIXELS) y debajo de la barra de arriba, que se\n// queda a la vista (atenuada) para no perder dónde se está; en el teléfono, separada de las orillas y casi de alto completo.\n// Antes ocupaba la pantalla de orilla a orilla: un rectángulo gris plano con renglones larguísimos (lo reportaron).\n//\n// Cómo se arma: un encabezado que no se mueve (el título y la X para cerrar) y un cuerpo que se desliza si no cabe.\n//\n// OJO: el alto de la barra superior no es fijo (con un aviso adentro mide más): se usa el que publica el layout\n// (APP_BAR_HEIGHT), no un número.\n//\n// Solo abre una vista: se cierra con la X, con Escape o con un clic afuera, sin confirmación.\n//\n// Genérico del paquete: function (regla de la casa). No sabe de negocio: el título y el contenido llegan ya traducidos.\nimport {useId} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Dialog from '@mui/material/Dialog';\nimport IconButton from '@mui/material/IconButton';\nimport Slide from '@mui/material/Slide';\nimport Typography from '@mui/material/Typography';\nimport CloseIcon from '@mui/icons-material/Close';\n\nimport {APP_BAR_HEIGHT} from '@/ui/layout/appBarHeight.js';\n\n// la hoja: lo ancho máximo en escritorio (un texto de 16 px se lee cómodo y cabe una tabla), su separación de las orillas y\n// la de su encabezado\nconst SHEET_MAX_WIDTH_PIXELS = 880;\nconst SHEET_GAP_PIXELS = 8;\nconst SHEET_PHONE_TOP_PIXELS = 24;\nconst SHEET_RADIUS_PIXELS = 20;\n\n/**\n * La hoja: fija a la ventana y centrada. Va como diálogo fullScreen (así el vidrio del tema la deja sólida y el fondo de\n * atrás no se desenfoca: la barra se sigue leyendo), con su tamaño y sus esquinas propios.\n * OJO: el alto del fullScreen de MUI es 100 %, que con top y bottom se saldría por abajo: va 'auto'.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction sheetSxOf(theme) {\n const palette = (theme.vars ?? theme).palette;\n return {\n position: 'fixed',\n m: 0,\n height: 'auto',\n maxHeight: 'none',\n borderRadius: `${SHEET_RADIUS_PIXELS}px`,\n // el que se desliza es el cuerpo, no la hoja: así el encabezado se queda quieto\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n backgroundColor: palette.background.paper,\n backgroundImage: 'none',\n boxShadow: `0 0 0 1px ${palette.divider}, 0 24px 64px rgba(0, 0, 0, 0.28)`,\n // en oscuro, un poco más clara que lo de atrás: lo que va al frente se aclara (HIG «Dark Mode»)\n ...theme.applyStyles('dark', {backgroundColor: `color-mix(in srgb, ${palette.background.paper}, white 6%)`}),\n // escritorio: centrada, debajo de la barra\n top: `calc(${APP_BAR_HEIGHT} + ${SHEET_GAP_PIXELS}px)`,\n bottom: SHEET_GAP_PIXELS,\n left: 0,\n right: 0,\n mx: 'auto',\n width: `min(${SHEET_MAX_WIDTH_PIXELS}px, calc(100% - ${SHEET_GAP_PIXELS * 4}px))`,\n maxWidth: 'none',\n // teléfono: separada de las orillas y casi de alto completo (la barra está abajo o es de MUI arriba: la hoja la tapa)\n [theme.breakpoints.down('md')]: {\n top: `calc(env(safe-area-inset-top) + ${SHEET_PHONE_TOP_PIXELS}px)`,\n bottom: `calc(env(safe-area-inset-bottom) + ${SHEET_GAP_PIXELS}px)`,\n width: `calc(100% - ${SHEET_GAP_PIXELS * 2}px)`,\n },\n };\n}\n\n/**\n * La animación de la hoja: entra desde abajo y sale por donde entró.\n *\n * @param {object} props lo que MUI le pasa a la animación de un diálogo (si está abierto, su contenido, sus tiempos)\n */\nfunction SlideUp(props) {\n return <Slide direction=\"up\" {...props} />;\n}\n\n/**\n * @param {object} props\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {string} props.title texto ya traducido\n * @param {import('react').ReactNode} props.children\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ContentDialog({open, onClose, title, children, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n const titleId = useId();\n\n return (\n <Dialog open={open} onClose={onClose} aria-labelledby={titleId} fullScreen\n slots={{transition: SlideUp}} slotProps={{paper: {sx: sheetSxOf}}}>\n <Box sx={{\n display: 'flex', alignItems: 'center', gap: 2, flexShrink: 0, minHeight: 60, pl: {xs: 2.5, md: 4}, pr: {xs: 1.5, md: 2}, py: 1,\n borderBottom: 1, borderColor: 'divider',\n }}>\n <Typography id={titleId} variant=\"h6\" component=\"h2\" sx={{flex: 1, minWidth: 0}}>{title}</Typography>\n {/* la X de Apple: en un círculo gris tenue, no suelta */}\n <IconButton onClick={onClose} aria-label={t('actions.close')} size=\"small\"\n sx={{bgcolor: 'action.hover', '&:hover': {bgcolor: 'action.selected'}}}>\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n </Box>\n <Box sx={{flex: 1, minHeight: 0, overflow: 'auto', px: {xs: 2.5, md: 4}, py: {xs: 2, md: 3}}}>\n {children}\n </Box>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AA8BA,MAAM,yBAAyB;AAC/B,MAAM,mBAAmB;AACzB,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;AAU5B,SAAS,UAAU,OAAO;AACtB,QAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,SAAO;AAAA,IACH,UAAU;AAAA,IACV,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc,GAAG,mBAAmB;AAAA;AAAA,IAEpC,SAAS;AAAA,IACT,eAAe;AAAA,IACf,UAAU;AAAA,IACV,iBAAiB,QAAQ,WAAW;AAAA,IACpC,iBAAiB;AAAA,IACjB,WAAW,aAAa,QAAQ,OAAO;AAAA;AAAA,IAEvC,GAAG,MAAM,YAAY,QAAQ,EAAC,iBAAiB,sBAAsB,QAAQ,WAAW,KAAK,cAAA,CAAc;AAAA;AAAA,IAE3G,KAAK,QAAQ,cAAc,MAAM,gBAAgB;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,OAAO,OAAO,sBAAsB,mBAAmB,mBAAmB,CAAC;AAAA,IAC3E,UAAU;AAAA;AAAA,IAEV,CAAC,MAAM,YAAY,KAAK,IAAI,CAAC,GAAG;AAAA,MAC5B,KAAK,mCAAmC,sBAAsB;AAAA,MAC9D,QAAQ,sCAAsC,gBAAgB;AAAA,MAC9D,OAAO,eAAe,mBAAmB,CAAC;AAAA,IAAA;AAAA,EAC9C;AAER;AAOA,SAAS,QAAQ,OAAO;AACpB,SAAO,oBAAC,OAAA,EAAM,WAAU,MAAM,GAAG,OAAO;AAC5C;AAUA,SAAwB,cAAc,EAAC,MAAM,SAAS,OAAO,UAAU,SAAQ;AAC3E,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,UAAU,MAAA;AAEhB,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAO;AAAA,MAAY;AAAA,MAAkB,mBAAiB;AAAA,MAAS,YAAU;AAAA,MAClE,OAAO,EAAC,YAAY,QAAA;AAAA,MAAU,WAAW,EAAC,OAAO,EAAC,IAAI,YAAS;AAAA,MACnE,UAAA;AAAA,QAAA,qBAAC,OAAI,IAAI;AAAA,UACL,SAAS;AAAA,UAAQ,YAAY;AAAA,UAAU,KAAK;AAAA,UAAG,YAAY;AAAA,UAAG,WAAW;AAAA,UAAI,IAAI,EAAC,IAAI,KAAK,IAAI,EAAA;AAAA,UAAI,IAAI,EAAC,IAAI,KAAK,IAAI,EAAA;AAAA,UAAI,IAAI;AAAA,UAC7H,cAAc;AAAA,UAAG,aAAa;AAAA,QAAA,GAE9B,UAAA;AAAA,UAAA,oBAAC,YAAA,EAAW,IAAI,SAAS,SAAQ,MAAK,WAAU,MAAK,IAAI,EAAC,MAAM,GAAG,UAAU,KAAK,UAAA,OAAM;AAAA,UAExF;AAAA,YAAC;AAAA,YAAA;AAAA,cAAW,SAAS;AAAA,cAAS,cAAY,EAAE,eAAe;AAAA,cAAG,MAAK;AAAA,cACvD,IAAI,EAAC,SAAS,gBAAgB,WAAW,EAAC,SAAS,oBAAiB;AAAA,cAC5E,UAAA,oBAAC,WAAA,EAAU,UAAS,QAAA,CAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QAChC,GACJ;AAAA,QACA,oBAAC,KAAA,EAAI,IAAI,EAAC,MAAM,GAAG,WAAW,GAAG,UAAU,QAAQ,IAAI,EAAC,IAAI,KAAK,IAAI,EAAA,GAAI,IAAI,EAAC,IAAI,GAAG,IAAI,EAAA,EAAC,GACrF,SAAA,CACL;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGZ;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
4
4
|
import { useToolkitNavigation } from "../../toolkit/toolkitContext.js";
|
|
5
5
|
import Button from "@mui/material/Button";
|
|
@@ -18,18 +18,32 @@ const ERROR_PAGE_LOOK = Object.freeze({
|
|
|
18
18
|
[ERROR_PAGE_KIND.newVersion]: { Icon: SystemUpdateAltRoundedIcon, color: "info", httpStatus: null, retryKey: "reload" }
|
|
19
19
|
});
|
|
20
20
|
const ICON_PIXELS = 56;
|
|
21
|
-
function ErrorPage({
|
|
22
|
-
|
|
21
|
+
function ErrorPage({
|
|
22
|
+
kind,
|
|
23
|
+
title,
|
|
24
|
+
text,
|
|
25
|
+
homePath = "/",
|
|
26
|
+
onRetry,
|
|
27
|
+
onGoHome,
|
|
28
|
+
extraActions,
|
|
29
|
+
actions,
|
|
30
|
+
icon,
|
|
31
|
+
children,
|
|
32
|
+
texts
|
|
33
|
+
}) {
|
|
34
|
+
const { t } = useToolkitTranslation("errorPage", texts);
|
|
23
35
|
const { Link } = useToolkitNavigation();
|
|
24
36
|
const look = ERROR_PAGE_LOOK[kind] ?? ERROR_PAGE_LOOK[ERROR_PAGE_KIND.serverError];
|
|
25
37
|
return /* @__PURE__ */ jsxs(Stack, { spacing: 2, sx: { alignItems: "center", textAlign: "center", py: { xs: 6, md: 10 }, px: 2, maxWidth: 560, mx: "auto" }, children: [
|
|
26
|
-
/* @__PURE__ */ jsx(look.Icon, { color: look.color, sx: { fontSize: ICON_PIXELS } }),
|
|
38
|
+
icon ?? /* @__PURE__ */ jsx(look.Icon, { color: look.color, sx: { fontSize: ICON_PIXELS } }),
|
|
27
39
|
/* @__PURE__ */ jsx(Typography, { variant: "h4", component: "h1", children: title ?? t(`${kind}.title`) }),
|
|
28
40
|
/* @__PURE__ */ jsx(Typography, { variant: "body1", color: "text.secondary", children: text ?? t(`${kind}.text`) }),
|
|
29
|
-
|
|
41
|
+
children,
|
|
42
|
+
/* @__PURE__ */ jsx(Stack, { direction: { xs: "column", sm: "row" }, spacing: 1.5, useFlexGap: true, sx: { pt: 1, flexWrap: "wrap", justifyContent: "center" }, children: actions ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30
43
|
onRetry && /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: onRetry, children: t(look.retryKey ?? "retry") }),
|
|
31
|
-
/* @__PURE__ */ jsx(Button, { variant: onRetry ? "outlined" : "contained", component: Link, to: homePath, children: t("goHome") })
|
|
32
|
-
|
|
44
|
+
/* @__PURE__ */ jsx(Button, { variant: onRetry ? "outlined" : "contained", component: Link, to: homePath, onClick: onGoHome, children: t("goHome") }),
|
|
45
|
+
extraActions
|
|
46
|
+
] }) }),
|
|
33
47
|
look.httpStatus && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.disabled", children: look.httpStatus })
|
|
34
48
|
] });
|
|
35
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorPage.js","sources":["../../../src/ui/layout/ErrorPage.jsx"],"sourcesContent":["// La pantalla de un error de navegación: la página no existe (404), no tiene permiso (403) o algo falló (500). Dice en\n// palabras normales qué pasó y qué hacer, con el número chico abajo como referencia técnica, y el botón para salir de ahí.\n//\n// <ErrorPage kind={ERROR_PAGE_KIND.notFound} /> el 404 del router (ruta '*')\n// <ErrorPage kind={ERROR_PAGE_KIND.serverError} onRetry={reload} /> la pantalla tronó (errorElement del router)\n// <ErrorPage kind={ERROR_PAGE_KIND.newVersion} onRetry={reload} /> se publicó otra versión con la pestaña abierta\n//\n// Los textos salen del paquete (namespace errorPage) en el idioma activo; title y text los cambian para un caso propio.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Button from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport SystemUpdateAltRoundedIcon from '@mui/icons-material/SystemUpdateAltRounded';\nimport TravelExploreOutlinedIcon from '@mui/icons-material/TravelExploreOutlined';\n\n/** Los errores que tiene pantalla propia. */\nexport const ERROR_PAGE_KIND = Object.freeze({notFound: 'notFound', forbidden: 'forbidden', serverError: 'serverError', newVersion: 'newVersion'});\n\n// el ícono, el color y el código HTTP de cada uno (el código va chico: es la referencia técnica, no el mensaje)\nconst ERROR_PAGE_LOOK = Object.freeze({\n [ERROR_PAGE_KIND.notFound]: {Icon: TravelExploreOutlinedIcon, color: 'info', httpStatus: 404},\n [ERROR_PAGE_KIND.forbidden]: {Icon: LockOutlinedIcon, color: 'warning', httpStatus: 403},\n [ERROR_PAGE_KIND.serverError]: {Icon: ErrorOutlineOutlinedIcon, color: 'error', httpStatus: 500},\n // no es un error de nadie: se publicó otra versión; sin código, y el botón dice «Recargar»\n [ERROR_PAGE_KIND.newVersion]: {Icon: SystemUpdateAltRoundedIcon, color: 'info', httpStatus: null, retryKey: 'reload'},\n});\nconst ICON_PIXELS = 56;\n\n/**\n * @param {object} props\n * @param {string} props.kind uno de ERROR_PAGE_KIND\n * @param {string} [props.title] ya traducido; por omisión el del paquete\n * @param {import('react').ReactNode} [props.text] ya traducido; por omisión el del paquete\n * @param {string} [props.homePath] a dónde lleva «Ir al inicio» (por omisión '/')\n * @param {() => void} [props.onRetry] con él sale «Intentar de nuevo»\n */\nexport default function ErrorPage({kind, title, text, homePath = '/', onRetry}) {\n const {t} = useToolkitTranslation('errorPage');\n const {Link} = useToolkitNavigation();\n const look = ERROR_PAGE_LOOK[kind] ?? ERROR_PAGE_LOOK[ERROR_PAGE_KIND.serverError];\n\n return (\n <Stack spacing={2} sx={{alignItems: 'center', textAlign: 'center', py: {xs: 6, md: 10}, px: 2, maxWidth: 560, mx: 'auto'}}>\n <look.Icon color={look.color} sx={{fontSize: ICON_PIXELS}}
|
|
1
|
+
{"version":3,"file":"ErrorPage.js","sources":["../../../src/ui/layout/ErrorPage.jsx"],"sourcesContent":["// La pantalla de un error de navegación: la página no existe (404), no tiene permiso (403) o algo falló (500). Dice en\n// palabras normales qué pasó y qué hacer, con el número chico abajo como referencia técnica, y el botón para salir de ahí.\n//\n// <ErrorPage kind={ERROR_PAGE_KIND.notFound} /> el 404 del router (ruta '*')\n// <ErrorPage kind={ERROR_PAGE_KIND.serverError} onRetry={reload} /> la pantalla tronó (errorElement del router)\n// <ErrorPage kind={ERROR_PAGE_KIND.newVersion} onRetry={reload} /> se publicó otra versión con la pestaña abierta\n//\n// Los textos salen del paquete (namespace errorPage) en el idioma activo; title y text los cambian para un caso propio, y\n// `texts` cualquier otro («Ir al inicio», «Intentar de nuevo»).\n//\n// Se extiende sin tocar el framework: botones suyos junto a los de la casa (extraActions: «Contactar a soporte») o en lugar\n// de ellos (actions), algo debajo del texto (children: el teléfono de soporte, un folio), otro ícono (icon) y el evento de\n// «Ir al inicio» (onGoHome). La de «sin permiso» de las rutas se cambia con withAccess(…, {fallback}) y la de error de ruta\n// pasándole estas mismas props a RouteErrorScreen (/react-router).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Button from '@mui/material/Button';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport SystemUpdateAltRoundedIcon from '@mui/icons-material/SystemUpdateAltRounded';\nimport TravelExploreOutlinedIcon from '@mui/icons-material/TravelExploreOutlined';\n\n/** Los errores que tiene pantalla propia. */\nexport const ERROR_PAGE_KIND = Object.freeze({notFound: 'notFound', forbidden: 'forbidden', serverError: 'serverError', newVersion: 'newVersion'});\n\n// el ícono, el color y el código HTTP de cada uno (el código va chico: es la referencia técnica, no el mensaje)\nconst ERROR_PAGE_LOOK = Object.freeze({\n [ERROR_PAGE_KIND.notFound]: {Icon: TravelExploreOutlinedIcon, color: 'info', httpStatus: 404},\n [ERROR_PAGE_KIND.forbidden]: {Icon: LockOutlinedIcon, color: 'warning', httpStatus: 403},\n [ERROR_PAGE_KIND.serverError]: {Icon: ErrorOutlineOutlinedIcon, color: 'error', httpStatus: 500},\n // no es un error de nadie: se publicó otra versión; sin código, y el botón dice «Recargar»\n [ERROR_PAGE_KIND.newVersion]: {Icon: SystemUpdateAltRoundedIcon, color: 'info', httpStatus: null, retryKey: 'reload'},\n});\nconst ICON_PIXELS = 56;\n\n/**\n * @param {object} props\n * @param {string} props.kind uno de ERROR_PAGE_KIND\n * @param {string} [props.title] ya traducido; por omisión el del paquete\n * @param {import('react').ReactNode} [props.text] ya traducido; por omisión el del paquete\n * @param {string} [props.homePath] a dónde lleva «Ir al inicio» (por omisión '/')\n * @param {() => void} [props.onRetry] con él sale «Intentar de nuevo»\n * @param {() => void} [props.onGoHome] al picar «Ir al inicio», antes de ir (para avisar o limpiar algo)\n * @param {import('react').ReactNode} [props.extraActions] botones suyos junto a los de la casa\n * @param {import('react').ReactNode} [props.actions] botones suyos EN LUGAR de los de la casa\n * @param {import('react').ReactNode} [props.icon] otro ícono; por omisión el de cada error\n * @param {import('react').ReactNode} [props.children] algo debajo del texto (el teléfono de soporte, un folio)\n * @param {object} [props.texts] los textos que cambian, con la forma de `errorPage` en los textos del paquete:\n * {goHome: 'Volver al tablero', notFound: {title: '…'}}\n */\nexport default function ErrorPage({\n kind, title, text, homePath = '/', onRetry, onGoHome, extraActions, actions, icon, children, texts,\n}) {\n const {t} = useToolkitTranslation('errorPage', texts);\n const {Link} = useToolkitNavigation();\n const look = ERROR_PAGE_LOOK[kind] ?? ERROR_PAGE_LOOK[ERROR_PAGE_KIND.serverError];\n\n return (\n <Stack spacing={2} sx={{alignItems: 'center', textAlign: 'center', py: {xs: 6, md: 10}, px: 2, maxWidth: 560, mx: 'auto'}}>\n {icon ?? <look.Icon color={look.color} sx={{fontSize: ICON_PIXELS}} />}\n <Typography variant=\"h4\" component=\"h1\">{title ?? t(`${kind}.title`)}</Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">{text ?? t(`${kind}.text`)}</Typography>\n {children}\n <Stack direction={{xs: 'column', sm: 'row'}} spacing={1.5} useFlexGap sx={{pt: 1, flexWrap: 'wrap', justifyContent: 'center'}}>\n {actions ?? (\n <>\n {onRetry && <Button variant=\"contained\" onClick={onRetry}>{t(look.retryKey ?? 'retry')}</Button>}\n <Button variant={onRetry ? 'outlined' : 'contained'} component={Link} to={homePath} onClick={onGoHome}>{t('goHome')}</Button>\n {extraActions}\n </>\n )}\n </Stack>\n {look.httpStatus && <Typography variant=\"caption\" color=\"text.disabled\">{look.httpStatus}</Typography>}\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AA2BO,MAAM,kBAAkB,OAAO,OAAO,EAAC,UAAU,YAAY,WAAW,aAAa,aAAa,eAAe,YAAY,aAAA,CAAa;AAGjJ,MAAM,kBAAkB,OAAO,OAAO;AAAA,EAClC,CAAC,gBAAgB,QAAQ,GAAG,EAAC,MAAM,2BAA2B,OAAO,QAAQ,YAAY,IAAA;AAAA,EACzF,CAAC,gBAAgB,SAAS,GAAG,EAAC,MAAM,kBAAkB,OAAO,WAAW,YAAY,IAAA;AAAA,EACpF,CAAC,gBAAgB,WAAW,GAAG,EAAC,MAAM,0BAA0B,OAAO,SAAS,YAAY,IAAA;AAAA;AAAA,EAE5F,CAAC,gBAAgB,UAAU,GAAG,EAAC,MAAM,4BAA4B,OAAO,QAAQ,YAAY,MAAM,UAAU,SAAA;AAChH,CAAC;AACD,MAAM,cAAc;AAiBpB,SAAwB,UAAU;AAAA,EAC9B;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM,WAAW;AAAA,EAAK;AAAA,EAAS;AAAA,EAAU;AAAA,EAAc;AAAA,EAAS;AAAA,EAAM;AAAA,EAAU;AACjG,GAAG;AACC,QAAM,EAAC,EAAA,IAAK,sBAAsB,aAAa,KAAK;AACpD,QAAM,EAAC,KAAA,IAAQ,qBAAA;AACf,QAAM,OAAO,gBAAgB,IAAI,KAAK,gBAAgB,gBAAgB,WAAW;AAEjF,SACI,qBAAC,SAAM,SAAS,GAAG,IAAI,EAAC,YAAY,UAAU,WAAW,UAAU,IAAI,EAAC,IAAI,GAAG,IAAI,GAAA,GAAK,IAAI,GAAG,UAAU,KAAK,IAAI,OAAA,GAC7G,UAAA;AAAA,IAAA,QAAQ,oBAAC,KAAK,MAAL,EAAU,OAAO,KAAK,OAAO,IAAI,EAAC,UAAU,YAAA,EAAW,CAAG;AAAA,IACpE,oBAAC,YAAA,EAAW,SAAQ,MAAK,WAAU,MAAM,UAAA,SAAS,EAAE,GAAG,IAAI,QAAQ,EAAA,CAAE;AAAA,IACrE,oBAAC,YAAA,EAAW,SAAQ,SAAQ,OAAM,kBAAkB,UAAA,QAAQ,EAAE,GAAG,IAAI,OAAO,EAAA,CAAE;AAAA,IAC7E;AAAA,IACD,oBAAC,SAAM,WAAW,EAAC,IAAI,UAAU,IAAI,MAAA,GAAQ,SAAS,KAAK,YAAU,MAAC,IAAI,EAAC,IAAI,GAAG,UAAU,QAAQ,gBAAgB,YAC/G,UAAA,WACG,qBAAA,UAAA,EACK,UAAA;AAAA,MAAA,WAAW,oBAAC,QAAA,EAAO,SAAQ,aAAY,SAAS,SAAU,UAAA,EAAE,KAAK,YAAY,OAAO,EAAA,CAAE;AAAA,MACvF,oBAAC,QAAA,EAAO,SAAS,UAAU,aAAa,aAAa,WAAW,MAAM,IAAI,UAAU,SAAS,UAAW,UAAA,EAAE,QAAQ,GAAE;AAAA,MACnH;AAAA,IAAA,EAAA,CACL,EAAA,CAER;AAAA,IACC,KAAK,cAAc,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,iBAAiB,UAAA,KAAK,WAAA,CAAW;AAAA,EAAA,GAC7F;AAER;"}
|
|
@@ -3,8 +3,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
4
4
|
import Box from "@mui/material/Box";
|
|
5
5
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
6
|
-
function FullPageLoader() {
|
|
7
|
-
const { t } = useToolkitTranslation("common");
|
|
6
|
+
function FullPageLoader({ texts } = {}) {
|
|
7
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
8
8
|
return /* @__PURE__ */ jsx(Box, { role: "status", "aria-live": "polite", sx: { display: "grid", placeItems: "center", minHeight: "100vh", bgcolor: "background.default" }, children: /* @__PURE__ */ jsx(CircularProgress, { "aria-label": t("status.loading") }) });
|
|
9
9
|
}
|
|
10
10
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FullPageLoader.js","sources":["../../../src/ui/layout/FullPageLoader.jsx"],"sourcesContent":["// Indicador de carga a pantalla completa, mientras se sabe si hay sesión.\n// Sin él, el formulario de login parpadea medio segundo antes de que aparezca la pantalla de quien ya entró.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport CircularProgress from '@mui/material/CircularProgress';\n\nexport default function FullPageLoader() {\n const {t} = useToolkitTranslation('common');\n\n return (\n <Box role=\"status\" aria-live=\"polite\" sx={{display: 'grid', placeItems: 'center', minHeight: '100vh', bgcolor: 'background.default'}}>\n <CircularProgress aria-label={t('status.loading')} />\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"FullPageLoader.js","sources":["../../../src/ui/layout/FullPageLoader.jsx"],"sourcesContent":["// Indicador de carga a pantalla completa, mientras se sabe si hay sesión.\n// Sin él, el formulario de login parpadea medio segundo antes de que aparezca la pantalla de quien ya entró.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport CircularProgress from '@mui/material/CircularProgress';\n\n/**\n * @param {object} [props]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function FullPageLoader({texts} = {}) {\n const {t} = useToolkitTranslation('common', texts);\n\n return (\n <Box role=\"status\" aria-live=\"polite\" sx={{display: 'grid', placeItems: 'center', minHeight: '100vh', bgcolor: 'background.default'}}>\n <CircularProgress aria-label={t('status.loading')} />\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;AAYA,SAAwB,eAAe,EAAC,MAAA,IAAS,IAAI;AACjD,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AAEjD,SACI,oBAAC,OAAI,MAAK,UAAS,aAAU,UAAS,IAAI,EAAC,SAAS,QAAQ,YAAY,UAAU,WAAW,SAAS,SAAS,qBAAA,GAC3G,UAAA,oBAAC,oBAAiB,cAAY,EAAE,gBAAgB,EAAA,CAAG,EAAA,CACvD;AAER;"}
|
|
@@ -15,8 +15,8 @@ import { menuBreadcrumbsOf } from "../../navigation/appMenu.js";
|
|
|
15
15
|
import PageBreadcrumbs from "../navigation/PageBreadcrumbs.js";
|
|
16
16
|
import DocumentTitle from "../navigation/DocumentTitle.js";
|
|
17
17
|
const MIN_BREADCRUMBS = 2;
|
|
18
|
-
function PageHeading({ title, description, breadcrumbs, actions, documentTitle }) {
|
|
19
|
-
const { t } = useToolkitTranslation("layout");
|
|
18
|
+
function PageHeading({ title, description, breadcrumbs, actions, documentTitle, texts }) {
|
|
19
|
+
const { t } = useToolkitTranslation("layout", texts);
|
|
20
20
|
const { useLocation } = useToolkitNavigation();
|
|
21
21
|
const { pathname } = useLocation();
|
|
22
22
|
const { menuItems } = useAppMenu();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageHeading.js","sources":["../../../src/ui/layout/PageHeading.jsx"],"sourcesContent":["// El encabezado de una pantalla al estilo de las plantillas de MUI (y del PageContainer de walk-ot): las migas arriba, el\n// título a la izquierda con los botones a la derecha, y una descripción corta abajo.\n//\n// Las migas salen SOLAS del menú de AppLayout (Inicio › Sección › Pantalla): la página no las escribe. Una pantalla que\n// no está en el menú (un detalle) pasa las suyas con `breadcrumbs`; breadcrumbs={false} las quita.\n// El título sube a la barra de AppLayout cuando sale de la vista al deslizar, y también es el título de la pestaña.\n// Con la barra en cápsulas, sus `actions` van a la barra en su propia cápsula (BarActions), como las barras de\n// herramientas de macOS; en el teléfono, y con la franja, se quedan aquí (con la franja, para no mover pantallas que ya\n// existen: quien las quiera en la franja usa BarActions).\n//\n// Cuál usar: PageHeading en las pantallas de trabajo (listas, formularios, tableros); PageHeader (centrado) en una\n// portada; DetailHeader en el detalle de un registro (título con sus insignias de estatus).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useAppMenu} from '@/appShell/appMenuContext.js';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport {useBarActionsSlot} from '@/appShell/barActionsSlot.js';\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport {useRegisterPageTitle} from '@/appShell/pageTitle.jsx';\nimport {menuBreadcrumbsOf} from '@/navigation/appMenu.js';\nimport PageBreadcrumbs from '@/ui/navigation/PageBreadcrumbs.jsx';\nimport DocumentTitle from '@/ui/navigation/DocumentTitle.jsx';\n\n// con una sola miga (estar en el inicio) no se pintan: no dicen nada\nconst MIN_BREADCRUMBS = 2;\n\n/**\n * @param {object} props\n * @param {string} props.title texto ya traducido\n * @param {import('react').ReactNode} [props.description]\n * @param {Array<{label: string, to?: string}>|false} [props.breadcrumbs] por omisión, las del menú\n * @param {import('react').ReactNode} [props.actions] botones a la derecha (en el teléfono, abajo y a lo ancho)\n * @param {string} [props.documentTitle] título de la pestaña si difiere del visible\n */\nexport default function PageHeading({title, description, breadcrumbs, actions, documentTitle}) {\n const {t} = useToolkitTranslation('layout');\n const {useLocation} = useToolkitNavigation();\n const {pathname} = useLocation();\n const {menuItems} = useAppMenu();\n const titleRef = useRef(null);\n useRegisterPageTitle(title, titleRef);\n const {usesCapsules} = useBarActionsSlot();\n // sin cápsulas: a la derecha del título y, en el teléfono, abajo y a lo ancho\n const inlineActionsSx = {display: 'flex', gap: 1, flexWrap: 'wrap', flex: {xs: '1 1 auto', sm: '0 0 auto'}, '& > .MuiButton-root': {width: {xs: '100%', sm: 'auto'}}};\n // con cápsulas suben a la barra (escritorio) o flotan arriba (teléfono). Si no hay dónde (la barra fundida al menú), la\n // cápsula de solo íconos se queda a la derecha del título, en el mismo renglón; el título se parte si no cabe\n const isActionsCapsule = Boolean(actions) && usesCapsules;\n\n const crumbs = breadcrumbs === false\n ? []\n : breadcrumbs ?? menuBreadcrumbsOf(menuItems, pathname).map((menuItem) => ({label: menuItemLabelOf(menuItem, t), to: menuItem.to}));\n\n return (\n <Box component=\"header\" sx={{mb: 3}}>\n <DocumentTitle title={documentTitle ?? title} />\n {crumbs.length >= MIN_BREADCRUMBS && <Box sx={{mb: 1}}><PageBreadcrumbs items={crumbs} /></Box>}\n <Stack direction={isActionsCapsule ? 'row' : {xs: 'column', sm: 'row'}} spacing={{xs: 1.5, sm: 2}}\n sx={{alignItems: isActionsCapsule ? 'flex-start' : {xs: 'stretch', sm: 'flex-start'}, justifyContent: 'space-between'}}>\n <Typography ref={titleRef} variant=\"h4\" component=\"h1\" sx={{minWidth: 0}}>{title}</Typography>\n {isActionsCapsule && <BarActions label={title} inlineSx={{flexShrink: 0}}>{actions}</BarActions>}\n {actions && !usesCapsules && <Box sx={inlineActionsSx}>{actions}</Box>}\n </Stack>\n {description && (\n <Typography variant=\"body1\" color=\"text.secondary\" component=\"div\" sx={{mt: 0.75, maxWidth: 760}}>{description}</Typography>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA+BA,MAAM,kBAAkB;
|
|
1
|
+
{"version":3,"file":"PageHeading.js","sources":["../../../src/ui/layout/PageHeading.jsx"],"sourcesContent":["// El encabezado de una pantalla al estilo de las plantillas de MUI (y del PageContainer de walk-ot): las migas arriba, el\n// título a la izquierda con los botones a la derecha, y una descripción corta abajo.\n//\n// Las migas salen SOLAS del menú de AppLayout (Inicio › Sección › Pantalla): la página no las escribe. Una pantalla que\n// no está en el menú (un detalle) pasa las suyas con `breadcrumbs`; breadcrumbs={false} las quita.\n// El título sube a la barra de AppLayout cuando sale de la vista al deslizar, y también es el título de la pestaña.\n// Con la barra en cápsulas, sus `actions` van a la barra en su propia cápsula (BarActions), como las barras de\n// herramientas de macOS; en el teléfono, y con la franja, se quedan aquí (con la franja, para no mover pantallas que ya\n// existen: quien las quiera en la franja usa BarActions).\n//\n// Cuál usar: PageHeading en las pantallas de trabajo (listas, formularios, tableros); PageHeader (centrado) en una\n// portada; DetailHeader en el detalle de un registro (título con sus insignias de estatus).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useRef} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\n\nimport {useAppMenu} from '@/appShell/appMenuContext.js';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport {useBarActionsSlot} from '@/appShell/barActionsSlot.js';\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport {useRegisterPageTitle} from '@/appShell/pageTitle.jsx';\nimport {menuBreadcrumbsOf} from '@/navigation/appMenu.js';\nimport PageBreadcrumbs from '@/ui/navigation/PageBreadcrumbs.jsx';\nimport DocumentTitle from '@/ui/navigation/DocumentTitle.jsx';\n\n// con una sola miga (estar en el inicio) no se pintan: no dicen nada\nconst MIN_BREADCRUMBS = 2;\n\n/**\n * @param {object} props\n * @param {string} props.title texto ya traducido\n * @param {import('react').ReactNode} [props.description]\n * @param {Array<{label: string, to?: string}>|false} [props.breadcrumbs] por omisión, las del menú\n * @param {import('react').ReactNode} [props.actions] botones a la derecha (en el teléfono, abajo y a lo ancho)\n * @param {string} [props.documentTitle] título de la pestaña si difiere del visible\n * @param {object} [props.texts] los textos que cambian, con la forma de `layout` en los textos del paquete\n */\nexport default function PageHeading({title, description, breadcrumbs, actions, documentTitle, texts}) {\n const {t} = useToolkitTranslation('layout', texts);\n const {useLocation} = useToolkitNavigation();\n const {pathname} = useLocation();\n const {menuItems} = useAppMenu();\n const titleRef = useRef(null);\n useRegisterPageTitle(title, titleRef);\n const {usesCapsules} = useBarActionsSlot();\n // sin cápsulas: a la derecha del título y, en el teléfono, abajo y a lo ancho\n const inlineActionsSx = {display: 'flex', gap: 1, flexWrap: 'wrap', flex: {xs: '1 1 auto', sm: '0 0 auto'}, '& > .MuiButton-root': {width: {xs: '100%', sm: 'auto'}}};\n // con cápsulas suben a la barra (escritorio) o flotan arriba (teléfono). Si no hay dónde (la barra fundida al menú), la\n // cápsula de solo íconos se queda a la derecha del título, en el mismo renglón; el título se parte si no cabe\n const isActionsCapsule = Boolean(actions) && usesCapsules;\n\n const crumbs = breadcrumbs === false\n ? []\n : breadcrumbs ?? menuBreadcrumbsOf(menuItems, pathname).map((menuItem) => ({label: menuItemLabelOf(menuItem, t), to: menuItem.to}));\n\n return (\n <Box component=\"header\" sx={{mb: 3}}>\n <DocumentTitle title={documentTitle ?? title} />\n {crumbs.length >= MIN_BREADCRUMBS && <Box sx={{mb: 1}}><PageBreadcrumbs items={crumbs} /></Box>}\n <Stack direction={isActionsCapsule ? 'row' : {xs: 'column', sm: 'row'}} spacing={{xs: 1.5, sm: 2}}\n sx={{alignItems: isActionsCapsule ? 'flex-start' : {xs: 'stretch', sm: 'flex-start'}, justifyContent: 'space-between'}}>\n <Typography ref={titleRef} variant=\"h4\" component=\"h1\" sx={{minWidth: 0}}>{title}</Typography>\n {isActionsCapsule && <BarActions label={title} inlineSx={{flexShrink: 0}}>{actions}</BarActions>}\n {actions && !usesCapsules && <Box sx={inlineActionsSx}>{actions}</Box>}\n </Stack>\n {description && (\n <Typography variant=\"body1\" color=\"text.secondary\" component=\"div\" sx={{mt: 0.75, maxWidth: 760}}>{description}</Typography>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA+BA,MAAM,kBAAkB;AAWxB,SAAwB,YAAY,EAAC,OAAO,aAAa,aAAa,SAAS,eAAe,SAAQ;AAClG,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,YAAA,IAAe,qBAAA;AACtB,QAAM,EAAC,SAAA,IAAY,YAAA;AACnB,QAAM,EAAC,UAAA,IAAa,WAAA;AACpB,QAAM,WAAW,OAAO,IAAI;AAC5B,uBAAqB,OAAO,QAAQ;AACpC,QAAM,EAAC,aAAA,IAAgB,kBAAA;AAEvB,QAAM,kBAAkB,EAAC,SAAS,QAAQ,KAAK,GAAG,UAAU,QAAQ,MAAM,EAAC,IAAI,YAAY,IAAI,WAAA,GAAa,uBAAuB,EAAC,OAAO,EAAC,IAAI,QAAQ,IAAI,OAAA,IAAO;AAGnK,QAAM,mBAAmB,QAAQ,OAAO,KAAK;AAE7C,QAAM,SAAS,gBAAgB,QACzB,CAAA,IACA,eAAe,kBAAkB,WAAW,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAC,OAAO,gBAAgB,UAAU,CAAC,GAAG,IAAI,SAAS,GAAA,EAAI;AAEtI,SACI,qBAAC,OAAI,WAAU,UAAS,IAAI,EAAC,IAAI,KAC7B,UAAA;AAAA,IAAA,oBAAC,eAAA,EAAc,OAAO,iBAAiB,MAAA,CAAO;AAAA,IAC7C,OAAO,UAAU,mBAAmB,oBAAC,OAAI,IAAI,EAAC,IAAI,EAAA,GAAI,UAAA,oBAAC,iBAAA,EAAgB,OAAO,QAAQ,GAAE;AAAA,IACzF;AAAA,MAAC;AAAA,MAAA;AAAA,QAAM,WAAW,mBAAmB,QAAQ,EAAC,IAAI,UAAU,IAAI,MAAA;AAAA,QAAQ,SAAS,EAAC,IAAI,KAAK,IAAI,EAAA;AAAA,QACxF,IAAI,EAAC,YAAY,mBAAmB,eAAe,EAAC,IAAI,WAAW,IAAI,gBAAe,gBAAgB,gBAAA;AAAA,QACzG,UAAA;AAAA,UAAA,oBAAC,YAAA,EAAW,KAAK,UAAU,SAAQ,MAAK,WAAU,MAAK,IAAI,EAAC,UAAU,EAAA,GAAK,UAAA,OAAM;AAAA,UAChF,oBAAoB,oBAAC,YAAA,EAAW,OAAO,OAAO,UAAU,EAAC,YAAY,EAAA,GAAK,UAAA,QAAA,CAAQ;AAAA,UAClF,WAAW,CAAC,oCAAiB,KAAA,EAAI,IAAI,iBAAkB,UAAA,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEnE,eACG,oBAAC,YAAA,EAAW,SAAQ,SAAQ,OAAM,kBAAiB,WAAU,OAAM,IAAI,EAAC,IAAI,MAAM,UAAU,IAAA,GAAO,UAAA,YAAA,CAAY;AAAA,EAAA,GAEvH;AAER;"}
|
|
@@ -8,28 +8,33 @@ import TableCell from "@mui/material/TableCell";
|
|
|
8
8
|
import TableRow from "@mui/material/TableRow";
|
|
9
9
|
import Typography from "@mui/material/Typography";
|
|
10
10
|
import { ResponsiveContainer, BarChart, XAxis, YAxis, Tooltip, Bar, LabelList } from "recharts";
|
|
11
|
-
import {
|
|
11
|
+
import { useFormatters } from "../../toolkit/useFormatters.js";
|
|
12
12
|
import ChartTooltip from "./ChartTooltip.js";
|
|
13
|
-
import TableToggle from "./TableToggle.js";
|
|
13
|
+
import TableToggle, { CHART_VIEW } from "./TableToggle.js";
|
|
14
14
|
import { BAR_MIN_POINT_SIZE, barLabelValueOf } from "./barListLabels.js";
|
|
15
|
+
import { useChartColors } from "./chartParts.js";
|
|
15
16
|
const ROW_HEIGHT_PIXELS = 36;
|
|
16
|
-
|
|
17
|
-
const MUTED_COLOR = "var(--mui-palette-text-secondary)";
|
|
18
|
-
function ValueLabel({ x, y, width, height, value }) {
|
|
17
|
+
function ValueLabel({ x, y, width, height, value, colors, formatNumber }) {
|
|
19
18
|
return /* @__PURE__ */ jsxs("text", { x: x + width + 8, y: y + height / 2, dominantBaseline: "central", fontSize: 13, children: [
|
|
20
|
-
/* @__PURE__ */ jsx("tspan", { fill:
|
|
21
|
-
value.detail && /* @__PURE__ */ jsx("tspan", { fill:
|
|
19
|
+
/* @__PURE__ */ jsx("tspan", { fill: colors.text, fontWeight: 600, children: formatNumber(value.value) }),
|
|
20
|
+
value.detail && /* @__PURE__ */ jsx("tspan", { fill: colors.muted, fontSize: 12, children: ` ${value.detail}` })
|
|
22
21
|
] });
|
|
23
22
|
}
|
|
24
|
-
function BarList({ items, onSelect, emptyLabel, tone = "primary" }) {
|
|
25
|
-
const [asTable, setAsTable] = useState(
|
|
23
|
+
function BarList({ items, onSelect, emptyLabel, tone = "primary", texts, defaultView = CHART_VIEW.chart, onViewChange }) {
|
|
24
|
+
const [asTable, setAsTable] = useState(defaultView === CHART_VIEW.table);
|
|
25
|
+
const toggleView = () => {
|
|
26
|
+
setAsTable(!asTable);
|
|
27
|
+
onViewChange?.(asTable ? CHART_VIEW.chart : CHART_VIEW.table);
|
|
28
|
+
};
|
|
29
|
+
const colors = useChartColors();
|
|
30
|
+
const { formatNumber } = useFormatters();
|
|
26
31
|
if (!items.length) {
|
|
27
32
|
return /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: emptyLabel });
|
|
28
33
|
}
|
|
29
34
|
const labelWidth = Math.min(220, 16 + Math.max(...items.map((item) => String(item.label ?? "").length)) * 7);
|
|
30
35
|
const longestValue = Math.max(...items.map((item) => `${formatNumber(item.value)} ${item.detail ?? ""}`.length));
|
|
31
36
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
32
|
-
/* @__PURE__ */ jsx(TableToggle, { asTable, onToggle:
|
|
37
|
+
/* @__PURE__ */ jsx(TableToggle, { asTable, onToggle: toggleView, texts }),
|
|
33
38
|
asTable ? /* @__PURE__ */ jsx(Table, { size: "small", children: /* @__PURE__ */ jsx(TableBody, { children: items.map((item) => /* @__PURE__ */ jsxs(
|
|
34
39
|
TableRow,
|
|
35
40
|
{
|
|
@@ -56,13 +61,13 @@ function BarList({ items, onSelect, emptyLabel, tone = "primary" }) {
|
|
|
56
61
|
width: labelWidth,
|
|
57
62
|
tickLine: false,
|
|
58
63
|
axisLine: false,
|
|
59
|
-
tick: { fill:
|
|
64
|
+
tick: { fill: colors.text, fontSize: 13 }
|
|
60
65
|
}
|
|
61
66
|
),
|
|
62
67
|
/* @__PURE__ */ jsx(
|
|
63
68
|
Tooltip,
|
|
64
69
|
{
|
|
65
|
-
cursor: { fill:
|
|
70
|
+
cursor: { fill: colors.hover },
|
|
66
71
|
content: /* @__PURE__ */ jsx(ChartTooltip, { render: (item) => item.tooltip ?? `${item.label}: ${formatNumber(item.value)}` })
|
|
67
72
|
}
|
|
68
73
|
),
|
|
@@ -77,7 +82,7 @@ function BarList({ items, onSelect, emptyLabel, tone = "primary" }) {
|
|
|
77
82
|
minPointSize: BAR_MIN_POINT_SIZE,
|
|
78
83
|
cursor: onSelect ? "pointer" : "default",
|
|
79
84
|
onClick: onSelect ? (entry) => onSelect(entry.payload) : void 0,
|
|
80
|
-
children: /* @__PURE__ */ jsx(LabelList, { valueAccessor: barLabelValueOf, content: (labelProps) => /* @__PURE__ */ jsx(ValueLabel, { ...labelProps }) })
|
|
85
|
+
children: /* @__PURE__ */ jsx(LabelList, { valueAccessor: barLabelValueOf, content: (labelProps) => /* @__PURE__ */ jsx(ValueLabel, { ...labelProps, colors, formatNumber }) })
|
|
81
86
|
}
|
|
82
87
|
)
|
|
83
88
|
] }) }) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BarList.js","sources":["../../../src/ui/metrics/BarList.jsx"],"sourcesContent":["// Barras horizontales de UNA serie con su nombre a la izquierda y su valor en la punta (ventas por producto,\n// cuentas por organización), dibujadas con Recharts.\n//\n// Horizontales porque los nombres son largos (\"Empresa / Área de ventas\"): se leen completos sin rotarlos. Una serie → un\n// color (el del tono, vía currentColor: los atributos SVG no leen var(...) pero sí currentColor); el valor va en tinta,\n// nunca del color de la barra; y cada barra puede llevar a la pantalla que la explica.\n//\n// OJO: el número y el detalle de la punta salen del renglón de la barra (barListLabels.js), nunca de items[index]: Recharts\n// descarta las barras de ancho 0 y renumera las etiquetas, y el detalle se corría al renglón de arriba.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Table from '@mui/material/Table';\nimport TableBody from '@mui/material/TableBody';\nimport TableCell from '@mui/material/TableCell';\nimport TableRow from '@mui/material/TableRow';\nimport Typography from '@mui/material/Typography';\nimport {Bar, BarChart, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';\n\nimport {
|
|
1
|
+
{"version":3,"file":"BarList.js","sources":["../../../src/ui/metrics/BarList.jsx"],"sourcesContent":["// Barras horizontales de UNA serie con su nombre a la izquierda y su valor en la punta (ventas por producto,\n// cuentas por organización), dibujadas con Recharts.\n//\n// Horizontales porque los nombres son largos (\"Empresa / Área de ventas\"): se leen completos sin rotarlos. Una serie → un\n// color (el del tono, vía currentColor: los atributos SVG no leen var(...) pero sí currentColor); el valor va en tinta,\n// nunca del color de la barra; y cada barra puede llevar a la pantalla que la explica.\n//\n// OJO: el número y el detalle de la punta salen del renglón de la barra (barListLabels.js), nunca de items[index]: Recharts\n// descarta las barras de ancho 0 y renumera las etiquetas, y el detalle se corría al renglón de arriba.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Table from '@mui/material/Table';\nimport TableBody from '@mui/material/TableBody';\nimport TableCell from '@mui/material/TableCell';\nimport TableRow from '@mui/material/TableRow';\nimport Typography from '@mui/material/Typography';\nimport {Bar, BarChart, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport ChartTooltip from '@/ui/metrics/ChartTooltip.jsx';\nimport TableToggle, {CHART_VIEW} from '@/ui/metrics/TableToggle.jsx';\nimport {BAR_MIN_POINT_SIZE, barLabelValueOf} from '@/ui/metrics/barListLabels.js';\nimport {useChartColors} from '@/ui/metrics/chartParts.jsx';\n\nconst ROW_HEIGHT_PIXELS = 36;\n\n/**\n * El valor en la punta: el número fuerte y el detalle (\"de 8\") tenue; value es {value, detail} de barLabelValueOf. colors y\n * formatNumber: los de useChartColors y useFormatters (los da BarList: este pedazo lo pinta recharts por su cuenta).\n */\nfunction ValueLabel({x, y, width, height, value, colors, formatNumber}) {\n return (\n <text x={x + width + 8} y={y + height / 2} dominantBaseline=\"central\" fontSize={13}>\n <tspan fill={colors.text} fontWeight={600}>{formatNumber(value.value)}</tspan>\n {value.detail && <tspan fill={colors.muted} fontSize={12}>{` ${value.detail}`}</tspan>}\n </text>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{key: string, label: string, value: number, detail?: string, tooltip?: string}>} props.items\n * @param {(item: any) => void} [props.onSelect] recibe el renglón de items que se picó\n * @param {string} props.emptyLabel ya traducido\n * @param {'primary'|'success'|'warning'|'error'|'info'} [props.tone]\n * @param {'chart'|'table'} [props.defaultView] cómo se ve al abrir (CHART_VIEW); por omisión dibujada\n * @param {(view: 'chart'|'table') => void} [props.onViewChange] la persona cambió a la tabla o a la gráfica\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function BarList({items, onSelect, emptyLabel, tone = 'primary', texts, defaultView = CHART_VIEW.chart, onViewChange}) {\n const [asTable, setAsTable] = useState(defaultView === CHART_VIEW.table);\n const toggleView = () => {\n setAsTable(!asTable);\n onViewChange?.(asTable ? CHART_VIEW.chart : CHART_VIEW.table);\n };\n const colors = useChartColors();\n const {formatNumber} = useFormatters();\n\n // guard clause: sin datos se dice, no se pinta un eje vacío\n if (!items.length) {\n return <Typography variant=\"body2\" color=\"text.secondary\">{emptyLabel}</Typography>;\n }\n\n // lo más largo de la etiqueta decide el ancho del eje: sin esto el nombre se corta o sobra medio contenedor\n // OJO: String(... ?? '') porque un renglón sin nombre (catálogo dado de baja) tumbaba toda la gráfica\n // OJO: 220 px de tope. Con 160 se cortaban etiquetas largas («Cuenta pendiente de activar») sin puntos suspensivos\n // ni forma de leerlas; el tooltip de la barra sigue mostrando el texto completo\n const labelWidth = Math.min(220, 16 + Math.max(...items.map((item) => String(item.label ?? '').length)) * 7);\n const longestValue = Math.max(...items.map((item) => `${formatNumber(item.value)} ${item.detail ?? ''}`.length));\n\n return (\n <Box>\n <TableToggle asTable={asTable} onToggle={toggleView} texts={texts} />\n {asTable ? (\n <Table size=\"small\">\n <TableBody>\n {items.map((item) => (\n <TableRow key={item.key} hover={Boolean(onSelect)} onClick={onSelect ? () => onSelect(item) : undefined}\n sx={{cursor: onSelect ? 'pointer' : 'default'}}>\n <TableCell>{item.label}</TableCell>\n <TableCell align=\"right\">{formatNumber(item.value)} {item.detail}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n ) : (\n <Box sx={{height: items.length * ROW_HEIGHT_PIXELS, color: `${tone}.main`}}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={items} layout=\"vertical\" margin={{top: 0, bottom: 0, left: 0, right: 16 + longestValue * 7}}>\n {/* OJO: con todo en 0 el dominio [0, 0] manda el 0 a la MITAD del eje (escala degenerada de d3) y la\n barra de 1 px con su «0 de 2» saldría a medio renglón; el tope mínimo de 1 lo deja en el origen */}\n <XAxis type=\"number\" hide domain={[0, (dataMax) => Math.max(dataMax, 1)]} />\n <YAxis type=\"category\" dataKey=\"label\" width={labelWidth} tickLine={false} axisLine={false}\n tick={{fill: colors.text, fontSize: 13}} />\n <Tooltip cursor={{fill: colors.hover}}\n content={<ChartTooltip render={(item) => item.tooltip ?? `${item.label}: ${formatNumber(item.value)}`} />} />\n <Bar dataKey=\"value\" fill=\"currentColor\" maxBarSize={16} radius={[0, 4, 4, 0]} isAnimationActive={false}\n minPointSize={BAR_MIN_POINT_SIZE} cursor={onSelect ? 'pointer' : 'default'}\n onClick={onSelect ? (entry) => onSelect(entry.payload) : undefined}>\n {/* OJO: SIN dataKey; con dataKey LabelList ignora valueAccessor y regresa el índice corrido */}\n <LabelList valueAccessor={barLabelValueOf} content={(labelProps) => <ValueLabel {...labelProps} colors={colors} formatNumber={formatNumber} />} />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </Box>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,9 @@ import TableHead from "@mui/material/TableHead";
|
|
|
11
11
|
import TableRow from "@mui/material/TableRow";
|
|
12
12
|
import Tooltip from "@mui/material/Tooltip";
|
|
13
13
|
import Typography from "@mui/material/Typography";
|
|
14
|
-
import {
|
|
14
|
+
import { useFormatters } from "../../toolkit/useFormatters.js";
|
|
15
15
|
import { calendarWeeksOf, levelOf, HEATMAP_LEVELS } from "./heatmapScale.js";
|
|
16
|
-
import TableToggle from "./TableToggle.js";
|
|
16
|
+
import TableToggle, { CHART_VIEW } from "./TableToggle.js";
|
|
17
17
|
const CELL_PIXELS = { xs: 12, md: 16 };
|
|
18
18
|
const GAP_PIXELS = 3;
|
|
19
19
|
const LABELED_WEEKDAYS = /* @__PURE__ */ new Set([1, 3, 5]);
|
|
@@ -25,15 +25,20 @@ function levelFill(theme, tone, level) {
|
|
|
25
25
|
}
|
|
26
26
|
return `color-mix(in srgb, ${palette[tone].main} ${LEVEL_TONE_PERCENT[level]}%, transparent)`;
|
|
27
27
|
}
|
|
28
|
-
function CalendarHeatmap({ startsOn, endsOn, days, ariaLabel, valueLabel, describeValue, summary, emptyLabel, tone = "primary" }) {
|
|
29
|
-
const { t } = useToolkitTranslation("common");
|
|
30
|
-
const
|
|
28
|
+
function CalendarHeatmap({ startsOn, endsOn, days, ariaLabel, valueLabel, describeValue, summary, emptyLabel, tone = "primary", texts, defaultView = CHART_VIEW.chart, onViewChange }) {
|
|
29
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
30
|
+
const { formatDate, formatMonthShortName, formatNumber, formatWeekdayShortName } = useFormatters();
|
|
31
|
+
const [asTable, setAsTable] = useState(defaultView === CHART_VIEW.table);
|
|
32
|
+
const toggleView = () => {
|
|
33
|
+
setAsTable(!asTable);
|
|
34
|
+
onViewChange?.(asTable ? CHART_VIEW.chart : CHART_VIEW.table);
|
|
35
|
+
};
|
|
31
36
|
const { weeks, monthMarks, thresholds } = useMemo(() => calendarWeeksOf({ startsOn, endsOn, days }), [startsOn, endsOn, days]);
|
|
32
37
|
const activeDays = days.filter((day) => day.value > 0);
|
|
33
38
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
34
39
|
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 2, sx: { justifyContent: "space-between", alignItems: "center", mb: 1 }, children: [
|
|
35
40
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: activeDays.length ? summary : emptyLabel }),
|
|
36
|
-
/* @__PURE__ */ jsx(Box, { sx: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(TableToggle, { asTable, onToggle:
|
|
41
|
+
/* @__PURE__ */ jsx(Box, { sx: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(TableToggle, { asTable, onToggle: toggleView, texts }) })
|
|
37
42
|
] }),
|
|
38
43
|
asTable ? /* @__PURE__ */ jsx(Box, { sx: { maxHeight: 260, overflowY: "auto" }, children: /* @__PURE__ */ jsxs(Table, { size: "small", "aria-label": ariaLabel, children: [
|
|
39
44
|
/* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CalendarHeatmap.js","sources":["../../../src/ui/metrics/CalendarHeatmap.jsx"],"sourcesContent":["// Calendario de actividad: un cuadro por día, semanas en columnas (el de GitHub).\n//\n// Sirve para ver RITMO de un vistazo: qué días llegan cargas, si hubo semanas muertas, si la actividad crece. Las\n// reglas de visualización:\n// - es MAGNITUD, así que va un solo tono de claro a oscuro (el del tono que se pida), en cuatro niveles más \"sin\n// actividad\". Nunca un arcoíris. Se arma con color-mix sobre el color del tema (su variable CSS si el tema las usa),\n// sin hexadecimales: en modo oscuro los mismos niveles se ven sobre su propia superficie;\n// - los niveles se cortan por CUARTILES de los días con actividad (heatmapScale.js), no por valores fijos;\n// - leyenda \"Menos → Más\", tooltip por día con el número exacto (el color solo nunca dice cuánto) y VISTA DE TABLA\n// (para lector de pantalla, para quien no distingue el tono y para copiar los números).\n//\n// Sin librería a propósito: el Heatmap de MUI X Charts es de la edición Pro, que pide licencia.\n//\n// Textos por props y del paquete, y fechas con los formatos del paquete.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Table from '@mui/material/Table';\nimport TableBody from '@mui/material/TableBody';\nimport TableCell from '@mui/material/TableCell';\nimport TableHead from '@mui/material/TableHead';\nimport TableRow from '@mui/material/TableRow';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\n\nimport {formatDate, formatMonthShortName, formatNumber, formatWeekdayShortName} from '@/formatting.js';\nimport {HEATMAP_LEVELS, calendarWeeksOf, levelOf} from '@/ui/metrics/heatmapScale.js';\nimport TableToggle from '@/ui/metrics/TableToggle.jsx';\n\n// cuadros de 12 px en teléfono (el calendario se desliza) y de 16 px desde md, donde el panel ocupa todo el ancho\nconst CELL_PIXELS = {xs: 12, md: 16};\nconst GAP_PIXELS = 3;\n// solo lunes, miércoles y viernes llevan rótulo: los siete encimados no se leen a 12 px\nconst LABELED_WEEKDAYS = new Set([1, 3, 5]);\n// porcentaje del tono en cada nivel; el nivel 0 es el gris de \"hover\" del tema\nconst LEVEL_TONE_PERCENT = {1: 30, 2: 55, 3: 78, 4: 100};\n\n/**\n * Relleno de un nivel con los colores del tema.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @param {string} tone primary, info, success...\n * @param {number} level 0 a 4\n * @returns {string} color CSS\n */\nfunction levelFill(theme, tone, level) {\n // un tema con variables CSS las trae en theme.vars; el tema por omisión de MUI, solo en theme.palette\n const {palette} = theme.vars ?? theme;\n if (level === 0) {\n return palette.action.hover;\n }\n return `color-mix(in srgb, ${palette[tone].main} ${LEVEL_TONE_PERCENT[level]}%, transparent)`;\n}\n\n/**\n * @param {object} props\n * @param {string} props.startsOn primer día del rango (\"2025-09-16\"), fecha de calendario\n * @param {string} props.endsOn último día del rango (\"2026-09-15\")\n * @param {Array<{date: string, value: number, detail?: string}>} props.days solo los días con actividad; el resto vale\n * 0\n * @param {string} props.ariaLabel qué muestra el calendario, ya traducido\n * @param {string} props.valueLabel encabezado de la columna de valor en la tabla (\"Registros leídos\")\n * @param {(value: number) => string} props.describeValue el valor en palabras para el tooltip (\"120 registros leídos\")\n * @param {import('react').ReactNode} [props.summary] renglón de resumen arriba del calendario\n * @param {string} [props.emptyLabel] qué decir si ningún día tiene actividad\n * @param {'primary'|'info'|'success'|'warning'|'error'} [props.tone]\n */\nexport default function CalendarHeatmap({startsOn, endsOn, days, ariaLabel, valueLabel, describeValue, summary, emptyLabel, tone = 'primary'}) {\n const {t} = useToolkitTranslation('common');\n const [asTable, setAsTable] = useState(false);\n\n const {weeks, monthMarks, thresholds} = useMemo(() => calendarWeeksOf({startsOn, endsOn, days}), [startsOn, endsOn, days]);\n const activeDays = days.filter((day) => day.value > 0);\n\n return (\n <Box>\n <Stack direction=\"row\" spacing={2} sx={{justifyContent: 'space-between', alignItems: 'center', mb: 1}}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {activeDays.length ? summary : emptyLabel}\n </Typography>\n <Box sx={{flexShrink: 0}}>\n <TableToggle asTable={asTable} onToggle={() => setAsTable((current) => !current)} />\n </Box>\n </Stack>\n\n {asTable ? (\n <Box sx={{maxHeight: 260, overflowY: 'auto'}}>\n <Table size=\"small\" aria-label={ariaLabel}>\n <TableHead>\n <TableRow>\n <TableCell>{t('charts.day')}</TableCell>\n <TableCell align=\"right\">{valueLabel}</TableCell>\n <TableCell />\n </TableRow>\n </TableHead>\n <TableBody>\n {[...activeDays].reverse().map((day) => (\n <TableRow key={day.date}>\n <TableCell sx={{whiteSpace: 'nowrap'}}>{formatDate(day.date)}</TableCell>\n <TableCell align=\"right\" sx={{fontVariantNumeric: 'tabular-nums'}}>{formatNumber(day.value)}</TableCell>\n <TableCell sx={{color: 'text.secondary'}}>{day.detail}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </Box>\n ) : (\n // el calendario no se encoge: en pantallas angostas se desliza y arranca mostrando lo más reciente\n <Box sx={{overflowX: 'auto', pb: 0.5}}\n ref={(node) => {\n if (node) {\n node.scrollLeft = node.scrollWidth;\n }\n }}>\n <Box role=\"img\" aria-label={ariaLabel}\n sx={{\n display: 'inline-grid',\n gridTemplateColumns: {\n xs: `auto repeat(${weeks.length}, ${CELL_PIXELS.xs}px)`,\n md: `auto repeat(${weeks.length}, ${CELL_PIXELS.md}px)`,\n },\n columnGap: `${GAP_PIXELS}px`,\n rowGap: `${GAP_PIXELS}px`,\n alignItems: 'center',\n }}>\n <Box />\n {monthMarks.map((month, column) => (\n <Typography key={`month-${column}`} variant=\"caption\" color=\"text.secondary\"\n sx={{fontSize: 10, lineHeight: 1, whiteSpace: 'nowrap', overflow: 'visible', width: CELL_PIXELS}}>\n {month ? formatMonthShortName(month) : ''}\n </Typography>\n ))}\n {[1, 2, 3, 4, 5, 6, 7].map((isoWeekday) => (\n <Box key={`weekday-${isoWeekday}`} sx={{display: 'contents'}}>\n <Typography variant=\"caption\" color=\"text.secondary\"\n sx={{fontSize: 10, lineHeight: {xs: `${CELL_PIXELS.xs}px`, md: `${CELL_PIXELS.md}px`}, pr: 0.75}}>\n {LABELED_WEEKDAYS.has(isoWeekday) ? formatWeekdayShortName(isoWeekday) : ''}\n </Typography>\n {weeks.map((week, column) => {\n const day = week[isoWeekday - 1];\n if (!day) {\n return <Box key={`outside-${column}-${isoWeekday}`} />;\n }\n const level = levelOf(day.value, thresholds);\n return (\n <Tooltip key={day.date} arrow disableInteractive\n title={`${formatDate(day.date)}: ${describeValue(day.value)}${day.detail ? ` · ${day.detail}` : ''}`}>\n <Box sx={(theme) => ({width: CELL_PIXELS, height: CELL_PIXELS, borderRadius: '3px', bgcolor: levelFill(theme, tone, level)})} />\n </Tooltip>\n );\n })}\n </Box>\n ))}\n </Box>\n </Box>\n )}\n\n {!asTable && (\n <Stack direction=\"row\" spacing={0.5} sx={{alignItems: 'center', justifyContent: 'flex-end', mt: 1}} aria-hidden=\"true\">\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{mr: 0.5}}>{t('charts.less')}</Typography>\n {HEATMAP_LEVELS.map((level) => (\n <Box key={level} sx={(theme) => ({width: CELL_PIXELS, height: CELL_PIXELS, borderRadius: '3px', bgcolor: levelFill(theme, tone, level)})} />\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ml: 0.5}}>{t('charts.more')}</Typography>\n </Stack>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CalendarHeatmap.js","sources":["../../../src/ui/metrics/CalendarHeatmap.jsx"],"sourcesContent":["// Calendario de actividad: un cuadro por día, semanas en columnas (el de GitHub).\n//\n// Sirve para ver RITMO de un vistazo: qué días llegan cargas, si hubo semanas muertas, si la actividad crece. Las\n// reglas de visualización:\n// - es MAGNITUD, así que va un solo tono de claro a oscuro (el del tono que se pida), en cuatro niveles más \"sin\n// actividad\". Nunca un arcoíris. Se arma con color-mix sobre el color del tema (su variable CSS si el tema las usa),\n// sin hexadecimales: en modo oscuro los mismos niveles se ven sobre su propia superficie;\n// - los niveles se cortan por CUARTILES de los días con actividad (heatmapScale.js), no por valores fijos;\n// - leyenda \"Menos → Más\", tooltip por día con el número exacto (el color solo nunca dice cuánto) y VISTA DE TABLA\n// (para lector de pantalla, para quien no distingue el tono y para copiar los números).\n//\n// Sin librería a propósito: el Heatmap de MUI X Charts es de la edición Pro, que pide licencia.\n//\n// Textos por props y del paquete, y fechas con los formatos del paquete.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport Table from '@mui/material/Table';\nimport TableBody from '@mui/material/TableBody';\nimport TableCell from '@mui/material/TableCell';\nimport TableHead from '@mui/material/TableHead';\nimport TableRow from '@mui/material/TableRow';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {HEATMAP_LEVELS, calendarWeeksOf, levelOf} from '@/ui/metrics/heatmapScale.js';\nimport TableToggle, {CHART_VIEW} from '@/ui/metrics/TableToggle.jsx';\n\n// cuadros de 12 px en teléfono (el calendario se desliza) y de 16 px desde md, donde el panel ocupa todo el ancho\nconst CELL_PIXELS = {xs: 12, md: 16};\nconst GAP_PIXELS = 3;\n// solo lunes, miércoles y viernes llevan rótulo: los siete encimados no se leen a 12 px\nconst LABELED_WEEKDAYS = new Set([1, 3, 5]);\n// porcentaje del tono en cada nivel; el nivel 0 es el gris de \"hover\" del tema\nconst LEVEL_TONE_PERCENT = {1: 30, 2: 55, 3: 78, 4: 100};\n\n/**\n * Relleno de un nivel con los colores del tema.\n *\n * @param {object} theme tema de MUI (con o sin variables CSS)\n * @param {string} tone primary, info, success...\n * @param {number} level 0 a 4\n * @returns {string} color CSS\n */\nfunction levelFill(theme, tone, level) {\n // un tema con variables CSS las trae en theme.vars; el tema por omisión de MUI, solo en theme.palette\n const {palette} = theme.vars ?? theme;\n if (level === 0) {\n return palette.action.hover;\n }\n return `color-mix(in srgb, ${palette[tone].main} ${LEVEL_TONE_PERCENT[level]}%, transparent)`;\n}\n\n/**\n * @param {object} props\n * @param {string} props.startsOn primer día del rango (\"2025-09-16\"), fecha de calendario\n * @param {string} props.endsOn último día del rango (\"2026-09-15\")\n * @param {Array<{date: string, value: number, detail?: string}>} props.days solo los días con actividad; el resto vale\n * 0\n * @param {string} props.ariaLabel qué muestra el calendario, ya traducido\n * @param {string} props.valueLabel encabezado de la columna de valor en la tabla (\"Registros leídos\")\n * @param {(value: number) => string} props.describeValue el valor en palabras para el tooltip (\"120 registros leídos\")\n * @param {import('react').ReactNode} [props.summary] renglón de resumen arriba del calendario\n * @param {string} [props.emptyLabel] qué decir si ningún día tiene actividad\n * @param {'primary'|'info'|'success'|'warning'|'error'} [props.tone]\n * @param {'chart'|'table'} [props.defaultView] cómo se ve al abrir (CHART_VIEW); por omisión dibujada\n * @param {(view: 'chart'|'table') => void} [props.onViewChange] la persona cambió a la tabla o a la gráfica\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function CalendarHeatmap({startsOn, endsOn, days, ariaLabel, valueLabel, describeValue, summary, emptyLabel, tone = 'primary', texts, defaultView = CHART_VIEW.chart, onViewChange}) {\n const {t} = useToolkitTranslation('common', texts);\n const {formatDate, formatMonthShortName, formatNumber, formatWeekdayShortName} = useFormatters();\n const [asTable, setAsTable] = useState(defaultView === CHART_VIEW.table);\n const toggleView = () => {\n setAsTable(!asTable);\n onViewChange?.(asTable ? CHART_VIEW.chart : CHART_VIEW.table);\n };\n\n const {weeks, monthMarks, thresholds} = useMemo(() => calendarWeeksOf({startsOn, endsOn, days}), [startsOn, endsOn, days]);\n const activeDays = days.filter((day) => day.value > 0);\n\n return (\n <Box>\n <Stack direction=\"row\" spacing={2} sx={{justifyContent: 'space-between', alignItems: 'center', mb: 1}}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {activeDays.length ? summary : emptyLabel}\n </Typography>\n <Box sx={{flexShrink: 0}}>\n <TableToggle asTable={asTable} onToggle={toggleView} texts={texts} />\n </Box>\n </Stack>\n\n {asTable ? (\n <Box sx={{maxHeight: 260, overflowY: 'auto'}}>\n <Table size=\"small\" aria-label={ariaLabel}>\n <TableHead>\n <TableRow>\n <TableCell>{t('charts.day')}</TableCell>\n <TableCell align=\"right\">{valueLabel}</TableCell>\n <TableCell />\n </TableRow>\n </TableHead>\n <TableBody>\n {[...activeDays].reverse().map((day) => (\n <TableRow key={day.date}>\n <TableCell sx={{whiteSpace: 'nowrap'}}>{formatDate(day.date)}</TableCell>\n <TableCell align=\"right\" sx={{fontVariantNumeric: 'tabular-nums'}}>{formatNumber(day.value)}</TableCell>\n <TableCell sx={{color: 'text.secondary'}}>{day.detail}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </Box>\n ) : (\n // el calendario no se encoge: en pantallas angostas se desliza y arranca mostrando lo más reciente\n <Box sx={{overflowX: 'auto', pb: 0.5}}\n ref={(node) => {\n if (node) {\n node.scrollLeft = node.scrollWidth;\n }\n }}>\n <Box role=\"img\" aria-label={ariaLabel}\n sx={{\n display: 'inline-grid',\n gridTemplateColumns: {\n xs: `auto repeat(${weeks.length}, ${CELL_PIXELS.xs}px)`,\n md: `auto repeat(${weeks.length}, ${CELL_PIXELS.md}px)`,\n },\n columnGap: `${GAP_PIXELS}px`,\n rowGap: `${GAP_PIXELS}px`,\n alignItems: 'center',\n }}>\n <Box />\n {monthMarks.map((month, column) => (\n <Typography key={`month-${column}`} variant=\"caption\" color=\"text.secondary\"\n sx={{fontSize: 10, lineHeight: 1, whiteSpace: 'nowrap', overflow: 'visible', width: CELL_PIXELS}}>\n {month ? formatMonthShortName(month) : ''}\n </Typography>\n ))}\n {[1, 2, 3, 4, 5, 6, 7].map((isoWeekday) => (\n <Box key={`weekday-${isoWeekday}`} sx={{display: 'contents'}}>\n <Typography variant=\"caption\" color=\"text.secondary\"\n sx={{fontSize: 10, lineHeight: {xs: `${CELL_PIXELS.xs}px`, md: `${CELL_PIXELS.md}px`}, pr: 0.75}}>\n {LABELED_WEEKDAYS.has(isoWeekday) ? formatWeekdayShortName(isoWeekday) : ''}\n </Typography>\n {weeks.map((week, column) => {\n const day = week[isoWeekday - 1];\n if (!day) {\n return <Box key={`outside-${column}-${isoWeekday}`} />;\n }\n const level = levelOf(day.value, thresholds);\n return (\n <Tooltip key={day.date} arrow disableInteractive\n title={`${formatDate(day.date)}: ${describeValue(day.value)}${day.detail ? ` · ${day.detail}` : ''}`}>\n <Box sx={(theme) => ({width: CELL_PIXELS, height: CELL_PIXELS, borderRadius: '3px', bgcolor: levelFill(theme, tone, level)})} />\n </Tooltip>\n );\n })}\n </Box>\n ))}\n </Box>\n </Box>\n )}\n\n {!asTable && (\n <Stack direction=\"row\" spacing={0.5} sx={{alignItems: 'center', justifyContent: 'flex-end', mt: 1}} aria-hidden=\"true\">\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{mr: 0.5}}>{t('charts.less')}</Typography>\n {HEATMAP_LEVELS.map((level) => (\n <Box key={level} sx={(theme) => ({width: CELL_PIXELS, height: CELL_PIXELS, borderRadius: '3px', bgcolor: levelFill(theme, tone, level)})} />\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ml: 0.5}}>{t('charts.more')}</Typography>\n </Stack>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ function ChartTooltip({ active, payload, render }) {
|
|
|
8
8
|
if (!active || !payload?.length) {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
-
return /* @__PURE__ */ jsx(Paper, { elevation: 3, sx: [{ px: 1.5, py: 1, pointerEvents: "none" }, (theme) => isGlassTheme(theme) ? glassStylesOf(theme, { opacity: CHART_TOOLTIP_OPACITY }) : {}], children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: render(payload[0].payload) }) });
|
|
11
|
+
return /* @__PURE__ */ jsx(Paper, { elevation: 3, sx: [{ px: 1.5, py: 1, pointerEvents: "none" }, (theme) => isGlassTheme(theme) ? glassStylesOf(theme, { opacity: CHART_TOOLTIP_OPACITY }) : {}], children: /* @__PURE__ */ jsx(Typography, { variant: "body2", component: "div", children: render(payload[0].payload) }) });
|
|
12
12
|
}
|
|
13
13
|
export {
|
|
14
14
|
ChartTooltip as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartTooltip.js","sources":["../../../src/ui/metrics/ChartTooltip.jsx"],"sourcesContent":["// El tooltip de las gráficas de Recharts con el look de la app
|
|
1
|
+
{"version":3,"file":"ChartTooltip.js","sources":["../../../src/ui/metrics/ChartTooltip.jsx"],"sourcesContent":["// El tooltip de las gráficas de Recharts con el look de la app. Se exporta para las gráficas propias del proyecto con\n// recharts (una de pastel, una de área): <Tooltip content={<ChartTooltip render={(row) => …} />} />.\n//\n// El de Recharts trae su propio estilo (fondo blanco, borde gris); este usa el Paper del tema y habla igual que los\n// tooltips del resto de la app (y se voltea solo cuando llegue el modo oscuro). Con el vidrio del tema\n// (createToolkitTheme({glass: true})) es de vidrio: flota sobre la gráfica.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Paper from '@mui/material/Paper';\nimport Typography from '@mui/material/Typography';\n\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\n\n// el vidrio del cuadrito, casi opaco (el material «grueso»): es para leer una cifra encima de la gráfica; con el vidrio de\n// los controles (60 %) se veía la barra de atrás a través del texto (lo reportaron)\nconst CHART_TOOLTIP_OPACITY = 0.92;\n\n/**\n * @param {{active?: boolean, payload?: Array<{payload: any}>, render: (item: any) => import('react').ReactNode}} props\n * active y payload los pone recharts; render recibe el renglón de data del punto o la columna\n */\nexport default function ChartTooltip({active, payload, render}) {\n // guard clause: Recharts lo monta siempre; solo se pinta sobre una barra\n if (!active || !payload?.length) {\n return null;\n }\n return (\n <Paper elevation={3} sx={[{px: 1.5, py: 1, pointerEvents: 'none'}, (theme) => (isGlassTheme(theme) ? glassStylesOf(theme, {opacity: CHART_TOOLTIP_OPACITY}) : {})]}>\n {/* div: el globo puede llevar varios renglones (una serie por renglón) */}\n <Typography variant=\"body2\" component=\"div\">{render(payload[0].payload)}</Typography>\n </Paper>\n );\n}\n"],"names":[],"mappings":";;;;;AAeA,MAAM,wBAAwB;AAM9B,SAAwB,aAAa,EAAC,QAAQ,SAAS,UAAS;AAE5D,MAAI,CAAC,UAAU,CAAC,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACX;AACA,6BACK,OAAA,EAAM,WAAW,GAAG,IAAI,CAAC,EAAC,IAAI,KAAK,IAAI,GAAG,eAAe,UAAS,CAAC,UAAW,aAAa,KAAK,IAAI,cAAc,OAAO,EAAC,SAAS,sBAAA,CAAsB,IAAI,EAAG,GAE7J,UAAA,oBAAC,YAAA,EAAW,SAAQ,SAAQ,WAAU,OAAO,UAAA,OAAO,QAAQ,CAAC,EAAE,OAAO,GAAE,GAC5E;AAER;"}
|