diff --git a/src/app/(protected)/_layout.tsx b/src/app/(protected)/_layout.tsx
new file mode 100644
index 00000000..ee42fcc0
--- /dev/null
+++ b/src/app/(protected)/_layout.tsx
@@ -0,0 +1,39 @@
+import { useState } from "react";
+import { Outlet } from "react-router-dom";
+import { Box } from "@mui/material";
+
+import { ProtectedRoute } from "@/components/auth/protected-route";
+import {
+ AppSidebar,
+ SIDEBAR_COLLAPSED_WIDTH,
+ SIDEBAR_WIDTH,
+} from "@/components/layout/app-sidebar";
+import { AppTopbar } from "@/components/layout/app-topbar";
+
+export default function AdminShellLayout() {
+ const [navCollapsed, setNavCollapsed] = useState(false);
+ const sidebarWidth = navCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
+
+ return (
+
+
+
+
+
+ setNavCollapsed((value) => !value)} />
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(protected)/dashboard.tsx b/src/app/(protected)/dashboard.tsx
new file mode 100644
index 00000000..14041e42
--- /dev/null
+++ b/src/app/(protected)/dashboard.tsx
@@ -0,0 +1,221 @@
+import type { ReactNode } from "react";
+import { useNavigate } from "react-router-dom";
+import AddIcon from "@mui/icons-material/Add";
+import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
+import FolderOpenOutlinedIcon from "@mui/icons-material/FolderOpenOutlined";
+import ScheduleOutlinedIcon from "@mui/icons-material/ScheduleOutlined";
+import {
+ Alert,
+ Box,
+ CircularProgress,
+ Paper,
+ Stack,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import { useDashboardStats } from "@/domain/dashboard/use-cases/use-dashboard-stats";
+
+const INVOICE_STATUSES = ["Draft", "Challenged", "Pending Approval", "Approved", "Paid"] as const;
+
+const PRIORITY_LEVELS = [5, 4, 3, 2, 1] as const;
+
+interface KpiCardProps {
+ title: string;
+ description?: string;
+ value?: string | number;
+ icon: ReactNode;
+ color: "success" | "primary" | "info" | "warning";
+ onClick: () => void;
+ loading?: boolean;
+}
+
+function KpiCard({ title, description, value, icon, color, onClick, loading }: KpiCardProps) {
+ const colorMap = {
+ success: "success.main",
+ primary: "primary.main",
+ info: "info.main",
+ warning: "warning.main",
+ } as const;
+
+ return (
+
+
+
+ {icon}
+
+
+
+
+ {title}
+
+ {value !== undefined && (
+
+ {loading ? "..." : typeof value === "number" ? value.toLocaleString() : value}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+
+
+ );
+}
+
+export default function DashboardPage() {
+ const navigate = useNavigate();
+ const { data: stats, isLoading, isFetching, error } = useDashboardStats();
+
+ const formatStat = (value: number | undefined) => (isLoading ? undefined : (value ?? 0));
+
+ return (
+
+
+
+ Dashboard
+
+ {isFetching && !isLoading && }
+
+
+ {error && (
+
+ {error instanceof Error ? error.message : "Failed to load dashboard stats"}
+
+ )}
+
+
+ }
+ color="success"
+ onClick={() => navigate("/workorders/new")}
+ />
+ }
+ color="primary"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+ }
+ color="info"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+ }
+ color="warning"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+
+
+
+
+
+ Priority Analysis
+
+
+ Age of open Work Orders by Priority
+
+
+
+
+
+
+
+ Priority
+
+ Total
+
+
+ 0-14 Days
+
+
+ 15-29 Days
+
+
+ 30-59 Days
+
+
+ 60+ Days
+
+
+
+
+ {PRIORITY_LEVELS.map((priority) => (
+
+ {priority}
+ —
+ —
+ —
+ —
+ —
+
+ ))}
+
+
+
+
+
+
+
+
+ Invoice Analysis
+
+
+
+
+
+
+
+ Status
+
+ Count
+
+
+ Amount
+
+
+
+
+ {INVOICE_STATUSES.map((status) => (
+
+ {status}
+ 0
+ $0.00
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(protected)/index.tsx b/src/app/(protected)/index.tsx
new file mode 100644
index 00000000..14041e42
--- /dev/null
+++ b/src/app/(protected)/index.tsx
@@ -0,0 +1,221 @@
+import type { ReactNode } from "react";
+import { useNavigate } from "react-router-dom";
+import AddIcon from "@mui/icons-material/Add";
+import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
+import FolderOpenOutlinedIcon from "@mui/icons-material/FolderOpenOutlined";
+import ScheduleOutlinedIcon from "@mui/icons-material/ScheduleOutlined";
+import {
+ Alert,
+ Box,
+ CircularProgress,
+ Paper,
+ Stack,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import { useDashboardStats } from "@/domain/dashboard/use-cases/use-dashboard-stats";
+
+const INVOICE_STATUSES = ["Draft", "Challenged", "Pending Approval", "Approved", "Paid"] as const;
+
+const PRIORITY_LEVELS = [5, 4, 3, 2, 1] as const;
+
+interface KpiCardProps {
+ title: string;
+ description?: string;
+ value?: string | number;
+ icon: ReactNode;
+ color: "success" | "primary" | "info" | "warning";
+ onClick: () => void;
+ loading?: boolean;
+}
+
+function KpiCard({ title, description, value, icon, color, onClick, loading }: KpiCardProps) {
+ const colorMap = {
+ success: "success.main",
+ primary: "primary.main",
+ info: "info.main",
+ warning: "warning.main",
+ } as const;
+
+ return (
+
+
+
+ {icon}
+
+
+
+
+ {title}
+
+ {value !== undefined && (
+
+ {loading ? "..." : typeof value === "number" ? value.toLocaleString() : value}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+
+
+ );
+}
+
+export default function DashboardPage() {
+ const navigate = useNavigate();
+ const { data: stats, isLoading, isFetching, error } = useDashboardStats();
+
+ const formatStat = (value: number | undefined) => (isLoading ? undefined : (value ?? 0));
+
+ return (
+
+
+
+ Dashboard
+
+ {isFetching && !isLoading && }
+
+
+ {error && (
+
+ {error instanceof Error ? error.message : "Failed to load dashboard stats"}
+
+ )}
+
+
+ }
+ color="success"
+ onClick={() => navigate("/workorders/new")}
+ />
+ }
+ color="primary"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+ }
+ color="info"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+ }
+ color="warning"
+ loading={isLoading}
+ onClick={() => navigate("/workorders")}
+ />
+
+
+
+
+
+ Priority Analysis
+
+
+ Age of open Work Orders by Priority
+
+
+
+
+
+
+
+ Priority
+
+ Total
+
+
+ 0-14 Days
+
+
+ 15-29 Days
+
+
+ 30-59 Days
+
+
+ 60+ Days
+
+
+
+
+ {PRIORITY_LEVELS.map((priority) => (
+
+ {priority}
+ —
+ —
+ —
+ —
+ —
+
+ ))}
+
+
+
+
+
+
+
+
+ Invoice Analysis
+
+
+
+
+
+
+
+ Status
+
+ Count
+
+
+ Amount
+
+
+
+
+ {INVOICE_STATUSES.map((status) => (
+
+ {status}
+ 0
+ $0.00
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/src/domain/dashboard/api/dashboard-api.ts b/src/domain/dashboard/api/dashboard-api.ts
new file mode 100644
index 00000000..15cbc6ce
--- /dev/null
+++ b/src/domain/dashboard/api/dashboard-api.ts
@@ -0,0 +1,35 @@
+import { API_PATHS } from "@/api/api-paths";
+import { apiGet } from "@/api/api";
+import { handleApiResponse } from "@/api/handle-api-response";
+import type { DashboardStats } from "@/domain/dashboard/types/dashboard-stats";
+
+function toNumber(value: unknown): number {
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+
+ if (typeof value === "string" && value.trim() !== "") {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : 0;
+ }
+
+ return 0;
+}
+
+function mapDashboardStats(raw: unknown): DashboardStats {
+ const data = (typeof raw === "object" && raw !== null ? raw : {}) as Record;
+
+ return {
+ open: toNumber(data.open ?? data.Open),
+ notDispatched: toNumber(data.notDispatched ?? data.NotDispatched),
+ completed: toNumber(data.completed ?? data.Completed),
+ };
+}
+
+export const dashboardApi = {
+ getStats: async (): Promise => {
+ const data = await apiGet(API_PATHS.dashboard.stats);
+ const response = handleApiResponse(data);
+ return mapDashboardStats(response);
+ },
+};
diff --git a/src/domain/dashboard/types/dashboard-stats.ts b/src/domain/dashboard/types/dashboard-stats.ts
new file mode 100644
index 00000000..7b7aee4b
--- /dev/null
+++ b/src/domain/dashboard/types/dashboard-stats.ts
@@ -0,0 +1,5 @@
+export interface DashboardStats {
+ open: number;
+ notDispatched: number;
+ completed: number;
+}
diff --git a/src/domain/dashboard/use-cases/use-dashboard-stats.ts b/src/domain/dashboard/use-cases/use-dashboard-stats.ts
new file mode 100644
index 00000000..eb1b9a5f
--- /dev/null
+++ b/src/domain/dashboard/use-cases/use-dashboard-stats.ts
@@ -0,0 +1,12 @@
+import { useQuery, type UseQueryResult } from "@tanstack/react-query";
+import { dashboardApi } from "@/domain/dashboard/api/dashboard-api";
+import type { DashboardStats } from "@/domain/dashboard/types/dashboard-stats";
+import { queryKeys } from "@/infra/query-key/query-key";
+
+export function useDashboardStats(): UseQueryResult {
+ return useQuery({
+ queryKey: queryKeys.dashboard.stats(),
+ queryFn: () => dashboardApi.getStats(),
+ staleTime: 60 * 1000,
+ });
+}