@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.
Files changed (369) hide show
  1. package/README.md +222 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +3 -15
  4. package/src/lib/actions/action-registry.spec.ts +52 -0
  5. package/src/lib/actions/action-registry.ts +126 -0
  6. package/src/lib/actions/action-service.spec.ts +215 -0
  7. package/src/lib/actions/action-service.ts +87 -0
  8. package/src/lib/actions/descriptors.spec.ts +155 -0
  9. package/src/lib/actions/descriptors.ts +154 -0
  10. package/src/lib/columns/column-cell.ts +33 -0
  11. package/src/lib/columns/column-defaults.spec.ts +99 -0
  12. package/src/lib/columns/column-defaults.ts +84 -0
  13. package/src/lib/columns/column-format.spec.ts +274 -0
  14. package/src/lib/columns/descriptors.ts +113 -0
  15. package/src/lib/columns/format-badge.ts +51 -0
  16. package/src/lib/columns/format-boolean.ts +58 -0
  17. package/src/lib/columns/format-currency.ts +30 -0
  18. package/src/lib/columns/format-date.ts +35 -0
  19. package/src/lib/columns/format-number.ts +32 -0
  20. package/src/lib/columns/format-text.ts +23 -0
  21. package/src/lib/columns/format-thumbnail.ts +63 -0
  22. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  23. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  24. package/src/lib/core/guards/auth.guard.ts +48 -0
  25. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  27. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  28. package/src/lib/core/models/api.model.ts +104 -0
  29. package/src/lib/core/models/schema-descriptions.ts +118 -0
  30. package/src/lib/core/models/schema.model.ts +1097 -0
  31. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  32. package/src/lib/core/registry/composition-validator.ts +270 -0
  33. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  34. package/src/lib/core/registry/field-validators.ts +440 -0
  35. package/src/lib/core/registry/render-host.spec.ts +208 -0
  36. package/src/lib/core/registry/render-host.ts +646 -0
  37. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  38. package/src/lib/core/registry/render-registry.ts +312 -0
  39. package/src/lib/core/registry/schema-catalog.ts +156 -0
  40. package/src/lib/core/services/api.service.spec.ts +1142 -0
  41. package/src/lib/core/services/api.service.ts +371 -0
  42. package/src/lib/core/services/auth.service.spec.ts +653 -0
  43. package/src/lib/core/services/auth.service.ts +503 -0
  44. package/src/lib/core/services/config.service.spec.ts +121 -0
  45. package/src/lib/core/services/config.service.ts +119 -0
  46. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  47. package/src/lib/core/services/data-source.service.ts +142 -0
  48. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  49. package/src/lib/core/services/error-mapper.service.ts +49 -0
  50. package/src/lib/core/services/notification.service.ts +73 -0
  51. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  52. package/src/lib/core/services/theme.service.spec.ts +106 -0
  53. package/src/lib/core/services/theme.service.ts +86 -0
  54. package/src/lib/core/services/transport.spec.ts +198 -0
  55. package/src/lib/core/services/transport.ts +164 -0
  56. package/src/lib/core/services/upload.service.ts +81 -0
  57. package/src/lib/core/testing/categories.schema.ts +125 -0
  58. package/src/lib/core/testing/orders.schema.ts +372 -0
  59. package/src/lib/core/testing/products.schema.ts +258 -0
  60. package/src/lib/core/testing/site.schema.ts +195 -0
  61. package/src/lib/core/testing/test-catalog.ts +21 -0
  62. package/src/lib/core/tokens.ts +112 -0
  63. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  64. package/src/lib/core/utils/clipboard.ts +90 -0
  65. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  66. package/src/lib/core/utils/context-resolver.ts +101 -0
  67. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  68. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  69. package/src/lib/core/utils/source-options.spec.ts +96 -0
  70. package/src/lib/core/utils/source-options.ts +54 -0
  71. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  72. package/src/lib/core/utils/template-interpolator.ts +56 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  74. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  75. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  76. package/src/lib/fields/boolean/boolean.ts +40 -0
  77. package/src/lib/fields/color/color.descriptor.ts +12 -0
  78. package/src/lib/fields/color/color.ts +34 -0
  79. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  80. package/src/lib/fields/currency/currency.ts +67 -0
  81. package/src/lib/fields/date/date.descriptor.ts +12 -0
  82. package/src/lib/fields/date/date.ts +36 -0
  83. package/src/lib/fields/descriptors.ts +122 -0
  84. package/src/lib/fields/display/display-badge.ts +65 -0
  85. package/src/lib/fields/display/display-date.ts +39 -0
  86. package/src/lib/fields/display/display-defaults.ts +84 -0
  87. package/src/lib/fields/display/display-file.ts +41 -0
  88. package/src/lib/fields/display/display-image.ts +54 -0
  89. package/src/lib/fields/display/display-list.ts +107 -0
  90. package/src/lib/fields/display/display-number.ts +41 -0
  91. package/src/lib/fields/display/display-text.ts +42 -0
  92. package/src/lib/fields/display/display.spec.ts +242 -0
  93. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  94. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  95. package/src/lib/fields/email/email.descriptor.ts +12 -0
  96. package/src/lib/fields/email/email.ts +28 -0
  97. package/src/lib/fields/field-aria.ts +60 -0
  98. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  99. package/src/lib/fields/field-host/field-host.ts +47 -0
  100. package/src/lib/fields/field-node.ts +88 -0
  101. package/src/lib/fields/file/file.descriptor.ts +14 -0
  102. package/src/lib/fields/file/file.spec.ts +134 -0
  103. package/src/lib/fields/file/file.ts +281 -0
  104. package/src/lib/fields/group/group.descriptor.ts +21 -0
  105. package/src/lib/fields/group/group.ts +110 -0
  106. package/src/lib/fields/image/image.descriptor.ts +12 -0
  107. package/src/lib/fields/image/image.spec.ts +117 -0
  108. package/src/lib/fields/image/image.ts +281 -0
  109. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  110. package/src/lib/fields/key-value/key-value.ts +283 -0
  111. package/src/lib/fields/list/list.descriptor.ts +21 -0
  112. package/src/lib/fields/list/list.ts +376 -0
  113. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  114. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  115. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  116. package/src/lib/fields/number/number.descriptor.ts +12 -0
  117. package/src/lib/fields/number/number.ts +40 -0
  118. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  119. package/src/lib/fields/phone/phone.ts +46 -0
  120. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  121. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  122. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  123. package/src/lib/fields/relation/relation.spec.ts +193 -0
  124. package/src/lib/fields/relation/relation.ts +224 -0
  125. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  126. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  127. package/src/lib/fields/select/radio-mode.ts +25 -0
  128. package/src/lib/fields/select/select.descriptor.ts +12 -0
  129. package/src/lib/fields/select/select.spec.ts +162 -0
  130. package/src/lib/fields/select/select.ts +210 -0
  131. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  132. package/src/lib/fields/slug/slug.ts +67 -0
  133. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  134. package/src/lib/fields/string-list/string-list.ts +222 -0
  135. package/src/lib/fields/text/text.descriptor.ts +12 -0
  136. package/src/lib/fields/text/text.ts +35 -0
  137. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  138. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  139. package/src/lib/fields/textarea/textarea.ts +126 -0
  140. package/src/lib/fields/time/time.descriptor.ts +12 -0
  141. package/src/lib/fields/time/time.ts +28 -0
  142. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  143. package/src/lib/fields/url/url.descriptor.ts +12 -0
  144. package/src/lib/fields/url/url.ts +53 -0
  145. package/src/lib/gas-admin-ui.spec.ts +21 -0
  146. package/src/lib/gas-admin-ui.ts +9 -0
  147. package/src/lib/layouts/descriptors.ts +9 -0
  148. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  149. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  150. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  151. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  152. package/src/lib/pages/form-view/form-model.ts +88 -0
  153. package/src/lib/pages/form-view/form-schema.ts +255 -0
  154. package/src/lib/pages/form-view/form-view.ts +1622 -0
  155. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  156. package/src/lib/pages/list-view/list-view.ts +882 -0
  157. package/src/lib/pages/login/login.spec.ts +109 -0
  158. package/src/lib/pages/login/login.ts +227 -0
  159. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  160. package/src/lib/shared/components/badge/badge.ts +35 -0
  161. package/src/lib/shared/components/button/button.ts +65 -0
  162. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  163. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  164. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  165. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  166. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  167. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  168. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  169. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  170. package/src/lib/shared/components/modal/modal.ts +145 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  172. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  173. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  174. package/src/lib/shared/components/toast/toast.ts +58 -0
  175. package/src/lib/shared/utils/col-span.ts +25 -0
  176. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  177. package/src/lib/shared/utils/depends-on.ts +48 -0
  178. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  179. package/src/lib/shared/utils/field-path.ts +49 -0
  180. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  181. package/src/lib/shared/utils/seed.spec.ts +64 -0
  182. package/src/lib/shared/utils/seed.ts +149 -0
  183. package/src/lib/shared/utils/slugify.ts +13 -0
  184. package/src/lib/shared/utils/validators.ts +41 -0
  185. package/src/lib/shell/layout/layout.ts +63 -0
  186. package/src/lib/shell/shell.routes.spec.ts +59 -0
  187. package/src/lib/shell/shell.routes.ts +122 -0
  188. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  189. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  190. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  191. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  192. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  193. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  194. package/src/lib/views/composer/composer-host.ts +248 -0
  195. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  196. package/src/lib/views/composer/view-context.spec.ts +117 -0
  197. package/src/lib/views/composer/view-context.ts +109 -0
  198. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  199. package/src/lib/views/composer/view-slot.ts +178 -0
  200. package/src/lib/views/descriptors.ts +46 -0
  201. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  202. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  203. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  206. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  207. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  208. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  209. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  210. package/src/lib/widgets/descriptors.ts +29 -0
  211. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  212. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  213. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  216. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  217. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  218. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  219. package/src/lib/widgets/record-list/record-list.ts +115 -0
  220. package/src/lib/widgets/series.spec.ts +42 -0
  221. package/src/lib/widgets/series.ts +56 -0
  222. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  223. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  224. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  225. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  226. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  227. package/src/public-api.ts +104 -0
  228. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  229. package/tsconfig.lib.json +13 -0
  230. package/tsconfig.lib.prod.json +11 -0
  231. package/tsconfig.spec.json +10 -0
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  233. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  235. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  237. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  239. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  241. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  243. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  245. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  247. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  249. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  251. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  253. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  255. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  257. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  259. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  261. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  263. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  265. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  267. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  269. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  271. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  273. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  275. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  277. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  279. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  281. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  283. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  285. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  287. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  289. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  291. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  293. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  295. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  297. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  299. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  301. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  303. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  305. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  307. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  309. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  311. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  313. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  315. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  317. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  319. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  321. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  323. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  325. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  327. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  329. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  333. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  335. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  337. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  338. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  340. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  342. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  344. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  346. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  348. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  350. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  352. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  354. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  356. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  358. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  360. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  362. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  364. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  366. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  368. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  369. 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
+ }