Skip to content

Commit 26d1854

Browse files
authored
feat(dashboard): give stackiq the KPI tiles it never had (#892)
Stackiq's dashboard was the one fleet dashboard with no KPI cards at all. Not a rendering fault: the page was type:"custom" rendering a 675-line hand-written view whose entire widget list was an info box and two object-statistics tables. The app's five manifest KPI widgets all sit on detail pages, never on the dashboard. The page is now type:"dashboard" with four declarative "stat" widgets -- Organisaties, Modules, Diensten, Contracten -- counted by OpenRegister through @resolve:voorzieningen_register, the same register/schema pairs every other page in this manifest already uses. The non-KPI content is preserved exactly. The info box and both tables move to src/components/CatalogPanels.vue (template restructured, script untouched) and mount as the "catalog-panels" widget. Worth knowing for the next app: 1. A dashboard widget TYPE resolves against the LIBRARY's widget catalog (registerDashboardWidget / getWidgetTypeEntry), not the app's registry prop and not the page's slots map -- both of those are for page components and slot overrides. An unregistered type renders "Widget not available" and logs nothing, so it looks exactly like a wiring mistake. 2. eslint-suppressions.json is keyed by FILE PATH. Renaming a suppressed file orphans its entries, and --prune-suppressions then deletes them, so every previously-suppressed error surfaces at once. The entry moved to the new path deliberately, minus two suppressions the rename genuinely fixed: vue/multi-word-component-names (Dashboard -> CatalogPanels) and an unused arg (route -> _route, docblock updated). Verified in the browser against the published @conduction/nextcloud-vue (USE_LOCAL_LIB=false): four cards render in the canonical horizontal white card, zero grey, no "Widget not available", the info box and both tables still render. The tiles read 0/0/0/0 and that is CORRECT -- the statistics tables beside them independently report Organization 0, Service 0, Contract 0 in this environment, so the tiles agree with the tables rather than reporting a confident zero on a failed fetch. eslint exits 0, matching the pre-change baseline; webpack compiles.
1 parent 3d4ba96 commit 26d1854

6 files changed

Lines changed: 285 additions & 206 deletions

File tree

eslint-suppressions.json

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"count": 2
55
}
66
},
7+
"src/components/CatalogPanels.vue": {
8+
"no-console": {
9+
"count": 10
10+
}
11+
},
712
"src/components/CollapsibleSection.vue": {
813
"vue/slot-name-casing": {
914
"count": 1
@@ -431,17 +436,6 @@
431436
"count": 2
432437
}
433438
},
434-
"src/views/Dashboard.vue": {
435-
"@typescript-eslint/no-unused-vars": {
436-
"count": 1
437-
},
438-
"no-console": {
439-
"count": 10
440-
},
441-
"vue/multi-word-component-names": {
442-
"count": 1
443-
}
444-
},
445439
"src/views/KwetsbaarhedenView.vue": {
446440
"@typescript-eslint/no-unused-vars": {
447441
"count": 1
@@ -542,4 +536,4 @@
542536
"count": 1
543537
}
544538
}
545-
}
539+
}
Lines changed: 117 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,128 @@
11
<template>
2-
<CnDashboardPage
3-
title="Dashboard"
4-
description="Overzicht van uw softwarecatalogus en configuraties"
5-
:widgets="widgetDefs"
6-
:layout="dashboardLayout"
7-
:loading="loading">
8-
<template #header-actions>
9-
<NcButton variant="secondary" @click="refreshAllData">
10-
<template #icon>
11-
<NcLoadingIcon v-if="loading" :size="20" />
12-
<Refresh v-else :size="20" />
13-
</template>
14-
Vernieuwen
15-
</NcButton>
16-
</template>
17-
18-
<!-- Beheer info box widget -->
19-
<template #widget-info-box>
20-
<NcNoteCard type="info" class="infoBox">
21-
<div class="infoBoxContent">
22-
<h3 class="infoBoxTitle">Beheer van Organisaties</h3>
23-
<p class="infoBoxText">
24-
Organisaties kunnen worden geaccepteerd en beheerd via de
25-
organisaties pagina. Het aanmaken en bewerken van gebruikers
26-
gaat ook via de organisatie pagina, omdat deze onderdeel zijn
27-
van organisaties.
28-
</p>
29-
<div class="infoBoxActions">
30-
<NcButton variant="primary" @click="navigateToOrganizations">
31-
<template #icon>
32-
<OfficeBuildingOutline :size="16" />
33-
</template>
34-
Ga naar Organisaties
35-
</NcButton>
36-
</div>
37-
</div>
38-
</NcNoteCard>
39-
</template>
40-
41-
<!-- Statistics table 1 widget -->
42-
<template #widget-stats-table-1>
43-
<div class="statisticsTableContainer">
44-
<div class="statisticsTableHeader">
45-
<span class="lastUpdated"
46-
>Laatst bijgewerkt: {{ formatDate(new Date()) }}</span
47-
>
2+
<div class="catalogPanels">
3+
<NcNoteCard type="info" class="infoBox">
4+
<div class="infoBoxContent">
5+
<h3 class="infoBoxTitle">Beheer van Organisaties</h3>
6+
<p class="infoBoxText">
7+
Organisaties kunnen worden geaccepteerd en beheerd via de
8+
organisaties pagina. Het aanmaken en bewerken van gebruikers
9+
gaat ook via de organisatie pagina, omdat deze onderdeel zijn
10+
van organisaties.
11+
</p>
12+
<div class="infoBoxActions">
13+
<NcButton variant="primary" @click="navigateToOrganizations">
14+
<template #icon>
15+
<OfficeBuildingOutline :size="16" />
16+
</template>
17+
Ga naar Organisaties
18+
</NcButton>
4819
</div>
20+
</div>
21+
</NcNoteCard>
22+
23+
<div class="statisticsTableContainer">
24+
<div class="statisticsTableHeader">
25+
<span class="lastUpdated"
26+
>Laatst bijgewerkt: {{ formatDate(new Date()) }}</span
27+
>
28+
</div>
4929

50-
<table class="objectStatisticsTable">
51-
<thead>
52-
<tr>
53-
<th scope="col">Object Type</th>
54-
<th scope="col" class="countHeader">Count</th>
55-
<th scope="col" class="manageHeader">Manage</th>
56-
</tr>
57-
</thead>
58-
<tbody>
59-
<tr
60-
v-for="stat in firstTableStats"
61-
:key="stat.objectType"
62-
style="cursor: pointer"
63-
@click="navigateToSchema(stat.slug)">
64-
<td>{{ stat.objectType }}</td>
65-
<td class="countCell">
66-
{{ stat.count.toLocaleString() }}
67-
</td>
68-
<td class="manageCell">
69-
<NcButton
70-
v-if="stat.slug === 'organization'"
71-
size="small"
72-
variant="tertiary"
73-
@click.stop="navigateToObjectType(stat.slug)">
74-
<template #icon>
75-
<component
76-
:is="getIconForObjectType(stat.slug)"
77-
:size="16" />
78-
</template>
79-
Manage
80-
</NcButton>
81-
<span v-else class="disabledManage">
30+
<table class="objectStatisticsTable">
31+
<thead>
32+
<tr>
33+
<th scope="col">Object Type</th>
34+
<th scope="col" class="countHeader">Count</th>
35+
<th scope="col" class="manageHeader">Manage</th>
36+
</tr>
37+
</thead>
38+
<tbody>
39+
<tr
40+
v-for="stat in firstTableStats"
41+
:key="stat.objectType"
42+
style="cursor: pointer"
43+
@click="navigateToSchema(stat.slug)">
44+
<td>{{ stat.objectType }}</td>
45+
<td class="countCell">
46+
{{ stat.count.toLocaleString() }}
47+
</td>
48+
<td class="manageCell">
49+
<NcButton
50+
v-if="stat.slug === 'organization'"
51+
size="small"
52+
variant="tertiary"
53+
@click.stop="navigateToObjectType(stat.slug)">
54+
<template #icon>
8255
<component
8356
:is="getIconForObjectType(stat.slug)"
8457
:size="16" />
85-
<span class="strikethrough">Manage</span>
86-
</span>
87-
</td>
88-
</tr>
89-
</tbody>
90-
</table>
58+
</template>
59+
Manage
60+
</NcButton>
61+
<span v-else class="disabledManage">
62+
<component
63+
:is="getIconForObjectType(stat.slug)"
64+
:size="16" />
65+
<span class="strikethrough">Manage</span>
66+
</span>
67+
</td>
68+
</tr>
69+
</tbody>
70+
</table>
71+
</div>
72+
73+
<div class="statisticsTableContainer">
74+
<div class="statisticsTableHeader">
75+
<span class="lastUpdated"
76+
>Laatst bijgewerkt: {{ formatDate(new Date()) }}</span
77+
>
9178
</div>
92-
</template>
93-
94-
<!-- Statistics table 2 widget -->
95-
<template #widget-stats-table-2>
96-
<div class="statisticsTableContainer">
97-
<div class="statisticsTableHeader">
98-
<span class="lastUpdated"
99-
>Laatst bijgewerkt: {{ formatDate(new Date()) }}</span
100-
>
101-
</div>
10279

103-
<table class="objectStatisticsTable">
104-
<thead>
105-
<tr>
106-
<th scope="col">Object Type</th>
107-
<th scope="col" class="countHeader">Count</th>
108-
<th scope="col" class="manageHeader">Manage</th>
109-
</tr>
110-
</thead>
111-
<tbody>
112-
<tr
113-
v-for="stat in secondTableStats"
114-
:key="stat.objectType"
115-
style="cursor: pointer"
116-
@click="navigateToSchema(stat.slug)">
117-
<td>{{ stat.objectType }}</td>
118-
<td class="countCell">
119-
{{ stat.count.toLocaleString() }}
120-
</td>
121-
<td class="manageCell">
122-
<NcButton
123-
v-if="stat.slug === 'organization'"
124-
size="small"
125-
variant="tertiary"
126-
@click.stop="navigateToObjectType(stat.slug)">
127-
<template #icon>
128-
<component
129-
:is="getIconForObjectType(stat.slug)"
130-
:size="16" />
131-
</template>
132-
Manage
133-
</NcButton>
134-
<span v-else class="disabledManage">
80+
<table class="objectStatisticsTable">
81+
<thead>
82+
<tr>
83+
<th scope="col">Object Type</th>
84+
<th scope="col" class="countHeader">Count</th>
85+
<th scope="col" class="manageHeader">Manage</th>
86+
</tr>
87+
</thead>
88+
<tbody>
89+
<tr
90+
v-for="stat in secondTableStats"
91+
:key="stat.objectType"
92+
style="cursor: pointer"
93+
@click="navigateToSchema(stat.slug)">
94+
<td>{{ stat.objectType }}</td>
95+
<td class="countCell">
96+
{{ stat.count.toLocaleString() }}
97+
</td>
98+
<td class="manageCell">
99+
<NcButton
100+
v-if="stat.slug === 'organization'"
101+
size="small"
102+
variant="tertiary"
103+
@click.stop="navigateToObjectType(stat.slug)">
104+
<template #icon>
135105
<component
136106
:is="getIconForObjectType(stat.slug)"
137107
:size="16" />
138-
<span class="strikethrough">Manage</span>
139-
</span>
140-
</td>
141-
</tr>
142-
</tbody>
143-
</table>
144-
</div>
145-
</template>
146-
</CnDashboardPage>
108+
</template>
109+
Manage
110+
</NcButton>
111+
<span v-else class="disabledManage">
112+
<component
113+
:is="getIconForObjectType(stat.slug)"
114+
:size="16" />
115+
<span class="strikethrough">Manage</span>
116+
</span>
117+
</td>
118+
</tr>
119+
</tbody>
120+
</table>
121+
</div>
122+
</div>
147123
</template>
148124

149125
<script>
150-
import { CnDashboardPage } from '@conduction/nextcloud-vue'
151126
import { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
152127
import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'
153128
import ApplicationCog from 'vue-material-design-icons/ApplicationCog.vue'
@@ -170,12 +145,12 @@ import { navigationStore, objectStore } from '../store/store.js'
170145
* @see https://github.com/OpenCatalogi/opencatalogi
171146
*
172147
* Dashboard view showing overview statistics and configuration status.
173-
* Uses CnDashboardPage for standard widget-based layout.
148+
* Rendered as the `catalog-panels` dashboard widget; the KPI tiles beside it
149+
* are declarative `stat` widgets in the manifest.
174150
*/
175151
export default {
176-
name: 'Dashboard',
152+
name: 'CatalogPanels',
177153
components: {
178-
CnDashboardPage,
179154
NcButton,
180155
NcLoadingIcon,
181156
NcNoteCard,
@@ -192,49 +167,10 @@ export default {
192167
data() {
193168
return {
194169
loading: true,
195-
dashboardLayout: [
196-
{
197-
id: 1,
198-
widgetId: 'info-box',
199-
gridX: 0,
200-
gridY: 0,
201-
gridWidth: 12,
202-
showTitle: false,
203-
},
204-
{
205-
id: 2,
206-
widgetId: 'stats-table-1',
207-
gridX: 0,
208-
gridY: 1,
209-
gridWidth: 6,
210-
showTitle: false,
211-
},
212-
{
213-
id: 3,
214-
widgetId: 'stats-table-2',
215-
gridX: 6,
216-
gridY: 1,
217-
gridWidth: 6,
218-
showTitle: false,
219-
},
220-
],
221170
}
222171
},
223172
224173
computed: {
225-
/**
226-
* Widget definitions for CnDashboardPage
227-
*
228-
* @return {Array} Widget definition array
229-
* @spec openspec/specs/fe-shell-navigation/spec.md
230-
*/
231-
widgetDefs() {
232-
return [
233-
{ id: 'info-box', title: 'Beheer Informatie' },
234-
{ id: 'stats-table-1', title: 'Object Statistieken (1)' },
235-
{ id: 'stats-table-2', title: 'Object Statistieken (2)' },
236-
]
237-
},
238174
239175
/**
240176
* Get object statistics for the table display
@@ -451,11 +387,12 @@ export default {
451387
/**
452388
* Navigate to configuration page - opens admin settings in new tab
453389
*
454-
* @param {string} route - Route to navigate to (legacy parameter)
390+
* @param {string} _route - Ignored; the destination is the fixed admin
391+
* settings URL below. Kept so existing call sites still type-check.
455392
* @return {void}
456393
* @spec openspec/specs/fe-shell-navigation/spec.md
457394
*/
458-
navigateToConfiguration(route) {
395+
navigateToConfiguration(_route) {
459396
const settingsUrl = `${window.location.protocol}//${window.location.host}/index.php/settings/admin/stackiq`
460397
window.open(settingsUrl, '_blank')
461398
},

0 commit comments

Comments
 (0)