- Update decision-log with UI layout decisions (Feb 18, 2026) - Update architecture with frontend layout patterns - Update config.yaml with TDD, documentation, UI standards rules - Create p00-api-documentation change (Scribe annotations) - Create p01-ui-foundation change (types, stores, Lucide) - Create p02-app-layout change (AppLayout, Sidebar, TopBar) - Create p03-dashboard-enhancement change (PageHeader, StatCard) - Create p04-content-patterns change (DataTable, FilterBar) - Create p05-page-migrations change (page migrations) - Fix E2E auth tests (11/11 passing) - Add JWT expiry validation to dashboard guard
5.7 KiB
5.7 KiB
Design: Dashboard Enhancement
PageHeader Component
src/lib/components/layout/PageHeader.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
<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
<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