Files
headroom/openspec/changes/archive/2026-02-18-p03-dashboard-enhancement/design.md
Santhosh Janardhanan 8e7bfbe517 feat(ui): Create content pattern components - DataTable, FilterBar, EmptyState, LoadingState
- Add LoadingState with table, card, list, and text skeleton patterns
- Add EmptyState with customizable icon, title, description, and action slot
- Add FilterBar with search input, clear button, and custom filter slot
- Add DataTable with TanStack Table integration, sorting, and row click
- Create barrel export index.ts for common components
- Install tanstack-table-8-svelte-5 for Svelte 5 compatibility
- Sync auth spec with authenticated user redirect requirements
- Archive p03-dashboard-enhancement

Refs: openspec/changes/p04-content-patterns
Closes: p04-content-patterns
2026-02-18 18:40:47 -05:00

237 lines
5.7 KiB
Markdown

# Design: Dashboard Enhancement
## PageHeader Component
### `src/lib/components/layout/PageHeader.svelte`
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
title: string;
description?: string;
children?: Snippet; // Action buttons
}
let { title, description, children }: Props = $props();
</script>
<div class="page-header mb-6">
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-base-content">{title}</h1>
{#if description}
<p class="text-base-content/70 mt-1">{description}</p>
{/if}
</div>
{#if children}
<div class="flex items-center gap-2">
{@render children()}
</div>
{/if}
</div>
</div>
```
---
## StatCard Component
### `src/lib/components/common/StatCard.svelte`
```svelte
<script lang="ts">
import type { Component } from 'svelte';
import { TrendingUp, TrendingDown, Minus } from 'lucide-svelte';
interface Props {
title: string;
value: string | number;
description?: string;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
icon?: Component;
}
let { title, value, description, trend = 'neutral', trendValue, icon: Icon }: Props = $props();
$derived trendColor = {
up: 'text-success',
down: 'text-error',
neutral: 'text-base-content/50'
}[trend];
$derived TrendIcon = {
up: TrendingUp,
down: TrendingDown,
neutral: Minus
}[trend];
</script>
<div class="stat-card card bg-base-100 shadow-sm border border-base-300">
<div class="card-body p-4">
<div class="flex items-start justify-between">
<div class="stat-title text-sm text-base-content/70">{title}</div>
{#if Icon}
<div class="text-base-content/50">
<Icon size={20} />
</div>
{/if}
</div>
<div class="stat-value text-3xl font-bold mt-1">{value}</div>
<div class="stat-desc flex items-center gap-1 mt-1">
{#if trendValue}
<span class={trendColor}>
<TrendIcon size={14} />
</span>
<span class={trendColor}>{trendValue}</span>
{/if}
{#if description}
<span class="text-base-content/50">{description}</span>
{/if}
</div>
</div>
</div>
```
---
## Enhanced Dashboard
### `src/routes/dashboard/+page.svelte`
```svelte
<script lang="ts">
import PageHeader from '$lib/components/layout/PageHeader.svelte';
import StatCard from '$lib/components/common/StatCard.svelte';
import { authStore } from '$lib/stores/auth';
import { periodStore } from '$lib/stores/period';
import {
Folder,
Users,
Calendar,
BarChart3,
Plus,
ArrowRight
} from 'lucide-svelte';
// TODO: Fetch from API in future
const stats = {
activeProjects: 14,
teamMembers: 8,
allocationsThisMonth: 186,
avgUtilization: 87
};
</script>
<svelte:head>
<title>Dashboard | Headroom</title>
</svelte:head>
<PageHeader
title="Dashboard"
description="Overview of your resource allocation"
>
<button slot="actions" class="btn btn-primary btn-sm gap-2">
<Plus size={16} />
New Allocation
</button>
</PageHeader>
<!-- KPI Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<StatCard
title="Active Projects"
value={stats.activeProjects}
trend="up"
trendValue="+2"
description="from last month"
icon={Folder}
/>
<StatCard
title="Team Members"
value={stats.teamMembers}
trend="neutral"
description="active"
icon={Users}
/>
<StatCard
title="Allocations (hrs)"
value={stats.allocationsThisMonth}
trend="down"
trendValue="-12"
description="vs capacity"
icon={Calendar}
/>
<StatCard
title="Avg Utilization"
value="{stats.avgUtilization}%"
trend="up"
trendValue="+5%"
description="from last month"
icon={BarChart3}
/>
</div>
<!-- Quick Links -->
<div class="card bg-base-100 shadow-sm border border-base-300 mb-6">
<div class="card-body">
<h2 class="card-title text-lg">Quick Actions</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-2">
<a href="/team-members" class="btn btn-ghost justify-start gap-2">
<Users size={18} />
Team
<ArrowRight size={14} class="ml-auto opacity-50" />
</a>
<a href="/projects" class="btn btn-ghost justify-start gap-2">
<Folder size={18} />
Projects
<ArrowRight size={14} class="ml-auto opacity-50" />
</a>
<a href="/allocations" class="btn btn-ghost justify-start gap-2">
<Calendar size={18} />
Allocate
<ArrowRight size={14} class="ml-auto opacity-50" />
</a>
<a href="/reports/forecast" class="btn btn-ghost justify-start gap-2">
<BarChart3 size={18} />
Forecast
<ArrowRight size={14} class="ml-auto opacity-50" />
</a>
</div>
</div>
</div>
<!-- Placeholder for Allocation Preview -->
<div class="card bg-base-100 shadow-sm border border-base-300">
<div class="card-body">
<h2 class="card-title text-lg">Allocation Preview</h2>
<p class="text-base-content/50 text-sm">
Allocation matrix for {$periodStore.selectedPeriod} will appear here.
</p>
<div class="skeleton h-48 w-full mt-4"></div>
</div>
</div>
```
---
## Login Page Polish
### Updates to `src/routes/login/+page.svelte`
- Center vertically using flexbox
- Add app logo/branding above form
- Consistent card styling with new layout
- Better error/success states
## File Structure
```
src/lib/components/
├── layout/
│ └── PageHeader.svelte # NEW
└── common/
└── StatCard.svelte # NEW
src/routes/
├── login/+page.svelte # UPDATE
└── dashboard/+page.svelte # UPDATE
```