@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,274 @@
|
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import type { ListColumn } from '../core/models/schema.model';
|
|
6
|
+
import { RenderHost } from '../core/registry/render-host';
|
|
7
|
+
import { COLUMN_FORMAT_TYPES } from './descriptors';
|
|
8
|
+
import type { ColumnCell } from './column-cell';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Los 7 formatos de columna (R4.3 · `kind: 'column-format'`).
|
|
12
|
+
*
|
|
13
|
+
* Cada caso es un `@case` del `@switch` que vivía en `list-view`: si el
|
|
14
|
+
* formato pinta distinto de lo que pintaba allí, este test se pone rojo. Se
|
|
15
|
+
* monta por `RenderHost` y no por el componente suelto para cubrir también
|
|
16
|
+
* la resolución del registro (descriptor, `inputs`, degradación).
|
|
17
|
+
*/
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'spec-column-host',
|
|
20
|
+
imports: [RenderHost],
|
|
21
|
+
template: `
|
|
22
|
+
<app-render-host kind="column-format" [type]="formato()" [config]="column()" [data]="cell()" />
|
|
23
|
+
`,
|
|
24
|
+
})
|
|
25
|
+
class Host {
|
|
26
|
+
readonly formato = input.required<string>();
|
|
27
|
+
readonly column = input.required<ListColumn>();
|
|
28
|
+
readonly cell = input.required<ColumnCell>();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('R4.3 — formatos de columna registrados', () => {
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await TestBed.configureTestingModule({ imports: [Host] }).compileComponents();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Monta el host y espera el `load()` perezoso del formato.
|
|
38
|
+
*
|
|
39
|
+
* `whenStable()` no cubre el `import()` dinámico por sí solo, así que se
|
|
40
|
+
* espera acotadamente mientras quede `<app-skeleton>`; si el formato nunca
|
|
41
|
+
* monta, la espera se agota y falla la aserción del test.
|
|
42
|
+
*/
|
|
43
|
+
async function pintar(column: ListColumn, cell: Partial<ColumnCell> = {}): Promise<HTMLElement> {
|
|
44
|
+
const fixture = TestBed.createComponent(Host);
|
|
45
|
+
fixture.componentRef.setInput('formato', column.format ?? 'text');
|
|
46
|
+
fixture.componentRef.setInput('column', column);
|
|
47
|
+
fixture.componentRef.setInput('cell', celda({ column, ...cell }));
|
|
48
|
+
fixture.detectChanges();
|
|
49
|
+
await fixture.whenStable();
|
|
50
|
+
for (
|
|
51
|
+
let intento = 0;
|
|
52
|
+
intento < 100 && fixture.nativeElement.querySelector('app-skeleton');
|
|
53
|
+
intento++
|
|
54
|
+
) {
|
|
55
|
+
fixture.detectChanges();
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
57
|
+
}
|
|
58
|
+
fixture.detectChanges();
|
|
59
|
+
return fixture.nativeElement as HTMLElement;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Texto de la celda con los espacios duros de `Intl` normalizados. */
|
|
63
|
+
function texto(nodo: HTMLElement): string {
|
|
64
|
+
return (nodo.textContent ?? '').replace(/\u00a0/g, ' ');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Celda mínima; cada caso sobreescribe lo que necesita. */
|
|
68
|
+
function celda(parcial: { column: ListColumn } & Partial<ColumnCell>): ColumnCell {
|
|
69
|
+
return {
|
|
70
|
+
row: {},
|
|
71
|
+
fields: [],
|
|
72
|
+
title: 'Una fila',
|
|
73
|
+
canUpdate: false,
|
|
74
|
+
saving: false,
|
|
75
|
+
currency: 'PEN',
|
|
76
|
+
...parcial,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
it('los 7 formatos de la unión están registrados', () => {
|
|
81
|
+
// `ListColumnFormat` vive en tipos y el manifiesto en runtime: sólo un
|
|
82
|
+
// test reconcilia los dos (mismo tripwire que campos y widgets).
|
|
83
|
+
expect(COLUMN_FORMAT_TYPES.length).toBe(7);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('text', () => {
|
|
87
|
+
const col: ListColumn = { key: 'nombre', label: 'Nombre', format: 'text' };
|
|
88
|
+
|
|
89
|
+
it('pinta el valor crudo', async () => {
|
|
90
|
+
const nodo = await pintar(col, { row: { nombre: 'ORD-42' } });
|
|
91
|
+
expect(nodo.textContent).toContain('ORD-42');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('null y undefined salen vacíos', async () => {
|
|
95
|
+
expect((await pintar(col, { row: { nombre: null } })).textContent?.trim()).toBe('');
|
|
96
|
+
expect((await pintar(col, { row: {} })).textContent?.trim()).toBe('');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('un objeto sale [object Object] (degradación heredada)', async () => {
|
|
100
|
+
const nodo = await pintar(col, { row: { nombre: { a: 1 } } });
|
|
101
|
+
expect(nodo.textContent).toContain('[object Object]');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('number', () => {
|
|
106
|
+
const col: ListColumn = { key: 'stock', label: 'Stock', format: 'number' };
|
|
107
|
+
|
|
108
|
+
it('agrupa con separador es-PE', async () => {
|
|
109
|
+
const nodo = await pintar(col, { row: { stock: 1234567.5 } });
|
|
110
|
+
expect(nodo.textContent).toContain('1,234,567.5');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('no numérico sale crudo y null sigue saliendo «0»', async () => {
|
|
114
|
+
expect((await pintar(col, { row: { stock: 'varios' } })).textContent).toContain('varios');
|
|
115
|
+
// Quirk heredado de `formatNumber`: `Number(null)` es 0 y por eso la
|
|
116
|
+
// lista ya mostraba «0» en vez de vacío. Se conserva — cambiarlo es
|
|
117
|
+
// otra decisión, no este registro.
|
|
118
|
+
expect((await pintar(col, { row: { stock: null } })).textContent?.trim()).toBe('0');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('currency', () => {
|
|
123
|
+
const col: ListColumn = { key: 'price', label: 'Precio', format: 'currency' };
|
|
124
|
+
|
|
125
|
+
it('usa la moneda que entrega la vista', async () => {
|
|
126
|
+
const nodo = await pintar(col, { row: { price: 1234.5 } });
|
|
127
|
+
// `Intl` separa el símbolo con espacio duro (U+00A0): se normaliza para
|
|
128
|
+
// que el aserto no dependa de ese byte.
|
|
129
|
+
expect(texto(nodo)).toContain('S/ 1,234.50');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('cambia de moneda con la celda', async () => {
|
|
133
|
+
const nodo = await pintar(col, { row: { price: 1234.5 }, currency: 'USD' });
|
|
134
|
+
expect(texto(nodo)).toContain('USD 1,234.50');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('date', () => {
|
|
139
|
+
const col: ListColumn = { key: 'fecha', label: 'Fecha', format: 'date' };
|
|
140
|
+
|
|
141
|
+
it('formato corto es-PE en UTC', async () => {
|
|
142
|
+
const nodo = await pintar(col, { row: { fecha: '2026-12-25' } });
|
|
143
|
+
expect(nodo.textContent).toContain('25/12/2026');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('no parseable sale crudo', async () => {
|
|
147
|
+
expect((await pintar(col, { row: { fecha: 'ayer' } })).textContent).toContain('ayer');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('badge', () => {
|
|
152
|
+
const col: ListColumn = { key: 'status', label: 'Estado', format: 'badge' };
|
|
153
|
+
|
|
154
|
+
it('clasifica el estado como el display del detalle (D41)', async () => {
|
|
155
|
+
// `warning` se pinta como `accent` (bg-accent/20 + text-accent-text).
|
|
156
|
+
const pendiente = await pintar(col, { row: { status: 'PENDIENTE' } });
|
|
157
|
+
expect(pendiente.querySelector('app-badge')?.className).toContain('bg-accent/20');
|
|
158
|
+
|
|
159
|
+
const entregado = await pintar(col, { row: { status: 'ENTREGADO' } });
|
|
160
|
+
expect(entregado.querySelector('app-badge')?.className).toContain('bg-success');
|
|
161
|
+
|
|
162
|
+
const cancelado = await pintar(col, { row: { status: 'Cancelado' } });
|
|
163
|
+
expect(cancelado.querySelector('app-badge')?.className).toContain('bg-danger');
|
|
164
|
+
|
|
165
|
+
const otro = await pintar(col, { row: { status: 'Vigente' } });
|
|
166
|
+
expect(otro.querySelector('app-badge')?.className).toContain('bg-neutral');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('un select pinta la etiqueta de la opción, no el valor crudo', async () => {
|
|
170
|
+
const nodo = await pintar(col, {
|
|
171
|
+
row: { status: 'preparacion' },
|
|
172
|
+
fields: [
|
|
173
|
+
{
|
|
174
|
+
key: 'status',
|
|
175
|
+
label: 'Estado',
|
|
176
|
+
type: 'select',
|
|
177
|
+
options: [
|
|
178
|
+
{ value: 'preparacion', label: 'En preparación' },
|
|
179
|
+
{ value: 'entregado', label: 'Entregado' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
expect(nodo.textContent).toContain('En preparación');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('el vacío sale «Sin dato» en danger', async () => {
|
|
188
|
+
const nodo = await pintar(col, { row: { status: '' } });
|
|
189
|
+
expect(nodo.textContent).toContain('Sin dato');
|
|
190
|
+
expect(nodo.querySelector('app-badge')?.className).toContain('bg-danger');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('un booleano usa trueLabel/falseLabel del campo', async () => {
|
|
194
|
+
const nodo = await pintar(col, {
|
|
195
|
+
row: { status: true },
|
|
196
|
+
fields: [{ key: 'status', label: 'Estado', type: 'boolean', trueLabel: 'Vigente' }],
|
|
197
|
+
});
|
|
198
|
+
expect(nodo.textContent).toContain('Vigente');
|
|
199
|
+
expect(nodo.querySelector('app-badge')?.className).toContain('bg-success');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('boolean', () => {
|
|
204
|
+
const col: ListColumn = { key: 'active', label: 'Activo', format: 'boolean' };
|
|
205
|
+
const booleano = { key: 'active', label: 'Activo', type: 'boolean' as const };
|
|
206
|
+
|
|
207
|
+
it('con permiso y campo booleano dibuja el interruptor', async () => {
|
|
208
|
+
const nodo = await pintar(col, {
|
|
209
|
+
row: { active: true },
|
|
210
|
+
fields: [booleano],
|
|
211
|
+
canUpdate: true,
|
|
212
|
+
});
|
|
213
|
+
const box = nodo.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
|
214
|
+
expect(box, 'debe dibujarse el interruptor').not.toBeNull();
|
|
215
|
+
expect(box?.checked).toBe(true);
|
|
216
|
+
expect(box?.getAttribute('aria-label')).toBe('Activo: Una fila');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('sin permiso cae al badge', async () => {
|
|
220
|
+
const nodo = await pintar(col, { row: { active: true }, fields: [booleano] });
|
|
221
|
+
expect(nodo.querySelector('input[type="checkbox"]')).toBeNull();
|
|
222
|
+
expect(nodo.textContent).toContain('Sí');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('mientras la fila guarda, el interruptor queda bloqueado', async () => {
|
|
226
|
+
const nodo = await pintar(col, {
|
|
227
|
+
row: { active: false },
|
|
228
|
+
fields: [booleano],
|
|
229
|
+
canUpdate: true,
|
|
230
|
+
saving: true,
|
|
231
|
+
});
|
|
232
|
+
const box = nodo.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
|
233
|
+
expect(box?.disabled, 'mientras guarda, la fila está bloqueada').toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('al alternar escribe por la celda', async () => {
|
|
237
|
+
const toggle = vi.fn();
|
|
238
|
+
const nodo = await pintar(col, {
|
|
239
|
+
row: { active: false },
|
|
240
|
+
fields: [booleano],
|
|
241
|
+
canUpdate: true,
|
|
242
|
+
toggle,
|
|
243
|
+
});
|
|
244
|
+
nodo.querySelector<HTMLInputElement>('input[type="checkbox"]')!.click();
|
|
245
|
+
expect(toggle).toHaveBeenCalledWith(true);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('thumbnail', () => {
|
|
250
|
+
const col: ListColumn = { key: 'image_url', label: 'Imagen', format: 'thumbnail' };
|
|
251
|
+
|
|
252
|
+
it('pinta la imagen con su alt', async () => {
|
|
253
|
+
const nodo = await pintar(col, { row: { image_url: 'https://x.test/a.png' } });
|
|
254
|
+
const img = nodo.querySelector('img');
|
|
255
|
+
expect(img?.getAttribute('src')).toBe('https://x.test/a.png');
|
|
256
|
+
expect(img?.getAttribute('alt')).toBe('');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('toma el primer item de images[] con su image_alt', async () => {
|
|
260
|
+
const nodo = await pintar(
|
|
261
|
+
{ key: 'images', label: 'Imágenes', format: 'thumbnail' },
|
|
262
|
+
{ row: { images: [{ image_url: 'https://x.test/b.png', image_alt: 'Frente' }] } },
|
|
263
|
+
);
|
|
264
|
+
expect(nodo.querySelector('img')?.getAttribute('src')).toBe('https://x.test/b.png');
|
|
265
|
+
expect(nodo.querySelector('img')?.getAttribute('alt')).toBe('Frente');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('sin URL pinta el guion', async () => {
|
|
269
|
+
const nodo = await pintar(col, { row: { image_url: '' } });
|
|
270
|
+
expect(nodo.textContent).toContain('—');
|
|
271
|
+
expect(nodo.querySelector('img')).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { RenderDescriptor, RenderExtras } from '../core/registry/render-registry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manifiesto de descriptores de formato de columna (R4.3 · `kind:
|
|
5
|
+
* 'column-format'`).
|
|
6
|
+
*
|
|
7
|
+
* Es el equivalente a `fields/descriptors.ts` y `widgets/descriptors.ts`:
|
|
8
|
+
* una línea por formato y ningún import de componente — cada `load()` guarda
|
|
9
|
+
* el cierre de un `import()` **con literal**, que es lo que permite al
|
|
10
|
+
* bundler partir el chunk; el registro los recoge al importar este módulo
|
|
11
|
+
* (`core/registry/render-registry.ts`).
|
|
12
|
+
*
|
|
13
|
+
* **Añadir un formato** ⇒ crear `format-<nombre>.ts` + una entrada acá.
|
|
14
|
+
* `list-view` no se toca: ése es el criterio de éxito de R4.3.
|
|
15
|
+
*
|
|
16
|
+
* Todos comparten el cableado: el descriptor sólo expone la celda, que la
|
|
17
|
+
* vista cuelga de `extras.data`.
|
|
18
|
+
*/
|
|
19
|
+
const inputs = (_config: unknown, extras: RenderExtras): Record<string, unknown> => ({
|
|
20
|
+
cell: extras.data,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const text: RenderDescriptor = {
|
|
24
|
+
kind: 'column-format',
|
|
25
|
+
type: 'text',
|
|
26
|
+
since: '1.0',
|
|
27
|
+
surfaces: ['list'],
|
|
28
|
+
load: () => import('./format-text').then((module) => module.ColumnFormatText),
|
|
29
|
+
inputs,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const badge: RenderDescriptor = {
|
|
33
|
+
kind: 'column-format',
|
|
34
|
+
type: 'badge',
|
|
35
|
+
since: '1.0',
|
|
36
|
+
surfaces: ['list'],
|
|
37
|
+
load: () => import('./format-badge').then((module) => module.ColumnFormatBadge),
|
|
38
|
+
inputs,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const thumbnail: RenderDescriptor = {
|
|
42
|
+
kind: 'column-format',
|
|
43
|
+
type: 'thumbnail',
|
|
44
|
+
since: '1.0',
|
|
45
|
+
surfaces: ['list'],
|
|
46
|
+
load: () => import('./format-thumbnail').then((module) => module.ColumnFormatThumbnail),
|
|
47
|
+
inputs,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const boolean: RenderDescriptor = {
|
|
51
|
+
kind: 'column-format',
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
since: '1.0',
|
|
54
|
+
surfaces: ['list'],
|
|
55
|
+
load: () => import('./format-boolean').then((module) => module.ColumnFormatBoolean),
|
|
56
|
+
inputs,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const number: RenderDescriptor = {
|
|
60
|
+
kind: 'column-format',
|
|
61
|
+
type: 'number',
|
|
62
|
+
since: '1.0',
|
|
63
|
+
surfaces: ['list'],
|
|
64
|
+
load: () => import('./format-number').then((module) => module.ColumnFormatNumber),
|
|
65
|
+
inputs,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const currency: RenderDescriptor = {
|
|
69
|
+
kind: 'column-format',
|
|
70
|
+
type: 'currency',
|
|
71
|
+
since: '1.0',
|
|
72
|
+
surfaces: ['list'],
|
|
73
|
+
load: () => import('./format-currency').then((module) => module.ColumnFormatCurrency),
|
|
74
|
+
inputs,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const date: RenderDescriptor = {
|
|
78
|
+
kind: 'column-format',
|
|
79
|
+
type: 'date',
|
|
80
|
+
since: '1.0',
|
|
81
|
+
surfaces: ['list'],
|
|
82
|
+
load: () => import('./format-date').then((module) => module.ColumnFormatDate),
|
|
83
|
+
inputs,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Los 7 formatos del `@switch` que este registro reemplaza (R4.3). */
|
|
87
|
+
export const columnFormatDescriptors: readonly RenderDescriptor[] = [
|
|
88
|
+
text,
|
|
89
|
+
badge,
|
|
90
|
+
thumbnail,
|
|
91
|
+
boolean,
|
|
92
|
+
number,
|
|
93
|
+
currency,
|
|
94
|
+
date,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Los 7 nombres, como dato.
|
|
99
|
+
*
|
|
100
|
+
* Va en paralelo a `ListColumnFormat` (unión de `schema.model`) y lo vigila
|
|
101
|
+
* `core/registry/contract.spec.ts`: la unión vive en tipos y el registro en
|
|
102
|
+
* runtime, y sólo un test reconcilia los dos — el mismo tripwire que ya
|
|
103
|
+
* existe para los 20 campos y los 6 widgets.
|
|
104
|
+
*/
|
|
105
|
+
export const COLUMN_FORMAT_TYPES = [
|
|
106
|
+
'text',
|
|
107
|
+
'badge',
|
|
108
|
+
'thumbnail',
|
|
109
|
+
'boolean',
|
|
110
|
+
'number',
|
|
111
|
+
'currency',
|
|
112
|
+
'date',
|
|
113
|
+
] as const;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { Badge, type BadgeKind } from '../shared/components/badge/badge';
|
|
4
|
+
import { resolveBadgeKind } from '../fields/display/display-badge';
|
|
5
|
+
import type { SelectField } from '../core/models/schema.model';
|
|
6
|
+
import { getValue } from '../shared/utils/field-path';
|
|
7
|
+
import type { ColumnCell } from './column-cell';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Formato `badge` (R4.3, afinado en R4.4).
|
|
11
|
+
*
|
|
12
|
+
* Un booleano usa `trueLabel`/`falseLabel` del campo (o «Sí»/«No») con
|
|
13
|
+
* `success`/`danger` — el vocabulario de la lista para el control que
|
|
14
|
+
* permite escribir. El vacío sigue siendo «Sin dato»/`danger`.
|
|
15
|
+
*
|
|
16
|
+
* El resto alinea el chip con el display del detalle (D41): un `select`
|
|
17
|
+
* pinta la **etiqueta** de la opción y los estados se clasifican con
|
|
18
|
+
* `resolveBadgeKind`, el mismo criterio que la cabecera de la ficha —
|
|
19
|
+
* antes, toda columna declarada `badge` salía verde sin mirar el valor.
|
|
20
|
+
*/
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-column-format-badge',
|
|
23
|
+
imports: [Badge],
|
|
24
|
+
template: `<app-badge [label]="badge().label" [kind]="badge().kind" />`,
|
|
25
|
+
})
|
|
26
|
+
export class ColumnFormatBadge {
|
|
27
|
+
readonly cell = input.required<ColumnCell>();
|
|
28
|
+
|
|
29
|
+
protected readonly badge = computed(() => badgeInfo(this.cell()));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function badgeInfo(cell: ColumnCell): { label: string; kind: BadgeKind } {
|
|
33
|
+
const value = getValue(cell.row, cell.column.key);
|
|
34
|
+
const field = cell.fields.find((candidate) => candidate.key === cell.column.key);
|
|
35
|
+
if (typeof value === 'boolean') {
|
|
36
|
+
const label =
|
|
37
|
+
value === true
|
|
38
|
+
? ((field?.type === 'boolean' ? field.trueLabel : undefined) ?? 'Sí')
|
|
39
|
+
: ((field?.type === 'boolean' ? field.falseLabel : undefined) ?? 'No');
|
|
40
|
+
return { label, kind: value ? 'success' : 'danger' };
|
|
41
|
+
}
|
|
42
|
+
const text = value == null ? '' : String(value);
|
|
43
|
+
if (text === '') {
|
|
44
|
+
return { label: 'Sin dato', kind: 'danger' };
|
|
45
|
+
}
|
|
46
|
+
const etiqueta =
|
|
47
|
+
field?.type === 'select'
|
|
48
|
+
? ((field as SelectField).options?.find((option) => option.value === text)?.label ?? text)
|
|
49
|
+
: text;
|
|
50
|
+
return { label: etiqueta, kind: resolveBadgeKind(text) };
|
|
51
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '../shared/components/badge/badge';
|
|
4
|
+
import { getValue } from '../shared/utils/field-path';
|
|
5
|
+
import type { ColumnCell } from './column-cell';
|
|
6
|
+
import { badgeInfo } from './format-badge';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Formato `boolean` (R4.3): interruptor o badge.
|
|
10
|
+
*
|
|
11
|
+
* Es la rama `@case ('boolean')` de `list-view`, con una sola diferencia de
|
|
12
|
+
* domicilio: hoy sólo monta este componente si el formato efectivo ya es
|
|
13
|
+
* `boolean`, así que la comprobación `column.format === 'boolean'` que hacía
|
|
14
|
+
* `isSwitchColumn` sobra. El criterio restante — `active` booleano y con
|
|
15
|
+
* permiso de edición (base.md §10) — vive acá, que es quien dibuja el control.
|
|
16
|
+
*/
|
|
17
|
+
@Component({
|
|
18
|
+
selector: 'app-column-format-boolean',
|
|
19
|
+
imports: [Badge],
|
|
20
|
+
template: `
|
|
21
|
+
@if (esInterruptor()) {
|
|
22
|
+
<input
|
|
23
|
+
type="checkbox"
|
|
24
|
+
class="size-5 cursor-pointer accent-primary"
|
|
25
|
+
[checked]="valor()"
|
|
26
|
+
[disabled]="cell().saving"
|
|
27
|
+
[attr.aria-label]="cell().column.label + ': ' + cell().title"
|
|
28
|
+
(click)="$event.stopPropagation()"
|
|
29
|
+
(change)="alternar($event)"
|
|
30
|
+
/>
|
|
31
|
+
} @else {
|
|
32
|
+
<app-badge [label]="badge().label" [kind]="badge().kind" />
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
export class ColumnFormatBoolean {
|
|
37
|
+
readonly cell = input.required<ColumnCell>();
|
|
38
|
+
|
|
39
|
+
protected readonly badge = computed(() => badgeInfo(this.cell()));
|
|
40
|
+
|
|
41
|
+
protected readonly valor = computed(() => {
|
|
42
|
+
const value = getValue(this.cell().row, this.cell().column.key);
|
|
43
|
+
return value === true || value === 'true';
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** `active` declarado como boolean en el schema → interruptor (base.md §10). */
|
|
47
|
+
protected readonly esInterruptor = computed(() => {
|
|
48
|
+
const cell = this.cell();
|
|
49
|
+
if (cell.column.key !== 'active' || !cell.canUpdate) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return cell.fields.some((field) => field.type === 'boolean' && field.key === 'active');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
protected alternar(event: Event): void {
|
|
56
|
+
this.cell().toggle?.((event.target as HTMLInputElement).checked);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { getValue } from '../shared/utils/field-path';
|
|
4
|
+
import type { ColumnCell } from './column-cell';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Formato `currency` (R4.3). Igual que el helper de `list-view`: `es-PE`,
|
|
8
|
+
* moneda tomada del schema (se pasaba desde la vista porque sólo ella la
|
|
9
|
+
* conoce) y texto crudo si el valor no es número.
|
|
10
|
+
*/
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'app-column-format-currency',
|
|
13
|
+
template: `{{ precio() }}`,
|
|
14
|
+
})
|
|
15
|
+
export class ColumnFormatCurrency {
|
|
16
|
+
readonly cell = input.required<ColumnCell>();
|
|
17
|
+
|
|
18
|
+
protected readonly precio = computed(() => {
|
|
19
|
+
const cell = this.cell();
|
|
20
|
+
const value = getValue(cell.row, cell.column.key);
|
|
21
|
+
const number = Number(value);
|
|
22
|
+
if (!Number.isFinite(number)) {
|
|
23
|
+
return value == null ? '' : String(value);
|
|
24
|
+
}
|
|
25
|
+
return new Intl.NumberFormat('es-PE', {
|
|
26
|
+
style: 'currency',
|
|
27
|
+
currency: cell.currency,
|
|
28
|
+
}).format(number);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { getValue } from '../shared/utils/field-path';
|
|
4
|
+
import type { ColumnCell } from './column-cell';
|
|
5
|
+
|
|
6
|
+
function formatDate(value: unknown): string {
|
|
7
|
+
if (typeof value !== 'string' || value === '') {
|
|
8
|
+
return value == null ? '' : String(value);
|
|
9
|
+
}
|
|
10
|
+
const date = new Date(value);
|
|
11
|
+
if (Number.isNaN(date.getTime())) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
// Fechas `YYYY-MM-DD`: forzar UTC para no desfasar un día.
|
|
15
|
+
return date.toLocaleDateString('es-PE', { timeZone: 'UTC' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Formato `date` (R4.3). Corto (`toLocaleDateString`), a diferencia del
|
|
20
|
+
* display de la ficha, que va con `dateStyle: 'medium'`: cada superficie
|
|
21
|
+
* con su formato, pero ambos desde su propio componente.
|
|
22
|
+
*/
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'app-column-format-date',
|
|
25
|
+
template: `{{ fecha() }}`,
|
|
26
|
+
})
|
|
27
|
+
export class ColumnFormatDate {
|
|
28
|
+
readonly cell = input.required<ColumnCell>();
|
|
29
|
+
|
|
30
|
+
protected readonly fecha = computed(() => formatDate(this.valor()));
|
|
31
|
+
|
|
32
|
+
private valor(): unknown {
|
|
33
|
+
return getValue(this.cell().row, this.cell().column.key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { getValue } from '../shared/utils/field-path';
|
|
4
|
+
import type { ColumnCell } from './column-cell';
|
|
5
|
+
|
|
6
|
+
const NUMBER_FORMAT = new Intl.NumberFormat('es-PE');
|
|
7
|
+
|
|
8
|
+
function formatNumber(value: unknown): string {
|
|
9
|
+
const number = Number(value);
|
|
10
|
+
if (!Number.isFinite(number)) {
|
|
11
|
+
return value == null ? '' : String(value);
|
|
12
|
+
}
|
|
13
|
+
return NUMBER_FORMAT.format(number);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Formato `number` (R4.3). Misma fórmula que el helper de `list-view`:
|
|
18
|
+
* miles con separador `es-PE` y texto crudo si el valor no es número.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-column-format-number',
|
|
22
|
+
template: `{{ numero() }}`,
|
|
23
|
+
})
|
|
24
|
+
export class ColumnFormatNumber {
|
|
25
|
+
readonly cell = input.required<ColumnCell>();
|
|
26
|
+
|
|
27
|
+
protected readonly numero = computed(() => formatNumber(this.valor()));
|
|
28
|
+
|
|
29
|
+
private valor(): unknown {
|
|
30
|
+
return getValue(this.cell().row, this.cell().column.key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { getValue } from '../shared/utils/field-path';
|
|
4
|
+
import type { ColumnCell } from './column-cell';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Formato `text` (R4.3 · el `@default` del `@switch` viejo).
|
|
8
|
+
*
|
|
9
|
+
* `cellText` de `list-view`: `null`/`undefined` se pintan vacíos y el resto
|
|
10
|
+
* pasa por `String()` — un objeto sale `[object Object]`, igual que antes.
|
|
11
|
+
*/
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'app-column-format-text',
|
|
14
|
+
template: `{{ texto() }}`,
|
|
15
|
+
})
|
|
16
|
+
export class ColumnFormatText {
|
|
17
|
+
readonly cell = input.required<ColumnCell>();
|
|
18
|
+
|
|
19
|
+
protected readonly texto = computed(() => {
|
|
20
|
+
const value = getValue(this.cell().row, this.cell().column.key);
|
|
21
|
+
return value == null ? '' : String(value);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { getValue } from '../shared/utils/field-path';
|
|
4
|
+
import type { ColumnCell } from './column-cell';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Formato `thumbnail` (R4.3): miniatura de la fila.
|
|
8
|
+
*
|
|
9
|
+
* Tanto `thumbnailSrc` como `thumbnailAlt` se mudan desde `list-view`: la
|
|
10
|
+
* columna puede apuntar a `image_url`, al primer elemento de `images[]` o a
|
|
11
|
+
* un objeto con `image_url`, y el texto alternativo sale del item o de la
|
|
12
|
+
* fila. Sin URL se pinta el `—` de siempre.
|
|
13
|
+
*/
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'app-column-format-thumbnail',
|
|
16
|
+
template: `
|
|
17
|
+
@if (src(); as url) {
|
|
18
|
+
<img [src]="url" [alt]="alt()" class="size-9 rounded-md object-cover" loading="lazy" />
|
|
19
|
+
} @else {
|
|
20
|
+
<span class="text-neutral" aria-hidden="true">—</span>
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
})
|
|
24
|
+
export class ColumnFormatThumbnail {
|
|
25
|
+
readonly cell = input.required<ColumnCell>();
|
|
26
|
+
|
|
27
|
+
protected readonly src = computed(() => {
|
|
28
|
+
const value = this.valor();
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
return value !== '' ? value : null;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
const first = value[0];
|
|
34
|
+
const url = typeof first === 'string' ? first : getValue(first, 'image_url');
|
|
35
|
+
return typeof url === 'string' && url !== '' ? url : null;
|
|
36
|
+
}
|
|
37
|
+
if (value && typeof value === 'object') {
|
|
38
|
+
const url = getValue(value, 'image_url');
|
|
39
|
+
return typeof url === 'string' && url !== '' ? url : null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
protected readonly alt = computed(() => {
|
|
45
|
+
const value = this.valor();
|
|
46
|
+
let item: unknown = this.cell().row;
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
item = value[0] ?? null;
|
|
49
|
+
} else if (value && typeof value === 'object') {
|
|
50
|
+
item = value;
|
|
51
|
+
}
|
|
52
|
+
const alt = item == null ? null : getValue(item, 'image_alt');
|
|
53
|
+
if (typeof alt === 'string' && alt !== '') {
|
|
54
|
+
return alt;
|
|
55
|
+
}
|
|
56
|
+
const rootAlt = this.cell().row['image_alt'];
|
|
57
|
+
return typeof rootAlt === 'string' ? rootAlt : '';
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
private valor(): unknown {
|
|
61
|
+
return getValue(this.cell().row, this.cell().column.key);
|
|
62
|
+
}
|
|
63
|
+
}
|