fix(app): equal-height KPI cards on dashboard and form failures

- Stretch row + flex column cards so tiles share height
- Form failures: uniform outlined cards; primary border for selection
  (replacing elevated vs outlined mismatch)
- Full-width state toggle with flex-grow buttons and wrap to fix overlap
- Responsive KPI columns sm6/lg3 for Form failures

Made-with: Cursor
This commit is contained in:
2026-04-29 00:44:27 +02:00
parent 7926634c76
commit c344efa511
2 changed files with 48 additions and 32 deletions

View File

@@ -130,22 +130,28 @@ function setStateFilter(s: FormFailureState | 'all') {
<template> <template>
<div> <div>
<!-- KPI tiles server-side aggregate (sessie 3c). --> <!-- KPI tiles server-side aggregate (sessie 3c). -->
<VRow class="mb-4"> <VRow
class="mb-4"
align="stretch"
>
<VCol <VCol
cols="12" cols="12"
md="3" sm="6"
lg="3"
class="d-flex"
> >
<VCard <VCard
density="compact" density="compact"
class="cursor-pointer" variant="outlined"
:variant="stateFilter === 'open' ? 'elevated' : 'outlined'" class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'open' ? 'border-opacity-100 border-primary' : ''"
@click="setStateFilter('open')" @click="setStateFilter('open')"
> >
<VCardText> <VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1"> <p class="text-caption text-disabled mb-1 text-no-wrap">
Open failures Open failures
</p> </p>
<h5 class="text-h5"> <h5 class="text-h5 mb-0 d-flex align-center">
<VIcon <VIcon
icon="tabler-alert-triangle" icon="tabler-alert-triangle"
color="error" color="error"
@@ -159,19 +165,22 @@ function setStateFilter(s: FormFailureState | 'all') {
</VCol> </VCol>
<VCol <VCol
cols="12" cols="12"
md="3" sm="6"
lg="3"
class="d-flex"
> >
<VCard <VCard
density="compact" density="compact"
class="cursor-pointer" variant="outlined"
:variant="stateFilter === 'resolved' ? 'elevated' : 'outlined'" class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'resolved' ? 'border-opacity-100 border-primary' : ''"
@click="setStateFilter('resolved')" @click="setStateFilter('resolved')"
> >
<VCardText> <VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1"> <p class="text-caption text-disabled mb-1 text-no-wrap">
Opgelost (30d) Opgelost (30d)
</p> </p>
<h5 class="text-h5"> <h5 class="text-h5 mb-0 d-flex align-center">
<VIcon <VIcon
icon="tabler-check" icon="tabler-check"
color="success" color="success"
@@ -185,19 +194,22 @@ function setStateFilter(s: FormFailureState | 'all') {
</VCol> </VCol>
<VCol <VCol
cols="12" cols="12"
md="3" sm="6"
lg="3"
class="d-flex"
> >
<VCard <VCard
density="compact" density="compact"
class="cursor-pointer" variant="outlined"
:variant="stateFilter === 'dismissed' ? 'elevated' : 'outlined'" class="flex-grow-1 w-100 cursor-pointer d-flex flex-column"
:class="stateFilter === 'dismissed' ? 'border-opacity-100 border-primary' : ''"
@click="setStateFilter('dismissed')" @click="setStateFilter('dismissed')"
> >
<VCardText> <VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1"> <p class="text-caption text-disabled mb-1 text-no-wrap">
Dismissed (30d) Dismissed (30d)
</p> </p>
<h5 class="text-h5"> <h5 class="text-h5 mb-0 d-flex align-center">
<VIcon <VIcon
icon="tabler-x" icon="tabler-x"
color="warning" color="warning"
@@ -211,17 +223,20 @@ function setStateFilter(s: FormFailureState | 'all') {
</VCol> </VCol>
<VCol <VCol
cols="12" cols="12"
md="3" sm="6"
lg="3"
class="d-flex"
> >
<VCard <VCard
density="compact" density="compact"
variant="outlined" variant="outlined"
class="flex-grow-1 w-100 d-flex flex-column"
> >
<VCardText> <VCardText class="flex-grow-1 d-flex flex-column justify-center">
<p class="text-caption text-disabled mb-1"> <p class="text-caption text-disabled mb-1 text-no-wrap">
Submissions Submissions
</p> </p>
<h5 class="text-h5"> <h5 class="text-h5 mb-0 d-flex align-center">
<VIcon <VIcon
icon="tabler-chart-bar" icon="tabler-chart-bar"
color="info" color="info"
@@ -254,12 +269,10 @@ function setStateFilter(s: FormFailureState | 'all') {
<VCard> <VCard>
<VCardText> <VCardText>
<VRow> <VRow>
<VCol <VCol cols="12">
cols="12"
md="6"
>
<VBtnToggle <VBtnToggle
:model-value="stateFilter" :model-value="stateFilter"
class="w-100 flex-wrap"
divided divided
density="comfortable" density="comfortable"
mandatory mandatory
@@ -269,14 +282,16 @@ function setStateFilter(s: FormFailureState | 'all') {
v-for="opt in stateOptions" v-for="opt in stateOptions"
:key="opt.value" :key="opt.value"
:value="opt.value" :value="opt.value"
class="flex-grow-1"
style="min-inline-size: 0"
> >
{{ opt.title }} <span class="text-truncate">{{ opt.title }}</span>
</VBtn> </VBtn>
</VBtnToggle> </VBtnToggle>
</VCol> </VCol>
<VCol <VCol
cols="12" cols="12"
md="6" md="12"
> >
<AppTextField <AppTextField
v-model="search" v-model="search"

View File

@@ -24,16 +24,17 @@ const stats = [
</VCardText> </VCardText>
</VCard> </VCard>
<VRow> <VRow align="stretch">
<VCol <VCol
v-for="stat in stats" v-for="stat in stats"
:key="stat.title" :key="stat.title"
cols="12" cols="12"
sm="6" sm="6"
md="3" md="3"
class="d-flex"
> >
<VCard> <VCard class="flex-grow-1 w-100 d-flex flex-column">
<VCardText class="d-flex align-center gap-x-4"> <VCardText class="d-flex align-center gap-x-4 flex-grow-1">
<VAvatar <VAvatar
:color="stat.color" :color="stat.color"
variant="tonal" variant="tonal"