feat(form-failures): admin list view with KPI tiles + filters (WS-6)

FormFailuresTable shared component drives both /platform/form-failures
(super_admin, all orgs) and /organisation/form-failures (org_admin,
scoped to the active organisation).

  - 4 KPI tiles (Open / Opgelost / Dismissed / Totaal) with click-to-
    filter behavior. Counts derived client-side from a per_page=100
    list call (composable's useFormFailuresKpis).
  - Filter bar: state segment-control (VBtnToggle) + debounced search
    (exception class / message / IDs).
  - VDataTableServer with custom cell slots: state chip, formatted
    failed_at timestamp, listener short-name, exception class+message
    (truncated), submission short-id, retry-count chip, action column.
  - Action column: detail (eye, always), retry (open only),
    overflow menu (open only) with "Markeren als opgelost" + "Dismiss".
  - Empty state with "Filters wissen" CTA.
  - All three action dialogs wired in; @success → refetch().

Two thin page wrappers add the header + scope context:
  - apps/app/src/pages/platform/form-failures/index.vue
  - apps/app/src/pages/organisation/form-failures/index.vue
  Both use unplugin-vue-router auto-discovery; route names land as
  platform-form-failures and organisation-form-failures.

Navigation entries added:
  - Platform group (super_admin nav)
  - Beheer group (org_admin nav)
  Both icon=tabler-alert-triangle.

Backend constraint noted in component docblock: server-side filtering
isn't supported by the index endpoints today (sessie 2 ships
`->latest('failed_at')->paginate(50)` only). Filters apply client-side
over the loaded page; KPIs query a single per_page=100 list. Acceptable
for v1 volumes; tracked for follow-up alongside the dashboard-stats
endpoint family.

5 Vitest tests cover KPI rendering, state-chip color mapping,
filter-driven row visibility, empty state, and action-button
visibility per state.

Refs: WS-6 sessie 3b admin UI Task 3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 21:47:31 +02:00
parent c39bd54958
commit 4c80289c47
5 changed files with 705 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue'
import FormFailuresTable from '@/components/form-failures/FormFailuresTable.vue'
import { useOrganisationStore } from '@/stores/useOrganisationStore'
definePage({
meta: {
navActiveLink: 'organisation-form-failures',
},
})
const orgStore = useOrganisationStore()
const orgId = computed(() => orgStore.activeOrganisationId ?? '')
</script>
<template>
<div>
<div class="mb-6">
<h4 class="text-h4">
Form failures
</h4>
<p class="text-body-1 text-disabled mb-0">
Mislukte data-bewerkingen na formulier-inzendingen
</p>
</div>
<FormFailuresTable
v-if="orgId"
scope="org"
:org-id="orgId"
/>
</div>
</template>