@tinya-code/gas-admin-ui 0.2.0-alpha.12 → 0.2.0-alpha.15
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 +222 -0
- package/ng-package.json +7 -0
- package/package.json +3 -15
- package/src/lib/actions/action-registry.spec.ts +52 -0
- package/src/lib/actions/action-registry.ts +126 -0
- package/src/lib/actions/action-service.spec.ts +215 -0
- package/src/lib/actions/action-service.ts +87 -0
- package/src/lib/actions/descriptors.spec.ts +155 -0
- package/src/lib/actions/descriptors.ts +154 -0
- package/src/lib/columns/column-cell.ts +33 -0
- package/src/lib/columns/column-defaults.spec.ts +99 -0
- package/src/lib/columns/column-defaults.ts +84 -0
- package/src/lib/columns/column-format.spec.ts +274 -0
- package/src/lib/columns/descriptors.ts +113 -0
- package/src/lib/columns/format-badge.ts +51 -0
- package/src/lib/columns/format-boolean.ts +58 -0
- package/src/lib/columns/format-currency.ts +30 -0
- package/src/lib/columns/format-date.ts +35 -0
- package/src/lib/columns/format-number.ts +32 -0
- package/src/lib/columns/format-text.ts +23 -0
- package/src/lib/columns/format-thumbnail.ts +63 -0
- package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
- package/src/lib/core/guards/auth.guard.spec.ts +88 -0
- package/src/lib/core/guards/auth.guard.ts +48 -0
- package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
- package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
- package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
- package/src/lib/core/models/api.model.ts +104 -0
- package/src/lib/core/models/schema-descriptions.ts +118 -0
- package/src/lib/core/models/schema.model.ts +1097 -0
- package/src/lib/core/registry/composition-validator.spec.ts +358 -0
- package/src/lib/core/registry/composition-validator.ts +270 -0
- package/src/lib/core/registry/field-validators.spec.ts +199 -0
- package/src/lib/core/registry/field-validators.ts +440 -0
- package/src/lib/core/registry/render-host.spec.ts +208 -0
- package/src/lib/core/registry/render-host.ts +646 -0
- package/src/lib/core/registry/render-registry.spec.ts +96 -0
- package/src/lib/core/registry/render-registry.ts +312 -0
- package/src/lib/core/registry/schema-catalog.ts +156 -0
- package/src/lib/core/services/api.service.spec.ts +1142 -0
- package/src/lib/core/services/api.service.ts +371 -0
- package/src/lib/core/services/auth.service.spec.ts +653 -0
- package/src/lib/core/services/auth.service.ts +503 -0
- package/src/lib/core/services/config.service.spec.ts +121 -0
- package/src/lib/core/services/config.service.ts +119 -0
- package/src/lib/core/services/data-source.service.spec.ts +179 -0
- package/src/lib/core/services/data-source.service.ts +142 -0
- package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
- package/src/lib/core/services/error-mapper.service.ts +49 -0
- package/src/lib/core/services/notification.service.ts +73 -0
- package/src/lib/core/services/relation-drawer.service.ts +41 -0
- package/src/lib/core/services/theme.service.spec.ts +106 -0
- package/src/lib/core/services/theme.service.ts +86 -0
- package/src/lib/core/services/transport.spec.ts +198 -0
- package/src/lib/core/services/transport.ts +164 -0
- package/src/lib/core/services/upload.service.ts +81 -0
- package/src/lib/core/testing/categories.schema.ts +125 -0
- package/src/lib/core/testing/orders.schema.ts +372 -0
- package/src/lib/core/testing/products.schema.ts +258 -0
- package/src/lib/core/testing/site.schema.ts +195 -0
- package/src/lib/core/testing/test-catalog.ts +21 -0
- package/src/lib/core/tokens.ts +112 -0
- package/src/lib/core/utils/clipboard.spec.ts +55 -0
- package/src/lib/core/utils/clipboard.ts +90 -0
- package/src/lib/core/utils/context-resolver.spec.ts +95 -0
- package/src/lib/core/utils/context-resolver.ts +101 -0
- package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
- package/src/lib/core/utils/response-unwrapper.ts +103 -0
- package/src/lib/core/utils/source-options.spec.ts +96 -0
- package/src/lib/core/utils/source-options.ts +54 -0
- package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
- package/src/lib/core/utils/template-interpolator.ts +56 -0
- package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
- package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
- package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
- package/src/lib/fields/boolean/boolean.ts +40 -0
- package/src/lib/fields/color/color.descriptor.ts +12 -0
- package/src/lib/fields/color/color.ts +34 -0
- package/src/lib/fields/currency/currency.descriptor.ts +12 -0
- package/src/lib/fields/currency/currency.ts +67 -0
- package/src/lib/fields/date/date.descriptor.ts +12 -0
- package/src/lib/fields/date/date.ts +36 -0
- package/src/lib/fields/descriptors.ts +122 -0
- package/src/lib/fields/display/display-badge.ts +65 -0
- package/src/lib/fields/display/display-date.ts +39 -0
- package/src/lib/fields/display/display-defaults.ts +84 -0
- package/src/lib/fields/display/display-file.ts +41 -0
- package/src/lib/fields/display/display-image.ts +54 -0
- package/src/lib/fields/display/display-list.ts +107 -0
- package/src/lib/fields/display/display-number.ts +41 -0
- package/src/lib/fields/display/display-text.ts +42 -0
- package/src/lib/fields/display/display.spec.ts +242 -0
- package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
- package/src/lib/fields/dropdown/dropdown.ts +45 -0
- package/src/lib/fields/email/email.descriptor.ts +12 -0
- package/src/lib/fields/email/email.ts +28 -0
- package/src/lib/fields/field-aria.ts +60 -0
- package/src/lib/fields/field-host/field-host.spec.ts +118 -0
- package/src/lib/fields/field-host/field-host.ts +47 -0
- package/src/lib/fields/field-node.ts +88 -0
- package/src/lib/fields/file/file.descriptor.ts +14 -0
- package/src/lib/fields/file/file.spec.ts +134 -0
- package/src/lib/fields/file/file.ts +281 -0
- package/src/lib/fields/group/group.descriptor.ts +21 -0
- package/src/lib/fields/group/group.ts +110 -0
- package/src/lib/fields/image/image.descriptor.ts +12 -0
- package/src/lib/fields/image/image.spec.ts +117 -0
- package/src/lib/fields/image/image.ts +281 -0
- package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
- package/src/lib/fields/key-value/key-value.ts +283 -0
- package/src/lib/fields/list/list.descriptor.ts +21 -0
- package/src/lib/fields/list/list.ts +376 -0
- package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
- package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
- package/src/lib/fields/multiselect/multiselect.ts +152 -0
- package/src/lib/fields/number/number.descriptor.ts +12 -0
- package/src/lib/fields/number/number.ts +40 -0
- package/src/lib/fields/phone/phone.descriptor.ts +12 -0
- package/src/lib/fields/phone/phone.ts +46 -0
- package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
- package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
- package/src/lib/fields/relation/relation.descriptor.ts +12 -0
- package/src/lib/fields/relation/relation.spec.ts +193 -0
- package/src/lib/fields/relation/relation.ts +224 -0
- package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
- package/src/lib/fields/rich-select/rich-select.ts +175 -0
- package/src/lib/fields/select/radio-mode.ts +25 -0
- package/src/lib/fields/select/select.descriptor.ts +12 -0
- package/src/lib/fields/select/select.spec.ts +162 -0
- package/src/lib/fields/select/select.ts +210 -0
- package/src/lib/fields/slug/slug.descriptor.ts +19 -0
- package/src/lib/fields/slug/slug.ts +67 -0
- package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
- package/src/lib/fields/string-list/string-list.ts +222 -0
- package/src/lib/fields/text/text.descriptor.ts +12 -0
- package/src/lib/fields/text/text.ts +35 -0
- package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
- package/src/lib/fields/textarea/textarea.spec.ts +117 -0
- package/src/lib/fields/textarea/textarea.ts +126 -0
- package/src/lib/fields/time/time.descriptor.ts +12 -0
- package/src/lib/fields/time/time.ts +28 -0
- package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
- package/src/lib/fields/url/url.descriptor.ts +12 -0
- package/src/lib/fields/url/url.ts +53 -0
- package/src/lib/gas-admin-ui.spec.ts +21 -0
- package/src/lib/gas-admin-ui.ts +9 -0
- package/src/lib/layouts/descriptors.ts +9 -0
- package/src/lib/pages/dashboard/dashboard.ts +167 -0
- package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
- package/src/lib/pages/detail-view/detail-view.ts +570 -0
- package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
- package/src/lib/pages/form-view/form-model.ts +88 -0
- package/src/lib/pages/form-view/form-schema.ts +255 -0
- package/src/lib/pages/form-view/form-view.ts +1622 -0
- package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
- package/src/lib/pages/list-view/list-view.ts +882 -0
- package/src/lib/pages/login/login.spec.ts +109 -0
- package/src/lib/pages/login/login.ts +227 -0
- package/src/lib/pages/resource-page/resource-page.ts +234 -0
- package/src/lib/shared/components/badge/badge.ts +35 -0
- package/src/lib/shared/components/button/button.ts +65 -0
- package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
- package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
- package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
- package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
- package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
- package/src/lib/shared/components/drawer/drawer.ts +172 -0
- package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
- package/src/lib/shared/components/modal/modal.spec.ts +149 -0
- package/src/lib/shared/components/modal/modal.ts +145 -0
- package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
- package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
- package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
- package/src/lib/shared/components/toast/toast.ts +58 -0
- package/src/lib/shared/utils/col-span.ts +25 -0
- package/src/lib/shared/utils/condition-evaluator.ts +53 -0
- package/src/lib/shared/utils/depends-on.ts +48 -0
- package/src/lib/shared/utils/dialog-focus.ts +28 -0
- package/src/lib/shared/utils/field-path.ts +49 -0
- package/src/lib/shared/utils/gap-sorting.ts +144 -0
- package/src/lib/shared/utils/seed.spec.ts +64 -0
- package/src/lib/shared/utils/seed.ts +149 -0
- package/src/lib/shared/utils/slugify.ts +13 -0
- package/src/lib/shared/utils/validators.ts +41 -0
- package/src/lib/shell/layout/layout.ts +63 -0
- package/src/lib/shell/shell.routes.spec.ts +59 -0
- package/src/lib/shell/shell.routes.ts +122 -0
- package/src/lib/shell/sidebar/sidebar.ts +139 -0
- package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
- package/src/lib/views/calendar-view/calendar-model.ts +426 -0
- package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
- package/src/lib/views/calendar-view/calendar-view.ts +568 -0
- package/src/lib/views/composer/composer-host.spec.ts +301 -0
- package/src/lib/views/composer/composer-host.ts +248 -0
- package/src/lib/views/composer/composer-imports.spec.ts +54 -0
- package/src/lib/views/composer/view-context.spec.ts +117 -0
- package/src/lib/views/composer/view-context.ts +109 -0
- package/src/lib/views/composer/view-slot.spec.ts +130 -0
- package/src/lib/views/composer/view-slot.ts +178 -0
- package/src/lib/views/descriptors.ts +46 -0
- package/src/lib/views/kanban-view/kanban-model.ts +173 -0
- package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
- package/src/lib/views/kanban-view/kanban-view.ts +327 -0
- package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
- package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
- package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
- package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
- package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
- package/src/lib/widgets/chart-line/chart-line.ts +219 -0
- package/src/lib/widgets/descriptors.ts +29 -0
- package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
- package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
- package/src/lib/widgets/metric-card/metric-card.ts +48 -0
- package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
- package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
- package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
- package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
- package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
- package/src/lib/widgets/record-list/record-list.ts +115 -0
- package/src/lib/widgets/series.spec.ts +42 -0
- package/src/lib/widgets/series.ts +56 -0
- package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
- package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
- package/src/lib/widgets/status-progress/status-progress.ts +104 -0
- package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
- package/src/lib/widgets/widget-host/widget-host.ts +99 -0
- package/src/public-api.ts +104 -0
- package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +10 -0
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
- package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
- package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
- package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
- package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
- package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { RouterOutlet } from '@angular/router';
|
|
3
|
+
|
|
4
|
+
import { Button } from '../../shared/components/button/button';
|
|
5
|
+
import { Sidebar } from '../sidebar/sidebar';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Esqueleto de la app autenticada: sidebar + área principal con outlet.
|
|
9
|
+
*
|
|
10
|
+
* Responsive: en pantallas chicas la sidebar es **offcanvas** (detrás de un
|
|
11
|
+
* botón hamburguesa + backdrop); desde `lg` vuelve a ser estática y siempre
|
|
12
|
+
* visible (clases `lg:*` del propio aside ganan sobre el estado `open`).
|
|
13
|
+
*/
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'app-layout',
|
|
16
|
+
imports: [RouterOutlet, Sidebar, Button],
|
|
17
|
+
template: `
|
|
18
|
+
<div class="flex min-h-screen bg-neutral/5">
|
|
19
|
+
<button
|
|
20
|
+
app-button
|
|
21
|
+
type="button"
|
|
22
|
+
variant="outline"
|
|
23
|
+
size="icon"
|
|
24
|
+
class="fixed top-[calc(env(safe-area-inset-top)+0.75rem)] left-3 z-40 shadow-md lg:hidden"
|
|
25
|
+
[attr.aria-expanded]="sidebarOpen()"
|
|
26
|
+
aria-controls="main-sidebar"
|
|
27
|
+
aria-label="Abrir menú"
|
|
28
|
+
(click)="sidebarOpen.set(true)"
|
|
29
|
+
>
|
|
30
|
+
<svg
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
class="size-5"
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
stroke-width="2"
|
|
36
|
+
viewBox="0 0 24 24"
|
|
37
|
+
>
|
|
38
|
+
<path stroke-linecap="round" d="M4 6h16M4 12h16M4 18h16" />
|
|
39
|
+
</svg>
|
|
40
|
+
</button>
|
|
41
|
+
@if (sidebarOpen()) {
|
|
42
|
+
<div
|
|
43
|
+
class="fixed inset-0 z-30 bg-black/30 lg:hidden"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
(click)="sidebarOpen.set(false)"
|
|
46
|
+
></div>
|
|
47
|
+
}
|
|
48
|
+
<app-sidebar id="main-sidebar" [(open)]="sidebarOpen" />
|
|
49
|
+
<!-- pt reafirmado en sm: sin esto la variante sm:p-6 (va después en la
|
|
50
|
+
cascada) pisa el pt-16 base y el contenido choca con la hamburguesa.
|
|
51
|
+
base/sm dejan pasar el safe-area del notch; en lg no hay hamburguesa. -->
|
|
52
|
+
<main
|
|
53
|
+
class="min-w-0 flex-1 p-4 pt-[calc(4rem+env(safe-area-inset-top))] sm:p-6 sm:pt-[calc(4rem+env(safe-area-inset-top))] lg:p-8"
|
|
54
|
+
>
|
|
55
|
+
<router-outlet />
|
|
56
|
+
</main>
|
|
57
|
+
</div>
|
|
58
|
+
`,
|
|
59
|
+
})
|
|
60
|
+
export class Layout {
|
|
61
|
+
/** Estado del offcanvas en mobile; en `lg` la sidebar es estática (siempre visible). */
|
|
62
|
+
readonly sidebarOpen = signal(false);
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Route, Routes } from '@angular/router';
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import type { ResourceSchema } from '../core/models/schema.model';
|
|
5
|
+
import { allSchemas, getAuth, setSchemaCatalog } from '../core/registry/schema-catalog';
|
|
6
|
+
import { shellRoutes } from './shell.routes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* R11 · orden de producción (regresión): `app.routes.ts` importa el barrel
|
|
10
|
+
* del paquete ANTES de que `provideGasAdmin` siembre el catálogo (corre en
|
|
11
|
+
* el cuerpo de `app.config`). Si `shellRoutes` construye sus hijos en la
|
|
12
|
+
* evaluación del módulo, quedan `[]` para siempre y toda URL protegida
|
|
13
|
+
* renderiza vacío — el paquete compilado es eager aunque los specs, con el
|
|
14
|
+
* transform de Vite, no lo sean.
|
|
15
|
+
*/
|
|
16
|
+
const schemaDemo: ResourceSchema = {
|
|
17
|
+
id: 'demo',
|
|
18
|
+
label: 'Demo',
|
|
19
|
+
labelPlural: 'Demos',
|
|
20
|
+
kind: 'collection',
|
|
21
|
+
endpoint: {},
|
|
22
|
+
keyField: 'id',
|
|
23
|
+
titleField: 'name',
|
|
24
|
+
listColumns: [],
|
|
25
|
+
fields: [],
|
|
26
|
+
uiViews: {
|
|
27
|
+
list: { type: 'list' },
|
|
28
|
+
det: { type: 'detail', path: ':key' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function materializar(): Promise<Route[]> {
|
|
33
|
+
const loader = shellRoutes[0]?.loadChildren;
|
|
34
|
+
expect(typeof loader, 'shellRoutes debe construir sus hijos al navegarse').toBe('function');
|
|
35
|
+
const hijos = await (loader as () => Promise<Routes> | Routes)();
|
|
36
|
+
const layout = hijos.find((route) => route.path === '');
|
|
37
|
+
return layout?.children ?? hijos;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('shellRoutes en orden de producción (import del barrel antes del seed)', () => {
|
|
41
|
+
const previo = { schemas: allSchemas(), auth: getAuth() };
|
|
42
|
+
afterEach(() => setSchemaCatalog(previo.schemas, previo.auth));
|
|
43
|
+
|
|
44
|
+
it('materializa las rutas cuando se navega, no al importar el módulo', async () => {
|
|
45
|
+
setSchemaCatalog([schemaDemo], undefined);
|
|
46
|
+
|
|
47
|
+
const rutas = (await materializar()).map((route) => route.path);
|
|
48
|
+
expect(rutas).toContain('demo');
|
|
49
|
+
expect(rutas).toContain('demo/:key');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('con el catálogo vacío no revienta: devuelve cero rutas y el seed posterior surte efecto', async () => {
|
|
53
|
+
setSchemaCatalog([], undefined);
|
|
54
|
+
expect(await materializar()).toEqual([]);
|
|
55
|
+
|
|
56
|
+
setSchemaCatalog([schemaDemo], undefined);
|
|
57
|
+
expect((await materializar()).map((route) => route.path)).toContain('demo');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Route, Routes } from '@angular/router';
|
|
2
|
+
import { inject } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
import { validateComposition } from '../core/registry/composition-validator';
|
|
5
|
+
import type { ResourceSchema } from '../core/models/schema.model';
|
|
6
|
+
import { get } from '../core/registry/render-registry';
|
|
7
|
+
import { roleGuard } from '../core/guards/auth.guard';
|
|
8
|
+
import { unsavedChangesGuard } from '../core/guards/unsaved-changes.guard';
|
|
9
|
+
import { ViewNavigationService } from '../core/view-navigation/view-navigation.service';
|
|
10
|
+
import { allSchemas } from '../core/registry/schema-catalog';
|
|
11
|
+
import { createViewContext } from '../views/composer/view-context';
|
|
12
|
+
import { Layout } from './layout/layout';
|
|
13
|
+
|
|
14
|
+
function pathRank(path: string): number[] {
|
|
15
|
+
const segments = path.split('/');
|
|
16
|
+
return [segments.length, segments.filter((segment) => !segment.startsWith(':')).length];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function compareRoutes(a: Route, b: Route): number {
|
|
20
|
+
const aRank = pathRank(a.path ?? '');
|
|
21
|
+
const bRank = pathRank(b.path ?? '');
|
|
22
|
+
return bRank[0] - aRank[0] || bRank[1] - aRank[1];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Rutas de los schemas recibidos (por defecto, la registry completa).
|
|
27
|
+
* Cada schema se valida con `validateComposition` ANTES de generar rutas
|
|
28
|
+
* (D3): una composición inválida revienta al arrancar, no en el render.
|
|
29
|
+
*/
|
|
30
|
+
export function resourceRoutes(catalog: readonly ResourceSchema[] = allSchemas()): Route[] {
|
|
31
|
+
return catalog
|
|
32
|
+
.flatMap((schema) => {
|
|
33
|
+
if (!schema.uiViews || Object.keys(schema.uiViews).length === 0) {
|
|
34
|
+
throw new Error(`El recurso '${schema.id}' no declara uiViews`);
|
|
35
|
+
}
|
|
36
|
+
validateComposition(schema);
|
|
37
|
+
return Object.entries(schema.uiViews).map(([name, view]) => {
|
|
38
|
+
const descriptor = get('view', view.type);
|
|
39
|
+
if (!descriptor) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`No existe descriptor kind:view type '${view.type}' para ${schema.id}.${name}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const relativePath = view.path ?? '';
|
|
45
|
+
if (
|
|
46
|
+
relativePath.startsWith('/') ||
|
|
47
|
+
relativePath.endsWith('/') ||
|
|
48
|
+
(relativePath !== '' &&
|
|
49
|
+
relativePath
|
|
50
|
+
.split('/')
|
|
51
|
+
.some((segment) => segment === '' || segment === '.' || segment === '..'))
|
|
52
|
+
) {
|
|
53
|
+
throw new Error(`Ruta uiViews inválida en ${schema.id}.${name}: '${relativePath}'`);
|
|
54
|
+
}
|
|
55
|
+
const path = [schema.id, relativePath].filter(Boolean).join('/');
|
|
56
|
+
const route: Route = {
|
|
57
|
+
path,
|
|
58
|
+
loadComponent: descriptor.load,
|
|
59
|
+
canActivate: [roleGuard],
|
|
60
|
+
data: {
|
|
61
|
+
resourceId: schema.id,
|
|
62
|
+
uiView: name,
|
|
63
|
+
viewType: view.type,
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* R9.9 · D1/D4 — el payload lleva SIEMPRE el `select` declarado
|
|
67
|
+
* (cualquier mode) para que la hoja aplique la decision table; el
|
|
68
|
+
* `emit` se liga aquí (contexto de inyección) a
|
|
69
|
+
* `ViewNavigationService.dispatch`, que resuelve el target,
|
|
70
|
+
* construye los segmentos y respeta guards + historial. Sólo un
|
|
71
|
+
* binding `mode:'route'` lleva `emit`; `none`/`inline`/ausente ⇒
|
|
72
|
+
* payload sin emisor.
|
|
73
|
+
*/
|
|
74
|
+
resolve: {
|
|
75
|
+
viewContext: () => {
|
|
76
|
+
const base = {
|
|
77
|
+
resourceId: schema.id,
|
|
78
|
+
uiView: name,
|
|
79
|
+
viewType: view.type,
|
|
80
|
+
...(view.select !== undefined ? { select: view.select } : {}),
|
|
81
|
+
};
|
|
82
|
+
const select = view.select;
|
|
83
|
+
if (select?.mode !== 'route') return createViewContext(base);
|
|
84
|
+
const nav = inject(ViewNavigationService);
|
|
85
|
+
return createViewContext(base, (event) => {
|
|
86
|
+
void nav.dispatch(select, event);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
if (view.type === 'form') route.canDeactivate = [unsavedChangesGuard];
|
|
92
|
+
return route;
|
|
93
|
+
});
|
|
94
|
+
})
|
|
95
|
+
.sort(compareRoutes);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Las rutas del shell salen de `uiViews`, no de `ResourceSchema.views` (que
|
|
100
|
+
* conserva los metadatos de consultas del backend). Añadir o cambiar una
|
|
101
|
+
* página requiere una declaración y un descriptor `kind: 'view'`.
|
|
102
|
+
*
|
|
103
|
+
* Los hijos se construyen DENTRO del `loadChildren` (en la primera
|
|
104
|
+
* navegación), nunca en la evaluación del módulo: el consumidor importa
|
|
105
|
+
* este barrel desde `app.routes.ts` ANTES de que `provideGasAdmin` siembre
|
|
106
|
+
* el catálogo (corre en el cuerpo de `app.config`), y el paquete compilado
|
|
107
|
+
* es eager — construir en el import congelaría `children: []` para siempre
|
|
108
|
+
* y toda URL protegida renderizaría vacío (regresión: shell.routes.spec en
|
|
109
|
+
* orden de producción).
|
|
110
|
+
*/
|
|
111
|
+
export const shellRoutes: Routes = [
|
|
112
|
+
{
|
|
113
|
+
path: '',
|
|
114
|
+
loadChildren: () => [
|
|
115
|
+
{
|
|
116
|
+
path: '',
|
|
117
|
+
component: Layout,
|
|
118
|
+
children: resourceRoutes(),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Component, computed, inject, model, signal } from '@angular/core';
|
|
2
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
3
|
+
import { LucideLogOut } from '@lucide/angular';
|
|
4
|
+
|
|
5
|
+
import { allSchemas } from '../../core/registry/schema-catalog';
|
|
6
|
+
import { AuthService } from '../../core/services/auth.service';
|
|
7
|
+
import { NotificationService } from '../../core/services/notification.service';
|
|
8
|
+
import { Button } from '../../shared/components/button/button';
|
|
9
|
+
import { ThemeToggle } from '../theme-toggle/theme-toggle';
|
|
10
|
+
import type { ResourceSchema } from '../../core/models/schema.model';
|
|
11
|
+
|
|
12
|
+
interface MenuEntry {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Menú lateral generado desde el catálogo EFECTIVO (F7-4): locales
|
|
19
|
+
* fusionados + recursos sintetizados del schema remoto — un recurso nuevo
|
|
20
|
+
* del backend aparece acá con 0 cambios en `src/`. Reactivo a la señal del
|
|
21
|
+
* schema remoto (el initializer lo resuelve antes del primer render, pero
|
|
22
|
+
* el `computed` cubre el caso de respuesta tardía).
|
|
23
|
+
* `labelPlural` + `permissions`: un recurso se oculta solo si declara
|
|
24
|
+
* create, update y remove explícitamente en falso (no hay permiso de
|
|
25
|
+
* lectura).
|
|
26
|
+
*/
|
|
27
|
+
@Component({
|
|
28
|
+
selector: 'app-sidebar',
|
|
29
|
+
imports: [RouterLink, RouterLinkActive, ThemeToggle, Button, LucideLogOut],
|
|
30
|
+
template: `
|
|
31
|
+
<!-- Offcanvas en mobile (transform); las clases lg: la dejan estática
|
|
32
|
+
y siempre visible en pantallas grandes. -->
|
|
33
|
+
<aside
|
|
34
|
+
class="fixed inset-y-0 left-0 z-40 w-60 shrink-0 h-[100svh] min-h-[80svh] lg:sticky border-r border-neutral/20 bg-surface-raised transition-transform duration-200 lg:static lg:z-auto lg:translate-x-0"
|
|
35
|
+
[class.-translate-x-full]="!open()"
|
|
36
|
+
>
|
|
37
|
+
<div class="flex items-center justify-between gap-2 border-b border-neutral/20 px-4 py-4">
|
|
38
|
+
<!-- min-w-0+truncate: si el toggle exige su ancho, el rótulo se
|
|
39
|
+
trunca en vez de desbordar la sidebar de 240 px. -->
|
|
40
|
+
<span class="min-w-0 truncate font-display text-base font-semibold">Admin</span>
|
|
41
|
+
<app-theme-toggle />
|
|
42
|
+
</div>
|
|
43
|
+
<nav aria-label="Principal" class="p-3">
|
|
44
|
+
<ul class="space-y-2 lg:space-y-1">
|
|
45
|
+
@for (item of menuItems(); track item.id) {
|
|
46
|
+
<li>
|
|
47
|
+
<a
|
|
48
|
+
[routerLink]="['/', item.id]"
|
|
49
|
+
routerLinkActive="bg-primary/10 text-primary"
|
|
50
|
+
class="flex min-h-11 items-center rounded-lg px-3 py-2 text-sm font-medium text-neutral hover:bg-neutral/10"
|
|
51
|
+
(click)="open.set(false)"
|
|
52
|
+
>
|
|
53
|
+
{{ item.label }}
|
|
54
|
+
</a>
|
|
55
|
+
</li>
|
|
56
|
+
}
|
|
57
|
+
</ul>
|
|
58
|
+
</nav>
|
|
59
|
+
@if (isAuthenticated()) {
|
|
60
|
+
<div class="border-t border-neutral/20 p-3">
|
|
61
|
+
<button
|
|
62
|
+
app-button
|
|
63
|
+
type="button"
|
|
64
|
+
variant="danger"
|
|
65
|
+
[loading]="loggingOut()"
|
|
66
|
+
[disabled]="loggingOut()"
|
|
67
|
+
(click)="closeSession()"
|
|
68
|
+
class="w-full"
|
|
69
|
+
>
|
|
70
|
+
<svg lucideLogOut size="16" />
|
|
71
|
+
Cerrar sesión
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
}
|
|
75
|
+
</aside>
|
|
76
|
+
`,
|
|
77
|
+
})
|
|
78
|
+
export class Sidebar {
|
|
79
|
+
/** Estado del offcanvas (sólo relevante en mobile; ver `layout.ts`).
|
|
80
|
+
* `model()`: el padre lo setea (abrir) y el hijo también (cerrar al
|
|
81
|
+
* navegar) — binding de dos vías `[(open)]`. */
|
|
82
|
+
readonly open = model(false);
|
|
83
|
+
|
|
84
|
+
private readonly auth = inject(AuthService);
|
|
85
|
+
private readonly notifications = inject(NotificationService);
|
|
86
|
+
readonly isAuthenticated = this.auth.isAuthenticated;
|
|
87
|
+
readonly loggingOut = signal(false);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* R2.10 — el menú se corta dos veces: `isVisible` (permisos declarados
|
|
91
|
+
* en el schema) y `canAccess` (auth.md §5.3, `access` de la sesión).
|
|
92
|
+
* Sólo UX: filtrar acá no protege nada, el back re-verifica en R2.6.
|
|
93
|
+
*/
|
|
94
|
+
readonly menuItems = computed<MenuEntry[]>(() =>
|
|
95
|
+
allSchemas()
|
|
96
|
+
.filter((schema) => isVisible(schema) && this.auth.canAccess(schema.access))
|
|
97
|
+
.map((schema) => ({
|
|
98
|
+
id: schema.id,
|
|
99
|
+
label: schema.labelPlural,
|
|
100
|
+
})),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
async closeSession(): Promise<void> {
|
|
104
|
+
const accepted = await this.notifications.confirm({
|
|
105
|
+
title: 'Cerrar sesión',
|
|
106
|
+
message: '¿Seguro que querés cerrar tu sesión?',
|
|
107
|
+
confirmLabel: 'Cerrar sesión',
|
|
108
|
+
});
|
|
109
|
+
if (!accepted) return;
|
|
110
|
+
|
|
111
|
+
this.loggingOut.set(true);
|
|
112
|
+
this.auth.logout().subscribe({
|
|
113
|
+
complete: () => this.loggingOut.set(false),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isVisible(schema: ResourceSchema): boolean {
|
|
119
|
+
// R7.5: usar policies cuando existan, fallback a permissions
|
|
120
|
+
if (schema.policies?.access) {
|
|
121
|
+
// Con policies, asumimos que si el usuario tiene sesión puede ver el recurso
|
|
122
|
+
// (el filtrado real lo hace `canAccess` en el computed, que evalúa el rol).
|
|
123
|
+
// Esta función sólo puede aplicar criterios estáticos, no dinámicos.
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback a permissions (legacy)
|
|
128
|
+
const permissions = schema.permissions;
|
|
129
|
+
if (!permissions) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
// Un recurso de sólo lectura (todas las escrituras en false) sigue siendo
|
|
133
|
+
// visible si tiene endpoint de lectura (list/get): no se oculta por ser
|
|
134
|
+
// read-only. Sólo se oculta si no puede leer NI escribir.
|
|
135
|
+
const hasReadEndpoint = !!schema.endpoint?.list || !!schema.endpoint?.get;
|
|
136
|
+
const canWrite =
|
|
137
|
+
permissions.create !== false || permissions.update !== false || permissions.remove !== false;
|
|
138
|
+
return hasReadEndpoint || canWrite;
|
|
139
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
|
+
import { LucideMonitor, LucideMoon, LucideSun } from '@lucide/angular';
|
|
3
|
+
|
|
4
|
+
import { ThemeService, type ThemeMode } from '../../core/services/theme.service';
|
|
5
|
+
|
|
6
|
+
const BASE =
|
|
7
|
+
'rounded-md p-1.5 transition-colors ' +
|
|
8
|
+
// 44×44 en mobile (guía ux-ui-mobile §8.2); en lg vuelve a 28 px (ratón).
|
|
9
|
+
'min-h-11 min-w-11 lg:min-h-0 lg:min-w-0 ' +
|
|
10
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary';
|
|
11
|
+
const ACTIVE = 'bg-primary/10 text-primary';
|
|
12
|
+
const IDLE = 'text-neutral hover:bg-neutral/10';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Selector de tema de tres posiciones: Sistema / Claro / Oscuro (§8.6).
|
|
16
|
+
*
|
|
17
|
+
* Van tres `<button>` con `aria-pressed` y no un `role="radiogroup"`: un
|
|
18
|
+
* radiogroup exige navegación con flechas para cumplir su contrato, mientras
|
|
19
|
+
* que tres botones ya son operables con Tab + Enter/Space y el lector de
|
|
20
|
+
* pantalla anuncia "presionado / no presionado" en cada uno. Es la opción
|
|
21
|
+
* con menos JS y sin degradar el teclado.
|
|
22
|
+
*
|
|
23
|
+
* Sólo icono, con `aria-label` y `title` para no ocupar ancho en la cabecera
|
|
24
|
+
* de la sidebar (240 px).
|
|
25
|
+
*/
|
|
26
|
+
@Component({
|
|
27
|
+
selector: 'app-theme-toggle',
|
|
28
|
+
imports: [LucideMonitor, LucideSun, LucideMoon],
|
|
29
|
+
template: `
|
|
30
|
+
<div
|
|
31
|
+
role="group"
|
|
32
|
+
aria-label="Tema del panel"
|
|
33
|
+
class="inline-flex items-center gap-0.5 rounded-lg border border-neutral/20 bg-surface p-0.5"
|
|
34
|
+
>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
[class]="classes('system')"
|
|
38
|
+
[attr.aria-pressed]="theme.mode() === 'system'"
|
|
39
|
+
aria-label="Tema: seguir al sistema"
|
|
40
|
+
title="Seguir al sistema"
|
|
41
|
+
(click)="theme.set('system')"
|
|
42
|
+
>
|
|
43
|
+
<svg lucideMonitor size="16" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
[class]="classes('light')"
|
|
48
|
+
[attr.aria-pressed]="theme.mode() === 'light'"
|
|
49
|
+
aria-label="Tema: claro"
|
|
50
|
+
title="Tema claro"
|
|
51
|
+
(click)="theme.set('light')"
|
|
52
|
+
>
|
|
53
|
+
<svg lucideSun size="16" />
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
[class]="classes('dark')"
|
|
58
|
+
[attr.aria-pressed]="theme.mode() === 'dark'"
|
|
59
|
+
aria-label="Tema: oscuro"
|
|
60
|
+
title="Tema oscuro"
|
|
61
|
+
(click)="theme.set('dark')"
|
|
62
|
+
>
|
|
63
|
+
<svg lucideMoon size="16" />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class ThemeToggle {
|
|
69
|
+
readonly theme = inject(ThemeService);
|
|
70
|
+
|
|
71
|
+
classes(mode: ThemeMode): string {
|
|
72
|
+
return `${BASE} ${this.theme.mode() === mode ? ACTIVE : IDLE}`;
|
|
73
|
+
}
|
|
74
|
+
}
|