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

View File

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