fix(app): align Form failures KPI row with AppKpiCard

Reuse AppKpiCard for the four tiles; selection uses borderAccent primary
(bottom stripe) instead of full border-primary outline. Update tests to
register AppKpiCard and stub VAvatar.

Made-with: Cursor
This commit is contained in:
2026-04-29 00:49:02 +02:00
parent 2ae90ed57f
commit fc0174061e
2 changed files with 33 additions and 83 deletions

View File

@@ -140,28 +140,15 @@ function setStateFilter(s: FormFailureState | 'all') {
lg="3"
class="d-flex"
>
<VCard
density="compact"
variant="outlined"
class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'open' ? 'border-opacity-100 border-primary' : ''"
<AppKpiCard
icon="tabler-alert-triangle"
icon-color="error"
:value="kpis?.open ?? 0"
title="Open failures"
clickable
:border-accent="stateFilter === 'open' ? 'primary' : undefined"
@click="setStateFilter('open')"
>
<VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1 text-no-wrap">
Open failures
</p>
<h5 class="text-h5 mb-0 d-flex align-center">
<VIcon
icon="tabler-alert-triangle"
color="error"
size="20"
class="me-1"
/>
{{ kpis?.open ?? 0 }}
</h5>
</VCardText>
</VCard>
/>
</VCol>
<VCol
cols="12"
@@ -169,28 +156,15 @@ function setStateFilter(s: FormFailureState | 'all') {
lg="3"
class="d-flex"
>
<VCard
density="compact"
variant="outlined"
class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'resolved' ? 'border-opacity-100 border-primary' : ''"
<AppKpiCard
icon="tabler-check"
icon-color="success"
:value="kpis?.resolved_30d ?? 0"
title="Opgelost (30d)"
clickable
:border-accent="stateFilter === 'resolved' ? 'primary' : undefined"
@click="setStateFilter('resolved')"
>
<VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1 text-no-wrap">
Opgelost (30d)
</p>
<h5 class="text-h5 mb-0 d-flex align-center">
<VIcon
icon="tabler-check"
color="success"
size="20"
class="me-1"
/>
{{ kpis?.resolved_30d ?? 0 }}
</h5>
</VCardText>
</VCard>
/>
</VCol>
<VCol
cols="12"
@@ -198,28 +172,15 @@ function setStateFilter(s: FormFailureState | 'all') {
lg="3"
class="d-flex"
>
<VCard
density="compact"
variant="outlined"
class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'dismissed' ? 'border-opacity-100 border-primary' : ''"
<AppKpiCard
icon="tabler-x"
icon-color="warning"
:value="kpis?.dismissed_30d ?? 0"
title="Dismissed (30d)"
clickable
:border-accent="stateFilter === 'dismissed' ? 'primary' : undefined"
@click="setStateFilter('dismissed')"
>
<VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1 text-no-wrap">
Dismissed (30d)
</p>
<h5 class="text-h5 mb-0 d-flex align-center">
<VIcon
icon="tabler-x"
color="warning"
size="20"
class="me-1"
/>
{{ kpis?.dismissed_30d ?? 0 }}
</h5>
</VCardText>
</VCard>
/>
</VCol>
<VCol
cols="12"
@@ -227,26 +188,12 @@ function setStateFilter(s: FormFailureState | 'all') {
lg="3"
class="d-flex"
>
<VCard
density="compact"
variant="outlined"
class="flex-grow-1 w-100 d-flex flex-column"
>
<VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1 text-no-wrap">
Submissions
</p>
<h5 class="text-h5 mb-0 d-flex align-center">
<VIcon
icon="tabler-chart-bar"
color="info"
size="20"
class="me-1"
/>
{{ kpis?.total_submissions ?? 0 }}
</h5>
</VCardText>
</VCard>
<AppKpiCard
icon="tabler-chart-bar"
icon-color="info"
:value="kpis?.total_submissions ?? 0"
title="Submissions"
/>
</VCol>
</VRow>

View File

@@ -3,6 +3,7 @@ import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import FormFailuresTable from '../FormFailuresTable.vue'
import AppKpiCard from '@/components/AppKpiCard.vue'
import type { FormFailure, FormFailuresKpis } from '@/types/form-failures'
const mockGet = vi.fn()
@@ -66,6 +67,7 @@ const stubs = {
props: ['modelValue'],
},
VIcon: { template: '<i :class="icon"></i>', props: ['icon', 'size', 'color'] },
VAvatar: { template: '<span class="v-avatar-stub"><slot /></span>', props: ['color', 'size', 'rounded', 'variant'] },
VChip: { template: '<span class="v-chip-stub" :data-color="color"><slot/></span>', props: ['color', 'size', 'variant'] },
AppTextField: {
template: '<input :value="modelValue" :placeholder="placeholder" :data-label="label" :type="type" @input="$emit(\'update:modelValue\', $event.target.value)"/>',
@@ -116,6 +118,7 @@ function mountTable(items: FormFailure[], kpis: FormFailuresKpis = { open: 0, re
return mount(FormFailuresTable, {
props: { scope: 'platform' },
global: {
components: { AppKpiCard },
stubs,
plugins: [[VueQueryPlugin, { queryClient: client }]],
},