@tinya-code/gas-admin-ui 0.2.0-alpha.13 → 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/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,570 @@
|
|
|
1
|
+
import { Component, computed, inject, input, output, signal } from '@angular/core';
|
|
2
|
+
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
3
|
+
import { Router } from '@angular/router';
|
|
4
|
+
|
|
5
|
+
import { ViewNavigationService } from '../../core/view-navigation/view-navigation.service';
|
|
6
|
+
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
LucideArrowLeft,
|
|
10
|
+
LucideExternalLink,
|
|
11
|
+
LucidePencil,
|
|
12
|
+
LucideRotateCw,
|
|
13
|
+
LucideTrash2,
|
|
14
|
+
} from '@lucide/angular';
|
|
15
|
+
import type {
|
|
16
|
+
CopyShareAction,
|
|
17
|
+
DetailViewSchema,
|
|
18
|
+
FieldSchema,
|
|
19
|
+
ResourceAction,
|
|
20
|
+
ResourceSchema,
|
|
21
|
+
TriggerAction,
|
|
22
|
+
} from '../../core/models/schema.model';
|
|
23
|
+
import {
|
|
24
|
+
isShareAction,
|
|
25
|
+
isTriggerAction,
|
|
26
|
+
resolveShare,
|
|
27
|
+
type ResolvedShare,
|
|
28
|
+
} from '../../actions/action-registry';
|
|
29
|
+
import { ActionService } from '../../actions/action-service';
|
|
30
|
+
import { RenderHost } from '../../core/registry/render-host';
|
|
31
|
+
import { ApiService } from '../../core/services/api.service';
|
|
32
|
+
import { AuthService } from '../../core/services/auth.service';
|
|
33
|
+
import { ConfigService } from '../../core/services/config.service';
|
|
34
|
+
import { NotificationService } from '../../core/services/notification.service';
|
|
35
|
+
import { Button } from '../../shared/components/button/button';
|
|
36
|
+
import { CopyShare } from '../../shared/components/copy-share/copy-share';
|
|
37
|
+
import { CopyButton } from '../../shared/components/copy-button/copy-button';
|
|
38
|
+
import { EmptyState } from '../../shared/components/empty-state/empty-state';
|
|
39
|
+
import { Skeleton } from '../../shared/components/skeleton/skeleton';
|
|
40
|
+
import { getValue } from '../../shared/utils/field-path';
|
|
41
|
+
|
|
42
|
+
type Row = Record<string, unknown>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Estado etiquetado del `get` del registro.
|
|
46
|
+
*
|
|
47
|
+
* `pending` distingue «todavía viaja» de «terminó sin datos» (sin
|
|
48
|
+
* `endpoint.get` contratado o sin key): antes ambos eran `null` y el
|
|
49
|
+
* detalle pintaba la fila parcial de la lista como si fuera todo, con los
|
|
50
|
+
* campos pesados en `—` que después mutaban solos (cargar ≠ vacío,
|
|
51
|
+
* doc/ux-ui-mobile.md §estados de UI).
|
|
52
|
+
*/
|
|
53
|
+
type FetchState =
|
|
54
|
+
| { phase: 'pending' }
|
|
55
|
+
| { phase: 'done'; data: Row | null }
|
|
56
|
+
/** D4: el error ORIGINAL se retiene para diagnóstico (identity intacta). */
|
|
57
|
+
| { phase: 'error'; error: unknown };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Campo ya resuelto contra el registro, **sin formatear**.
|
|
61
|
+
*
|
|
62
|
+
* Antes de R4.2 este objeto traía `value` + `kind` + `badgeKind`, que era
|
|
63
|
+
* como `detail-view` derivaba a mano qué pintar (`image | badge | text`).
|
|
64
|
+
* Hoy sólo aporta agrupación —clave, etiqueta, campo y valor crudo— y el
|
|
65
|
+
* pintado lo decide el registro vía `<app-render-host mode="display">`.
|
|
66
|
+
*/
|
|
67
|
+
interface FieldValue {
|
|
68
|
+
key: string;
|
|
69
|
+
label: string;
|
|
70
|
+
/** Campo del schema (o un `text` sintético si la clave no existe). */
|
|
71
|
+
field: FieldSchema;
|
|
72
|
+
/** Valor crudo del registro. */
|
|
73
|
+
raw: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface DetailSection {
|
|
77
|
+
id: string;
|
|
78
|
+
label: string;
|
|
79
|
+
fields: FieldValue[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Vista de detalle genérica: muestra los campos de un registro en modo lectura.
|
|
84
|
+
* Incluye cabecera estilizada, timeline interactivo para listas anidadas
|
|
85
|
+
* (como history) y barra de acciones.
|
|
86
|
+
*
|
|
87
|
+
* **R4.2 · F3 (`refactormotor.md`):** dejó de derivar `kind` a mano y de
|
|
88
|
+
* tener plantilla propia de campo. Agrupa (`sections()`, `resolveFieldValue()`,
|
|
89
|
+
* el `<dt>`/`<dd>`, el bloque de historial y `hasLargeContent()`) y el valor
|
|
90
|
+
* lo pinta el registro con `mode="display"` — la misma tabla por tipo que
|
|
91
|
+
* cualquier otra superficie. Lo único que queda por fuera son las secciones
|
|
92
|
+
* que no son campos: cabecera, `copy-share` y acciones.
|
|
93
|
+
*/
|
|
94
|
+
@Component({
|
|
95
|
+
selector: 'app-detail-view',
|
|
96
|
+
imports: [
|
|
97
|
+
Button,
|
|
98
|
+
CopyShare,
|
|
99
|
+
CopyButton,
|
|
100
|
+
EmptyState,
|
|
101
|
+
LucideArrowLeft,
|
|
102
|
+
LucideExternalLink,
|
|
103
|
+
LucidePencil,
|
|
104
|
+
LucideRotateCw,
|
|
105
|
+
LucideTrash2,
|
|
106
|
+
RenderHost,
|
|
107
|
+
Skeleton,
|
|
108
|
+
],
|
|
109
|
+
template: `
|
|
110
|
+
<div class="space-y-6">
|
|
111
|
+
<!-- ── Cabecera Principal ── -->
|
|
112
|
+
<header
|
|
113
|
+
class="flex flex-wrap items-start justify-between gap-4 rounded-2xl border border-neutral/15 bg-surface p-6 shadow-xs"
|
|
114
|
+
>
|
|
115
|
+
<div class="space-y-2">
|
|
116
|
+
<div class="flex flex-wrap items-center gap-2.5">
|
|
117
|
+
<h1 class="font-display text-2xl font-bold tracking-tight text-neutral-light">
|
|
118
|
+
{{ title() }}
|
|
119
|
+
</h1>
|
|
120
|
+
@for (campo of headerFields(); track campo.key) {
|
|
121
|
+
@if (!vacio(campo.raw)) {
|
|
122
|
+
<!-- La cabecera siempre mostró los headerFields como chips:
|
|
123
|
+
el matiz viaja al host, no un @switch por tipo acá. -->
|
|
124
|
+
<app-render-host
|
|
125
|
+
mode="display"
|
|
126
|
+
[chip]="true"
|
|
127
|
+
[type]="campo.field.type"
|
|
128
|
+
[config]="campo.field"
|
|
129
|
+
[value]="campo.raw"
|
|
130
|
+
/>
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
</div>
|
|
134
|
+
<p class="text-sm text-neutral">
|
|
135
|
+
{{ schema().label }} · Identificador:
|
|
136
|
+
<span class="font-mono font-medium text-neutral-light">{{ recordKey() }}</span>
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div class="flex flex-wrap items-center gap-2.5">
|
|
141
|
+
<button app-button variant="outline" type="button" (click)="back.emit()">
|
|
142
|
+
<svg lucideArrowLeft size="16" />
|
|
143
|
+
Volver
|
|
144
|
+
</button>
|
|
145
|
+
@if (detail().allowEdit !== false && status() !== 'error') {
|
|
146
|
+
<button app-button variant="primary" type="button" (click)="edit.emit(record())">
|
|
147
|
+
<svg lucidePencil size="16" />
|
|
148
|
+
Editar
|
|
149
|
+
</button>
|
|
150
|
+
}
|
|
151
|
+
@if (detail().allowDelete !== false && status() !== 'error') {
|
|
152
|
+
<button app-button variant="danger" type="button" (click)="delete.emit(record())">
|
|
153
|
+
<svg lucideTrash2 size="16" />
|
|
154
|
+
Eliminar
|
|
155
|
+
</button>
|
|
156
|
+
}
|
|
157
|
+
</div>
|
|
158
|
+
</header>
|
|
159
|
+
|
|
160
|
+
<!-- ── Acciones Destacadas del Registro (si existen) ── -->
|
|
161
|
+
@if (copyShareActions().length > 0 || triggerActions().length > 0) {
|
|
162
|
+
<section
|
|
163
|
+
class="rounded-2xl border border-primary/25 bg-gradient-to-r from-primary/10 via-primary/5 to-transparent p-5 shadow-xs"
|
|
164
|
+
>
|
|
165
|
+
<div class="mb-3 flex items-center gap-2">
|
|
166
|
+
<svg lucideExternalLink size="16" class="text-primary" />
|
|
167
|
+
<h2 class="text-sm font-semibold text-primary">Acciones del Registro</h2>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap">
|
|
170
|
+
@for (action of copyShareActions(); track action.id) {
|
|
171
|
+
<div class="flex-1 min-w-[280px]">
|
|
172
|
+
@let share = shareOf(action);
|
|
173
|
+
<app-copy-share
|
|
174
|
+
[url]="share.url"
|
|
175
|
+
[shareText]="share.shareText"
|
|
176
|
+
[label]="action.label"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
}
|
|
180
|
+
@for (action of triggerActions(); track action.id) {
|
|
181
|
+
<button app-button variant="outline" type="button" (click)="runTrigger(action)">
|
|
182
|
+
{{ action.label }}
|
|
183
|
+
</button>
|
|
184
|
+
}
|
|
185
|
+
</div>
|
|
186
|
+
</section>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
<!-- ── Secciones de Datos ── -->
|
|
190
|
+
@switch (status()) {
|
|
191
|
+
@case ('loading') {
|
|
192
|
+
<!-- Skeleton con la forma de las tarjetas de sección: reserva el
|
|
193
|
+
espacio (sin layout jump) y distingue «cargando» de «vacío».
|
|
194
|
+
La cabecera y las acciones de arriba sí se pintan ya: son la
|
|
195
|
+
fila de la lista, datos reales que no están en disputa. -->
|
|
196
|
+
<div aria-busy="true" class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
197
|
+
@for (placeholder of [0, 1]; track placeholder) {
|
|
198
|
+
<section
|
|
199
|
+
class="rounded-2xl border border-neutral/15 bg-surface shadow-xs overflow-hidden"
|
|
200
|
+
>
|
|
201
|
+
<header class="border-b border-neutral/10 bg-neutral/5 px-5 py-3.5">
|
|
202
|
+
<app-skeleton [lines]="1" [height]="14" width="30%" />
|
|
203
|
+
</header>
|
|
204
|
+
<div class="p-5">
|
|
205
|
+
<app-skeleton [lines]="6" [height]="44" />
|
|
206
|
+
</div>
|
|
207
|
+
</section>
|
|
208
|
+
}
|
|
209
|
+
</div>
|
|
210
|
+
}
|
|
211
|
+
@case ('error') {
|
|
212
|
+
<app-empty-state
|
|
213
|
+
title="No se pudo cargar el registro"
|
|
214
|
+
message="Revisa tu conexión e intenta de nuevo."
|
|
215
|
+
>
|
|
216
|
+
<button app-button variant="outline" type="button" (click)="retry()">
|
|
217
|
+
<svg lucideRotateCw size="16" />
|
|
218
|
+
Reintentar
|
|
219
|
+
</button>
|
|
220
|
+
<button app-button variant="outline" type="button" (click)="volverDeError()">
|
|
221
|
+
<svg lucideArrowLeft size="16" />
|
|
222
|
+
Volver
|
|
223
|
+
</button>
|
|
224
|
+
</app-empty-state>
|
|
225
|
+
}
|
|
226
|
+
@case ('ready') {
|
|
227
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
228
|
+
@for (section of sections(); track section.id) {
|
|
229
|
+
<section
|
|
230
|
+
class="rounded-2xl border border-neutral/15 bg-surface shadow-xs overflow-hidden flex flex-col"
|
|
231
|
+
[class.lg:col-span-2]="hasLargeContent(section)"
|
|
232
|
+
>
|
|
233
|
+
<header class="border-b border-neutral/10 bg-neutral/5 px-5 py-3.5">
|
|
234
|
+
<h2 class="text-sm font-semibold tracking-wide text-neutral-light uppercase">
|
|
235
|
+
{{ section.label }}
|
|
236
|
+
</h2>
|
|
237
|
+
</header>
|
|
238
|
+
|
|
239
|
+
<div class="p-5 flex-1 space-y-4">
|
|
240
|
+
<!-- Grid para campos regulares -->
|
|
241
|
+
@if (regularFields(section).length > 0) {
|
|
242
|
+
<dl class="grid grid-cols-1 gap-x-6 gap-y-4 lg:grid-cols-12">
|
|
243
|
+
@for (campo of regularFields(section); track campo.key) {
|
|
244
|
+
<div class="space-y-1">
|
|
245
|
+
<dt class="col-span-12 sm:col-span-12 lg:col-span-3 text-xs font-medium text-neutral">{{ campo.label }}</dt>
|
|
246
|
+
<dd class="col-span-12 sm:col-span-12 lg:col-span-{{ colSpanFor(campo.field.width) }} text-sm font-medium text-neutral-light break-words">
|
|
247
|
+
<app-render-host
|
|
248
|
+
mode="display"
|
|
249
|
+
[type]="campo.field.type"
|
|
250
|
+
[config]="campo.field"
|
|
251
|
+
[value]="campo.raw"
|
|
252
|
+
/>
|
|
253
|
+
@if (isCopyable(campo.field.type) && campo.raw !== null && campo.raw !== undefined && campo.raw !== '') {
|
|
254
|
+
<app-copy-button [value]="campo.raw" />
|
|
255
|
+
}
|
|
256
|
+
</dd>
|
|
257
|
+
</div>
|
|
258
|
+
}
|
|
259
|
+
</dl>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
<!-- Renderizado de listas anidadas (Timeline de eventos / items) -->
|
|
263
|
+
@for (campo of listFields(section); track campo.key) {
|
|
264
|
+
<div class="pt-2">
|
|
265
|
+
<h3 class="mb-3 text-xs font-semibold text-neutral uppercase">
|
|
266
|
+
{{ campo.label }}
|
|
267
|
+
</h3>
|
|
268
|
+
<app-render-host
|
|
269
|
+
mode="display"
|
|
270
|
+
[type]="campo.field.type"
|
|
271
|
+
[config]="campo.field"
|
|
272
|
+
[value]="campo.raw"
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
}
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
278
|
+
}
|
|
279
|
+
</div>
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
</div>
|
|
283
|
+
`,
|
|
284
|
+
})
|
|
285
|
+
export class DetailView {
|
|
286
|
+
private readonly api = inject(ApiService, { optional: true });
|
|
287
|
+
private readonly actionService = inject(ActionService);
|
|
288
|
+
private readonly auth = inject(AuthService);
|
|
289
|
+
// R8.8: el trigger (Regenerar PIN) confirma vía toast y dispara la
|
|
290
|
+
// recarga del detalle — el mismo puerto que ya usa list-view.
|
|
291
|
+
private readonly router = inject(Router);
|
|
292
|
+
private readonly nav = inject(ViewNavigationService);
|
|
293
|
+
private readonly notifications = inject(NotificationService);
|
|
294
|
+
private readonly configService = inject(ConfigService);
|
|
295
|
+
|
|
296
|
+
constructor() {
|
|
297
|
+
// Este detalle presenta `<app-copy-share>` sin pasar por `ActionService`:
|
|
298
|
+
// si el load del bootstrap falló, el mensaje resolvería a «sólo la
|
|
299
|
+
// URL». Reintentamos acá —con la sesión ya iniciada—; `globalConfig` es
|
|
300
|
+
// signal, así que `shareOf` se recalcula cuando llega.
|
|
301
|
+
void this.configService.ensureLoaded();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
readonly schema = input.required<ResourceSchema>();
|
|
305
|
+
readonly record = input.required<Row>();
|
|
306
|
+
|
|
307
|
+
readonly edit = output<Row>();
|
|
308
|
+
readonly back = output<void>();
|
|
309
|
+
readonly delete = output<Row>();
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* R8.8: contador que fuerza la relectura del registro. Una acción de
|
|
313
|
+
* dominio muta la fila en el back (el PIN regenerado vive en la hoja);
|
|
314
|
+
* sin este tick, `toObservable(this.record)` no emite (la referencia no
|
|
315
|
+
* cambió) y el detalle mostraría el PIN viejo.
|
|
316
|
+
*/
|
|
317
|
+
private readonly refreshTick = signal(0);
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Obtiene el registro completo desde el endpoint get si existe.
|
|
321
|
+
* Esto asegura que campos pesados o no proyectados en la lista (como fotos, notas o historial)
|
|
322
|
+
* se carguen y muestren completos. El resultado va etiquetado (`FetchState`)
|
|
323
|
+
* para que la vista sepa distinguir «cargando» de «sin datos» de «error».
|
|
324
|
+
*/
|
|
325
|
+
private readonly fetched = toSignal(
|
|
326
|
+
combineLatest([toObservable(this.record), toObservable(this.refreshTick)]).pipe(
|
|
327
|
+
switchMap(([rec]) => {
|
|
328
|
+
const schema = this.schema();
|
|
329
|
+
const key = rec ? String(rec[schema.keyField] ?? '') : '';
|
|
330
|
+
const getEndpoint = schema.endpoint?.get;
|
|
331
|
+
if (!key || !getEndpoint || typeof this.api?.get !== 'function') {
|
|
332
|
+
// Sin get contratado la fila de la lista ES todo lo que hay.
|
|
333
|
+
return of<FetchState>({ phase: 'done', data: null });
|
|
334
|
+
}
|
|
335
|
+
return this.api.get<Row>(schema.endpoint, key).pipe(
|
|
336
|
+
map((data): FetchState => ({ phase: 'done', data })),
|
|
337
|
+
catchError((error) => of<FetchState>({ phase: 'error', error })),
|
|
338
|
+
);
|
|
339
|
+
}),
|
|
340
|
+
),
|
|
341
|
+
{ initialValue: { phase: 'pending' } as FetchState },
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Estados universales del detalle — el mismo trío que `list-view` y
|
|
346
|
+
* `form-view`. El skeleton reserva el espacio de las secciones mientras
|
|
347
|
+
* viaja el `get` (Apps Script tarda ~1s): sin él, el operador veía una
|
|
348
|
+
* pantalla «completa» con campos vacíos que después aparecían solos.
|
|
349
|
+
*/
|
|
350
|
+
protected readonly status = computed<'loading' | 'ready' | 'error'>(() => {
|
|
351
|
+
const fetched = this.fetched();
|
|
352
|
+
if (fetched.phase === 'pending') return 'loading';
|
|
353
|
+
if (fetched.phase === 'error') return 'error';
|
|
354
|
+
return 'ready';
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/** Reintenta el `get` del registro desde el estado de error. */
|
|
358
|
+
protected retry(): void {
|
|
359
|
+
this.refreshTick.update((tick) => tick + 1);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* D4 — el error original con IDENTIDAD intacta (no un string mapeado):
|
|
364
|
+
* el mensaje mostrado es display-only, el objeto crudo queda aquí para
|
|
365
|
+
* diagnóstico. `undefined` cuando no hay fallo de carga.
|
|
366
|
+
*/
|
|
367
|
+
errorDeCarga(): unknown {
|
|
368
|
+
const fetched = this.fetched();
|
|
369
|
+
return fetched.phase === 'error' ? fetched.error : undefined;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* D4 — «way back» desde el estado de error: emite el `back` (el host
|
|
374
|
+
* embebido sigue gestionando su propia vuelta) Y navega por el servicio
|
|
375
|
+
* declarado — en la ruta directa (`/:id/:key`) nadie escucha el output,
|
|
376
|
+
* y un estado de error sin salida es una pantalla muerta.
|
|
377
|
+
*/
|
|
378
|
+
protected volverDeError(): void {
|
|
379
|
+
this.back.emit();
|
|
380
|
+
void this.nav.back();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
protected readonly effectiveRecord = computed<Row>(() => {
|
|
384
|
+
const initial = this.record();
|
|
385
|
+
const fetched = this.fetched();
|
|
386
|
+
if (
|
|
387
|
+
fetched.phase === 'done' &&
|
|
388
|
+
fetched.data &&
|
|
389
|
+
typeof fetched.data === 'object' &&
|
|
390
|
+
Object.keys(fetched.data).length > 0
|
|
391
|
+
) {
|
|
392
|
+
return { ...initial, ...fetched.data };
|
|
393
|
+
}
|
|
394
|
+
return initial;
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
protected readonly detail = computed<DetailViewSchema>(
|
|
398
|
+
() => this.schema().detail ?? { enabled: true },
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
protected readonly title = computed(() => {
|
|
402
|
+
const rec = this.effectiveRecord();
|
|
403
|
+
const schema = this.schema();
|
|
404
|
+
const raw = rec[schema.titleField];
|
|
405
|
+
return raw !== undefined && raw !== null && raw !== '' ? String(raw) : schema.label;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
protected readonly recordKey = computed(() => {
|
|
409
|
+
const rec = this.effectiveRecord();
|
|
410
|
+
const schema = this.schema();
|
|
411
|
+
return String(rec[schema.keyField] ?? '—');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
protected readonly headerFields = computed<FieldValue[]>(() => {
|
|
415
|
+
const headerKeys = this.detail().headerFields ?? [];
|
|
416
|
+
return headerKeys
|
|
417
|
+
.map((key) => this.resolveFieldValue(key))
|
|
418
|
+
.filter((f): f is FieldValue => f !== null);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
protected readonly sections = computed<DetailSection[]>(() => {
|
|
422
|
+
const schema = this.schema();
|
|
423
|
+
const detailConfig = this.detail();
|
|
424
|
+
|
|
425
|
+
if (detailConfig.sections && detailConfig.sections.length > 0) {
|
|
426
|
+
return detailConfig.sections
|
|
427
|
+
.map((sec) => ({
|
|
428
|
+
id: sec.id,
|
|
429
|
+
label: sec.label,
|
|
430
|
+
fields: sec.fields
|
|
431
|
+
.map((key) => this.resolveFieldValue(key))
|
|
432
|
+
.filter((f): f is FieldValue => f !== null),
|
|
433
|
+
}))
|
|
434
|
+
.filter((sec) => sec.fields.length > 0);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const layoutSections = schema.layout?.sections ?? [];
|
|
438
|
+
const sectionMap = new Map<string, DetailSection>();
|
|
439
|
+
|
|
440
|
+
for (const layoutSection of layoutSections) {
|
|
441
|
+
sectionMap.set(layoutSection.id, {
|
|
442
|
+
id: layoutSection.id,
|
|
443
|
+
label: layoutSection.label,
|
|
444
|
+
fields: [],
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const unsectioned: DetailSection = { id: '_unsectioned', label: schema.label, fields: [] };
|
|
449
|
+
|
|
450
|
+
for (const field of schema.fields) {
|
|
451
|
+
const value = this.resolveFieldValue(field.key);
|
|
452
|
+
if (value === null) continue; // R7.5: campo oculto por políticas
|
|
453
|
+
if (field.section && sectionMap.has(field.section)) {
|
|
454
|
+
sectionMap.get(field.section)!.fields.push(value);
|
|
455
|
+
} else {
|
|
456
|
+
unsectioned.fields.push(value);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const result: DetailSection[] = [];
|
|
461
|
+
if (unsectioned.fields.length > 0) result.push(unsectioned);
|
|
462
|
+
for (const sec of sectionMap.values()) {
|
|
463
|
+
if (sec.fields.length > 0) result.push(sec);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return result;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
protected readonly copyShareActions = computed<CopyShareAction[]>(() => {
|
|
470
|
+
const actions: ResourceAction[] = this.schema().actions ?? this.detail().actions ?? [];
|
|
471
|
+
return actions.filter(isShareAction);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
/** R8.8: acciones de dominio del back (trigger) visibles en el detalle. */
|
|
475
|
+
protected readonly triggerActions = computed<TriggerAction[]>(() => {
|
|
476
|
+
const actions: ResourceAction[] = this.schema().actions ?? this.detail().actions ?? [];
|
|
477
|
+
return actions.filter(isTriggerAction);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* R8.8: dispara un `trigger` y, si tocó datos (`reload`), fuerza la
|
|
482
|
+
* relectura del registro. La confirmación (si el descriptor la pide) la
|
|
483
|
+
* resuelve `actionService.run` — este método no decide por tipo.
|
|
484
|
+
*/
|
|
485
|
+
protected async runTrigger(action: TriggerAction): Promise<void> {
|
|
486
|
+
if (!this.api) return; // sin API no hay ruta que disparar
|
|
487
|
+
const result = await this.actionService.run(action, {
|
|
488
|
+
record: this.effectiveRecord(),
|
|
489
|
+
resource: this.schema().id,
|
|
490
|
+
api: this.api,
|
|
491
|
+
router: this.router,
|
|
492
|
+
toast: this.notifications,
|
|
493
|
+
user: this.auth.user(),
|
|
494
|
+
keyField: this.schema().keyField,
|
|
495
|
+
});
|
|
496
|
+
if (result.ok && result.reload) {
|
|
497
|
+
this.refreshTick.update((tick) => tick + 1);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Agrupación, no formato: devuelve el campo y el valor crudo. El pintado
|
|
503
|
+
* es cosa del registro (R4.2).
|
|
504
|
+
*
|
|
505
|
+
* R7.5: oculta campos según `field.access.read` (sólo UX; el back
|
|
506
|
+
* vuelve a filtrar la respuesta).
|
|
507
|
+
*/
|
|
508
|
+
private resolveFieldValue(key: string): FieldValue | null {
|
|
509
|
+
const schema = this.schema();
|
|
510
|
+
const rec = this.effectiveRecord();
|
|
511
|
+
const field = schema.fields.find((f) => f.key === key);
|
|
512
|
+
const raw = getValue(rec, key);
|
|
513
|
+
|
|
514
|
+
// Una clave que ningún schema declara no tiene descriptor: se resuelve
|
|
515
|
+
// como texto crudo, que era exactamente lo que hacía el `default` del
|
|
516
|
+
// `formatValue` viejo.
|
|
517
|
+
const declarado: FieldSchema = field ?? { key, label: key.replace(/_/g, ' '), type: 'text' };
|
|
518
|
+
|
|
519
|
+
// R7.5: ocultar si el usuario no tiene permiso de lectura
|
|
520
|
+
if (!this.auth.canReadField(declarado)) {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
key,
|
|
526
|
+
label: field?.label ?? declarado.label,
|
|
527
|
+
field: declarado,
|
|
528
|
+
raw,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Valor vacío con el criterio de siempre (`null`, `undefined`, `''`): ni
|
|
534
|
+
* el chip de la cabecera ni el `—` del cuerpo cambian de criterio.
|
|
535
|
+
*/
|
|
536
|
+
protected vacio(valor: unknown): boolean {
|
|
537
|
+
return valor === null || valor === undefined || valor === '';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Un historial sólo es historial si hay valor que recorrer. */
|
|
541
|
+
private esHistorial(campo: FieldValue): boolean {
|
|
542
|
+
return campo.field.type === 'list' && !this.vacio(campo.raw);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
protected regularFields(section: DetailSection): FieldValue[] {
|
|
546
|
+
return section.fields.filter((f) => !this.esHistorial(f));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
protected listFields(section: DetailSection): FieldValue[] {
|
|
550
|
+
return section.fields.filter((f) => this.esHistorial(f));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
protected hasLargeContent(section: DetailSection): boolean {
|
|
554
|
+
return section.fields.some((f) => this.esHistorial(f) || f.key === 'description');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
protected colSpanFor(width: number | undefined): string {
|
|
558
|
+
const w = width ?? 6;
|
|
559
|
+
return `lg:col-span-${Math.min(w, 9)}`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
protected isCopyable(type: string): boolean {
|
|
563
|
+
return ['text', 'url', 'email', 'phone', 'readonly-text', 'slug', 'color', 'number', 'currency', 'date', 'time'].includes(type);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Enlace y mensaje, resueltos por `resolveShare` (R3.10: una sola copia). */
|
|
567
|
+
protected shareOf(action: CopyShareAction): ResolvedShare {
|
|
568
|
+
return resolveShare(action, this.actionService.vars(this.effectiveRecord()));
|
|
569
|
+
}
|
|
570
|
+
}
|