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

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