@conduction/nextcloud-vue 2.82.0 → 2.83.0

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 (78) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +2 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +24 -0
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +9 -4
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +9 -0
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  12. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +96 -12
  13. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +95 -50
  15. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js +2 -2
  17. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js.map +1 -1
  18. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js +2 -2
  19. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js.map +1 -1
  20. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +1 -1
  21. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +16 -2
  22. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -1
  23. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +3 -3
  24. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -1
  25. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  26. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +43 -0
  27. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +130 -53
  29. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  30. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +1 -1
  31. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +70 -5
  32. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -1
  33. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +61 -35
  34. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  36. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +3 -1
  37. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  40. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +55 -3
  41. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  42. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +88 -71
  43. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  44. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +1 -1
  45. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +18 -4
  46. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -1
  48. package/dist/esm/l10n/nl.json.js +3 -3
  49. package/dist/esm/library-style.js +1 -1
  50. package/dist/esm/nextcloud-vue.css +549 -338
  51. package/dist/esm/utils/fetchAggregate.js +25 -2
  52. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  53. package/dist/esm/utils/switchValues.js +62 -0
  54. package/dist/esm/utils/switchValues.js.map +1 -0
  55. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  56. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  57. package/dist/nextcloud-vue.cjs.js +36086 -35536
  58. package/dist/nextcloud-vue.cjs.js.map +2 -2
  59. package/dist/nextcloud-vue.css +549 -338
  60. package/l10n/nl.json +3 -3
  61. package/package.json +1 -1
  62. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +31 -2
  63. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -0
  64. package/src/components/CnIndexPage/CnIndexPage.vue +125 -15
  65. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +54 -3
  66. package/src/components/CnReportsPage/CnReportsPage.vue +84 -4
  67. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +110 -18
  68. package/src/components/CnStatWidget/CnStatWidget.vue +3 -1
  69. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +92 -5
  70. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +18 -4
  71. package/src/css/index-page.css +11 -0
  72. package/src/css/index.css +1 -0
  73. package/src/css/look-board-reports.css +117 -0
  74. package/src/css/look-board.css +32 -1
  75. package/src/schemas/app-manifest-v2.schema.json +2 -2
  76. package/src/utils/fetchAggregate.js +24 -1
  77. package/src/utils/switchValues.js +59 -0
  78. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnReportsPage.vue2.js","sources":["../../../../src/components/CnReportsPage/CnReportsPage.vue"],"sourcesContent":["<!--\n CnReportsPage — Manifest-driven Reports surface.\n\n v2 manifest page primitive for `type: \"reports\"`. One page holding every\n report an app offers, as cards, instead of a Reports SUBMENU with one entry\n per report.\n\n WHY THIS IS A PAGE AND NOT A MENU BRANCH. A submenu grows one entry per\n report and never shrinks. shillinq reached 96 report types; as menu children\n that is a scroll, not a navigation. Cards carry what a menu item cannot — a\n description, a category, a count — and a category filter turns 96 into a\n short list. shillinq proved the shape in `ReportingComplianceOverview`; this\n is that shape with the app-specific parts taken out, so every app declares it\n rather than copying 600 lines of Vue.\n\n Declare it as:\n\n { \"id\": \"Reports\", \"route\": \"/reports\", \"type\": \"reports\",\n \"config\": {\n \"categories\": { \"operational\": \"Operational\" },\n \"cards\": [\n { \"id\": \"Throughput\", \"label\": \"Processing time\",\n \"description\": \"How long cases take, by type.\",\n \"icon\": \"ChartLine\", \"category\": \"operational\",\n \"route\": \"Doorlooptijd\" }\n ]\n } }\n\n A card names a ROUTE by name, never a path: a path is editable per app and a\n card pointing at a stale one is a dead end that still looks like a report.\n\n Spec: hydra openspec/architecture/adr-112-reports-are-one-page.md (ConductionNL/hydra#640).\n-->\n<template>\n\t<div class=\"cn-reports-page\">\n\t\t<div class=\"cn-reports-page__header\">\n\t\t\t<h2 class=\"cn-reports-page__title\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"resolvedDescription\" class=\"cn-reports-page__description\">\n\t\t\t\t{{ resolvedDescription }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<div v-if=\"categoryOptions.length > 1\" class=\"cn-reports-page__filter\">\n\t\t\t<label class=\"cn-reports-page__filter-label\" :for=\"filterId\">\n\t\t\t\t{{ resolvedCategoryLabel }}\n\t\t\t</label>\n\t\t\t<select :id=\"filterId\"\n\t\t\t\tv-model=\"activeCategory\"\n\t\t\t\tclass=\"cn-reports-page__filter-select\"\n\t\t\t\tdata-testid=\"cn-reports-category\">\n\t\t\t\t<option v-for=\"option in categoryOptions\"\n\t\t\t\t\t:key=\"option.value\"\n\t\t\t\t\t:value=\"option.value\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</div>\n\n\t\t<p v-if=\"visibleCards.length === 0\"\n\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t{{ resolvedEmptyLabel }}\n\t\t</p>\n\n\t\t<ul v-else class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t<li v-for=\"card in visibleCards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"categoryName(card)\" class=\"cn-reports-page__card-category\">\n\t\t\t\t\t\t{{ categoryName(card) }}\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<!-- `widgets` + `layout`: a grid of dashboard widgets under the cards,\n\t\t such as a highlighted report with stat tiles and a chart. -->\n\t\t<div v-if=\"sectionWidgets.length > 0\" class=\"cn-reports-page__widgets\" data-testid=\"cn-reports-widgets\">\n\t\t\t<CnDashboardPage\n\t\t\t\t:widgets=\"sectionWidgets\"\n\t\t\t\t:layout=\"sectionLayout\"\n\t\t\t\t:showHeader=\"false\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * The Reports page: one surface listing an app's reports as cards.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { defineAsyncComponent } from 'vue'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\n\nexport default {\n\tname: 'CnReportsPage',\n\n\tcomponents: {\n\t\t// Loaded only for a page that declares widgets: a cards-only reports\n\t\t// page does not pull the dashboard in.\n\t\tCnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The consuming app's translate function, provided by CnAppRoot.\n\t\t * Defaults to identity so the page still renders outside one.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The manifest page.\n\t\t *\n\t\t * ⚠️ CnPageRenderer does NOT pass this. `resolvedProps()` returns\n\t\t * `{ ...topLevel, ...normalizedConfig, ...params }` and has no `page`\n\t\t * key, so a dispatched `type: \"reports\"` page arrived here with the\n\t\t * default `{}` — `config` resolved to `{}`, `cards` to `[]`, and every\n\t\t * reports page in the fleet rendered its empty state while its manifest\n\t\t * declared cards. Kept because a host that renders this component\n\t\t * directly can still hand it the whole page; the flattened props below\n\t\t * are what the renderer actually supplies, and they win.\n\t\t */\n\t\tpage: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The report cards, as CnPageRenderer flattens them out of `config`.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tcards: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Category key/label pairs, flattened out of `config`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The lead paragraph, flattened out of `config` or lifted from the page.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The heading, lifted from the page by the renderer.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the \"all categories\" filter option.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tallCategoriesLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the category filter control.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcategoryLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Text shown when the active filter admits no card.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Dashboard widgets drawn under the report cards (`config.widgets`),\n\t\t * placed by `layout`, as on a `type: \"dashboard\"` page.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Grid placement of `widgets` (`config.layout`): `{ id, widgetId,\n\t\t * gridX, gridY, gridWidth, gridHeight }` per widget. Without it each\n\t\t * widget takes a full row in the order declared.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveCategory: 'all',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translate function actually in force.\n\t\t *\n\t\t * @return {(key: string) => string} The translator.\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The declared config, always an object, from whichever direction it\n\t\t * arrived.\n\t\t *\n\t\t * TWO CALLERS, TWO SHAPES. CnPageRenderer flattens `page.config.*` into\n\t\t * individual props and passes NO `page`, so under the renderer the keys\n\t\t * arrive as props. A host that mounts this component itself hands it the\n\t\t * whole `page`. Reading only `page.config` is what made every dispatched\n\t\t * reports page render empty.\n\t\t *\n\t\t * Props win where both are present: they are the nearer declaration, and\n\t\t * the renderer has already resolved route sentinels in them.\n\t\t *\n\t\t * @return {object} The config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst fromPage = (this.page && this.page.config) || {}\n\t\t\tconst fromProps = {}\n\t\t\tfor (const key of [\n\t\t\t\t'cards',\n\t\t\t\t'categories',\n\t\t\t\t'description',\n\t\t\t\t'title',\n\t\t\t\t'allCategoriesLabel',\n\t\t\t\t'categoryLabel',\n\t\t\t\t'emptyLabel',\n\t\t\t\t'widgets',\n\t\t\t\t'layout',\n\t\t\t]) {\n\t\t\t\tif (this[key] !== null && this[key] !== undefined) {\n\t\t\t\t\tfromProps[key] = this[key]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ...fromPage, ...fromProps }\n\t\t},\n\n\t\t/**\n\t\t * The widgets under the cards.\n\t\t *\n\t\t * @return {Array<object>} The declared widgets, or none.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionWidgets() {\n\t\t\tconst widgets = this.config.widgets\n\t\t\treturn Array.isArray(widgets) ? widgets.filter((w) => w && typeof w === 'object' && w.id) : []\n\t\t},\n\n\t\t/**\n\t\t * Where the widgets go: the declared `layout`, else one full row per\n\t\t * widget in the order declared.\n\t\t *\n\t\t * @return {Array<object>} The placements.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionLayout() {\n\t\t\tif (Array.isArray(this.config.layout) && this.config.layout.length > 0) {\n\t\t\t\treturn this.config.layout\n\t\t\t}\n\t\t\tlet y = 0\n\t\t\treturn this.sectionWidgets.map((widget, index) => {\n\t\t\t\tconst placement = { id: String(index + 1), widgetId: widget.id, gridX: 0, gridY: y, gridWidth: 12, gridHeight: 4 }\n\t\t\t\ty += 4\n\t\t\t\treturn placement\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The heading.\n\t\t *\n\t\t * @return {string} The title.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst declared = this.config.title || (this.page && this.page.title)\n\n\t\t\treturn declared ? this.tr(declared) : t('nextcloud-vue', 'Reports')\n\t\t},\n\n\t\t/**\n\t\t * The optional lead paragraph.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.tr(this.config.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The declared cards, ignoring any that name no route.\n\t\t *\n\t\t * A card with no route is a card that cannot be opened. Rendering it\n\t\t * would put a report on the page that silently does nothing, which is\n\t\t * worse than leaving it off.\n\t\t *\n\t\t * @return {Array<object>} The usable cards.\n\t\t */\n\t\tresolvedCards() {\n\t\t\tconst declared = this.config.cards\n\t\t\tif (!Array.isArray(declared)) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn declared\n\t\t\t\t.filter((card) => card && card.route && card.label)\n\t\t\t\t.map((card) => ({\n\t\t\t\t\t...card,\n\t\t\t\t\tlabel: this.tr(card.label),\n\t\t\t\t\tdescription: this.tr(card.description || ''),\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Category value/label pairs, offered only for categories that hold a\n\t\t * card. A filter listing empty categories teaches the reader that the\n\t\t * app has reports it does not have.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tcategoryOptions() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst used = new Set(this.resolvedCards.map((card) => card.category).filter(Boolean))\n\t\t\tconst options = [{ value: 'all', label: this.allLabel }]\n\n\t\t\tObject.keys(declared)\n\t\t\t\t.filter((key) => used.has(key))\n\t\t\t\t.forEach((key) => options.push({ value: key, label: this.tr(declared[key]) }))\n\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The cards the active filter admits.\n\t\t *\n\t\t * @return {Array<object>} The visible cards.\n\t\t */\n\t\tvisibleCards() {\n\t\t\tif (this.activeCategory === 'all') {\n\t\t\t\treturn this.resolvedCards\n\t\t\t}\n\n\t\t\treturn this.resolvedCards.filter((card) => card.category === this.activeCategory)\n\t\t},\n\n\t\t/**\n\t\t * A stable id for the filter control's label association.\n\t\t *\n\t\t * @return {string} The id.\n\t\t */\n\t\tfilterId() {\n\t\t\treturn `cn-reports-category-${(this.page && this.page.id) || 'page'}`\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The \"all categories\" option label.\n\t\t */\n\t\tallLabel() {\n\t\t\treturn this.config.allCategoriesLabel\n\t\t\t\t? this.tr(this.config.allCategoriesLabel)\n\t\t\t\t: t('nextcloud-vue', 'All categories')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The filter's label.\n\t\t */\n\t\tresolvedCategoryLabel() {\n\t\t\treturn this.config.categoryLabel\n\t\t\t\t? this.tr(this.config.categoryLabel)\n\t\t\t\t: t('nextcloud-vue', 'Category')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The empty-state text.\n\t\t */\n\t\tresolvedEmptyLabel() {\n\t\t\treturn this.config.emptyLabel\n\t\t\t\t? this.tr(this.config.emptyLabel)\n\t\t\t\t: t('nextcloud-vue', 'No reports in this category.')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a manifest-declared string through the app's own\n\t\t * translate function.\n\t\t *\n\t\t * The manifest is data the renderer walks, not source the extractor\n\t\t * reads, so every string it carries has to be handed to the app's\n\t\t * translator explicitly. A card rendered straight from the manifest\n\t\t * shows its source language whatever locale the reader is in.\n\t\t *\n\t\t * @param {string} value The declared string.\n\t\t * @return {string} The translated string, or the input unchanged.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn this.effectiveTranslate(value)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a card's category, if it has one.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The label, or an empty string.\n\t\t */\n\t\tcategoryName(card) {\n\t\t\tconst declared = this.config.categories || {}\n\n\t\t\treturn this.tr((card.category && declared[card.category]) || '')\n\t\t},\n\n\t\t/**\n\t\t * A real href for the card, so it is a link a reader can middle-click\n\t\t * or open in a new tab rather than a div that only answers to a click.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The href.\n\t\t */\n\t\threfFor(card) {\n\t\t\tif (!this.$router) {\n\t\t\t\treturn '#'\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: card.route }).href\n\t\t\t} catch {\n\t\t\t\treturn '#'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate by route NAME on a plain click; a modified or middle click\n\t\t * is left to the browser so it can open the card's href elsewhere.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @param {MouseEvent} event The click.\n\t\t * @return {void}\n\t\t */\n\t\topen(card, event) {\n\t\t\tfollowLinkClick(event, { name: card.route }, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-reports-page {\n\tpadding: 16px;\n}\n\n/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n positioned at the left edge of .app-content) plus 12px breathing room, as\n CnPageHeader does. Only the header shifts; the body keeps the full width. */\n.cn-reports-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-reports-page__title {\n\tmargin: 0 0 4px;\n}\n\n.cn-reports-page__description {\n\tmargin: 0 0 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__filter {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 16px;\n}\n\n.cn-reports-page__filter-select {\n\tmin-width: 220px;\n}\n\n.cn-reports-page__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 12px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-reports-page__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\theight: 100%;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\n.cn-reports-page__card:hover,\n.cn-reports-page__card:focus {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-reports-page__card-title {\n\tfont-weight: bold;\n}\n\n.cn-reports-page__card-description,\n.cn-reports-page__card-category {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-reports-page__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__widgets {\n\tmargin-top: 20px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnReportsPage.vue2.js","sources":["../../../../src/components/CnReportsPage/CnReportsPage.vue"],"sourcesContent":["<!--\n CnReportsPage — Manifest-driven Reports surface.\n\n v2 manifest page primitive for `type: \"reports\"`. One page holding every\n report an app offers, as cards, instead of a Reports SUBMENU with one entry\n per report.\n\n WHY THIS IS A PAGE AND NOT A MENU BRANCH. A submenu grows one entry per\n report and never shrinks. shillinq reached 96 report types; as menu children\n that is a scroll, not a navigation. Cards carry what a menu item cannot — a\n description, a category, a count — and a category filter turns 96 into a\n short list. shillinq proved the shape in `ReportingComplianceOverview`; this\n is that shape with the app-specific parts taken out, so every app declares it\n rather than copying 600 lines of Vue.\n\n Declare it as:\n\n { \"id\": \"Reports\", \"route\": \"/reports\", \"type\": \"reports\",\n \"config\": {\n \"categories\": { \"operational\": \"Operational\" },\n \"cards\": [\n { \"id\": \"Throughput\", \"label\": \"Processing time\",\n \"description\": \"How long cases take, by type.\",\n \"icon\": \"ChartLine\", \"category\": \"operational\",\n \"route\": \"Doorlooptijd\" }\n ]\n } }\n\n A card names a ROUTE by name, never a path: a path is editable per app and a\n card pointing at a stale one is a dead end that still looks like a report.\n\n Spec: hydra openspec/architecture/adr-112-reports-are-one-page.md (ConductionNL/hydra#640).\n-->\n<template>\n\t<div class=\"cn-reports-page\" :class=\"{ 'cn-reports-page--board': isBoard }\">\n\t\t<div class=\"cn-reports-page__header\">\n\t\t\t<h2 class=\"cn-reports-page__title\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"resolvedDescription\" class=\"cn-reports-page__description\">\n\t\t\t\t{{ resolvedDescription }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<!-- The board look (board DcRapportages): one section per category,\n\t\t headed by its label, instead of the category filter; each card\n\t\t draws its icon in a tile. -->\n\t\t<template v-if=\"isBoard\">\n\t\t\t<p v-if=\"resolvedCards.length === 0\"\n\t\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t\t{{ resolvedEmptyLabel }}\n\t\t\t</p>\n\t\t\t<section v-for=\"section in boardSections\"\n\t\t\t\t:key=\"section.key\"\n\t\t\t\tclass=\"cn-reports-page__section\"\n\t\t\t\tdata-testid=\"cn-reports-section\">\n\t\t\t\t<h3 v-if=\"section.label\" class=\"cn-reports-page__section-title\">\n\t\t\t\t\t{{ section.label }}\n\t\t\t\t</h3>\n\t\t\t\t<ul class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t\t\t<li v-for=\"card in section.cards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t\t\t<span v-if=\"card.icon\" class=\"cn-reports-page__card-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnIcon :name=\"card.icon\" :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-reports-page__card-text\">\n\t\t\t\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</template>\n\n\t\t<div v-if=\"!isBoard && categoryOptions.length > 1\" class=\"cn-reports-page__filter\">\n\t\t\t<label class=\"cn-reports-page__filter-label\" :for=\"filterId\">\n\t\t\t\t{{ resolvedCategoryLabel }}\n\t\t\t</label>\n\t\t\t<select :id=\"filterId\"\n\t\t\t\tv-model=\"activeCategory\"\n\t\t\t\tclass=\"cn-reports-page__filter-select\"\n\t\t\t\tdata-testid=\"cn-reports-category\">\n\t\t\t\t<option v-for=\"option in categoryOptions\"\n\t\t\t\t\t:key=\"option.value\"\n\t\t\t\t\t:value=\"option.value\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</div>\n\n\t\t<p v-if=\"!isBoard && visibleCards.length === 0\"\n\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t{{ resolvedEmptyLabel }}\n\t\t</p>\n\n\t\t<ul v-else-if=\"!isBoard\" class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t<li v-for=\"card in visibleCards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"categoryName(card)\" class=\"cn-reports-page__card-category\">\n\t\t\t\t\t\t{{ categoryName(card) }}\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<!-- `widgets` + `layout`: a grid of dashboard widgets under the cards,\n\t\t such as a highlighted report with stat tiles and a chart. -->\n\t\t<div v-if=\"sectionWidgets.length > 0\" class=\"cn-reports-page__widgets\" data-testid=\"cn-reports-widgets\">\n\t\t\t<CnDashboardPage\n\t\t\t\t:widgets=\"sectionWidgets\"\n\t\t\t\t:layout=\"sectionLayout\"\n\t\t\t\t:showHeader=\"false\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * The Reports page: one surface listing an app's reports as cards.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { defineAsyncComponent } from 'vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { useLook } from '../../composables/useLook.js'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\n\nexport default {\n\tname: 'CnReportsPage',\n\n\tcomponents: {\n\t\t// Loaded only for a page that declares widgets: a cards-only reports\n\t\t// page does not pull the dashboard in.\n\t\tCnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\t\tCnIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The consuming app's translate function, provided by CnAppRoot.\n\t\t * Defaults to identity so the page still renders outside one.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The manifest page.\n\t\t *\n\t\t * ⚠️ CnPageRenderer does NOT pass this. `resolvedProps()` returns\n\t\t * `{ ...topLevel, ...normalizedConfig, ...params }` and has no `page`\n\t\t * key, so a dispatched `type: \"reports\"` page arrived here with the\n\t\t * default `{}` — `config` resolved to `{}`, `cards` to `[]`, and every\n\t\t * reports page in the fleet rendered its empty state while its manifest\n\t\t * declared cards. Kept because a host that renders this component\n\t\t * directly can still hand it the whole page; the flattened props below\n\t\t * are what the renderer actually supplies, and they win.\n\t\t */\n\t\tpage: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The report cards, as CnPageRenderer flattens them out of `config`.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tcards: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Category key/label pairs, flattened out of `config`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The lead paragraph, flattened out of `config` or lifted from the page.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The heading, lifted from the page by the renderer.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the \"all categories\" filter option.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tallCategoriesLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the category filter control.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcategoryLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Text shown when the active filter admits no card.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Dashboard widgets drawn under the report cards (`config.widgets`),\n\t\t * placed by `layout`, as on a `type: \"dashboard\"` page.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Grid placement of `widgets` (`config.layout`): `{ id, widgetId,\n\t\t * gridX, gridY, gridWidth, gridHeight }` per widget. Without it each\n\t\t * widget takes a full row in the order declared.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The look to draw in: `board` or `nextcloud`. Absent: the look\n\t\t * CnAppRoot provides (`cnLook`), else `nextcloud`.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\tconst { isBoard } = useLook(props)\n\t\treturn { isBoard }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveCategory: 'all',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translate function actually in force.\n\t\t *\n\t\t * @return {(key: string) => string} The translator.\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The declared config, always an object, from whichever direction it\n\t\t * arrived.\n\t\t *\n\t\t * TWO CALLERS, TWO SHAPES. CnPageRenderer flattens `page.config.*` into\n\t\t * individual props and passes NO `page`, so under the renderer the keys\n\t\t * arrive as props. A host that mounts this component itself hands it the\n\t\t * whole `page`. Reading only `page.config` is what made every dispatched\n\t\t * reports page render empty.\n\t\t *\n\t\t * Props win where both are present: they are the nearer declaration, and\n\t\t * the renderer has already resolved route sentinels in them.\n\t\t *\n\t\t * @return {object} The config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst fromPage = (this.page && this.page.config) || {}\n\t\t\tconst fromProps = {}\n\t\t\tfor (const key of [\n\t\t\t\t'cards',\n\t\t\t\t'categories',\n\t\t\t\t'description',\n\t\t\t\t'title',\n\t\t\t\t'allCategoriesLabel',\n\t\t\t\t'categoryLabel',\n\t\t\t\t'emptyLabel',\n\t\t\t\t'widgets',\n\t\t\t\t'layout',\n\t\t\t]) {\n\t\t\t\tif (this[key] !== null && this[key] !== undefined) {\n\t\t\t\t\tfromProps[key] = this[key]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ...fromPage, ...fromProps }\n\t\t},\n\n\t\t/**\n\t\t * The widgets under the cards.\n\t\t *\n\t\t * @return {Array<object>} The declared widgets, or none.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionWidgets() {\n\t\t\tconst widgets = this.config.widgets\n\t\t\treturn Array.isArray(widgets) ? widgets.filter((w) => w && typeof w === 'object' && w.id) : []\n\t\t},\n\n\t\t/**\n\t\t * Where the widgets go: the declared `layout`, else one full row per\n\t\t * widget in the order declared.\n\t\t *\n\t\t * @return {Array<object>} The placements.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionLayout() {\n\t\t\tif (Array.isArray(this.config.layout) && this.config.layout.length > 0) {\n\t\t\t\treturn this.config.layout\n\t\t\t}\n\t\t\tlet y = 0\n\t\t\treturn this.sectionWidgets.map((widget, index) => {\n\t\t\t\tconst placement = { id: String(index + 1), widgetId: widget.id, gridX: 0, gridY: y, gridWidth: 12, gridHeight: 4 }\n\t\t\t\ty += 4\n\t\t\t\treturn placement\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The heading.\n\t\t *\n\t\t * @return {string} The title.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst declared = this.config.title || (this.page && this.page.title)\n\n\t\t\treturn declared ? this.tr(declared) : t('nextcloud-vue', 'Reports')\n\t\t},\n\n\t\t/**\n\t\t * The optional lead paragraph.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.tr(this.config.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The declared cards, ignoring any that name no route.\n\t\t *\n\t\t * A card with no route is a card that cannot be opened. Rendering it\n\t\t * would put a report on the page that silently does nothing, which is\n\t\t * worse than leaving it off.\n\t\t *\n\t\t * @return {Array<object>} The usable cards.\n\t\t */\n\t\tresolvedCards() {\n\t\t\tconst declared = this.config.cards\n\t\t\tif (!Array.isArray(declared)) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn declared\n\t\t\t\t.filter((card) => card && card.route && card.label)\n\t\t\t\t.map((card) => ({\n\t\t\t\t\t...card,\n\t\t\t\t\tlabel: this.tr(card.label),\n\t\t\t\t\tdescription: this.tr(card.description || ''),\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Category value/label pairs, offered only for categories that hold a\n\t\t * card. A filter listing empty categories teaches the reader that the\n\t\t * app has reports it does not have.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tcategoryOptions() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst used = new Set(this.resolvedCards.map((card) => card.category).filter(Boolean))\n\t\t\tconst options = [{ value: 'all', label: this.allLabel }]\n\n\t\t\tObject.keys(declared)\n\t\t\t\t.filter((key) => used.has(key))\n\t\t\t\t.forEach((key) => options.push({ value: key, label: this.tr(declared[key]) }))\n\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The board look's sections: the cards without a category first, under\n\t\t * no heading, then one section per declared category that holds a\n\t\t * card, in the declared order, headed by its label.\n\t\t *\n\t\t * @spec openspec/changes/screens-reports-page-board-look/specs/reports-page/spec.md#requirement-the-reports-page-takes-the-board-look\n\t\t * @return {Array<{key: string, label: string, cards: Array<object>}>} The sections.\n\t\t */\n\t\tboardSections() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst sections = []\n\t\t\tconst loose = this.resolvedCards.filter((card) => !card.category || !Object.hasOwn(declared, card.category))\n\t\t\tif (loose.length > 0) {\n\t\t\t\tsections.push({ key: '__none', label: '', cards: loose })\n\t\t\t}\n\t\t\tfor (const key of Object.keys(declared)) {\n\t\t\t\tconst cards = this.resolvedCards.filter((card) => card.category === key)\n\t\t\t\tif (cards.length > 0) {\n\t\t\t\t\tsections.push({ key, label: this.tr(declared[key]), cards })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sections\n\t\t},\n\n\t\t/**\n\t\t * The cards the active filter admits.\n\t\t *\n\t\t * @return {Array<object>} The visible cards.\n\t\t */\n\t\tvisibleCards() {\n\t\t\tif (this.activeCategory === 'all') {\n\t\t\t\treturn this.resolvedCards\n\t\t\t}\n\n\t\t\treturn this.resolvedCards.filter((card) => card.category === this.activeCategory)\n\t\t},\n\n\t\t/**\n\t\t * A stable id for the filter control's label association.\n\t\t *\n\t\t * @return {string} The id.\n\t\t */\n\t\tfilterId() {\n\t\t\treturn `cn-reports-category-${(this.page && this.page.id) || 'page'}`\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The \"all categories\" option label.\n\t\t */\n\t\tallLabel() {\n\t\t\treturn this.config.allCategoriesLabel\n\t\t\t\t? this.tr(this.config.allCategoriesLabel)\n\t\t\t\t: t('nextcloud-vue', 'All categories')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The filter's label.\n\t\t */\n\t\tresolvedCategoryLabel() {\n\t\t\treturn this.config.categoryLabel\n\t\t\t\t? this.tr(this.config.categoryLabel)\n\t\t\t\t: t('nextcloud-vue', 'Category')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The empty-state text.\n\t\t */\n\t\tresolvedEmptyLabel() {\n\t\t\treturn this.config.emptyLabel\n\t\t\t\t? this.tr(this.config.emptyLabel)\n\t\t\t\t: t('nextcloud-vue', 'No reports in this category.')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a manifest-declared string through the app's own\n\t\t * translate function.\n\t\t *\n\t\t * The manifest is data the renderer walks, not source the extractor\n\t\t * reads, so every string it carries has to be handed to the app's\n\t\t * translator explicitly. A card rendered straight from the manifest\n\t\t * shows its source language whatever locale the reader is in.\n\t\t *\n\t\t * @param {string} value The declared string.\n\t\t * @return {string} The translated string, or the input unchanged.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn this.effectiveTranslate(value)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a card's category, if it has one.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The label, or an empty string.\n\t\t */\n\t\tcategoryName(card) {\n\t\t\tconst declared = this.config.categories || {}\n\n\t\t\treturn this.tr((card.category && declared[card.category]) || '')\n\t\t},\n\n\t\t/**\n\t\t * A real href for the card, so it is a link a reader can middle-click\n\t\t * or open in a new tab rather than a div that only answers to a click.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The href.\n\t\t */\n\t\threfFor(card) {\n\t\t\tif (!this.$router) {\n\t\t\t\treturn '#'\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: card.route }).href\n\t\t\t} catch {\n\t\t\t\treturn '#'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate by route NAME on a plain click; a modified or middle click\n\t\t * is left to the browser so it can open the card's href elsewhere.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @param {MouseEvent} event The click.\n\t\t * @return {void}\n\t\t */\n\t\topen(card, event) {\n\t\t\tfollowLinkClick(event, { name: card.route }, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-reports-page {\n\tpadding: 16px;\n}\n\n/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n positioned at the left edge of .app-content) plus 12px breathing room, as\n CnPageHeader does. Only the header shifts; the body keeps the full width. */\n.cn-reports-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-reports-page__title {\n\tmargin: 0 0 4px;\n}\n\n.cn-reports-page__description {\n\tmargin: 0 0 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__filter {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 16px;\n}\n\n.cn-reports-page__filter-select {\n\tmin-width: 220px;\n}\n\n.cn-reports-page__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 12px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-reports-page__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\theight: 100%;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\n.cn-reports-page__card:hover,\n.cn-reports-page__card:focus {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-reports-page__card-title {\n\tfont-weight: bold;\n}\n\n.cn-reports-page__card-description,\n.cn-reports-page__card-category {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-reports-page__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__widgets {\n\tmargin-top: 20px;\n}\n</style>\n"],"names":["CnIcon","t"],"mappings":"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"}
@@ -1,61 +1,136 @@
1
- import { resolveComponent, openBlock, createElementBlock, createElementVNode, toDisplayString, createCommentVNode, withDirectives, Fragment, renderList, vModelSelect, createVNode } from 'vue';
1
+ import { resolveComponent, openBlock, createElementBlock, normalizeClass, createElementVNode, toDisplayString, createCommentVNode, Fragment, renderList, createVNode, withDirectives, vModelSelect } from 'vue';
2
2
 
3
- const _hoisted_1 = { class: "cn-reports-page" };
4
- const _hoisted_2 = { class: "cn-reports-page__header" };
5
- const _hoisted_3 = { class: "cn-reports-page__title" };
6
- const _hoisted_4 = {
3
+ const _hoisted_1 = { class: "cn-reports-page__header" };
4
+ const _hoisted_2 = { class: "cn-reports-page__title" };
5
+ const _hoisted_3 = {
7
6
  key: 0,
8
7
  class: "cn-reports-page__description"
9
8
  };
9
+ const _hoisted_4 = {
10
+ key: 0,
11
+ class: "cn-reports-page__empty",
12
+ "data-testid": "cn-reports-empty"
13
+ };
10
14
  const _hoisted_5 = {
11
15
  key: 0,
12
- class: "cn-reports-page__filter"
16
+ class: "cn-reports-page__section-title"
17
+ };
18
+ const _hoisted_6 = {
19
+ class: "cn-reports-page__grid",
20
+ "data-testid": "cn-reports-grid"
21
+ };
22
+ const _hoisted_7 = ["href", "onClick"];
23
+ const _hoisted_8 = {
24
+ key: 0,
25
+ class: "cn-reports-page__card-icon",
26
+ "aria-hidden": "true"
27
+ };
28
+ const _hoisted_9 = { class: "cn-reports-page__card-text" };
29
+ const _hoisted_10 = { class: "cn-reports-page__card-title" };
30
+ const _hoisted_11 = {
31
+ key: 0,
32
+ class: "cn-reports-page__card-description"
13
33
  };
14
- const _hoisted_6 = ["for"];
15
- const _hoisted_7 = ["id"];
16
- const _hoisted_8 = ["value"];
17
- const _hoisted_9 = {
34
+ const _hoisted_12 = {
18
35
  key: 1,
36
+ class: "cn-reports-page__filter"
37
+ };
38
+ const _hoisted_13 = ["for"];
39
+ const _hoisted_14 = ["id"];
40
+ const _hoisted_15 = ["value"];
41
+ const _hoisted_16 = {
42
+ key: 2,
19
43
  class: "cn-reports-page__empty",
20
44
  "data-testid": "cn-reports-empty"
21
45
  };
22
- const _hoisted_10 = {
23
- key: 2,
46
+ const _hoisted_17 = {
47
+ key: 3,
24
48
  class: "cn-reports-page__grid",
25
49
  "data-testid": "cn-reports-grid"
26
50
  };
27
- const _hoisted_11 = ["href", "onClick"];
28
- const _hoisted_12 = { class: "cn-reports-page__card-title" };
29
- const _hoisted_13 = {
51
+ const _hoisted_18 = ["href", "onClick"];
52
+ const _hoisted_19 = { class: "cn-reports-page__card-title" };
53
+ const _hoisted_20 = {
30
54
  key: 0,
31
55
  class: "cn-reports-page__card-description"
32
56
  };
33
- const _hoisted_14 = {
57
+ const _hoisted_21 = {
34
58
  key: 1,
35
59
  class: "cn-reports-page__card-category"
36
60
  };
37
- const _hoisted_15 = {
38
- key: 3,
61
+ const _hoisted_22 = {
62
+ key: 4,
39
63
  class: "cn-reports-page__widgets",
40
64
  "data-testid": "cn-reports-widgets"
41
65
  };
42
66
 
43
67
  function render(_ctx, _cache, $props, $setup, $data, $options) {
68
+ const _component_CnIcon = resolveComponent("CnIcon");
44
69
  const _component_CnDashboardPage = resolveComponent("CnDashboardPage");
45
70
 
46
- return (openBlock(), createElementBlock("div", _hoisted_1, [
47
- createElementVNode("div", _hoisted_2, [
48
- createElementVNode("h2", _hoisted_3, toDisplayString($options.resolvedTitle), 1 /* TEXT */),
71
+ return (openBlock(), createElementBlock("div", {
72
+ class: normalizeClass(["cn-reports-page", { 'cn-reports-page--board': $setup.isBoard }])
73
+ }, [
74
+ createElementVNode("div", _hoisted_1, [
75
+ createElementVNode("h2", _hoisted_2, toDisplayString($options.resolvedTitle), 1 /* TEXT */),
49
76
  ($options.resolvedDescription)
50
- ? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString($options.resolvedDescription), 1 /* TEXT */))
77
+ ? (openBlock(), createElementBlock("p", _hoisted_3, toDisplayString($options.resolvedDescription), 1 /* TEXT */))
51
78
  : createCommentVNode("v-if", true)
52
79
  ]),
53
- ($options.categoryOptions.length > 1)
54
- ? (openBlock(), createElementBlock("div", _hoisted_5, [
80
+ ($setup.isBoard)
81
+ ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
82
+ ($options.resolvedCards.length === 0)
83
+ ? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString($options.resolvedEmptyLabel), 1 /* TEXT */))
84
+ : createCommentVNode("v-if", true),
85
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.boardSections, (section) => {
86
+ return (openBlock(), createElementBlock("section", {
87
+ key: section.key,
88
+ class: "cn-reports-page__section",
89
+ "data-testid": "cn-reports-section"
90
+ }, [
91
+ (section.label)
92
+ ? (openBlock(), createElementBlock("h3", _hoisted_5, toDisplayString(section.label), 1 /* TEXT */))
93
+ : createCommentVNode("v-if", true),
94
+ createElementVNode("ul", _hoisted_6, [
95
+ (openBlock(true), createElementBlock(Fragment, null, renderList(section.cards, (card) => {
96
+ return (openBlock(), createElementBlock("li", {
97
+ key: card.id,
98
+ class: "cn-reports-page__cell"
99
+ }, [
100
+ createElementVNode("a", {
101
+ class: "cn-reports-page__card",
102
+ "data-testid": "cn-report-card",
103
+ href: $options.hrefFor(card),
104
+ onClick: $event => ($options.open(card, $event))
105
+ }, [
106
+ (card.icon)
107
+ ? (openBlock(), createElementBlock("span", _hoisted_8, [
108
+ createVNode(_component_CnIcon, {
109
+ name: card.icon,
110
+ size: 22
111
+ }, null, 8 /* PROPS */, ["name"])
112
+ ]))
113
+ : createCommentVNode("v-if", true),
114
+ createElementVNode("span", _hoisted_9, [
115
+ createElementVNode("span", _hoisted_10, toDisplayString(card.label), 1 /* TEXT */),
116
+ (card.description)
117
+ ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(card.description), 1 /* TEXT */))
118
+ : createCommentVNode("v-if", true)
119
+ ])
120
+ ], 8 /* PROPS */, _hoisted_7)
121
+ ]))
122
+ }), 128 /* KEYED_FRAGMENT */))
123
+ ])
124
+ ]))
125
+ }), 128 /* KEYED_FRAGMENT */))
126
+ ], 64 /* STABLE_FRAGMENT */))
127
+ : createCommentVNode("v-if", true),
128
+ (!$setup.isBoard && $options.categoryOptions.length > 1)
129
+ ? (openBlock(), createElementBlock("div", _hoisted_12, [
55
130
  createElementVNode("label", {
56
131
  class: "cn-reports-page__filter-label",
57
132
  for: $options.filterId
58
- }, toDisplayString($options.resolvedCategoryLabel), 9 /* TEXT, PROPS */, _hoisted_6),
133
+ }, toDisplayString($options.resolvedCategoryLabel), 9 /* TEXT, PROPS */, _hoisted_13),
59
134
  withDirectives(createElementVNode("select", {
60
135
  id: $options.filterId,
61
136
  "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($data.activeCategory) = $event)),
@@ -66,40 +141,42 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
66
141
  return (openBlock(), createElementBlock("option", {
67
142
  key: option.value,
68
143
  value: option.value
69
- }, toDisplayString(option.label), 9 /* TEXT, PROPS */, _hoisted_8))
144
+ }, toDisplayString(option.label), 9 /* TEXT, PROPS */, _hoisted_15))
70
145
  }), 128 /* KEYED_FRAGMENT */))
71
- ], 8 /* PROPS */, _hoisted_7), [
146
+ ], 8 /* PROPS */, _hoisted_14), [
72
147
  [vModelSelect, $data.activeCategory]
73
148
  ])
74
149
  ]))
75
150
  : createCommentVNode("v-if", true),
76
- ($options.visibleCards.length === 0)
77
- ? (openBlock(), createElementBlock("p", _hoisted_9, toDisplayString($options.resolvedEmptyLabel), 1 /* TEXT */))
78
- : (openBlock(), createElementBlock("ul", _hoisted_10, [
79
- (openBlock(true), createElementBlock(Fragment, null, renderList($options.visibleCards, (card) => {
80
- return (openBlock(), createElementBlock("li", {
81
- key: card.id,
82
- class: "cn-reports-page__cell"
83
- }, [
84
- createElementVNode("a", {
85
- class: "cn-reports-page__card",
86
- "data-testid": "cn-report-card",
87
- href: $options.hrefFor(card),
88
- onClick: $event => ($options.open(card, $event))
151
+ (!$setup.isBoard && $options.visibleCards.length === 0)
152
+ ? (openBlock(), createElementBlock("p", _hoisted_16, toDisplayString($options.resolvedEmptyLabel), 1 /* TEXT */))
153
+ : (!$setup.isBoard)
154
+ ? (openBlock(), createElementBlock("ul", _hoisted_17, [
155
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.visibleCards, (card) => {
156
+ return (openBlock(), createElementBlock("li", {
157
+ key: card.id,
158
+ class: "cn-reports-page__cell"
89
159
  }, [
90
- createElementVNode("span", _hoisted_12, toDisplayString(card.label), 1 /* TEXT */),
91
- (card.description)
92
- ? (openBlock(), createElementBlock("span", _hoisted_13, toDisplayString(card.description), 1 /* TEXT */))
93
- : createCommentVNode("v-if", true),
94
- ($options.categoryName(card))
95
- ? (openBlock(), createElementBlock("span", _hoisted_14, toDisplayString($options.categoryName(card)), 1 /* TEXT */))
96
- : createCommentVNode("v-if", true)
97
- ], 8 /* PROPS */, _hoisted_11)
98
- ]))
99
- }), 128 /* KEYED_FRAGMENT */))
100
- ])),
160
+ createElementVNode("a", {
161
+ class: "cn-reports-page__card",
162
+ "data-testid": "cn-report-card",
163
+ href: $options.hrefFor(card),
164
+ onClick: $event => ($options.open(card, $event))
165
+ }, [
166
+ createElementVNode("span", _hoisted_19, toDisplayString(card.label), 1 /* TEXT */),
167
+ (card.description)
168
+ ? (openBlock(), createElementBlock("span", _hoisted_20, toDisplayString(card.description), 1 /* TEXT */))
169
+ : createCommentVNode("v-if", true),
170
+ ($options.categoryName(card))
171
+ ? (openBlock(), createElementBlock("span", _hoisted_21, toDisplayString($options.categoryName(card)), 1 /* TEXT */))
172
+ : createCommentVNode("v-if", true)
173
+ ], 8 /* PROPS */, _hoisted_18)
174
+ ]))
175
+ }), 128 /* KEYED_FRAGMENT */))
176
+ ]))
177
+ : createCommentVNode("v-if", true),
101
178
  ($options.sectionWidgets.length > 0)
102
- ? (openBlock(), createElementBlock("div", _hoisted_15, [
179
+ ? (openBlock(), createElementBlock("div", _hoisted_22, [
103
180
  createVNode(_component_CnDashboardPage, {
104
181
  widgets: $options.sectionWidgets,
105
182
  layout: $options.sectionLayout,
@@ -107,7 +184,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
107
184
  }, null, 8 /* PROPS */, ["widgets", "layout"])
108
185
  ]))
109
186
  : createCommentVNode("v-if", true)
110
- ]))
187
+ ], 2 /* CLASS */))
111
188
  }
112
189
 
113
190
  export { render };
@@ -1 +1 @@
1
- {"version":3,"file":"CnReportsPage.vue3.js","sources":["../../../../src/components/CnReportsPage/CnReportsPage.vue?vue&type=template&id=7597ff10&lang.js"],"sourcesContent":["<!--\n CnReportsPage — Manifest-driven Reports surface.\n\n v2 manifest page primitive for `type: \"reports\"`. One page holding every\n report an app offers, as cards, instead of a Reports SUBMENU with one entry\n per report.\n\n WHY THIS IS A PAGE AND NOT A MENU BRANCH. A submenu grows one entry per\n report and never shrinks. shillinq reached 96 report types; as menu children\n that is a scroll, not a navigation. Cards carry what a menu item cannot — a\n description, a category, a count — and a category filter turns 96 into a\n short list. shillinq proved the shape in `ReportingComplianceOverview`; this\n is that shape with the app-specific parts taken out, so every app declares it\n rather than copying 600 lines of Vue.\n\n Declare it as:\n\n { \"id\": \"Reports\", \"route\": \"/reports\", \"type\": \"reports\",\n \"config\": {\n \"categories\": { \"operational\": \"Operational\" },\n \"cards\": [\n { \"id\": \"Throughput\", \"label\": \"Processing time\",\n \"description\": \"How long cases take, by type.\",\n \"icon\": \"ChartLine\", \"category\": \"operational\",\n \"route\": \"Doorlooptijd\" }\n ]\n } }\n\n A card names a ROUTE by name, never a path: a path is editable per app and a\n card pointing at a stale one is a dead end that still looks like a report.\n\n Spec: hydra openspec/architecture/adr-112-reports-are-one-page.md (ConductionNL/hydra#640).\n-->\n<template>\n\t<div class=\"cn-reports-page\">\n\t\t<div class=\"cn-reports-page__header\">\n\t\t\t<h2 class=\"cn-reports-page__title\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"resolvedDescription\" class=\"cn-reports-page__description\">\n\t\t\t\t{{ resolvedDescription }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<div v-if=\"categoryOptions.length > 1\" class=\"cn-reports-page__filter\">\n\t\t\t<label class=\"cn-reports-page__filter-label\" :for=\"filterId\">\n\t\t\t\t{{ resolvedCategoryLabel }}\n\t\t\t</label>\n\t\t\t<select :id=\"filterId\"\n\t\t\t\tv-model=\"activeCategory\"\n\t\t\t\tclass=\"cn-reports-page__filter-select\"\n\t\t\t\tdata-testid=\"cn-reports-category\">\n\t\t\t\t<option v-for=\"option in categoryOptions\"\n\t\t\t\t\t:key=\"option.value\"\n\t\t\t\t\t:value=\"option.value\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</div>\n\n\t\t<p v-if=\"visibleCards.length === 0\"\n\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t{{ resolvedEmptyLabel }}\n\t\t</p>\n\n\t\t<ul v-else class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t<li v-for=\"card in visibleCards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"categoryName(card)\" class=\"cn-reports-page__card-category\">\n\t\t\t\t\t\t{{ categoryName(card) }}\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<!-- `widgets` + `layout`: a grid of dashboard widgets under the cards,\n\t\t such as a highlighted report with stat tiles and a chart. -->\n\t\t<div v-if=\"sectionWidgets.length > 0\" class=\"cn-reports-page__widgets\" data-testid=\"cn-reports-widgets\">\n\t\t\t<CnDashboardPage\n\t\t\t\t:widgets=\"sectionWidgets\"\n\t\t\t\t:layout=\"sectionLayout\"\n\t\t\t\t:showHeader=\"false\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * The Reports page: one surface listing an app's reports as cards.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { defineAsyncComponent } from 'vue'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\n\nexport default {\n\tname: 'CnReportsPage',\n\n\tcomponents: {\n\t\t// Loaded only for a page that declares widgets: a cards-only reports\n\t\t// page does not pull the dashboard in.\n\t\tCnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The consuming app's translate function, provided by CnAppRoot.\n\t\t * Defaults to identity so the page still renders outside one.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The manifest page.\n\t\t *\n\t\t * ⚠️ CnPageRenderer does NOT pass this. `resolvedProps()` returns\n\t\t * `{ ...topLevel, ...normalizedConfig, ...params }` and has no `page`\n\t\t * key, so a dispatched `type: \"reports\"` page arrived here with the\n\t\t * default `{}` — `config` resolved to `{}`, `cards` to `[]`, and every\n\t\t * reports page in the fleet rendered its empty state while its manifest\n\t\t * declared cards. Kept because a host that renders this component\n\t\t * directly can still hand it the whole page; the flattened props below\n\t\t * are what the renderer actually supplies, and they win.\n\t\t */\n\t\tpage: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The report cards, as CnPageRenderer flattens them out of `config`.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tcards: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Category key/label pairs, flattened out of `config`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The lead paragraph, flattened out of `config` or lifted from the page.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The heading, lifted from the page by the renderer.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the \"all categories\" filter option.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tallCategoriesLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the category filter control.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcategoryLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Text shown when the active filter admits no card.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Dashboard widgets drawn under the report cards (`config.widgets`),\n\t\t * placed by `layout`, as on a `type: \"dashboard\"` page.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Grid placement of `widgets` (`config.layout`): `{ id, widgetId,\n\t\t * gridX, gridY, gridWidth, gridHeight }` per widget. Without it each\n\t\t * widget takes a full row in the order declared.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveCategory: 'all',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translate function actually in force.\n\t\t *\n\t\t * @return {(key: string) => string} The translator.\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The declared config, always an object, from whichever direction it\n\t\t * arrived.\n\t\t *\n\t\t * TWO CALLERS, TWO SHAPES. CnPageRenderer flattens `page.config.*` into\n\t\t * individual props and passes NO `page`, so under the renderer the keys\n\t\t * arrive as props. A host that mounts this component itself hands it the\n\t\t * whole `page`. Reading only `page.config` is what made every dispatched\n\t\t * reports page render empty.\n\t\t *\n\t\t * Props win where both are present: they are the nearer declaration, and\n\t\t * the renderer has already resolved route sentinels in them.\n\t\t *\n\t\t * @return {object} The config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst fromPage = (this.page && this.page.config) || {}\n\t\t\tconst fromProps = {}\n\t\t\tfor (const key of [\n\t\t\t\t'cards',\n\t\t\t\t'categories',\n\t\t\t\t'description',\n\t\t\t\t'title',\n\t\t\t\t'allCategoriesLabel',\n\t\t\t\t'categoryLabel',\n\t\t\t\t'emptyLabel',\n\t\t\t\t'widgets',\n\t\t\t\t'layout',\n\t\t\t]) {\n\t\t\t\tif (this[key] !== null && this[key] !== undefined) {\n\t\t\t\t\tfromProps[key] = this[key]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ...fromPage, ...fromProps }\n\t\t},\n\n\t\t/**\n\t\t * The widgets under the cards.\n\t\t *\n\t\t * @return {Array<object>} The declared widgets, or none.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionWidgets() {\n\t\t\tconst widgets = this.config.widgets\n\t\t\treturn Array.isArray(widgets) ? widgets.filter((w) => w && typeof w === 'object' && w.id) : []\n\t\t},\n\n\t\t/**\n\t\t * Where the widgets go: the declared `layout`, else one full row per\n\t\t * widget in the order declared.\n\t\t *\n\t\t * @return {Array<object>} The placements.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionLayout() {\n\t\t\tif (Array.isArray(this.config.layout) && this.config.layout.length > 0) {\n\t\t\t\treturn this.config.layout\n\t\t\t}\n\t\t\tlet y = 0\n\t\t\treturn this.sectionWidgets.map((widget, index) => {\n\t\t\t\tconst placement = { id: String(index + 1), widgetId: widget.id, gridX: 0, gridY: y, gridWidth: 12, gridHeight: 4 }\n\t\t\t\ty += 4\n\t\t\t\treturn placement\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The heading.\n\t\t *\n\t\t * @return {string} The title.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst declared = this.config.title || (this.page && this.page.title)\n\n\t\t\treturn declared ? this.tr(declared) : t('nextcloud-vue', 'Reports')\n\t\t},\n\n\t\t/**\n\t\t * The optional lead paragraph.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.tr(this.config.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The declared cards, ignoring any that name no route.\n\t\t *\n\t\t * A card with no route is a card that cannot be opened. Rendering it\n\t\t * would put a report on the page that silently does nothing, which is\n\t\t * worse than leaving it off.\n\t\t *\n\t\t * @return {Array<object>} The usable cards.\n\t\t */\n\t\tresolvedCards() {\n\t\t\tconst declared = this.config.cards\n\t\t\tif (!Array.isArray(declared)) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn declared\n\t\t\t\t.filter((card) => card && card.route && card.label)\n\t\t\t\t.map((card) => ({\n\t\t\t\t\t...card,\n\t\t\t\t\tlabel: this.tr(card.label),\n\t\t\t\t\tdescription: this.tr(card.description || ''),\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Category value/label pairs, offered only for categories that hold a\n\t\t * card. A filter listing empty categories teaches the reader that the\n\t\t * app has reports it does not have.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tcategoryOptions() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst used = new Set(this.resolvedCards.map((card) => card.category).filter(Boolean))\n\t\t\tconst options = [{ value: 'all', label: this.allLabel }]\n\n\t\t\tObject.keys(declared)\n\t\t\t\t.filter((key) => used.has(key))\n\t\t\t\t.forEach((key) => options.push({ value: key, label: this.tr(declared[key]) }))\n\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The cards the active filter admits.\n\t\t *\n\t\t * @return {Array<object>} The visible cards.\n\t\t */\n\t\tvisibleCards() {\n\t\t\tif (this.activeCategory === 'all') {\n\t\t\t\treturn this.resolvedCards\n\t\t\t}\n\n\t\t\treturn this.resolvedCards.filter((card) => card.category === this.activeCategory)\n\t\t},\n\n\t\t/**\n\t\t * A stable id for the filter control's label association.\n\t\t *\n\t\t * @return {string} The id.\n\t\t */\n\t\tfilterId() {\n\t\t\treturn `cn-reports-category-${(this.page && this.page.id) || 'page'}`\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The \"all categories\" option label.\n\t\t */\n\t\tallLabel() {\n\t\t\treturn this.config.allCategoriesLabel\n\t\t\t\t? this.tr(this.config.allCategoriesLabel)\n\t\t\t\t: t('nextcloud-vue', 'All categories')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The filter's label.\n\t\t */\n\t\tresolvedCategoryLabel() {\n\t\t\treturn this.config.categoryLabel\n\t\t\t\t? this.tr(this.config.categoryLabel)\n\t\t\t\t: t('nextcloud-vue', 'Category')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The empty-state text.\n\t\t */\n\t\tresolvedEmptyLabel() {\n\t\t\treturn this.config.emptyLabel\n\t\t\t\t? this.tr(this.config.emptyLabel)\n\t\t\t\t: t('nextcloud-vue', 'No reports in this category.')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a manifest-declared string through the app's own\n\t\t * translate function.\n\t\t *\n\t\t * The manifest is data the renderer walks, not source the extractor\n\t\t * reads, so every string it carries has to be handed to the app's\n\t\t * translator explicitly. A card rendered straight from the manifest\n\t\t * shows its source language whatever locale the reader is in.\n\t\t *\n\t\t * @param {string} value The declared string.\n\t\t * @return {string} The translated string, or the input unchanged.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn this.effectiveTranslate(value)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a card's category, if it has one.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The label, or an empty string.\n\t\t */\n\t\tcategoryName(card) {\n\t\t\tconst declared = this.config.categories || {}\n\n\t\t\treturn this.tr((card.category && declared[card.category]) || '')\n\t\t},\n\n\t\t/**\n\t\t * A real href for the card, so it is a link a reader can middle-click\n\t\t * or open in a new tab rather than a div that only answers to a click.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The href.\n\t\t */\n\t\threfFor(card) {\n\t\t\tif (!this.$router) {\n\t\t\t\treturn '#'\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: card.route }).href\n\t\t\t} catch {\n\t\t\t\treturn '#'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate by route NAME on a plain click; a modified or middle click\n\t\t * is left to the browser so it can open the card's href elsewhere.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @param {MouseEvent} event The click.\n\t\t * @return {void}\n\t\t */\n\t\topen(card, event) {\n\t\t\tfollowLinkClick(event, { name: card.route }, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-reports-page {\n\tpadding: 16px;\n}\n\n/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n positioned at the left edge of .app-content) plus 12px breathing room, as\n CnPageHeader does. Only the header shifts; the body keeps the full width. */\n.cn-reports-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-reports-page__title {\n\tmargin: 0 0 4px;\n}\n\n.cn-reports-page__description {\n\tmargin: 0 0 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__filter {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 16px;\n}\n\n.cn-reports-page__filter-select {\n\tmin-width: 220px;\n}\n\n.cn-reports-page__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 12px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-reports-page__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\theight: 100%;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\n.cn-reports-page__card:hover,\n.cn-reports-page__card:focus {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-reports-page__card-title {\n\tfont-weight: bold;\n}\n\n.cn-reports-page__card-description,\n.cn-reports-page__card-category {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-reports-page__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__widgets {\n\tmargin-top: 20px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList"],"mappings":";;AAmEK,MAAA,UAAA,GAAA,EAAA,KAAM,EAAC,iBAAiB,GAAA;AACtB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,yBAAsB,GAAA;AAC5B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,wBAAmB,GAAA;;;AAGC,EAAA,KAAK,EAAC,8BAA8B;;;;AAKxC,EAAA,KAAA,EAAA,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAV1B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;AACC,IAAAC,kBAAA,CAOI,OAPJ,UAOI,EAAA;wBAJA,CAAA,IAAA,YAAA,EAAAC,eAAA,CADD,QAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;OAER,QAAmB,CAAA,mBAAA;0CAExB,CAAA,GAAA,YAAA,EAAAA,eAAA,CADA,QAAmB,CAAA,mBAAA,CAAA,EAAA,CAAA,YAAA;;;AAIb,IAAA,CAAA,QAAA,CAAA,eAAe,CAAC,MAAA,GAAA,CAAA;AAAvB,SAAAH,SAAA,EAAA,EAAAC,kBAAA,CAcA,OAdA,UAcA,EAAA;UAbDC,kBAEE,CAAA,OAAA,EAAA;AAFG,YAAA,KAAC,EAAA,+BAAA;AAAA,YAAA,GAAA,EAAA,QAAA,CAAA,QAAA;6BACJ,QAAA,CAAA,qBAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;yBAEJA,kBASI,CAAA,QAAA,EAAA;AATD,YAAA,EAAA,EAAA,QAAA,CAAA,QAAA;;AAEF,YAAA,KAAK,EAAC,gCAAgC;yBAC3B,EAAE,qBAAoB;;8BACjCD,kBAIS,CAAAG,QAAA,EAAA,IAAA,EAAAC,UAAA,CAJgB,QAAe,CAAA,eAAA,EAAA,CAAzB,MAAM,KAAA;mCAArBJ,kBAIS,CAAA,QAAA,EAAA;gBAHP,GAAG,EAAE,MAAM,CAAC,KAAA;gBACZ,KAAK,EAAE,MAAM,CAAC,KAAK;AACjB,eAAA,EAAAE,eAAA,CAAA,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnReportsPage.vue3.js","sources":["../../../../src/components/CnReportsPage/CnReportsPage.vue?vue&type=template&id=7629715a&lang.js"],"sourcesContent":["<!--\n CnReportsPage — Manifest-driven Reports surface.\n\n v2 manifest page primitive for `type: \"reports\"`. One page holding every\n report an app offers, as cards, instead of a Reports SUBMENU with one entry\n per report.\n\n WHY THIS IS A PAGE AND NOT A MENU BRANCH. A submenu grows one entry per\n report and never shrinks. shillinq reached 96 report types; as menu children\n that is a scroll, not a navigation. Cards carry what a menu item cannot — a\n description, a category, a count — and a category filter turns 96 into a\n short list. shillinq proved the shape in `ReportingComplianceOverview`; this\n is that shape with the app-specific parts taken out, so every app declares it\n rather than copying 600 lines of Vue.\n\n Declare it as:\n\n { \"id\": \"Reports\", \"route\": \"/reports\", \"type\": \"reports\",\n \"config\": {\n \"categories\": { \"operational\": \"Operational\" },\n \"cards\": [\n { \"id\": \"Throughput\", \"label\": \"Processing time\",\n \"description\": \"How long cases take, by type.\",\n \"icon\": \"ChartLine\", \"category\": \"operational\",\n \"route\": \"Doorlooptijd\" }\n ]\n } }\n\n A card names a ROUTE by name, never a path: a path is editable per app and a\n card pointing at a stale one is a dead end that still looks like a report.\n\n Spec: hydra openspec/architecture/adr-112-reports-are-one-page.md (ConductionNL/hydra#640).\n-->\n<template>\n\t<div class=\"cn-reports-page\" :class=\"{ 'cn-reports-page--board': isBoard }\">\n\t\t<div class=\"cn-reports-page__header\">\n\t\t\t<h2 class=\"cn-reports-page__title\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"resolvedDescription\" class=\"cn-reports-page__description\">\n\t\t\t\t{{ resolvedDescription }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<!-- The board look (board DcRapportages): one section per category,\n\t\t headed by its label, instead of the category filter; each card\n\t\t draws its icon in a tile. -->\n\t\t<template v-if=\"isBoard\">\n\t\t\t<p v-if=\"resolvedCards.length === 0\"\n\t\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t\t{{ resolvedEmptyLabel }}\n\t\t\t</p>\n\t\t\t<section v-for=\"section in boardSections\"\n\t\t\t\t:key=\"section.key\"\n\t\t\t\tclass=\"cn-reports-page__section\"\n\t\t\t\tdata-testid=\"cn-reports-section\">\n\t\t\t\t<h3 v-if=\"section.label\" class=\"cn-reports-page__section-title\">\n\t\t\t\t\t{{ section.label }}\n\t\t\t\t</h3>\n\t\t\t\t<ul class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t\t\t<li v-for=\"card in section.cards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t\t\t<span v-if=\"card.icon\" class=\"cn-reports-page__card-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<CnIcon :name=\"card.icon\" :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-reports-page__card-text\">\n\t\t\t\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</template>\n\n\t\t<div v-if=\"!isBoard && categoryOptions.length > 1\" class=\"cn-reports-page__filter\">\n\t\t\t<label class=\"cn-reports-page__filter-label\" :for=\"filterId\">\n\t\t\t\t{{ resolvedCategoryLabel }}\n\t\t\t</label>\n\t\t\t<select :id=\"filterId\"\n\t\t\t\tv-model=\"activeCategory\"\n\t\t\t\tclass=\"cn-reports-page__filter-select\"\n\t\t\t\tdata-testid=\"cn-reports-category\">\n\t\t\t\t<option v-for=\"option in categoryOptions\"\n\t\t\t\t\t:key=\"option.value\"\n\t\t\t\t\t:value=\"option.value\">\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</div>\n\n\t\t<p v-if=\"!isBoard && visibleCards.length === 0\"\n\t\t\tclass=\"cn-reports-page__empty\"\n\t\t\tdata-testid=\"cn-reports-empty\">\n\t\t\t{{ resolvedEmptyLabel }}\n\t\t</p>\n\n\t\t<ul v-else-if=\"!isBoard\" class=\"cn-reports-page__grid\" data-testid=\"cn-reports-grid\">\n\t\t\t<li v-for=\"card in visibleCards\" :key=\"card.id\" class=\"cn-reports-page__cell\">\n\t\t\t\t<a class=\"cn-reports-page__card\"\n\t\t\t\t\tdata-testid=\"cn-report-card\"\n\t\t\t\t\t:href=\"hrefFor(card)\"\n\t\t\t\t\t@click=\"open(card, $event)\">\n\t\t\t\t\t<span class=\"cn-reports-page__card-title\">{{ card.label }}</span>\n\t\t\t\t\t<span v-if=\"card.description\" class=\"cn-reports-page__card-description\">\n\t\t\t\t\t\t{{ card.description }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"categoryName(card)\" class=\"cn-reports-page__card-category\">\n\t\t\t\t\t\t{{ categoryName(card) }}\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<!-- `widgets` + `layout`: a grid of dashboard widgets under the cards,\n\t\t such as a highlighted report with stat tiles and a chart. -->\n\t\t<div v-if=\"sectionWidgets.length > 0\" class=\"cn-reports-page__widgets\" data-testid=\"cn-reports-widgets\">\n\t\t\t<CnDashboardPage\n\t\t\t\t:widgets=\"sectionWidgets\"\n\t\t\t\t:layout=\"sectionLayout\"\n\t\t\t\t:showHeader=\"false\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * The Reports page: one surface listing an app's reports as cards.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { defineAsyncComponent } from 'vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { useLook } from '../../composables/useLook.js'\nimport { followLinkClick } from '../../utils/linkNavigation.js'\n\nexport default {\n\tname: 'CnReportsPage',\n\n\tcomponents: {\n\t\t// Loaded only for a page that declares widgets: a cards-only reports\n\t\t// page does not pull the dashboard in.\n\t\tCnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\t\tCnIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The consuming app's translate function, provided by CnAppRoot.\n\t\t * Defaults to identity so the page still renders outside one.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The manifest page.\n\t\t *\n\t\t * ⚠️ CnPageRenderer does NOT pass this. `resolvedProps()` returns\n\t\t * `{ ...topLevel, ...normalizedConfig, ...params }` and has no `page`\n\t\t * key, so a dispatched `type: \"reports\"` page arrived here with the\n\t\t * default `{}` — `config` resolved to `{}`, `cards` to `[]`, and every\n\t\t * reports page in the fleet rendered its empty state while its manifest\n\t\t * declared cards. Kept because a host that renders this component\n\t\t * directly can still hand it the whole page; the flattened props below\n\t\t * are what the renderer actually supplies, and they win.\n\t\t */\n\t\tpage: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * The report cards, as CnPageRenderer flattens them out of `config`.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tcards: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Category key/label pairs, flattened out of `config`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tcategories: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The lead paragraph, flattened out of `config` or lifted from the page.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The heading, lifted from the page by the renderer.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the \"all categories\" filter option.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tallCategoriesLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Label for the category filter control.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tcategoryLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Text shown when the active filter admits no card.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Dashboard widgets drawn under the report cards (`config.widgets`),\n\t\t * placed by `layout`, as on a `type: \"dashboard\"` page.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Grid placement of `widgets` (`config.layout`): `{ id, widgetId,\n\t\t * gridX, gridY, gridWidth, gridHeight }` per widget. Without it each\n\t\t * widget takes a full row in the order declared.\n\t\t *\n\t\t * @type {Array<object>|null}\n\t\t */\n\t\tlayout: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The look to draw in: `board` or `nextcloud`. Absent: the look\n\t\t * CnAppRoot provides (`cnLook`), else `nextcloud`.\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to injected `cnTranslate`.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\tconst { isBoard } = useLook(props)\n\t\treturn { isBoard }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveCategory: 'all',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translate function actually in force.\n\t\t *\n\t\t * @return {(key: string) => string} The translator.\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The declared config, always an object, from whichever direction it\n\t\t * arrived.\n\t\t *\n\t\t * TWO CALLERS, TWO SHAPES. CnPageRenderer flattens `page.config.*` into\n\t\t * individual props and passes NO `page`, so under the renderer the keys\n\t\t * arrive as props. A host that mounts this component itself hands it the\n\t\t * whole `page`. Reading only `page.config` is what made every dispatched\n\t\t * reports page render empty.\n\t\t *\n\t\t * Props win where both are present: they are the nearer declaration, and\n\t\t * the renderer has already resolved route sentinels in them.\n\t\t *\n\t\t * @return {object} The config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst fromPage = (this.page && this.page.config) || {}\n\t\t\tconst fromProps = {}\n\t\t\tfor (const key of [\n\t\t\t\t'cards',\n\t\t\t\t'categories',\n\t\t\t\t'description',\n\t\t\t\t'title',\n\t\t\t\t'allCategoriesLabel',\n\t\t\t\t'categoryLabel',\n\t\t\t\t'emptyLabel',\n\t\t\t\t'widgets',\n\t\t\t\t'layout',\n\t\t\t]) {\n\t\t\t\tif (this[key] !== null && this[key] !== undefined) {\n\t\t\t\t\tfromProps[key] = this[key]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ...fromPage, ...fromProps }\n\t\t},\n\n\t\t/**\n\t\t * The widgets under the cards.\n\t\t *\n\t\t * @return {Array<object>} The declared widgets, or none.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionWidgets() {\n\t\t\tconst widgets = this.config.widgets\n\t\t\treturn Array.isArray(widgets) ? widgets.filter((w) => w && typeof w === 'object' && w.id) : []\n\t\t},\n\n\t\t/**\n\t\t * Where the widgets go: the declared `layout`, else one full row per\n\t\t * widget in the order declared.\n\t\t *\n\t\t * @return {Array<object>} The placements.\n\t\t * @spec openspec/changes/screens-reports-page-widgets/specs/reports-page/spec.md\n\t\t */\n\t\tsectionLayout() {\n\t\t\tif (Array.isArray(this.config.layout) && this.config.layout.length > 0) {\n\t\t\t\treturn this.config.layout\n\t\t\t}\n\t\t\tlet y = 0\n\t\t\treturn this.sectionWidgets.map((widget, index) => {\n\t\t\t\tconst placement = { id: String(index + 1), widgetId: widget.id, gridX: 0, gridY: y, gridWidth: 12, gridHeight: 4 }\n\t\t\t\ty += 4\n\t\t\t\treturn placement\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The heading.\n\t\t *\n\t\t * @return {string} The title.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst declared = this.config.title || (this.page && this.page.title)\n\n\t\t\treturn declared ? this.tr(declared) : t('nextcloud-vue', 'Reports')\n\t\t},\n\n\t\t/**\n\t\t * The optional lead paragraph.\n\t\t *\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.tr(this.config.description || '')\n\t\t},\n\n\t\t/**\n\t\t * The declared cards, ignoring any that name no route.\n\t\t *\n\t\t * A card with no route is a card that cannot be opened. Rendering it\n\t\t * would put a report on the page that silently does nothing, which is\n\t\t * worse than leaving it off.\n\t\t *\n\t\t * @return {Array<object>} The usable cards.\n\t\t */\n\t\tresolvedCards() {\n\t\t\tconst declared = this.config.cards\n\t\t\tif (!Array.isArray(declared)) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn declared\n\t\t\t\t.filter((card) => card && card.route && card.label)\n\t\t\t\t.map((card) => ({\n\t\t\t\t\t...card,\n\t\t\t\t\tlabel: this.tr(card.label),\n\t\t\t\t\tdescription: this.tr(card.description || ''),\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Category value/label pairs, offered only for categories that hold a\n\t\t * card. A filter listing empty categories teaches the reader that the\n\t\t * app has reports it does not have.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tcategoryOptions() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst used = new Set(this.resolvedCards.map((card) => card.category).filter(Boolean))\n\t\t\tconst options = [{ value: 'all', label: this.allLabel }]\n\n\t\t\tObject.keys(declared)\n\t\t\t\t.filter((key) => used.has(key))\n\t\t\t\t.forEach((key) => options.push({ value: key, label: this.tr(declared[key]) }))\n\n\t\t\treturn options\n\t\t},\n\n\t\t/**\n\t\t * The board look's sections: the cards without a category first, under\n\t\t * no heading, then one section per declared category that holds a\n\t\t * card, in the declared order, headed by its label.\n\t\t *\n\t\t * @spec openspec/changes/screens-reports-page-board-look/specs/reports-page/spec.md#requirement-the-reports-page-takes-the-board-look\n\t\t * @return {Array<{key: string, label: string, cards: Array<object>}>} The sections.\n\t\t */\n\t\tboardSections() {\n\t\t\tconst declared = this.config.categories || {}\n\t\t\tconst sections = []\n\t\t\tconst loose = this.resolvedCards.filter((card) => !card.category || !Object.hasOwn(declared, card.category))\n\t\t\tif (loose.length > 0) {\n\t\t\t\tsections.push({ key: '__none', label: '', cards: loose })\n\t\t\t}\n\t\t\tfor (const key of Object.keys(declared)) {\n\t\t\t\tconst cards = this.resolvedCards.filter((card) => card.category === key)\n\t\t\t\tif (cards.length > 0) {\n\t\t\t\t\tsections.push({ key, label: this.tr(declared[key]), cards })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sections\n\t\t},\n\n\t\t/**\n\t\t * The cards the active filter admits.\n\t\t *\n\t\t * @return {Array<object>} The visible cards.\n\t\t */\n\t\tvisibleCards() {\n\t\t\tif (this.activeCategory === 'all') {\n\t\t\t\treturn this.resolvedCards\n\t\t\t}\n\n\t\t\treturn this.resolvedCards.filter((card) => card.category === this.activeCategory)\n\t\t},\n\n\t\t/**\n\t\t * A stable id for the filter control's label association.\n\t\t *\n\t\t * @return {string} The id.\n\t\t */\n\t\tfilterId() {\n\t\t\treturn `cn-reports-category-${(this.page && this.page.id) || 'page'}`\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The \"all categories\" option label.\n\t\t */\n\t\tallLabel() {\n\t\t\treturn this.config.allCategoriesLabel\n\t\t\t\t? this.tr(this.config.allCategoriesLabel)\n\t\t\t\t: t('nextcloud-vue', 'All categories')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The filter's label.\n\t\t */\n\t\tresolvedCategoryLabel() {\n\t\t\treturn this.config.categoryLabel\n\t\t\t\t? this.tr(this.config.categoryLabel)\n\t\t\t\t: t('nextcloud-vue', 'Category')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The empty-state text.\n\t\t */\n\t\tresolvedEmptyLabel() {\n\t\t\treturn this.config.emptyLabel\n\t\t\t\t? this.tr(this.config.emptyLabel)\n\t\t\t\t: t('nextcloud-vue', 'No reports in this category.')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a manifest-declared string through the app's own\n\t\t * translate function.\n\t\t *\n\t\t * The manifest is data the renderer walks, not source the extractor\n\t\t * reads, so every string it carries has to be handed to the app's\n\t\t * translator explicitly. A card rendered straight from the manifest\n\t\t * shows its source language whatever locale the reader is in.\n\t\t *\n\t\t * @param {string} value The declared string.\n\t\t * @return {string} The translated string, or the input unchanged.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn this.effectiveTranslate(value)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a card's category, if it has one.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The label, or an empty string.\n\t\t */\n\t\tcategoryName(card) {\n\t\t\tconst declared = this.config.categories || {}\n\n\t\t\treturn this.tr((card.category && declared[card.category]) || '')\n\t\t},\n\n\t\t/**\n\t\t * A real href for the card, so it is a link a reader can middle-click\n\t\t * or open in a new tab rather than a div that only answers to a click.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @return {string} The href.\n\t\t */\n\t\threfFor(card) {\n\t\t\tif (!this.$router) {\n\t\t\t\treturn '#'\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: card.route }).href\n\t\t\t} catch {\n\t\t\t\treturn '#'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate by route NAME on a plain click; a modified or middle click\n\t\t * is left to the browser so it can open the card's href elsewhere.\n\t\t *\n\t\t * @param {object} card The card.\n\t\t * @param {MouseEvent} event The click.\n\t\t * @return {void}\n\t\t */\n\t\topen(card, event) {\n\t\t\tfollowLinkClick(event, { name: card.route }, this.$router)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-reports-page {\n\tpadding: 16px;\n}\n\n/* Clear the Nextcloud navigation toggle button (44px wide, absolutely\n positioned at the left edge of .app-content) plus 12px breathing room, as\n CnPageHeader does. Only the header shifts; the body keeps the full width. */\n.cn-reports-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-reports-page__title {\n\tmargin: 0 0 4px;\n}\n\n.cn-reports-page__description {\n\tmargin: 0 0 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__filter {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 16px;\n}\n\n.cn-reports-page__filter-select {\n\tmin-width: 220px;\n}\n\n.cn-reports-page__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n\tgap: 12px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-reports-page__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\theight: 100%;\n\tpadding: 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\n.cn-reports-page__card:hover,\n.cn-reports-page__card:focus {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-reports-page__card-title {\n\tfont-weight: bold;\n}\n\n.cn-reports-page__card-description,\n.cn-reports-page__card-category {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-reports-page__empty {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-reports-page__widgets {\n\tmargin-top: 20px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","_Fragment","_renderList","_openBlock","_createVNode"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnSettingsFormWidget.vue2.js';
2
2
  import { render } from './CnSettingsFormWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-66086856";
5
+ script.__scopeId = "data-v-7325a0ee";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };