From 12cff8c03a2e032d18e1714a9c233bf55313be44 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Mon, 18 May 2026 10:41:34 +0200 Subject: [PATCH] feat(gui-v2): StatCard (PrimeVue Card KPI tile, replaces AppKpiCard) + story Co-Authored-By: Claude Sonnet 4.6 --- .../components-v2/shared/StatCard.stories.ts | 22 +++++++ .../app/src/components-v2/shared/StatCard.vue | 64 +++++++++++++++++++ .../shared/__tests__/StatCard.spec.ts | 29 +++++++++ 3 files changed, 115 insertions(+) create mode 100644 apps/app/src/components-v2/shared/StatCard.stories.ts create mode 100644 apps/app/src/components-v2/shared/StatCard.vue create mode 100644 apps/app/src/components-v2/shared/__tests__/StatCard.spec.ts diff --git a/apps/app/src/components-v2/shared/StatCard.stories.ts b/apps/app/src/components-v2/shared/StatCard.stories.ts new file mode 100644 index 00000000..8ce59f04 --- /dev/null +++ b/apps/app/src/components-v2/shared/StatCard.stories.ts @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import StatCard from '@/components-v2/shared/StatCard.vue' + +const meta: Meta = { + title: 'Shared/StatCard', + component: StatCard, + tags: ['autodocs'], + argTypes: { + icon: { control: 'text' }, + label: { control: 'text' }, + value: { control: 'text' }, + trend: { control: 'text' }, + trendDir: { control: 'inline-radio', options: ['', 'up', 'down'] }, + }, +} + +export default meta +type Story = StoryObj + +export const Plain: Story = { args: { icon: 'tabler-users', label: 'Vrijwilligers', value: 128 } } +export const TrendUp: Story = { args: { icon: 'tabler-calendar-event', label: 'Diensten', value: 42, trend: '+12% vs vorige week', trendDir: 'up' } } +export const TrendDown: Story = { args: { icon: 'tabler-alert-triangle', label: 'No-shows', value: 3, trend: '-2 vs vorige week', trendDir: 'down' } } diff --git a/apps/app/src/components-v2/shared/StatCard.vue b/apps/app/src/components-v2/shared/StatCard.vue new file mode 100644 index 00000000..ed2cb2c5 --- /dev/null +++ b/apps/app/src/components-v2/shared/StatCard.vue @@ -0,0 +1,64 @@ + + + diff --git a/apps/app/src/components-v2/shared/__tests__/StatCard.spec.ts b/apps/app/src/components-v2/shared/__tests__/StatCard.spec.ts new file mode 100644 index 00000000..6ed7fcb9 --- /dev/null +++ b/apps/app/src/components-v2/shared/__tests__/StatCard.spec.ts @@ -0,0 +1,29 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import { defineComponent } from 'vue' +import StatCard from '@/components-v2/shared/StatCard.vue' + +const CardStub = defineComponent({ name: 'CardStub', template: '
' }) +const IconStub = defineComponent({ name: 'Icon', props: ['name', 'size'], template: '' }) + +const mountCard = (props: { icon: string; label: string; value: string | number; trend?: string; trendDir?: 'up' | 'down' }) => + mount(StatCard, { props, global: { stubs: { Card: CardStub, Icon: IconStub } } }) + +describe('StatCard', () => { + it('renders label, value and the leading icon', () => { + const w = mountCard({ icon: 'tabler-users', label: 'Vrijwilligers', value: 128 }) + + expect(w.text()).toContain('Vrijwilligers') + expect(w.text()).toContain('128') + expect(w.get('.icon-stub').attributes('data-icon')).toBe('tabler-users') + }) + + it('shows the trend row with direction class only when trend is set', () => { + expect(mountCard({ icon: 'tabler-users', label: 'X', value: 1 }).find('[data-trend]').exists()).toBe(false) + + const up = mountCard({ icon: 'tabler-users', label: 'X', value: 1, trend: '+12%', trendDir: 'up' }) + + expect(up.get('[data-trend]').attributes('data-trend')).toBe('up') + expect(up.text()).toContain('+12%') + }) +})