Admin Dashboard Phase 2.2 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Implement Phase 2.2 Admin dashboard UI as an architecture-first, design-system-based page that consumes the /dashboard/admin aggregate contract and satisfies section/empty-state acceptance criteria.
Architecture: The Admin dashboard page becomes a composition root that only orchestrates loading/error/empty flows, page header, KPI cards, and eight focused Admin section components. Each section lives in its own file with typed props and design-system primitives (PageHeader, StatsCard, DataTable, EmptyState, LoadingSkeleton) to keep boundaries clear and maintainable. Tests are written first at page level to validate section presence, empty state, and absence of Super Admin-only content.
Tech Stack: React 19 + TypeScript, Ant Design 6, TanStack Query, Vitest + Testing Library, Bun, Biome.
File Structure Map
Modify
frontend/src/pages/dashboard/admin/AdminDashboard.tsx- Replace placeholder-only rendering with architecture-first page composition.
frontend/src/types/dashboard/admin-dashboard.types.ts- Replace placeholder shape with full typed Admin contract matching backend DTO.
Create
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsx- Component tests for section rendering, empty state, and role-sensitive absence.
frontend/src/pages/dashboard/admin/components/OrganizationOverview.tsx- Admin organization summary block.
frontend/src/pages/dashboard/admin/components/OrganizationDeviceSummary.tsx- Device overview block.
frontend/src/pages/dashboard/admin/components/ReportsSnapshot.tsx- Reports snapshot block.
frontend/src/pages/dashboard/admin/components/FarmPerformanceTable.tsx- Performance table with visible-limit behavior.
frontend/src/pages/dashboard/admin/components/FarmRanking.tsx- Ranking table.
frontend/src/pages/dashboard/admin/components/AdminAlertCenter.tsx- Alert center table.
frontend/src/pages/dashboard/admin/components/DataInterventionQueue.tsx- Intervention queue table.
frontend/src/pages/dashboard/admin/components/OwnerStaffActivity.tsx- Owner/staff activity table.
Verification Commands
bun --cwd frontend run test -- src/pages/dashboard/admin/AdminDashboard.spec.tsxbun --cwd frontend run test -- src/pages/dashboardbun run check
Task 1: Write failing Admin dashboard tests (RED)
Files:
Create:
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsxTest:
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsxStep 1: Write the failing test file with required Phase 2.2 expectations
import { screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { AdminDashboard } from '@/pages/dashboard/admin/AdminDashboard';
import { renderWithProviders } from '@/test/renderWithProviders';
import type { AdminDashboardResponse } from '@/types/dashboard/admin-dashboard.types';
const useAdminDashboardMock = vi.fn();
vi.mock('@/hooks/dashboard/useAdminDashboard', () => ({
useAdminDashboard: () => useAdminDashboardMock(),
}));
const buildResponse = (): AdminDashboardResponse => ({
generatedAt: '2026-04-25T00:00:00.000Z',
scope: 'organization',
kpis: { total: 2 },
sections: {
organizationOverview: {
totalFarms: 2,
totalPonds: 10,
activePonds: 7,
totalDevices: 20,
activeAlerts: 3,
},
farmPerformanceTable: [
{
farmId: 'farm-1',
farmName: 'Trại A',
totalPonds: 6,
activePonds: 5,
activeAlerts: 2,
onlineDeviceRate: 90,
},
],
farmRanking: [
{
farmId: 'farm-1',
farmName: 'Trại A',
totalPonds: 6,
activePonds: 5,
activeAlerts: 2,
onlineDeviceRate: 90,
score: 84,
},
],
alertCenter: [
{
id: 'alert-1',
pondId: 'pond-1',
pondName: 'Ao 1',
type: 'oxygen_low',
severity: 'high',
message: 'DO thấp',
status: 'active',
createdAt: '2026-04-25T00:00:00.000Z',
},
],
organizationDeviceSummary: {
total: 20,
online: 18,
offline: 1,
error: 1,
onlineRate: 90,
},
dataInterventionQueue: [
{
id: 'intervention-1',
issueType: 'oxygen_low',
target: 'Ao 1',
suggestedAction: 'Kiểm tra và xử lý ngay theo SOP',
detectedAt: '2026-04-25T00:00:00.000Z',
},
],
ownerStaffActivity: [
{
userId: 'u-1',
fullName: 'Nguyễn Văn A',
role: 'OWNER',
activityCountLast7Days: 8,
lastActivityAt: '2026-04-25T00:00:00.000Z',
},
],
reportsSnapshot: {
activeCycles: 4,
todayLogs: 12,
growthRecordsLast7Days: 30,
},
},
});
describe('AdminDashboard', () => {
it('renders all Admin sections and excludes Super Admin-only sections', async () => {
useAdminDashboardMock.mockReturnValue({
isLoading: false,
isError: false,
data: buildResponse(),
});
renderWithProviders(<AdminDashboard />);
expect(await screen.findByText('Dashboard quản trị tổ chức')).toBeInTheDocument();
expect(screen.getByText('Tổng quan tổ chức')).toBeInTheDocument();
expect(screen.getByText('Bảng hiệu suất trang trại')).toBeInTheDocument();
expect(screen.getByText('Xếp hạng trang trại')).toBeInTheDocument();
expect(screen.getByText('Trung tâm cảnh báo')).toBeInTheDocument();
expect(screen.getByText('Tình trạng thiết bị toàn tổ chức')).toBeInTheDocument();
expect(screen.getByText('Hàng đợi can thiệp dữ liệu')).toBeInTheDocument();
expect(screen.getByText('Hoạt động chủ trại và nhân sự')).toBeInTheDocument();
expect(screen.getByText('Tóm tắt báo cáo vận hành')).toBeInTheDocument();
expect(screen.queryByText(/System Health/i)).not.toBeInTheDocument();
expect(screen.queryByText(/Audit Security/i)).not.toBeInTheDocument();
});
it('renders a clear empty state when organization has no farms', async () => {
const emptyResponse = buildResponse();
emptyResponse.sections.organizationOverview.totalFarms = 0;
emptyResponse.sections.farmPerformanceTable = [];
emptyResponse.sections.farmRanking = [];
emptyResponse.sections.alertCenter = [];
emptyResponse.sections.dataInterventionQueue = [];
emptyResponse.sections.ownerStaffActivity = [];
useAdminDashboardMock.mockReturnValue({
isLoading: false,
isError: false,
data: emptyResponse,
});
renderWithProviders(<AdminDashboard />);
expect(await screen.findByText('Tổ chức chưa có trang trại')).toBeInTheDocument();
expect(screen.getByText('Vui lòng tạo trang trại để bắt đầu theo dõi dashboard.')).toBeInTheDocument();
});
});
- Step 2: Run test to verify it fails
Run:
bun --cwd frontend run test -- src/pages/dashboard/admin/AdminDashboard.spec.tsx
Expected: FAIL because current AdminDashboard.tsx is still placeholder and does not render required Vietnamese sections/empty state.
- Step 3: Commit failing test
git add frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsx
git commit -m "test: define admin dashboard phase 2.2 UI expectations"
Task 2: Upgrade Admin dashboard type contract
Files:
Modify:
frontend/src/types/dashboard/admin-dashboard.types.tsTest:
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsxStep 1: Write explicit Admin section types (no placeholders)
import type { DashboardBaseResponse } from '@/types/dashboard/dashboard-base.types';
export interface AdminOrganizationOverview {
totalFarms: number;
totalPonds: number;
activePonds: number;
totalDevices: number;
activeAlerts: number;
}
export interface AdminFarmPerformanceItem {
farmId: string;
farmName: string;
totalPonds: number;
activePonds: number;
activeAlerts: number;
onlineDeviceRate: number;
}
export interface AdminFarmRankingItem extends AdminFarmPerformanceItem {
score: number;
}
export interface AdminAlertCenterItem {
id: string;
pondId: string | null;
pondName: string | null;
type: string;
severity: string;
message: string;
status: string;
createdAt: string;
}
export interface AdminOrganizationDeviceSummary {
total: number;
online: number;
offline: number;
error: number;
onlineRate: number;
}
export interface AdminDataInterventionItem {
id: string;
issueType: string;
target: string;
suggestedAction: string;
detectedAt: string;
}
export interface AdminOwnerStaffActivityItem {
userId: string;
fullName: string;
role: string;
activityCountLast7Days: number;
lastActivityAt: string | null;
}
export interface AdminReportsSnapshot {
activeCycles: number;
todayLogs: number;
growthRecordsLast7Days: number;
}
export interface AdminDashboardSections {
organizationOverview: AdminOrganizationOverview;
farmPerformanceTable: AdminFarmPerformanceItem[];
farmRanking: AdminFarmRankingItem[];
alertCenter: AdminAlertCenterItem[];
organizationDeviceSummary: AdminOrganizationDeviceSummary;
dataInterventionQueue: AdminDataInterventionItem[];
ownerStaffActivity: AdminOwnerStaffActivityItem[];
reportsSnapshot: AdminReportsSnapshot;
}
export type AdminDashboardResponse = DashboardBaseResponse<AdminDashboardSections>;
- Step 2: Run failing test again to keep RED state
Run:
bun --cwd frontend run test -- src/pages/dashboard/admin/AdminDashboard.spec.tsx
Expected: still FAIL (type contract is ready, UI composition not implemented yet).
- Step 3: Commit type contract update
git add frontend/src/types/dashboard/admin-dashboard.types.ts
git commit -m "refactor: align admin dashboard frontend types with backend dto"
Task 3: Create Admin section components using design system
Files:
Create:
frontend/src/pages/dashboard/admin/components/OrganizationOverview.tsxCreate:
frontend/src/pages/dashboard/admin/components/OrganizationDeviceSummary.tsxCreate:
frontend/src/pages/dashboard/admin/components/ReportsSnapshot.tsxCreate:
frontend/src/pages/dashboard/admin/components/FarmPerformanceTable.tsxCreate:
frontend/src/pages/dashboard/admin/components/FarmRanking.tsxCreate:
frontend/src/pages/dashboard/admin/components/AdminAlertCenter.tsxCreate:
frontend/src/pages/dashboard/admin/components/DataInterventionQueue.tsxCreate:
frontend/src/pages/dashboard/admin/components/OwnerStaffActivity.tsxTest:
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsxStep 1: Create
OrganizationOverview.tsx
import { Card, Col, Row, Statistic } from 'antd';
import type { AdminOrganizationOverview } from '@/types/dashboard/admin-dashboard.types';
interface OrganizationOverviewProps {
data: AdminOrganizationOverview;
}
export const OrganizationOverview = ({ data }: OrganizationOverviewProps) => (
<Card title="Tổng quan tổ chức">
<Row gutter={[12, 12]}>
<Col xs={12} md={8}>
<Statistic title="Tổng trang trại" value={data.totalFarms} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Tổng ao" value={data.totalPonds} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Ao hoạt động" value={data.activePonds} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Tổng thiết bị" value={data.totalDevices} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Cảnh báo active" value={data.activeAlerts} />
</Col>
</Row>
</Card>
);
- Step 2: Create
OrganizationDeviceSummary.tsx
import { Card, Col, Row, Statistic } from 'antd';
import type { AdminOrganizationDeviceSummary } from '@/types/dashboard/admin-dashboard.types';
interface OrganizationDeviceSummaryProps {
data: AdminOrganizationDeviceSummary;
}
export const OrganizationDeviceSummary = ({ data }: OrganizationDeviceSummaryProps) => (
<Card title="Tình trạng thiết bị toàn tổ chức">
<Row gutter={[12, 12]}>
<Col xs={12} md={8}>
<Statistic title="Tổng" value={data.total} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Online" value={data.online} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Offline" value={data.offline} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Error" value={data.error} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Tỷ lệ online (%)" value={data.onlineRate} />
</Col>
</Row>
</Card>
);
- Step 3: Create
ReportsSnapshot.tsx
import { Card, Col, Row, Statistic } from 'antd';
import type { AdminReportsSnapshot } from '@/types/dashboard/admin-dashboard.types';
interface ReportsSnapshotProps {
data: AdminReportsSnapshot;
}
export const ReportsSnapshot = ({ data }: ReportsSnapshotProps) => (
<Card title="Tóm tắt báo cáo vận hành">
<Row gutter={[12, 12]}>
<Col xs={12} md={8}>
<Statistic title="Chu kỳ active" value={data.activeCycles} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Nhật ký hôm nay" value={data.todayLogs} />
</Col>
<Col xs={12} md={8}>
<Statistic title="Growth 7 ngày" value={data.growthRecordsLast7Days} />
</Col>
</Row>
</Card>
);
- Step 4: Create
FarmPerformanceTable.tsx
import type { ColumnsType } from 'antd/es/table';
import { Card, Typography } from 'antd';
import { useMemo } from 'react';
import { DataTable } from '@/components/common/DataTable';
import type { AdminFarmPerformanceItem } from '@/types/dashboard/admin-dashboard.types';
interface FarmPerformanceTableProps {
data: AdminFarmPerformanceItem[];
}
export const FarmPerformanceTable = ({ data }: FarmPerformanceTableProps) => {
const columns = useMemo<ColumnsType<AdminFarmPerformanceItem>>(
() => [
{ title: 'Trang trại', dataIndex: 'farmName', key: 'farmName' },
{ title: 'Tổng ao', dataIndex: 'totalPonds', key: 'totalPonds' },
{ title: 'Ao hoạt động', dataIndex: 'activePonds', key: 'activePonds' },
{ title: 'Cảnh báo active', dataIndex: 'activeAlerts', key: 'activeAlerts' },
{
title: 'Online device (%)',
dataIndex: 'onlineDeviceRate',
key: 'onlineDeviceRate',
render: (value: number) => `${value}%`,
},
],
[],
);
return (
<Card title="Bảng hiệu suất trang trại">
<Typography.Text type="secondary">Hiển thị tối đa 10 trang trại</Typography.Text>
<DataTable<AdminFarmPerformanceItem>
columns={columns}
dataSource={data}
rowKey="farmId"
pagination={false}
/>
</Card>
);
};
- Step 5: Create
FarmRanking.tsx
import type { ColumnsType } from 'antd/es/table';
import { Card, Typography } from 'antd';
import { useMemo } from 'react';
import { DataTable } from '@/components/common/DataTable';
import type { AdminFarmRankingItem } from '@/types/dashboard/admin-dashboard.types';
interface FarmRankingProps {
data: AdminFarmRankingItem[];
}
type RankedFarm = AdminFarmRankingItem & { rank: number };
export const FarmRanking = ({ data }: FarmRankingProps) => {
const rankedData = useMemo<RankedFarm[]>(
() => data.map((item, index) => ({ ...item, rank: index + 1 })),
[data],
);
const columns = useMemo<ColumnsType<RankedFarm>>(
() => [
{ title: 'Hạng', dataIndex: 'rank', key: 'rank' },
{ title: 'Trang trại', dataIndex: 'farmName', key: 'farmName' },
{ title: 'Điểm', dataIndex: 'score', key: 'score' },
{ title: 'Ao hoạt động', dataIndex: 'activePonds', key: 'activePonds' },
{
title: 'Online device (%)',
dataIndex: 'onlineDeviceRate',
key: 'onlineDeviceRate',
render: (value: number) => `${value}%`,
},
],
[],
);
return (
<Card title="Xếp hạng trang trại">
<Typography.Text type="secondary">Hiển thị tối đa 10 trang trại</Typography.Text>
<DataTable<RankedFarm> columns={columns} dataSource={rankedData} rowKey="farmId" pagination={false} />
</Card>
);
};
- Step 6: Create
AdminAlertCenter.tsx
import type { ColumnsType } from 'antd/es/table';
import { Card, Tag } from 'antd';
import { useMemo } from 'react';
import { DataTable } from '@/components/common/DataTable';
import type { AdminAlertCenterItem } from '@/types/dashboard/admin-dashboard.types';
interface AdminAlertCenterProps {
data: AdminAlertCenterItem[];
}
const mapSeverityColor = (severity: string) => {
if (severity === 'high') return 'error';
if (severity === 'medium') return 'warning';
return 'default';
};
export const AdminAlertCenter = ({ data }: AdminAlertCenterProps) => {
const columns = useMemo<ColumnsType<AdminAlertCenterItem>>(
() => [
{ title: 'Ao', dataIndex: 'pondName', key: 'pondName', render: (value: string | null) => value ?? '-' },
{ title: 'Loại', dataIndex: 'type', key: 'type' },
{
title: 'Mức độ',
dataIndex: 'severity',
key: 'severity',
render: (value: string) => <Tag color={mapSeverityColor(value)}>{value.toUpperCase()}</Tag>,
},
{ title: 'Thông điệp', dataIndex: 'message', key: 'message' },
{ title: 'Trạng thái', dataIndex: 'status', key: 'status' },
{
title: 'Thời gian',
dataIndex: 'createdAt',
key: 'createdAt',
render: (value: string) => new Date(value).toLocaleString('vi-VN'),
},
],
[],
);
return (
<Card title="Trung tâm cảnh báo">
<DataTable<AdminAlertCenterItem>
columns={columns}
dataSource={data}
rowKey="id"
pagination={{ pageSize: 10, showSizeChanger: false }}
/>
</Card>
);
};
- Step 7: Create
DataInterventionQueue.tsx
import type { ColumnsType } from 'antd/es/table';
import { Card } from 'antd';
import { useMemo } from 'react';
import { DataTable } from '@/components/common/DataTable';
import type { AdminDataInterventionItem } from '@/types/dashboard/admin-dashboard.types';
interface DataInterventionQueueProps {
data: AdminDataInterventionItem[];
}
export const DataInterventionQueue = ({ data }: DataInterventionQueueProps) => {
const columns = useMemo<ColumnsType<AdminDataInterventionItem>>(
() => [
{ title: 'Loại vấn đề', dataIndex: 'issueType', key: 'issueType' },
{ title: 'Mục tiêu', dataIndex: 'target', key: 'target' },
{ title: 'Hành động gợi ý', dataIndex: 'suggestedAction', key: 'suggestedAction' },
{
title: 'Phát hiện lúc',
dataIndex: 'detectedAt',
key: 'detectedAt',
render: (value: string) => new Date(value).toLocaleString('vi-VN'),
},
],
[],
);
return (
<Card title="Hàng đợi can thiệp dữ liệu">
<DataTable<AdminDataInterventionItem>
columns={columns}
dataSource={data}
rowKey="id"
pagination={{ pageSize: 10, showSizeChanger: false }}
/>
</Card>
);
};
- Step 8: Create
OwnerStaffActivity.tsx
import type { ColumnsType } from 'antd/es/table';
import { Card } from 'antd';
import { useMemo } from 'react';
import { DataTable } from '@/components/common/DataTable';
import type { AdminOwnerStaffActivityItem } from '@/types/dashboard/admin-dashboard.types';
interface OwnerStaffActivityProps {
data: AdminOwnerStaffActivityItem[];
}
export const OwnerStaffActivity = ({ data }: OwnerStaffActivityProps) => {
const columns = useMemo<ColumnsType<AdminOwnerStaffActivityItem>>(
() => [
{ title: 'Họ tên', dataIndex: 'fullName', key: 'fullName' },
{ title: 'Vai trò', dataIndex: 'role', key: 'role' },
{
title: 'Hoạt động 7 ngày',
dataIndex: 'activityCountLast7Days',
key: 'activityCountLast7Days',
},
{
title: 'Hoạt động gần nhất',
dataIndex: 'lastActivityAt',
key: 'lastActivityAt',
render: (value: string | null) => (value ? new Date(value).toLocaleString('vi-VN') : '-'),
},
],
[],
);
return (
<Card title="Hoạt động chủ trại và nhân sự">
<DataTable<AdminOwnerStaffActivityItem>
columns={columns}
dataSource={data}
rowKey="userId"
pagination={{ pageSize: 10, showSizeChanger: false }}
/>
</Card>
);
};
- Step 9: Run test to keep RED before page composition
Run:
bun --cwd frontend run test -- src/pages/dashboard/admin/AdminDashboard.spec.tsx
Expected: still FAIL until AdminDashboard.tsx composes these sections.
- Step 10: Commit component scaffolding
git add frontend/src/pages/dashboard/admin/components
git commit -m "feat: add admin dashboard section components"
Task 4: Compose Admin dashboard page with design system (GREEN)
Files:
Modify:
frontend/src/pages/dashboard/admin/AdminDashboard.tsxTest:
frontend/src/pages/dashboard/admin/AdminDashboard.spec.tsxStep 1: Replace placeholder page with architecture-first composition
import { Col, Row } from 'antd';
import { EmptyState } from '@/components/common/EmptyState';
import { LoadingSkeleton } from '@/components/common/LoadingSkeleton';
import { PageHeader } from '@/components/common/PageHeader';
import { StatsCard } from '@/components/common/StatsCard';
import { useAdminDashboard } from '@/hooks/dashboard/useAdminDashboard';
import { AdminAlertCenter } from '@/pages/dashboard/admin/components/AdminAlertCenter';
import { DataInterventionQueue } from '@/pages/dashboard/admin/components/DataInterventionQueue';
import { FarmPerformanceTable } from '@/pages/dashboard/admin/components/FarmPerformanceTable';
import { FarmRanking } from '@/pages/dashboard/admin/components/FarmRanking';
import { OrganizationDeviceSummary } from '@/pages/dashboard/admin/components/OrganizationDeviceSummary';
import { OrganizationOverview } from '@/pages/dashboard/admin/components/OrganizationOverview';
import { OwnerStaffActivity } from '@/pages/dashboard/admin/components/OwnerStaffActivity';
import { ReportsSnapshot } from '@/pages/dashboard/admin/components/ReportsSnapshot';
export const AdminDashboard = () => {
const query = useAdminDashboard();
if (query.isLoading) {
return <LoadingSkeleton type="card" />;
}
if (query.isError || !query.data) {
return (
<EmptyState
title="Lỗi tải dashboard quản trị"
description="Không thể tải dữ liệu dashboard admin. Vui lòng thử lại sau."
/>
);
}
const { sections } = query.data;
if (sections.organizationOverview.totalFarms === 0) {
return (
<div>
<PageHeader
title="Dashboard quản trị tổ chức"
subtitle="Theo dõi vận hành đa trang trại trong tổ chức"
/>
<EmptyState
title="Tổ chức chưa có trang trại"
description="Vui lòng tạo trang trại để bắt đầu theo dõi dashboard."
/>
</div>
);
}
return (
<div>
<PageHeader
title="Dashboard quản trị tổ chức"
subtitle="Theo dõi vận hành đa trang trại trong tổ chức"
/>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col xs={24} sm={12} lg={6}>
<StatsCard title="Tổng trang trại" value={sections.organizationOverview.totalFarms} color="blue" />
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard title="Tổng ao" value={sections.organizationOverview.totalPonds} color="green" />
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard title="Cảnh báo active" value={sections.organizationOverview.activeAlerts} color="red" />
</Col>
<Col xs={24} sm={12} lg={6}>
<StatsCard title="Tỷ lệ online (%)" value={sections.organizationDeviceSummary.onlineRate} color="blue" />
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col xs={24} xl={12}>
<OrganizationOverview data={sections.organizationOverview} />
</Col>
<Col xs={24} xl={12}>
<OrganizationDeviceSummary data={sections.organizationDeviceSummary} />
</Col>
<Col xs={24}>
<ReportsSnapshot data={sections.reportsSnapshot} />
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col xs={24}>
<FarmPerformanceTable data={sections.farmPerformanceTable} />
</Col>
<Col xs={24}>
<FarmRanking data={sections.farmRanking} />
</Col>
<Col xs={24}>
<AdminAlertCenter data={sections.alertCenter} />
</Col>
<Col xs={24}>
<DataInterventionQueue data={sections.dataInterventionQueue} />
</Col>
<Col xs={24}>
<OwnerStaffActivity data={sections.ownerStaffActivity} />
</Col>
</Row>
</div>
);
};
- Step 2: Run test to verify GREEN
Run:
bun --cwd frontend run test -- src/pages/dashboard/admin/AdminDashboard.spec.tsx
Expected: PASS.
- Step 3: Commit page composition
git add frontend/src/pages/dashboard/admin/AdminDashboard.tsx
git commit -m "feat: implement admin dashboard page with design-system composition"
Task 5: Refactor and verify broader dashboard stability
Files:
Modify (if needed by Biome autofix): files from Tasks 1-4
Test:
frontend/src/pages/dashboard/**/*.spec.tsxStep 1: Run dashboard test suite
Run:
bun --cwd frontend run test -- src/pages/dashboard
Expected: PASS for DashboardRoute, OwnerDashboard, StaffDashboard, and new AdminDashboard tests.
- Step 2: Run repository-wide check
Run:
bun run check
Expected: PASS with no Biome errors.
- Step 3: Commit verification-safe formatting adjustments
git add frontend/src/pages/dashboard/admin frontend/src/types/dashboard/admin-dashboard.types.ts
git commit -m "test: verify admin dashboard phase 2.2 integration and formatting"
Acceptance Mapping
- Admin sees required sections: enforced by
AdminDashboard.spec.tsxfirst test. - Large tables have pagination or visible limit:
FarmPerformanceTable/FarmRankingshow visible max-10 note and non-paginated limited data; operational tables usepaginationconfig. - No Super Admin-only system sections: first test asserts absence of system/audit labels.
- Empty state present: second test asserts organization-no-farms empty state messaging.
- No
any: explicit interface typing across all new/modified files. - Frontend alias imports only: all snippets use
@/imports.
Spec Self-Review
- Spec coverage: Phase 2.2 Admin UI sections, table behavior, empty state, role-sensitive absence, and check command all mapped to tasks.
- Placeholder scan: No TODO/TBD placeholders in tasks or code snippets.
- Type consistency:
AdminDashboardResponse/section item names are consistent between tests, page, and section components.
Plan complete and saved to docs/superpowers/plans/2026-04-25-admin-dashboard-phase-2-2-design-system.md. Two execution options:
1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration
2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints
Which approach?