@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,219 @@
1
+ import { Component, OnDestroy, computed, effect, input, viewChild } from '@angular/core';
2
+ import type { ElementRef } from '@angular/core';
3
+ import {
4
+ CategoryScale,
5
+ Chart,
6
+ LineController,
7
+ LineElement,
8
+ LinearScale,
9
+ PointElement,
10
+ Tooltip,
11
+ type ChartConfiguration,
12
+ } from 'chart.js';
13
+
14
+ import { Skeleton } from '../../shared/components/skeleton/skeleton';
15
+ import type { BarDatum } from '../bar-chart/bar-chart';
16
+
17
+ // Chart.js usa registro explícito en vez de `chart.js/auto`: sólo entra lo que
18
+ // esta serie necesita (sin DoughnutController, sin Filler, sin Legend).
19
+ Chart.register(CategoryScale, LineController, LineElement, LinearScale, PointElement, Tooltip);
20
+
21
+ /**
22
+ * Serie temporal con Chart.js (viabilidad B2.1).
23
+ *
24
+ * La librería —61,4 K gz medidos— sólo se arrastra con ESTE componente y se
25
+ * descarga en su propio chunk: el descriptor del widget la trae con
26
+ * `import('./chart-line')` y es esa llamada la que hace que esbuild la
27
+ * aparte del bundle inicial (aquí se importa estáticamente, así que ningún
28
+ * `@defer` de otro fichero podría separarla).
29
+ *
30
+ * El `@defer (on viewport)` de abajo cumple el otro objetivo y vive aquí, y
31
+ * no en un host: el chunk llega con el panel, pero el lienzo y la instancia
32
+ * de `Chart` sólo se crean cuando el gráfico entra en pantalla. Un host no
33
+ * puede decidir eso — desde R3.6 el motor no conoce ningún tipo concreto
34
+ * (R3.6 · `doc/plan-refactor-motor.md`).
35
+ *
36
+ * La condición `on viewport` necesita `IntersectionObserver`, que jsdom no
37
+ * trae: los tests configuran `DeferBlockBehavior.Manual` y pintan el bloque
38
+ * a mano, igual que hacía `widget-host.spec`.
39
+ */
40
+ @Component({
41
+ selector: 'app-chart-line',
42
+ imports: [Skeleton],
43
+ template: `
44
+ <div class="rounded-lg border border-neutral/20 bg-surface p-4">
45
+ <p class="mb-3 text-sm text-neutral">{{ label() }}</p>
46
+
47
+ @if (data().length === 0) {
48
+ <p class="text-sm text-neutral/60" data-testid="empty">Sin datos.</p>
49
+ } @else {
50
+ <div aria-live="polite" aria-atomic="true">
51
+ @defer (on viewport) {
52
+ <div role="img" [attr.aria-label]="summary()" class="relative h-32">
53
+ <canvas #canvas class="absolute inset-0 h-full w-full"></canvas>
54
+ </div>
55
+ } @placeholder {
56
+ <div class="relative h-32" aria-hidden="true">
57
+ <svg
58
+ viewBox="0 0 100 40"
59
+ preserveAspectRatio="none"
60
+ class="absolute inset-0 h-full w-full text-primary"
61
+ >
62
+ <polyline
63
+ fill="none"
64
+ stroke="currentColor"
65
+ stroke-width="2"
66
+ vector-effect="non-scaling-stroke"
67
+ [attr.points]="sparkPoints()"
68
+ />
69
+ </svg>
70
+ </div>
71
+ } @loading {
72
+ <app-skeleton [lines]="3" [height]="24" />
73
+ } @error {
74
+ <p class="text-sm text-neutral/60" data-testid="chart-error">
75
+ No se pudo cargar el gráfico.
76
+ </p>
77
+ }
78
+ </div>
79
+ }
80
+ </div>
81
+ `,
82
+ })
83
+ export class ChartLine implements OnDestroy {
84
+ /** Título del gráfico. */
85
+ readonly label = input.required<string>();
86
+ /** Series a dibujar; vacío ⇒ estado «Sin datos». */
87
+ readonly data = input<BarDatum[]>([]);
88
+ /** Sufijo de las cifras del eje («unidades», «S/»). */
89
+ readonly unit = input('');
90
+
91
+ /**
92
+ * Opcional y no `required`: el lienzo sólo existe una vez completado el
93
+ * `@defer`, así que el `effect` de abajo se re-ejecuta solo al aparecer y
94
+ * dibuja en ese momento. Un `viewChild.required` reventaría en el primer
95
+ * ciclo, cuando el bloque todavía está en placeholder.
96
+ */
97
+ private readonly canvas = viewChild<ElementRef<HTMLCanvasElement>>('canvas');
98
+
99
+ private chart: Chart<'line'> | null = null;
100
+
101
+ constructor() {
102
+ effect(() => {
103
+ const lienzo = this.canvas();
104
+ this.data();
105
+ this.label();
106
+ this.unit();
107
+
108
+ if (lienzo) {
109
+ this.render(lienzo.nativeElement);
110
+ } else {
111
+ // Sin lienzo (sin datos, o el bloque aún en placeholder) no hay nada
112
+ // que mantener vivo: una instancia huérfana seguiría redibujando.
113
+ this.destroy();
114
+ }
115
+ });
116
+ }
117
+
118
+ ngOnDestroy(): void {
119
+ this.destroy();
120
+ }
121
+
122
+ protected readonly summary = computed(() => {
123
+ const rows = this.data();
124
+ if (rows.length === 0) {
125
+ return 'Sin datos.';
126
+ }
127
+ const unit = this.unit();
128
+ const first = rows[0];
129
+ const last = rows[rows.length - 1];
130
+ const delta = last.value - first.value;
131
+ const trend = delta > 0 ? 'sube' : delta < 0 ? 'baja' : 'se mantiene';
132
+ return (
133
+ `Serie de ${rows.length} puntos, de ${first.label} (${first.value}${unit}) ` +
134
+ `a ${last.label} (${last.value}${unit}): ${trend}.`
135
+ );
136
+ });
137
+
138
+ /** Miniatura del placeholder: la misma serie, dibujada con SVG y sin JS. */
139
+ protected readonly sparkPoints = computed(() => {
140
+ const rows = this.data();
141
+ if (rows.length === 0) {
142
+ return '';
143
+ }
144
+ const max = Math.max(0, ...rows.map((row) => row.value));
145
+ const last = rows.length - 1;
146
+ return rows
147
+ .map((row, index) => {
148
+ const x = last === 0 ? 50 : (index / last) * 100;
149
+ const y = max === 0 ? 40 : 40 - (row.value / max) * 36;
150
+ return `${x.toFixed(2)},${y.toFixed(2)}`;
151
+ })
152
+ .join(' ');
153
+ });
154
+
155
+ private render(canvas: HTMLCanvasElement): void {
156
+ const rows = this.data();
157
+
158
+ // Sin datos no hay lienzo en el template: crear un `Chart` sobre él
159
+ // reventaría (y el `@if` ya se lo habría llevado por delante).
160
+ if (rows.length === 0) {
161
+ this.destroy();
162
+ return;
163
+ }
164
+
165
+ const config: ChartConfiguration<'line'> = {
166
+ type: 'line',
167
+ data: {
168
+ labels: rows.map((row) => row.label),
169
+ datasets: [
170
+ {
171
+ label: this.label(),
172
+ data: rows.map((row) => row.value),
173
+ borderColor: this.cssColor('--color-primary'),
174
+ borderWidth: 2,
175
+ pointRadius: 3,
176
+ pointHoverRadius: 5,
177
+ tension: 0.35,
178
+ },
179
+ ],
180
+ },
181
+ options: {
182
+ responsive: true,
183
+ maintainAspectRatio: false,
184
+ plugins: {
185
+ legend: { display: false },
186
+ tooltip: { enabled: true, mode: 'index', intersect: false },
187
+ },
188
+ scales: {
189
+ x: { grid: { display: false } },
190
+ y: {
191
+ beginAtZero: true,
192
+ ticks: {
193
+ callback: (value) =>
194
+ typeof value === 'number' ? `${value}${this.unit()}` : String(value),
195
+ },
196
+ },
197
+ },
198
+ },
199
+ };
200
+
201
+ this.destroy();
202
+ this.chart = new Chart<'line'>(canvas, config);
203
+ }
204
+
205
+ private destroy(): void {
206
+ this.chart?.destroy();
207
+ this.chart = null;
208
+ }
209
+
210
+ /**
211
+ * Chart.js pinta en el lienzo y NO entiende `var(--…)`, así que el token se
212
+ * resuelve en tiempo de dibujo. Se lee en cada render —no al arrancar—
213
+ * porque el modo oscuro sobre-escribe `--color-primary` (`styles/theme.css`).
214
+ */
215
+ private cssColor(token: string): string {
216
+ const value = getComputedStyle(document.documentElement).getPropertyValue(token).trim();
217
+ return value || '#1d69f0';
218
+ }
219
+ }
@@ -0,0 +1,29 @@
1
+ import type { RenderDescriptor } from '../core/registry/render-registry';
2
+
3
+ import { descriptor as barChartDescriptor } from './bar-chart/bar-chart.descriptor';
4
+ import { descriptor as chartLineDescriptor } from './chart-line/chart-line.descriptor';
5
+ import { descriptor as metricCardDescriptor } from './metric-card/metric-card.descriptor';
6
+ import { descriptor as quickActionsDescriptor } from './quick-actions/quick-actions.descriptor';
7
+ import { descriptor as recordListDescriptor } from './record-list/record-list.descriptor';
8
+ import { descriptor as statusProgressDescriptor } from './status-progress/status-progress.descriptor';
9
+
10
+ /**
11
+ * Manifiesto de descriptores de widget (R3.6 · registro único de render).
12
+ *
13
+ * Mismo criterio que `fields/descriptors.ts`: una línea por tipo y el
14
+ * registro los recoge al importar este módulo. Es la **única** lista manual
15
+ * del catálogo de widgets — `import.meta.glob` no sobrevive a `ng build`
16
+ * (D9-1), así que se mantiene el «registro manual de una línea».
17
+ *
18
+ * Añadir un widget ⇒ crear la carpeta + su `.descriptor.ts` + una línea
19
+ * acá. **Ningún host se toca** (R3.8): si el componente sigue la forma
20
+ * estándar `{widget, data}` ni siquiera hay que escribir `inputs`.
21
+ */
22
+ export const widgetDescriptors: readonly RenderDescriptor[] = [
23
+ barChartDescriptor,
24
+ chartLineDescriptor,
25
+ metricCardDescriptor,
26
+ quickActionsDescriptor,
27
+ recordListDescriptor,
28
+ statusProgressDescriptor,
29
+ ];
@@ -0,0 +1,41 @@
1
+ import type { MetricCardWidget } from '../../core/models/schema.model';
2
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
3
+
4
+ /**
5
+ * El dato de un KPI es un ESCALAR y puede llegar de dos formas:
6
+ *
7
+ * - **payload**: `data[widget.key]` ya es la cifra (la resuelve el handler
8
+ * del panel);
9
+ * - **fuente declarada** (F8 · R6.5): una vista con `aggregate` devuelve
10
+ * `[{ value: … }]` y la cifra es ese `value`.
11
+ *
12
+ * Cualquier otra forma ⇒ `null`, que `MetricCard` pinta como vacío. Se
13
+ * traduce acá, en el borde — el componente no debería saber de dónde salió
14
+ * el número.
15
+ */
16
+ function valorDeKpi(data: unknown): number | string | null {
17
+ if (Array.isArray(data)) {
18
+ const first = data[0];
19
+ if (typeof first !== 'object' || first === null) return null;
20
+ const value = (first as { value?: unknown }).value;
21
+ return value === undefined || value === null ? null : (value as number | string);
22
+ }
23
+ if (data === undefined) return null;
24
+ return data as number | string | null;
25
+ }
26
+
27
+ /** Descriptor del widget `metric-card` (R3.6). */
28
+ export const descriptor: RenderDescriptor = {
29
+ kind: 'widget',
30
+ type: 'metric-card',
31
+ since: '1.0',
32
+ load: () => import('./metric-card').then((module) => module.MetricCard),
33
+ surfaces: ['display'],
34
+ // El KPI no recibe el widget entero: sólo vive de su rótulo, del dato y de
35
+ // la nota al pie. Pasa el dato ya traducido — `MetricCard` distingue el
36
+ // `0` de un vacío, y el `0` de un agregado llega como `[{ value: 0 }]`.
37
+ inputs: (config, extras) => {
38
+ const cfg = config as MetricCardWidget;
39
+ return { label: cfg.label, value: valorDeKpi(extras.data), hint: cfg.hint ?? '' };
40
+ },
41
+ };
@@ -0,0 +1,52 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { MetricCard } from './metric-card';
4
+
5
+ describe('MetricCard', () => {
6
+ let fixture: ComponentFixture<MetricCard>;
7
+
8
+ const root = (): HTMLElement => fixture.nativeElement as HTMLElement;
9
+ const value = (): HTMLElement | null => root().querySelector('[data-testid="value"]');
10
+ const empty = (): HTMLElement | null => root().querySelector('[data-testid="value-empty"]');
11
+
12
+ beforeEach(async () => {
13
+ await TestBed.configureTestingModule({ imports: [MetricCard] }).compileComponents();
14
+ fixture = TestBed.createComponent(MetricCard);
15
+ fixture.componentRef.setInput('label', 'Pendientes');
16
+ });
17
+
18
+ it('renderiza la etiqueta y la cifra', () => {
19
+ fixture.componentRef.setInput('value', 3);
20
+ fixture.detectChanges();
21
+
22
+ expect(root().textContent).toContain('Pendientes');
23
+ expect(value()?.textContent?.trim()).toBe('3');
24
+ expect(empty()).toBeNull();
25
+ });
26
+
27
+ it('value null ⇒ estado vacío con el placeholder', () => {
28
+ fixture.componentRef.setInput('value', null);
29
+ fixture.detectChanges();
30
+
31
+ expect(empty()).not.toBeNull();
32
+ expect(value()).toBeNull();
33
+ });
34
+
35
+ it('0 es un valor, no un vacío', () => {
36
+ fixture.componentRef.setInput('value', 0);
37
+ fixture.detectChanges();
38
+
39
+ expect(value()?.textContent?.trim()).toBe('0');
40
+ expect(empty()).toBeNull();
41
+ });
42
+
43
+ it('sin hint no pinta la línea secundaria', () => {
44
+ fixture.componentRef.setInput('value', 3);
45
+ fixture.detectChanges();
46
+ expect(root().textContent).not.toContain('hint');
47
+
48
+ fixture.componentRef.setInput('hint', 'de 12 totales');
49
+ fixture.detectChanges();
50
+ expect(root().textContent).toContain('de 12 totales');
51
+ });
52
+ });
@@ -0,0 +1,48 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ /**
4
+ * KPI de una cifra suelta («3 pendientes»).
5
+ *
6
+ * Cero dependencias: es sólo presentación — el valor lo trae el
7
+ * orquestador del panel desde el endpoint de métricas.
8
+ *
9
+ * Sin `icon`: el schema no tiene hoy un registro de nombres de icono
10
+ * (los Lucide se importan estáticamente), así que un `icon?: string` no
11
+ * podría resolverse declarativamente. Se añade cuando exista ese registro.
12
+ */
13
+ @Component({
14
+ selector: 'app-metric-card',
15
+ template: `
16
+ <article class="rounded-lg border border-neutral/20 bg-surface p-4">
17
+ <p class="text-sm text-neutral">{{ label() }}</p>
18
+
19
+ @if (empty()) {
20
+ <p class="font-display text-3xl font-semibold text-neutral/40" data-testid="value-empty">
21
+ —
22
+ </p>
23
+ } @else {
24
+ <p class="font-display text-3xl font-semibold" data-testid="value">{{ display() }}</p>
25
+ }
26
+
27
+ @if (hint()) {
28
+ <p class="mt-1 text-xs text-neutral">{{ hint() }}</p>
29
+ }
30
+ </article>
31
+ `,
32
+ })
33
+ export class MetricCard {
34
+ /** Rótulo del KPI. */
35
+ readonly label = input.required<string>();
36
+ /** Cifra a mostrar; `null`/`undefined` ⇒ estado vacío. */
37
+ readonly value = input<unknown>(null);
38
+ /** Texto secundario estático bajo la cifra. */
39
+ readonly hint = input('');
40
+
41
+ /** `0` es un valor, no un vacío: sólo `null`/`undefined` vacían la tarjeta. */
42
+ protected readonly empty = computed(() => this.value() === null || this.value() === undefined);
43
+
44
+ protected readonly display = computed(() => {
45
+ const value = this.value();
46
+ return typeof value === 'number' ? new Intl.NumberFormat('es-PE').format(value) : String(value);
47
+ });
48
+ }
@@ -0,0 +1,13 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del widget `quick-actions` (R3.6). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'widget',
6
+ type: 'quick-actions',
7
+ since: '1.0',
8
+ load: () => import('./quick-actions').then((module) => module.QuickActions),
9
+ surfaces: ['display'],
10
+ // El único widget sin dato: sus botones salen todos del schema. Por eso no
11
+ // recibe `data` — `ngComponentOutletInputs` no admite inputs inexistentes.
12
+ inputs: (config) => ({ widget: config }),
13
+ };
@@ -0,0 +1,61 @@
1
+ import { provideRouter } from '@angular/router';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { describe, expect, it, beforeEach } from 'vitest';
4
+
5
+ import type { QuickActionsWidget } from '../../core/models/schema.model';
6
+ import { QuickActions } from './quick-actions';
7
+
8
+ describe('QuickActions', () => {
9
+ let fixture: ComponentFixture<QuickActions>;
10
+
11
+ const widget: QuickActionsWidget = {
12
+ type: 'quick-actions',
13
+ key: 'acciones_rapidas',
14
+ label: 'Accesos Directos',
15
+ actions: [
16
+ { label: 'Nuevo Pedido', icon: 'plus', navigateTo: '/admin/orders?action=create' },
17
+ { label: 'Configuración', icon: 'settings', navigateTo: '/admin/pickpass' },
18
+ { label: 'Guía Externa', icon: 'external', navigateTo: 'https://ejemplo.com/guia' },
19
+ ],
20
+ };
21
+
22
+ beforeEach(async () => {
23
+ await TestBed.configureTestingModule({
24
+ imports: [QuickActions],
25
+ providers: [provideRouter([])],
26
+ }).compileComponents();
27
+ fixture = TestBed.createComponent(QuickActions);
28
+ });
29
+
30
+ it('1. renderiza todas las acciones declaradas con sus textos', () => {
31
+ fixture.componentRef.setInput('widget', widget);
32
+ fixture.detectChanges();
33
+
34
+ const root = fixture.nativeElement as HTMLElement;
35
+ expect(root.textContent).toContain('Accesos Directos');
36
+ expect(root.textContent).toContain('Nuevo Pedido');
37
+ expect(root.textContent).toContain('Configuración');
38
+ expect(root.textContent).toContain('Guía Externa');
39
+ });
40
+
41
+ it('2. genera enlaces internos con routerLink', () => {
42
+ fixture.componentRef.setInput('widget', widget);
43
+ fixture.detectChanges();
44
+
45
+ const root = fixture.nativeElement as HTMLElement;
46
+ const links = root.querySelectorAll('a');
47
+ expect(links.length).toBe(3);
48
+ expect(links[0].getAttribute('href')).toBe('/admin/orders?action=create');
49
+ expect(links[1].getAttribute('href')).toBe('/admin/pickpass');
50
+ });
51
+
52
+ it('3. genera enlaces externos con target blank y rel noopener', () => {
53
+ fixture.componentRef.setInput('widget', widget);
54
+ fixture.detectChanges();
55
+
56
+ const root = fixture.nativeElement as HTMLElement;
57
+ const links = root.querySelectorAll('a');
58
+ expect(links[2].getAttribute('href')).toBe('https://ejemplo.com/guia');
59
+ expect(links[2].getAttribute('target')).toBe('_blank');
60
+ });
61
+ });
@@ -0,0 +1,100 @@
1
+ import { Component, input } from '@angular/core';
2
+ import { RouterLink } from '@angular/router';
3
+ import {
4
+ LucideExternalLink,
5
+ LucideFileText,
6
+ LucidePlus,
7
+ LucideSettings,
8
+ LucideZap,
9
+ } from '@lucide/angular';
10
+
11
+ import type { QuickActionsWidget } from '../../core/models/schema.model';
12
+ import { parseNavigationUrl, type ParsedRoute } from '../../core/utils/template-interpolator';
13
+
14
+ const BUTTON_CLASSES: Record<string, string> = {
15
+ primary:
16
+ 'bg-primary text-surface hover:bg-primary/90 font-medium px-4 py-2.5 rounded-lg inline-flex items-center gap-2 text-sm transition-colors',
17
+ outline:
18
+ 'border border-neutral/40 bg-surface text-neutral hover:bg-neutral/10 font-medium px-4 py-2.5 rounded-lg inline-flex items-center gap-2 text-sm transition-colors',
19
+ secondary:
20
+ 'bg-neutral/10 text-neutral-light hover:bg-neutral/20 font-medium px-4 py-2.5 rounded-lg inline-flex items-center gap-2 text-sm transition-colors',
21
+ };
22
+
23
+ /**
24
+ * Grid de accesos directos y atajos operativos para el Dashboard.
25
+ */
26
+ @Component({
27
+ selector: 'app-quick-actions',
28
+ imports: [RouterLink, LucidePlus, LucideSettings, LucideFileText, LucideExternalLink, LucideZap],
29
+ template: `
30
+ <article class="flex h-full flex-col rounded-xl border border-neutral/20 bg-surface p-4">
31
+ <h2 class="mb-3 text-sm font-semibold text-neutral-light">{{ widget().label }}</h2>
32
+
33
+ <div class="grid flex-1 grid-cols-1 gap-2.5 sm:grid-cols-2">
34
+ @for (action of widget().actions; track action.label) {
35
+ @if (isExternal(action.navigateTo)) {
36
+ <a
37
+ [href]="action.navigateTo"
38
+ target="_blank"
39
+ rel="noopener noreferrer"
40
+ [class]="buttonClass(action.variant)"
41
+ >
42
+ @switch (action.icon) {
43
+ @case ('plus') {
44
+ <svg lucidePlus size="16" class="shrink-0" />
45
+ }
46
+ @case ('settings') {
47
+ <svg lucideSettings size="16" class="shrink-0" />
48
+ }
49
+ @case ('document') {
50
+ <svg lucideFileText size="16" class="shrink-0" />
51
+ }
52
+ @default {
53
+ <svg lucideExternalLink size="16" class="shrink-0" />
54
+ }
55
+ }
56
+ <span class="truncate">{{ action.label }}</span>
57
+ </a>
58
+ } @else {
59
+ <a
60
+ [routerLink]="parseRoute(action.navigateTo).path"
61
+ [queryParams]="parseRoute(action.navigateTo).queryParams"
62
+ [class]="buttonClass(action.variant)"
63
+ >
64
+ @switch (action.icon) {
65
+ @case ('plus') {
66
+ <svg lucidePlus size="16" class="shrink-0" />
67
+ }
68
+ @case ('settings') {
69
+ <svg lucideSettings size="16" class="shrink-0" />
70
+ }
71
+ @case ('document') {
72
+ <svg lucideFileText size="16" class="shrink-0" />
73
+ }
74
+ @default {
75
+ <svg lucideZap size="16" class="shrink-0" />
76
+ }
77
+ }
78
+ <span class="truncate">{{ action.label }}</span>
79
+ </a>
80
+ }
81
+ }
82
+ </div>
83
+ </article>
84
+ `,
85
+ })
86
+ export class QuickActions {
87
+ readonly widget = input.required<QuickActionsWidget>();
88
+
89
+ protected isExternal(url: string): boolean {
90
+ return /^https?:\/\//i.test(url);
91
+ }
92
+
93
+ protected parseRoute(url: string): ParsedRoute {
94
+ return parseNavigationUrl(url);
95
+ }
96
+
97
+ protected buttonClass(variant?: string): string {
98
+ return BUTTON_CLASSES[variant || 'secondary'] || BUTTON_CLASSES['secondary'];
99
+ }
100
+ }
@@ -0,0 +1,16 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del widget `record-list` (R3.6). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'widget',
6
+ type: 'record-list',
7
+ since: '1.0',
8
+ load: () => import('./record-list').then((module) => module.RecordList),
9
+ surfaces: ['display'],
10
+ // `RecordList` dibuja filas: fuera de un array no hay nada que listar, y un
11
+ // objeto suelto no debe colarse como si fuera una única fila.
12
+ inputs: (config, extras) => ({
13
+ widget: config,
14
+ data: Array.isArray(extras.data) ? (extras.data as Record<string, unknown>[]) : null,
15
+ }),
16
+ };
@@ -0,0 +1,79 @@
1
+ import { provideRouter } from '@angular/router';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { describe, expect, it, beforeEach } from 'vitest';
4
+
5
+ import type { RecordListWidget } from '../../core/models/schema.model';
6
+ import { RecordList } from './record-list';
7
+
8
+ describe('RecordList', () => {
9
+ let fixture: ComponentFixture<RecordList>;
10
+
11
+ const widget: RecordListWidget = {
12
+ type: 'record-list',
13
+ key: 'pedidos_pendientes',
14
+ label: 'Pedidos que Requieren Atención',
15
+ columns: ['ref', 'customer_name', 'status'],
16
+ action: {
17
+ label: 'Ver Ficha',
18
+ routeTemplate: '/admin/orders?id={ref}',
19
+ },
20
+ };
21
+
22
+ beforeEach(async () => {
23
+ await TestBed.configureTestingModule({
24
+ imports: [RecordList],
25
+ providers: [provideRouter([])],
26
+ }).compileComponents();
27
+ fixture = TestBed.createComponent(RecordList);
28
+ });
29
+
30
+ it('1. renderiza las filas y columnas declaradas', () => {
31
+ const data = [
32
+ { ref: 'ORD-001', customer_name: 'Cliente Uno', status: 'PENDIENTE' },
33
+ { ref: 'ORD-002', customer_name: 'Cliente Dos', status: 'PENDIENTE' },
34
+ ];
35
+
36
+ fixture.componentRef.setInput('widget', widget);
37
+ fixture.componentRef.setInput('data', data);
38
+ fixture.detectChanges();
39
+
40
+ const root = fixture.nativeElement as HTMLElement;
41
+ expect(root.textContent).toContain('Pedidos que Requieren Atención');
42
+ expect(root.textContent).toContain('ORD-001');
43
+ expect(root.textContent).toContain('Cliente Uno');
44
+ expect(root.textContent).toContain('ORD-002');
45
+ expect(root.textContent).toContain('Cliente Dos');
46
+ });
47
+
48
+ it('2. renderiza estado vacío cuando no hay datos', () => {
49
+ fixture.componentRef.setInput('widget', widget);
50
+ fixture.componentRef.setInput('data', []);
51
+ fixture.detectChanges();
52
+
53
+ const root = fixture.nativeElement as HTMLElement;
54
+ expect(root.textContent).toContain('Sin registros pendientes');
55
+ });
56
+
57
+ it('3. genera el enlace de acción con la ruta interpolada', () => {
58
+ const data = [{ ref: 'ORD-777', customer_name: 'Carlos', status: 'PENDIENTE' }];
59
+
60
+ fixture.componentRef.setInput('widget', widget);
61
+ fixture.componentRef.setInput('data', data);
62
+ fixture.detectChanges();
63
+
64
+ const root = fixture.nativeElement as HTMLElement;
65
+ const link = root.querySelector('a');
66
+ expect(link).not.toBeNull();
67
+ expect(link?.getAttribute('href')).toBe('/admin/orders?id=ORD-777');
68
+ expect(link?.textContent?.trim()).toBe('Ver Ficha');
69
+ });
70
+
71
+ it('4. maneja datos nulos sin romper', () => {
72
+ fixture.componentRef.setInput('widget', widget);
73
+ fixture.componentRef.setInput('data', null);
74
+ fixture.detectChanges();
75
+
76
+ const root = fixture.nativeElement as HTMLElement;
77
+ expect(root.textContent).toContain('Sin registros pendientes');
78
+ });
79
+ });