Skip to main content

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.

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

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

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

AspectDashboardOverview
Route/{core}/dashboard/{core}
PurposeWidget-based KPIsNavigation hub
ComponentModuleDashboardOverviewPageWrapper
WidgetsCustomizable, draggableStatic layout
PTRBuilt-inVia 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
interface PageHeaderProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  actions?: React.ReactNode;
  backLink?: string;
  backLabel?: string;
}
Examples:
// 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.
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:
// 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:
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:
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

<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:
// 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

BreakpointStat CardsNav Cards
Mobile1 col1 col
md (768px)2 cols2 cols
lg (1024px)4 cols3 cols

Section Organization

With Section Headers

<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:
<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

{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

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

Existing Implementations

Reference these standardized overview pages:
ModulePageLocation
HRHROverview/hr/dashboard
RHRHOverview/rh/dashboard
FAFAOverview/fa/dashboard
GRGROverview/gr/dashboard
FWFWOverview/fw/dashboard
FMFMOverview/fm/dashboard
LOLOOverview/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

// ❌ 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} />

Patterns

Standards