diff --git a/frontend/src/app.css b/frontend/src/app.css index 0477fb42..c5f267f6 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1,5 +1,5 @@ @import "tailwindcss"; -@import "daisyui"; +@import "daisyui/daisyui.css"; :root { --sidebar-width-expanded: 240px; diff --git a/frontend/src/app.html b/frontend/src/app.html index a95435fa..53671bdb 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/src/lib/components/common/StatCard.spec.ts b/frontend/src/lib/components/common/StatCard.spec.ts new file mode 100644 index 00000000..ee33708f --- /dev/null +++ b/frontend/src/lib/components/common/StatCard.spec.ts @@ -0,0 +1,110 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import StatCard from './StatCard.svelte'; +import { Folder } from 'lucide-svelte'; + +describe('StatCard', () => { + it('renders value', () => { + render(StatCard, { + props: { + title: 'Active Projects', + value: 14 + } + }); + expect(screen.getByText('14')).toBeInTheDocument(); + expect(screen.getByText('Active Projects')).toBeInTheDocument(); + }); + + it('renders string value', () => { + render(StatCard, { + props: { + title: 'Utilization', + value: '87%' + } + }); + expect(screen.getByText('87%')).toBeInTheDocument(); + }); + + it('renders description when provided', () => { + render(StatCard, { + props: { + title: 'Team Members', + value: 8, + description: 'active' + } + }); + expect(screen.getByText('active')).toBeInTheDocument(); + }); + + it('applies correct trend colors for up trend', () => { + const { container } = render(StatCard, { + props: { + title: 'Projects', + value: 14, + trend: 'up', + trendValue: '+2' + } + }); + + const trendElement = container.querySelector('.text-success'); + expect(trendElement).toBeInTheDocument(); + expect(screen.getByText('+2')).toBeInTheDocument(); + }); + + it('applies correct trend colors for down trend', () => { + const { container } = render(StatCard, { + props: { + title: 'Allocations', + value: 186, + trend: 'down', + trendValue: '-12' + } + }); + + const trendElement = container.querySelector('.text-error'); + expect(trendElement).toBeInTheDocument(); + expect(screen.getByText('-12')).toBeInTheDocument(); + }); + + it('applies neutral trend color when trend is neutral', () => { + const { container } = render(StatCard, { + props: { + title: 'Team', + value: 8, + trend: 'neutral', + trendValue: '0' + } + }); + + const trendElement = container.querySelector('.text-base-content\\/50'); + expect(trendElement).toBeInTheDocument(); + }); + + it('renders icon when provided', () => { + const { container } = render(StatCard, { + props: { + title: 'Projects', + value: 14, + icon: Folder + } + }); + + // Icon should render an SVG + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); + + it('uses DaisyUI card classes', () => { + const { container } = render(StatCard, { + props: { + title: 'Test', + value: 100 + } + }); + + const card = container.querySelector('.card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('bg-base-100'); + expect(card).toHaveClass('shadow-sm'); + }); +}); diff --git a/frontend/src/lib/components/common/StatCard.svelte b/frontend/src/lib/components/common/StatCard.svelte new file mode 100644 index 00000000..7cc17731 --- /dev/null +++ b/frontend/src/lib/components/common/StatCard.svelte @@ -0,0 +1,59 @@ + + +
+
+
+
{title}
+ {#if Icon} +
+ +
+ {/if} +
+
{value}
+
+ {#if trendValue} + + + + {trendValue} + {/if} + {#if description} + {description} + {/if} +
+
+
diff --git a/frontend/src/lib/components/layout/Breadcrumbs.svelte b/frontend/src/lib/components/layout/Breadcrumbs.svelte index 5fe5b0a4..8793c513 100644 --- a/frontend/src/lib/components/layout/Breadcrumbs.svelte +++ b/frontend/src/lib/components/layout/Breadcrumbs.svelte @@ -6,7 +6,7 @@ export function generateBreadcrumbs(pathname: string): Breadcrumb[] { const segments = pathname.split('/').filter(Boolean); - const crumbs: Breadcrumb[] = [{ label: 'Home', href: '/' }]; + const crumbs: Breadcrumb[] = [{ label: 'Home', href: '/dashboard' }]; let current = ''; for (const segment of segments) { @@ -33,7 +33,7 @@