> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Dashboard & Overview Pattern Guide

> Version: 1.0.0 Last Updated: 2025-12-31 Owning Module: PF (Platform Foundation)

**Version:** 1.0.0\
**Last Updated:** 2025-12-31\
**Owning Module:** PF (Platform Foundation)

## Overview

This guide documents the standardized pattern for module dashboard/overview pages. All overview pages should use `PageHeader` and `StatCard` components for consistency.

## Architecture

```text theme={null}
Overview Page
├── PageHeader (title, description, icon, actions)
├── Stat Card Grid (key metrics)
│   └── StatCard (individual metric)
├── Section Headers (optional)
├── Navigation Cards (AreaNavigationCard)
└── Additional Content (charts, tables, etc.)
```

## Quick Start

```tsx theme={null}
import { PageHeader, StatCard } from '@/shared/components';
import { Users, Clock, AlertCircle } from 'lucide-react';

function HROverview() {
  const { data: stats, isLoading } = useHRDashboardStats();

  const statCards = [
    {
      title: 'Total Employees',
      value: stats?.totalEmployees ?? 0,
      description: 'Active staff members',
      icon: Users,
      onClick: () => navigate('/hr/employees'),
    },
    {
      title: 'Pending Requests',
      value: stats?.pendingRequests ?? 0,
      description: 'Awaiting approval',
      icon: Clock,
      onClick: () => navigate('/hr/requests'),
      variant: stats?.pendingRequests > 10 ? 'warning' : 'default',
    },
  ];

  return (
    <OverviewPageWrapper refreshQueryKeys={['hr']} spacing="md">
      <PageHeader
        title="Workforce"
        description="Manage employees, scheduling, and HR operations"
        icon={Users}
      />

      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
        {statCards.map((card) => (
          <StatCard
            key={card.title}
            {...card}
            loading={isLoading}
          />
        ))}
      </div>
    </OverviewPageWrapper>
  );
}
```

## Dashboards vs Overviews

| Aspect    | Dashboard               | Overview              |
| --------- | ----------------------- | --------------------- |
| Route     | `/{core}/dashboard`     | `/{core}`             |
| Purpose   | Widget-based KPIs       | Navigation hub        |
| Component | `ModuleDashboard`       | `OverviewPageWrapper` |
| Widgets   | Customizable, draggable | Static layout         |
| PTR       | Built-in                | Via wrapper           |

### When to Use Each

* **Dashboard:** When users need customizable widgets and real-time KPIs
* **Overview:** When users need a landing page with navigation to sub-areas

```tsx theme={null}
interface PageHeaderProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  actions?: React.ReactNode;
  backLink?: string;
  backLabel?: string;
}
```

**Examples:**

```tsx theme={null}
// Basic
<PageHeader
  title="Workforce"
  description="Manage employees and HR operations"
  icon={Users}
/>

// With actions
<PageHeader
  title="Employees"
  description="View and manage staff"
  icon={Users}
  actions={
    <Button onClick={() => navigate('/hr/employees/new')}>
      <Plus className="h-4 w-4 mr-2" />
      Add Employee
    </Button>
  }
/>

// With back link
<PageHeader
  title="Employee Details"
  backLink="/hr/employees"
  backLabel="Back to Employees"
/>
```

### StatCard

Metric display card with optional click action and variants.

```tsx theme={null}
interface StatCardProps {
  title: string;
  value: string | number;
  description?: string;
  icon?: LucideIcon;
  onClick?: () => void;
  variant?: 'default' | 'success' | 'warning' | 'destructive' | 'info';
  loading?: boolean;
  trend?: {
    value: number;
    direction: 'up' | 'down';
  };
}
```

**Examples:**

```tsx theme={null}
// Basic
<StatCard
  title="Total Residents"
  value={42}
  description="Current census"
  icon={Users}
/>

// With click action
<StatCard
  title="Pending Approvals"
  value={5}
  description="Awaiting review"
  icon={Clock}
  onClick={() => navigate('/approvals')}
/>

// With variant (for alerts)
<StatCard
  title="Overdue Tasks"
  value={12}
  description="Need attention"
  icon={AlertCircle}
  variant="destructive"
/>

// With loading state
<StatCard
  title="Revenue"
  value={stats?.revenue ?? 0}
  loading={isLoading}
/>

// With trend
<StatCard
  title="Monthly Revenue"
  value="$45,231"
  trend={{ value: 12, direction: 'up' }}
/>
```

## Stat Card Patterns

### Defining Stat Cards

Use arrays for maintainability:

```tsx theme={null}
const statCards = [
  {
    title: 'Active Policies',
    value: stats?.activePolicies ?? 0,
    icon: FileText,
    description: 'Published policies',
    onClick: () => navigate('/gr/policies'),
  },
  {
    title: 'Overdue Reviews',
    value: stats?.overdueReviews ?? 0,
    icon: AlertCircle,
    description: 'Need attention',
    variant: getVariant(stats?.overdueReviews),
    onClick: () => navigate('/gr/reviews'),
  },
];
```

### Conditional Variants

Use a helper function for type safety:

```tsx theme={null}
function getVariant(value: number | undefined): 'default' | 'destructive' {
  return value && value > 0 ? 'destructive' : 'default';
}

// Or for more complex logic
function getStatusVariant(
  value: number | undefined,
  warningThreshold: number,
  errorThreshold: number
): StatCardVariant {
  if (!value) return 'default';
  if (value >= errorThreshold) return 'destructive';
  if (value >= warningThreshold) return 'warning';
  return 'default';
}
```

### Rendering Stat Cards

```tsx theme={null}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
  {statCards.map((card) => (
    <StatCard
      key={card.title}
      title={card.title}
      value={card.value}
      description={card.description}
      icon={card.icon}
      onClick={card.onClick}
      variant={card.variant}
      loading={isLoading}
    />
  ))}
</div>
```

## Grid Layouts

### Standard Gaps

Use consistent gap sizing:

```tsx theme={null}
// Stat cards
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">

// Navigation cards
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">

// Content sections
<div className="space-y-6">
```

### Responsive Columns

| Breakpoint  | Stat Cards | Nav Cards |
| ----------- | ---------- | --------- |
| Mobile      | 1 col      | 1 col     |
| md (768px)  | 2 cols     | 2 cols    |
| lg (1024px) | 4 cols     | 3 cols    |

## Section Organization

### With Section Headers

```tsx theme={null}
<div className="space-y-6">
  <PageHeader title="Governance" icon={Shield} />

  {/* Stat section */}
  <div>
    <h2 className="text-lg font-semibold mb-3">Policy Management</h2>
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
      {policyStats.map(card => <StatCard key={card.title} {...card} />)}
    </div>
  </div>

  {/* Another section */}
  <div>
    <h2 className="text-lg font-semibold mb-3">Compliance</h2>
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
      {complianceStats.map(card => <StatCard key={card.title} {...card} />)}
    </div>
  </div>
</div>
```

### Without Section Headers

For simpler pages:

```tsx theme={null}
<div className="space-y-6">
  <PageHeader title="Dashboard" icon={LayoutDashboard} />

  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
    {allStats.map(card => <StatCard key={card.title} {...card} />)}
  </div>

  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
    {navigationCards.map(card => <AreaNavigationCard key={card.title} {...card} />)}
  </div>
</div>
```

## Conditional Rendering

### AI Features

```tsx theme={null}
{aiEnabled && (
  <div>
    <h2 className="text-lg font-semibold mb-3">AI Assistant</h2>
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
      {aiCards.map(card => <StatCard key={card.title} {...card} />)}
    </div>
  </div>
)}
```

### Permission-Based

```tsx theme={null}
{hasPermission('hr.reports.view') && (
  <StatCard
    title="Reports"
    value="View"
    icon={BarChart}
    onClick={() => navigate('/hr/reports')}
  />
)}
```

## Existing Implementations

Reference these standardized overview pages:

| Module | Page       | Location        |
| ------ | ---------- | --------------- |
| HR     | HROverview | `/hr/dashboard` |
| RH     | RHOverview | `/rh/dashboard` |
| FA     | FAOverview | `/fa/dashboard` |
| GR     | GROverview | `/gr/dashboard` |
| FW     | FWOverview | `/fw/dashboard` |
| FM     | FMOverview | `/fm/dashboard` |
| LO     | LOOverview | `/lo/dashboard` |

## Best Practices

1. **Always use PageHeader** - Never custom headers
2. **Always use StatCard** - Never inline Card components
3. **Define cards as arrays** - Better maintainability
4. **Handle loading states** - Pass `loading` prop to StatCard
5. **Use semantic variants** - `destructive` for errors, `warning` for alerts
6. **Keep grids consistent** - Use standard gap and column patterns
7. **Group related metrics** - Use section headers when needed
8. **Make cards clickable** - Navigate to relevant detail pages

## Anti-Patterns to Avoid

```tsx theme={null}
// ❌ DON'T: Inline Card components
<Card className="cursor-pointer" onClick={...}>
  <CardHeader>
    <CardTitle>{title}</CardTitle>
  </CardHeader>
  <CardContent>
    {isLoading ? <Skeleton /> : <div>{value}</div>}
  </CardContent>
</Card>

// ✅ DO: Use StatCard
<StatCard
  title={title}
  value={value}
  loading={isLoading}
  onClick={...}
/>

// ❌ DON'T: Custom header divs
<div className="flex items-center justify-between">
  <h1 className="text-3xl font-bold">{title}</h1>
</div>

// ✅ DO: Use PageHeader
<PageHeader title={title} icon={icon} />
```

## Related Documentation

### Patterns

* [Breadcrumb Implementation Guide](./breadcrumb-implementation-guide.md) - Breadcrumb implementation patterns
* [Settings Pattern Guide](./settings-pattern-guide.md) - Settings page patterns

### Standards

* [UI/UX Standards](./UI_UX_STANDARDS.md) - Complete UI/UX standards and design system
