@tinya-code/gas-admin-ui 0.2.0-alpha.13 → 0.2.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +3 -15
  3. package/src/lib/actions/action-registry.spec.ts +52 -0
  4. package/src/lib/actions/action-registry.ts +126 -0
  5. package/src/lib/actions/action-service.spec.ts +215 -0
  6. package/src/lib/actions/action-service.ts +87 -0
  7. package/src/lib/actions/descriptors.spec.ts +155 -0
  8. package/src/lib/actions/descriptors.ts +154 -0
  9. package/src/lib/columns/column-cell.ts +33 -0
  10. package/src/lib/columns/column-defaults.spec.ts +99 -0
  11. package/src/lib/columns/column-defaults.ts +84 -0
  12. package/src/lib/columns/column-format.spec.ts +274 -0
  13. package/src/lib/columns/descriptors.ts +113 -0
  14. package/src/lib/columns/format-badge.ts +51 -0
  15. package/src/lib/columns/format-boolean.ts +58 -0
  16. package/src/lib/columns/format-currency.ts +30 -0
  17. package/src/lib/columns/format-date.ts +35 -0
  18. package/src/lib/columns/format-number.ts +32 -0
  19. package/src/lib/columns/format-text.ts +23 -0
  20. package/src/lib/columns/format-thumbnail.ts +63 -0
  21. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  22. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  23. package/src/lib/core/guards/auth.guard.ts +48 -0
  24. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  25. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  27. package/src/lib/core/models/api.model.ts +104 -0
  28. package/src/lib/core/models/schema-descriptions.ts +118 -0
  29. package/src/lib/core/models/schema.model.ts +1097 -0
  30. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  31. package/src/lib/core/registry/composition-validator.ts +270 -0
  32. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  33. package/src/lib/core/registry/field-validators.ts +440 -0
  34. package/src/lib/core/registry/render-host.spec.ts +208 -0
  35. package/src/lib/core/registry/render-host.ts +646 -0
  36. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  37. package/src/lib/core/registry/render-registry.ts +312 -0
  38. package/src/lib/core/registry/schema-catalog.ts +156 -0
  39. package/src/lib/core/services/api.service.spec.ts +1142 -0
  40. package/src/lib/core/services/api.service.ts +371 -0
  41. package/src/lib/core/services/auth.service.spec.ts +653 -0
  42. package/src/lib/core/services/auth.service.ts +503 -0
  43. package/src/lib/core/services/config.service.spec.ts +121 -0
  44. package/src/lib/core/services/config.service.ts +119 -0
  45. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  46. package/src/lib/core/services/data-source.service.ts +142 -0
  47. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  48. package/src/lib/core/services/error-mapper.service.ts +49 -0
  49. package/src/lib/core/services/notification.service.ts +73 -0
  50. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  51. package/src/lib/core/services/theme.service.spec.ts +106 -0
  52. package/src/lib/core/services/theme.service.ts +86 -0
  53. package/src/lib/core/services/transport.spec.ts +198 -0
  54. package/src/lib/core/services/transport.ts +164 -0
  55. package/src/lib/core/services/upload.service.ts +81 -0
  56. package/src/lib/core/testing/categories.schema.ts +125 -0
  57. package/src/lib/core/testing/orders.schema.ts +372 -0
  58. package/src/lib/core/testing/products.schema.ts +258 -0
  59. package/src/lib/core/testing/site.schema.ts +195 -0
  60. package/src/lib/core/testing/test-catalog.ts +21 -0
  61. package/src/lib/core/tokens.ts +112 -0
  62. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  63. package/src/lib/core/utils/clipboard.ts +90 -0
  64. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  65. package/src/lib/core/utils/context-resolver.ts +101 -0
  66. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  67. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  68. package/src/lib/core/utils/source-options.spec.ts +96 -0
  69. package/src/lib/core/utils/source-options.ts +54 -0
  70. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  71. package/src/lib/core/utils/template-interpolator.ts +56 -0
  72. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  74. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  75. package/src/lib/fields/boolean/boolean.ts +40 -0
  76. package/src/lib/fields/color/color.descriptor.ts +12 -0
  77. package/src/lib/fields/color/color.ts +34 -0
  78. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  79. package/src/lib/fields/currency/currency.ts +67 -0
  80. package/src/lib/fields/date/date.descriptor.ts +12 -0
  81. package/src/lib/fields/date/date.ts +36 -0
  82. package/src/lib/fields/descriptors.ts +122 -0
  83. package/src/lib/fields/display/display-badge.ts +65 -0
  84. package/src/lib/fields/display/display-date.ts +39 -0
  85. package/src/lib/fields/display/display-defaults.ts +84 -0
  86. package/src/lib/fields/display/display-file.ts +41 -0
  87. package/src/lib/fields/display/display-image.ts +54 -0
  88. package/src/lib/fields/display/display-list.ts +107 -0
  89. package/src/lib/fields/display/display-number.ts +41 -0
  90. package/src/lib/fields/display/display-text.ts +42 -0
  91. package/src/lib/fields/display/display.spec.ts +242 -0
  92. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  93. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  94. package/src/lib/fields/email/email.descriptor.ts +12 -0
  95. package/src/lib/fields/email/email.ts +28 -0
  96. package/src/lib/fields/field-aria.ts +60 -0
  97. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  98. package/src/lib/fields/field-host/field-host.ts +47 -0
  99. package/src/lib/fields/field-node.ts +88 -0
  100. package/src/lib/fields/file/file.descriptor.ts +14 -0
  101. package/src/lib/fields/file/file.spec.ts +134 -0
  102. package/src/lib/fields/file/file.ts +281 -0
  103. package/src/lib/fields/group/group.descriptor.ts +21 -0
  104. package/src/lib/fields/group/group.ts +110 -0
  105. package/src/lib/fields/image/image.descriptor.ts +12 -0
  106. package/src/lib/fields/image/image.spec.ts +117 -0
  107. package/src/lib/fields/image/image.ts +281 -0
  108. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  109. package/src/lib/fields/key-value/key-value.ts +283 -0
  110. package/src/lib/fields/list/list.descriptor.ts +21 -0
  111. package/src/lib/fields/list/list.ts +376 -0
  112. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  113. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  114. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  115. package/src/lib/fields/number/number.descriptor.ts +12 -0
  116. package/src/lib/fields/number/number.ts +40 -0
  117. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  118. package/src/lib/fields/phone/phone.ts +46 -0
  119. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  120. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  121. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  122. package/src/lib/fields/relation/relation.spec.ts +193 -0
  123. package/src/lib/fields/relation/relation.ts +224 -0
  124. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  125. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  126. package/src/lib/fields/select/radio-mode.ts +25 -0
  127. package/src/lib/fields/select/select.descriptor.ts +12 -0
  128. package/src/lib/fields/select/select.spec.ts +162 -0
  129. package/src/lib/fields/select/select.ts +210 -0
  130. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  131. package/src/lib/fields/slug/slug.ts +67 -0
  132. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  133. package/src/lib/fields/string-list/string-list.ts +222 -0
  134. package/src/lib/fields/text/text.descriptor.ts +12 -0
  135. package/src/lib/fields/text/text.ts +35 -0
  136. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  137. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  138. package/src/lib/fields/textarea/textarea.ts +126 -0
  139. package/src/lib/fields/time/time.descriptor.ts +12 -0
  140. package/src/lib/fields/time/time.ts +28 -0
  141. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  142. package/src/lib/fields/url/url.descriptor.ts +12 -0
  143. package/src/lib/fields/url/url.ts +53 -0
  144. package/src/lib/gas-admin-ui.spec.ts +21 -0
  145. package/src/lib/gas-admin-ui.ts +9 -0
  146. package/src/lib/layouts/descriptors.ts +9 -0
  147. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  148. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  149. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  150. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  151. package/src/lib/pages/form-view/form-model.ts +88 -0
  152. package/src/lib/pages/form-view/form-schema.ts +255 -0
  153. package/src/lib/pages/form-view/form-view.ts +1622 -0
  154. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  155. package/src/lib/pages/list-view/list-view.ts +882 -0
  156. package/src/lib/pages/login/login.spec.ts +109 -0
  157. package/src/lib/pages/login/login.ts +227 -0
  158. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  159. package/src/lib/shared/components/badge/badge.ts +35 -0
  160. package/src/lib/shared/components/button/button.ts +65 -0
  161. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  162. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  163. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  164. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  165. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  166. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  167. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  168. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  169. package/src/lib/shared/components/modal/modal.ts +145 -0
  170. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  172. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  173. package/src/lib/shared/components/toast/toast.ts +58 -0
  174. package/src/lib/shared/utils/col-span.ts +25 -0
  175. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  176. package/src/lib/shared/utils/depends-on.ts +48 -0
  177. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  178. package/src/lib/shared/utils/field-path.ts +49 -0
  179. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  180. package/src/lib/shared/utils/seed.spec.ts +64 -0
  181. package/src/lib/shared/utils/seed.ts +149 -0
  182. package/src/lib/shared/utils/slugify.ts +13 -0
  183. package/src/lib/shared/utils/validators.ts +41 -0
  184. package/src/lib/shell/layout/layout.ts +63 -0
  185. package/src/lib/shell/shell.routes.spec.ts +59 -0
  186. package/src/lib/shell/shell.routes.ts +122 -0
  187. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  188. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  189. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  190. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  191. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  192. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  193. package/src/lib/views/composer/composer-host.ts +248 -0
  194. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  195. package/src/lib/views/composer/view-context.spec.ts +117 -0
  196. package/src/lib/views/composer/view-context.ts +109 -0
  197. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  198. package/src/lib/views/composer/view-slot.ts +178 -0
  199. package/src/lib/views/descriptors.ts +46 -0
  200. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  201. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  202. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  203. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  206. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  207. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  208. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  209. package/src/lib/widgets/descriptors.ts +29 -0
  210. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  211. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  212. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  213. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  216. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  217. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  218. package/src/lib/widgets/record-list/record-list.ts +115 -0
  219. package/src/lib/widgets/series.spec.ts +42 -0
  220. package/src/lib/widgets/series.ts +56 -0
  221. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  222. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  223. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  224. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  225. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  226. package/src/public-api.ts +104 -0
  227. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  228. package/tsconfig.lib.json +13 -0
  229. package/tsconfig.lib.prod.json +11 -0
  230. package/tsconfig.spec.json +10 -0
  231. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  233. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  235. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  237. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  239. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  241. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  243. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  245. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  247. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  249. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  251. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  253. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  255. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  257. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  259. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  261. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  263. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  265. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  267. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  269. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  271. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  273. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  275. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  277. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  279. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  281. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  283. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  285. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  287. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  289. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  291. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  293. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  295. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  297. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  299. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  301. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  303. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  305. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  307. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  309. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  311. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  313. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  315. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  317. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  319. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  321. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  323. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  325. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  327. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  329. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  333. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  335. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  337. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  338. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  340. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  342. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  344. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  346. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  348. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  350. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  352. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  354. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  356. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  358. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  360. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  362. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  364. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  366. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  368. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -0,0 +1,115 @@
1
+ import { Component, computed, inject, input } from '@angular/core';
2
+ import { Router, RouterLink } from '@angular/router';
3
+
4
+ import type { RecordListWidget } from '../../core/models/schema.model';
5
+ import { interpolateTemplate, parseNavigationUrl } from '../../core/utils/template-interpolator';
6
+ import { EmptyState } from '../../shared/components/empty-state/empty-state';
7
+
8
+ /**
9
+ * Tabla embebida de registros operativos para el Dashboard.
10
+ * Permite visualizar filas clave (ej. pedidos que requieren atención)
11
+ * con enlace de acción por fila hacia su ficha de detalle.
12
+ */
13
+ @Component({
14
+ selector: 'app-record-list',
15
+ imports: [EmptyState, RouterLink],
16
+ template: `
17
+ <article class="flex h-full flex-col rounded-xl border border-neutral/20 bg-surface p-4">
18
+ <header class="mb-3 flex items-center justify-between">
19
+ <div class="flex items-center gap-2">
20
+ <h2 class="text-sm font-semibold text-neutral-light">{{ widget().label }}</h2>
21
+ @if (rows().length > 0) {
22
+ <span
23
+ class="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary"
24
+ >
25
+ {{ rows().length }}
26
+ </span>
27
+ }
28
+ </div>
29
+ </header>
30
+
31
+ @if (rows().length === 0) {
32
+ <div class="flex flex-1 items-center justify-center py-6">
33
+ <app-empty-state
34
+ title="Sin registros pendientes"
35
+ message="No hay registros que requieran atención en este momento."
36
+ />
37
+ </div>
38
+ } @else {
39
+ <div class="flex-1 overflow-x-auto">
40
+ <table class="w-full text-left text-sm" role="table">
41
+ <thead>
42
+ <tr class="border-b border-neutral/10 text-xs text-neutral">
43
+ @for (col of widget().columns; track col) {
44
+ <th scope="col" class="pb-2 font-medium capitalize">{{ formatHeader(col) }}</th>
45
+ }
46
+ @if (widget().action) {
47
+ <th scope="col" class="pb-2 text-right font-medium">Acción</th>
48
+ }
49
+ </tr>
50
+ </thead>
51
+ <tbody class="divide-y divide-neutral/10">
52
+ @for (row of rows(); track trackRow(row, $index)) {
53
+ <tr class="transition-colors hover:bg-neutral/5">
54
+ @for (col of widget().columns; track col) {
55
+ <td class="py-2.5 pr-3 text-neutral-light">
56
+ <span class="line-clamp-1">{{ formatCell(row[col]) }}</span>
57
+ </td>
58
+ }
59
+ @if (widget().action) {
60
+ <td class="py-2.5 text-right">
61
+ <a
62
+ [routerLink]="resolveRoute(row).path"
63
+ [queryParams]="resolveRoute(row).queryParams"
64
+ class="inline-flex items-center rounded-lg border border-neutral/30 bg-surface px-2.5 py-1 text-xs font-medium text-neutral-light transition-colors hover:bg-neutral/10"
65
+ >
66
+ {{ widget().action?.label || 'Ver' }}
67
+ </a>
68
+ </td>
69
+ }
70
+ </tr>
71
+ }
72
+ </tbody>
73
+ </table>
74
+ </div>
75
+ }
76
+ </article>
77
+ `,
78
+ })
79
+ export class RecordList {
80
+ protected readonly router = inject(Router, { optional: true });
81
+
82
+ readonly widget = input.required<RecordListWidget>();
83
+ readonly data = input<Record<string, unknown>[] | null>(null);
84
+
85
+ protected readonly rows = computed<Record<string, unknown>[]>(() => {
86
+ const val = this.data();
87
+ if (!Array.isArray(val)) return [];
88
+ return val.filter(
89
+ (item): item is Record<string, unknown> => typeof item === 'object' && item !== null,
90
+ );
91
+ });
92
+
93
+ protected formatHeader(key: string): string {
94
+ return key.replace(/_/g, ' ');
95
+ }
96
+
97
+ protected formatCell(value: unknown): string {
98
+ if (value === null || value === undefined || value === '') return '—';
99
+ if (typeof value === 'object') return JSON.stringify(value);
100
+ return String(value);
101
+ }
102
+
103
+ protected trackRow(row: Record<string, unknown>, index: number): string | number {
104
+ return (row['id'] as string) || (row['ref'] as string) || (row['key'] as string) || index;
105
+ }
106
+
107
+ protected resolveRoute(row: Record<string, unknown>): {
108
+ path: string;
109
+ queryParams: Record<string, string>;
110
+ } {
111
+ const template = this.widget().action?.routeTemplate;
112
+ if (!template) return { path: '', queryParams: {} };
113
+ return parseNavigationUrl(interpolateTemplate(template, row));
114
+ }
115
+ }
@@ -0,0 +1,42 @@
1
+ import { toSeries } from './series';
2
+
3
+ // R6.3 — el agregado con groupBy del backend (R6.2) devuelve
4
+ // [{ group, value }], no [{ label, value }]. `toSeries` es el puente
5
+ // entre el contrato del motor y el de los gráficos: debe aceptar AMBAS
6
+ // formas. Sin esto, chart-line/bar-chart pintarían [] con un agregado
7
+ // agrupado declarado.
8
+ describe('toSeries', () => {
9
+ it('normaliza las filas del agregado agrupado { group, value }', () => {
10
+ expect(
11
+ toSeries([
12
+ { group: '2024-03-15', value: 30 },
13
+ { group: '2024-03-16', value: 7 },
14
+ ]),
15
+ ).toEqual([
16
+ { label: '2024-03-15', value: 30 },
17
+ { label: '2024-03-16', value: 7 },
18
+ ]);
19
+ });
20
+
21
+ it('conserva la forma histórica { label, value }', () => {
22
+ expect(toSeries([{ label: 'Lun', value: 3 }])).toEqual([{ label: 'Lun', value: 3 }]);
23
+ });
24
+
25
+ it('descarta filas cuyo group no es string o value no es number', () => {
26
+ expect(
27
+ toSeries([
28
+ { group: 'ok', value: 1 },
29
+ { group: 42, value: 2 },
30
+ { group: 'mal', value: 'x' },
31
+ null,
32
+ 'fila',
33
+ ]),
34
+ ).toEqual([{ label: 'ok', value: 1 }]);
35
+ });
36
+
37
+ it('devuelve [] ante cualquier cosa que no sea array', () => {
38
+ expect(toSeries(null)).toEqual([]);
39
+ expect(toSeries({ group: 'x', value: 1 })).toEqual([]);
40
+ expect(toSeries(7)).toEqual([]);
41
+ });
42
+ });
@@ -0,0 +1,56 @@
1
+ import type { BarDatum } from './bar-chart/bar-chart';
2
+
3
+ /**
4
+ * Normaliza el payload de un gráfico a `{ label: string, value: number }`.
5
+ *
6
+ * El dashboard recibe filas de una tabla y no todas llegan limpias, así que
7
+ * el filtro se hace en el **borde** — en el descriptor, antes de que el dato
8
+ * llegue al componente — y no dentro de cada gráfico. `bar-chart` y
9
+ * `chart-line` comparten la misma regla, de ahí la función suelta.
10
+ *
11
+ * Devuelve `[]` ante cualquier cosa que no sea un array: un `metric-card`
12
+ * pasado por error a un gráfico no puede romper la tarjeta.
13
+ */
14
+ export function toSeries(data: unknown): BarDatum[] {
15
+ if (!Array.isArray(data)) {
16
+ return [];
17
+ }
18
+ return data
19
+ .map((row): BarDatum | null => {
20
+ if (typeof row !== 'object' || row === null) return null;
21
+ const r = row as { label?: unknown; group?: unknown; value?: unknown };
22
+ // El agregado agrupado del backend (R6.2) llega como { group, value };
23
+ // la forma histórica de tabla es { label, value }. El gráfico sólo
24
+ // entiende label ⇒ traducimos acá, en el borde.
25
+ const label = typeof r.label === 'string' ? r.label : r.group;
26
+ if (typeof label !== 'string' || typeof r.value !== 'number') return null;
27
+ return { label, value: r.value };
28
+ })
29
+ .filter((row): row is BarDatum => row !== null);
30
+ }
31
+
32
+ /**
33
+ * Normaliza el payload de un contador repartido por segmentos a
34
+ * `{ clave: número }`.
35
+ *
36
+ * Dos formas, igual que `toSeries`:
37
+ * - **payload** del panel: ya es un objeto plano de contadores por clave;
38
+ * - **fuente declarada** (F8 · R6.5): una vista con `groupBy` devuelve filas
39
+ * `[{ group, value }]` y cada grupo es el nombre de un segmento.
40
+ *
41
+ * `null` ante cualquier otra cosa: `StatusProgress` lo pinta como vacío en
42
+ * vez de sumar un `[object Object]`.
43
+ */
44
+ export function toCounters(data: unknown): Record<string, unknown> | null {
45
+ if (Array.isArray(data)) {
46
+ const out: Record<string, unknown> = {};
47
+ for (const row of data) {
48
+ if (typeof row !== 'object' || row === null) continue;
49
+ const r = row as { group?: unknown; value?: unknown };
50
+ if (typeof r.group === 'string' && 'value' in r) out[r.group] = r.value;
51
+ }
52
+ return out;
53
+ }
54
+ if (typeof data === 'object' && data !== null) return data as Record<string, unknown>;
55
+ return null;
56
+ }
@@ -0,0 +1,19 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ import { toCounters } from '../series';
4
+
5
+ /** Descriptor del widget `status-progress` (R3.6). */
6
+ export const descriptor: RenderDescriptor = {
7
+ kind: 'widget',
8
+ type: 'status-progress',
9
+ since: '1.0',
10
+ load: () => import('./status-progress').then((module) => module.StatusProgress),
11
+ surfaces: ['display'],
12
+ // Contadores por clave: un objeto plano del payload O las filas de una
13
+ // fuente con `groupBy` (F8 · R6.5) — `toCounters` traduce en el borde y
14
+ // devuelve `null` si no hay nada que contar.
15
+ inputs: (config, extras) => ({
16
+ widget: config,
17
+ data: toCounters(extras.data),
18
+ }),
19
+ };
@@ -0,0 +1,63 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { describe, expect, it, beforeEach } from 'vitest';
3
+
4
+ import type { StatusProgressWidget } from '../../core/models/schema.model';
5
+ import { StatusProgress } from './status-progress';
6
+
7
+ describe('StatusProgress', () => {
8
+ let fixture: ComponentFixture<StatusProgress>;
9
+
10
+ const widget: StatusProgressWidget = {
11
+ type: 'status-progress',
12
+ key: 'balance_dia',
13
+ label: 'Progreso de Pedidos del Día',
14
+ segments: [
15
+ { key: 'entregados', label: 'Entregados', color: 'success' },
16
+ { key: 'pendientes', label: 'Pendientes', color: 'warning' },
17
+ { key: 'cancelados', label: 'Cancelados', color: 'danger' },
18
+ ],
19
+ };
20
+
21
+ beforeEach(async () => {
22
+ await TestBed.configureTestingModule({
23
+ imports: [StatusProgress],
24
+ }).compileComponents();
25
+ fixture = TestBed.createComponent(StatusProgress);
26
+ });
27
+
28
+ it('1. calcula y muestra los valores y porcentajes correctamente', () => {
29
+ const data = { entregados: 5, pendientes: 3, cancelados: 2 };
30
+
31
+ fixture.componentRef.setInput('widget', widget);
32
+ fixture.componentRef.setInput('data', data);
33
+ fixture.detectChanges();
34
+
35
+ const root = fixture.nativeElement as HTMLElement;
36
+ expect(root.textContent).toContain('Progreso de Pedidos del Día');
37
+ expect(root.textContent).toContain('Total: 10');
38
+ expect(root.textContent).toContain('5 (50%)');
39
+ expect(root.textContent).toContain('3 (30%)');
40
+ expect(root.textContent).toContain('2 (20%)');
41
+ });
42
+
43
+ it('2. maneja total en 0 sin dividir por cero', () => {
44
+ const data = { entregados: 0, pendientes: 0, cancelados: 0 };
45
+
46
+ fixture.componentRef.setInput('widget', widget);
47
+ fixture.componentRef.setInput('data', data);
48
+ fixture.detectChanges();
49
+
50
+ const root = fixture.nativeElement as HTMLElement;
51
+ expect(root.textContent).toContain('Total: 0');
52
+ expect(root.textContent).toContain('0 (0%)');
53
+ });
54
+
55
+ it('3. maneja datos nulos asignando 0%', () => {
56
+ fixture.componentRef.setInput('widget', widget);
57
+ fixture.componentRef.setInput('data', null);
58
+ fixture.detectChanges();
59
+
60
+ const root = fixture.nativeElement as HTMLElement;
61
+ expect(root.textContent).toContain('Total: 0');
62
+ });
63
+ });
@@ -0,0 +1,104 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import type { StatusProgressWidget } from '../../core/models/schema.model';
4
+
5
+ interface SegmentDisplay {
6
+ key: string;
7
+ label: string;
8
+ value: number;
9
+ percentage: number;
10
+ barClass: string;
11
+ dotClass: string;
12
+ }
13
+
14
+ const COLOR_MAP: Record<string, { bar: string; dot: string }> = {
15
+ success: { bar: 'bg-emerald-500', dot: 'bg-emerald-500' },
16
+ warning: { bar: 'bg-amber-500', dot: 'bg-amber-500' },
17
+ danger: { bar: 'bg-rose-500', dot: 'bg-rose-500' },
18
+ neutral: { bar: 'bg-neutral/40', dot: 'bg-neutral/40' },
19
+ };
20
+
21
+ /**
22
+ * Barra de distribución porcentual de estados operativos.
23
+ */
24
+ @Component({
25
+ selector: 'app-status-progress',
26
+ template: `
27
+ <article class="flex h-full flex-col rounded-xl border border-neutral/20 bg-surface p-4">
28
+ <header class="mb-3 flex items-center justify-between">
29
+ <h2 class="text-sm font-semibold text-neutral-light">{{ widget().label }}</h2>
30
+ <span class="text-xs text-neutral" data-testid="total-count">
31
+ Total: {{ totalCount() }}
32
+ </span>
33
+ </header>
34
+
35
+ <!-- Barra segmentada -->
36
+ <div
37
+ class="flex h-3 w-full overflow-hidden rounded-full bg-neutral/10"
38
+ role="progressbar"
39
+ [attr.aria-valuenow]="totalCount()"
40
+ [attr.aria-valuemin]="0"
41
+ [attr.aria-valuemax]="totalCount()"
42
+ [attr.aria-label]="widget().label"
43
+ >
44
+ @if (totalCount() === 0) {
45
+ <div class="h-full w-full bg-neutral/20"></div>
46
+ } @else {
47
+ @for (seg of calculatedSegments(); track seg.key) {
48
+ @if (seg.percentage > 0) {
49
+ <div
50
+ [class]="seg.barClass"
51
+ [style.width.%]="seg.percentage"
52
+ [title]="seg.label + ': ' + seg.value + ' (' + seg.percentage.toFixed(1) + '%)'"
53
+ ></div>
54
+ }
55
+ }
56
+ }
57
+ </div>
58
+
59
+ <!-- Leyenda -->
60
+ <div class="mt-3.5 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs">
61
+ @for (seg of calculatedSegments(); track seg.key) {
62
+ <div class="flex items-center gap-1.5 text-neutral-light">
63
+ <span class="h-2 w-2 rounded-full" [class]="seg.dotClass"></span>
64
+ <span class="font-medium">{{ seg.label }}:</span>
65
+ <span class="text-neutral">{{ seg.value }} ({{ seg.percentage.toFixed(0) }}%)</span>
66
+ </div>
67
+ }
68
+ </div>
69
+ </article>
70
+ `,
71
+ })
72
+ export class StatusProgress {
73
+ readonly widget = input.required<StatusProgressWidget>();
74
+ readonly data = input<Record<string, unknown> | null>(null);
75
+
76
+ protected readonly totalCount = computed(() => {
77
+ const d = this.data() || {};
78
+ return this.widget().segments.reduce((acc, seg) => {
79
+ const val = Number(d[seg.key]);
80
+ return acc + (isNaN(val) ? 0 : Math.max(0, val));
81
+ }, 0);
82
+ });
83
+
84
+ protected readonly calculatedSegments = computed<SegmentDisplay[]>(() => {
85
+ const d = this.data() || {};
86
+ const total = this.totalCount();
87
+
88
+ return this.widget().segments.map((seg) => {
89
+ const raw = Number(d[seg.key]);
90
+ const value = isNaN(raw) ? 0 : Math.max(0, raw);
91
+ const percentage = total > 0 ? (value / total) * 100 : 0;
92
+ const colors = COLOR_MAP[seg.color] || COLOR_MAP['neutral'];
93
+
94
+ return {
95
+ key: seg.key,
96
+ label: seg.label,
97
+ value,
98
+ percentage,
99
+ barClass: colors.bar,
100
+ dotClass: colors.dot,
101
+ };
102
+ });
103
+ });
104
+ }
@@ -0,0 +1,179 @@
1
+ import { provideRouter } from '@angular/router';
2
+ import { DeferBlockBehavior, ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { isSupported } from '../../core/registry/render-registry';
6
+ import type { WidgetSchema } from '../../core/models/schema.model';
7
+ import { WidgetHost } from './widget-host';
8
+
9
+ describe('WidgetHost', () => {
10
+ let fixture: ComponentFixture<WidgetHost>;
11
+
12
+ const root = (): HTMLElement => fixture.nativeElement as HTMLElement;
13
+ const bars = (): HTMLElement[] => [...root().querySelectorAll<HTMLElement>('[role="img"] > div')];
14
+
15
+ /**
16
+ * El componente sale del registro con un `import()`, igual que los campos:
17
+ * `whenStable()` sólo lo cubre porque `RenderHost` lo registra como tarea
18
+ * pendiente (D28). Sin este ciclo el outlet todavía muestra el skeleton.
19
+ */
20
+ const pintar = async (): Promise<void> => {
21
+ fixture.detectChanges();
22
+ await fixture.whenStable();
23
+ fixture.detectChanges();
24
+ };
25
+
26
+ beforeEach(async () => {
27
+ await TestBed.configureTestingModule({
28
+ imports: [WidgetHost],
29
+ providers: [provideRouter([])],
30
+ // `chart-line` lleva su propio `@defer (on viewport)` y jsdom no tiene
31
+ // `IntersectionObserver`: sin Manual el bloque reventaría al evaluarse.
32
+ deferBlockBehavior: DeferBlockBehavior.Manual,
33
+ }).compileComponents();
34
+ fixture = TestBed.createComponent(WidgetHost);
35
+ });
36
+
37
+ it('mismo contrato que field-host: fuera del catálogo no se dibuja', async () => {
38
+ expect(isSupported('widget', 'metric-card')).toBe(true);
39
+ expect(isSupported('widget', 'bar-chart')).toBe(true);
40
+ expect(isSupported('widget', 'chart-line')).toBe(true);
41
+ expect(isSupported('widget', 'record-list')).toBe(true);
42
+ expect(isSupported('widget', 'quick-actions')).toBe(true);
43
+ expect(isSupported('widget', 'status-progress')).toBe(true);
44
+ expect(isSupported('widget', 'pie-chart')).toBe(false);
45
+
46
+ fixture.componentRef.setInput('widget', {
47
+ type: 'pie-chart',
48
+ key: 'x',
49
+ label: 'X',
50
+ } as unknown as WidgetSchema);
51
+ await pintar();
52
+
53
+ expect(root().querySelector('app-metric-card')).toBeNull();
54
+ expect(root().querySelector('app-bar-chart')).toBeNull();
55
+ expect(root().querySelector('app-record-list')).toBeNull();
56
+ expect(root().querySelector('app-quick-actions')).toBeNull();
57
+ expect(root().querySelector('app-status-progress')).toBeNull();
58
+ });
59
+
60
+ it('despacha metric-card a la tarjeta con el dato recibido', async () => {
61
+ fixture.componentRef.setInput('widget', {
62
+ type: 'metric-card',
63
+ key: 'pendientes',
64
+ label: 'Pendientes',
65
+ });
66
+ fixture.componentRef.setInput('data', 3);
67
+ await pintar();
68
+
69
+ expect(root().querySelector('app-metric-card')).not.toBeNull();
70
+ expect(root().textContent).toContain('Pendientes');
71
+ expect(root().querySelector('[data-testid="value"]')?.textContent?.trim()).toBe('3');
72
+ });
73
+
74
+ it('despacha record-list con datos tabulares', async () => {
75
+ fixture.componentRef.setInput('widget', {
76
+ type: 'record-list',
77
+ key: 'pedidos',
78
+ label: 'Pedidos Activos',
79
+ columns: ['ref', 'status'],
80
+ });
81
+ fixture.componentRef.setInput('data', [{ ref: 'ORD-10', status: 'PENDIENTE' }]);
82
+ await pintar();
83
+
84
+ expect(root().querySelector('app-record-list')).not.toBeNull();
85
+ expect(root().textContent).toContain('Pedidos Activos');
86
+ expect(root().textContent).toContain('ORD-10');
87
+ });
88
+
89
+ it('despacha quick-actions con botones de atajo', async () => {
90
+ fixture.componentRef.setInput('widget', {
91
+ type: 'quick-actions',
92
+ key: 'accesos',
93
+ label: 'Acciones Rápidas',
94
+ actions: [{ label: 'Nuevo Pedido', navigateTo: '/orders/new' }],
95
+ });
96
+ await pintar();
97
+
98
+ expect(root().querySelector('app-quick-actions')).not.toBeNull();
99
+ expect(root().textContent).toContain('Acciones Rápidas');
100
+ expect(root().textContent).toContain('Nuevo Pedido');
101
+ });
102
+
103
+ it('despacha status-progress con barra porcentual', async () => {
104
+ fixture.componentRef.setInput('widget', {
105
+ type: 'status-progress',
106
+ key: 'balance',
107
+ label: 'Balance Diario',
108
+ segments: [{ key: 'ok', label: 'Completado', color: 'success' }],
109
+ });
110
+ fixture.componentRef.setInput('data', { ok: 10 });
111
+ await pintar();
112
+
113
+ expect(root().querySelector('app-status-progress')).not.toBeNull();
114
+ expect(root().textContent).toContain('Balance Diario');
115
+ expect(root().textContent).toContain('Completado');
116
+ });
117
+
118
+ it('despacha chart-line con su serie (el defer lo decide el propio widget)', async () => {
119
+ fixture.componentRef.setInput('widget', {
120
+ type: 'chart-line',
121
+ key: 'serie',
122
+ label: 'Entregas por día',
123
+ });
124
+ fixture.componentRef.setInput('data', []);
125
+ await pintar();
126
+
127
+ expect(root().querySelector('app-chart-line')).not.toBeNull();
128
+ expect(root().querySelector('[data-testid="empty"]')?.textContent?.trim()).toBe('Sin datos.');
129
+ });
130
+
131
+ it('con loading reserva su espacio con skeleton sin montar el widget', () => {
132
+ // Cargar ≠ vacío (doc/ux-ui-mobile.md §estados): la tarjeta no pinta
133
+ // su estado vacío mientras el dato viaja, y la grilla conserva el span.
134
+ fixture.componentRef.setInput('widget', {
135
+ type: 'metric-card',
136
+ key: 'kpi',
137
+ label: 'KPI',
138
+ width: 8,
139
+ });
140
+ fixture.componentRef.setInput('loading', true);
141
+ fixture.detectChanges();
142
+
143
+ expect(root().querySelector('app-skeleton')).not.toBeNull();
144
+ expect(root().querySelector('app-metric-card')).toBeNull();
145
+ expect(root().className).toContain('col-span-12 lg:col-span-8');
146
+ });
147
+
148
+ it('calcula la clase de ancho de columna en la grilla', async () => {
149
+ fixture.componentRef.setInput('widget', {
150
+ type: 'metric-card',
151
+ key: 'kpi',
152
+ label: 'KPI',
153
+ width: 8,
154
+ });
155
+ await pintar();
156
+
157
+ expect(root().className).toContain('col-span-12 lg:col-span-8');
158
+ });
159
+
160
+ it('bar-chart descarta las series que no sean { label: string, value: number }', async () => {
161
+ fixture.componentRef.setInput('widget', {
162
+ type: 'bar-chart',
163
+ key: 'por_dia',
164
+ label: 'Entregas',
165
+ });
166
+ fixture.componentRef.setInput('data', [
167
+ { label: 'Lun', value: 2 },
168
+ { label: 'Mal', value: 'no-numérico' },
169
+ 'no-objeto',
170
+ { label: 'Mar', value: 5 },
171
+ ]);
172
+ await pintar();
173
+
174
+ expect(bars().length).toBe(2);
175
+ expect(root().textContent).toContain('Lun');
176
+ expect(root().textContent).toContain('Mar');
177
+ expect(root().textContent).not.toContain('Mal');
178
+ });
179
+ });
@@ -0,0 +1,99 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import type { WidgetSchema } from '../../core/models/schema.model';
4
+ import { RenderHost } from '../../core/registry/render-host';
5
+ import { Skeleton } from '../../shared/components/skeleton/skeleton';
6
+
7
+ /** Ancho mobile (base, sin prefijo) — literales para JIT de Tailwind v4. */
8
+ const MOBILE_SPANS: Record<number, string> = {
9
+ 1: 'col-span-1',
10
+ 2: 'col-span-2',
11
+ 3: 'col-span-3',
12
+ 4: 'col-span-4',
13
+ 5: 'col-span-5',
14
+ 6: 'col-span-6',
15
+ 7: 'col-span-7',
16
+ 8: 'col-span-8',
17
+ 9: 'col-span-9',
18
+ 10: 'col-span-10',
19
+ 11: 'col-span-11',
20
+ 12: 'col-span-12',
21
+ };
22
+
23
+ /** Ancho responsive (sm/lg) — literales para JIT de Tailwind v4. */
24
+ const RESPONSIVE_SPANS: Record<number, string> = {
25
+ 12: '',
26
+ 11: 'lg:col-span-11',
27
+ 10: 'lg:col-span-10',
28
+ 9: 'lg:col-span-9',
29
+ 8: 'lg:col-span-8',
30
+ 7: 'sm:col-span-6 lg:col-span-7',
31
+ 6: 'sm:col-span-6',
32
+ 5: 'sm:col-span-6 lg:col-span-5',
33
+ 4: 'sm:col-span-6 lg:col-span-4',
34
+ 3: 'sm:col-span-6 lg:col-span-3',
35
+ 2: 'sm:col-span-6 lg:col-span-2',
36
+ 1: 'sm:col-span-6 lg:col-span-1',
37
+ };
38
+
39
+ /**
40
+ * Adaptador de widgets (R3.6 · `doc/plan-refactor-motor.md` §3.6).
41
+ *
42
+ * Ya no despacha: `WidgetHost` era un `@switch` de seis ramas con los seis
43
+ * componentes importados a mano, exactamente lo que el registro viene a
44
+ * eliminar. Hoy sólo conserva lo que es **suyo** — traducir el contrato
45
+ * histórico `widget`/`data` al contrato genérico del motor
46
+ * (`type`/`config`/`data`) y pintar la clase de ancho en la grilla, que es
47
+ * una decisión del panel y no del motor de render.
48
+ *
49
+ * El `@switch`, los seis imports y `SUPPORTED_WIDGET_TYPES` desaparecieron:
50
+ * el tipo sale por `[type]`, el registro devuelve el componente y el
51
+ * `<app-render-host>` lo monta. Añadir un widget ⇒ carpeta + descriptor +
52
+ * una línea en `widgets/descriptors.ts` (R3.8).
53
+ *
54
+ * Nota sobre la accesibilidad: los widgets no llevan `aria-live`. Cada uno
55
+ * ya publica su propio estado (`role="img"` con resumen, `data-testid`,
56
+ * texto visible) y envolver el outlet entero en un `aria-live` anunciaría
57
+ * el panel completo con cada actualización de datos.
58
+ */
59
+ @Component({
60
+ selector: 'app-widget-host',
61
+ imports: [RenderHost, Skeleton],
62
+ host: {
63
+ '[class]': 'gridSpanClass()',
64
+ },
65
+ template: `
66
+ @if (loading()) {
67
+ <!-- Reserva el espacio del widget mientras su dato viaja (la red
68
+ Apps Script tarda ~1s): sin esto el panel se veía "completo"
69
+ con valores vacíos que después aparecían solos (cargar ≠ vacío,
70
+ doc/ux-ui-mobile.md §estados de UI). La grilla mantiene su
71
+ span porque la clase la pone el host. -->
72
+ <div class="rounded-lg border border-neutral/20 bg-surface p-4">
73
+ <app-skeleton [lines]="1" [height]="14" width="40%" />
74
+ <div class="mt-3">
75
+ <app-skeleton [lines]="2" [height]="32" />
76
+ </div>
77
+ </div>
78
+ } @else {
79
+ <app-render-host kind="widget" [type]="widget().type" [config]="widget()" [data]="data()" />
80
+ }
81
+ `,
82
+ })
83
+ export class WidgetHost {
84
+ readonly widget = input.required<WidgetSchema>();
85
+ readonly data = input<unknown>(null);
86
+ /** El dato del widget todavía viaja: la tarjeta muestra skeleton. */
87
+ readonly loading = input(false);
88
+
89
+ /** Clases de ancho responsive (literales: Tailwind v4 es JIT, no interpolar). */
90
+ protected readonly gridSpanClass = computed(() => {
91
+ const w = this.widget().width ?? 4;
92
+ const mw = this.widget().mobileWidth ?? 12;
93
+
94
+ const mobile = MOBILE_SPANS[mw] ?? 'col-span-12';
95
+ const responsive = RESPONSIVE_SPANS[w] ?? 'sm:col-span-6 lg:col-span-4';
96
+
97
+ return [mobile, responsive, 'block'].filter(Boolean).join(' ');
98
+ });
99
+ }