Files
fusion/packages/dashboard/app/components/DataBoundary.tsx
gsxdsm b95e7f7575 FN-5894: share project data-boundary and status helpers
Centralize project loading and status-state handling across dashboard surfaces.

- add a reusable DataBoundary component for loading, empty, error, and loaded states
- extract shared project status config helpers for labels, colors, icons, and initializing detection
- update project overview, cards, badges, and selector UI to use the shared helpers
- add regression coverage for data-boundary behavior, project overview empty/loading transitions, and status config fallbacks

Files changed:
 packages/dashboard/app/components/DataBoundary.tsx | 64 ++++++++++++++++++++++
 packages/dashboard/app/components/ProjectCard.tsx  | 48 ++--------------
 .../app/components/ProjectHealthBadge.tsx          | 45 +--------------
 .../dashboard/app/components/ProjectOverview.tsx   |  4 +-
 .../dashboard/app/components/ProjectSelector.tsx   | 27 +--------
 .../app/components/__tests__/DataBoundary.test.tsx | 62 +++++++++++++++++++++
 .../components/__tests__/ProjectOverview.test.tsx  | 25 +++++++++
 .../__tests__/ProjectThemeTokens.test.tsx          | 37 ++++---------
 .../utils/__tests__/projectStatusConfig.test.ts    | 53 ++++++++++++++++++
 .../dashboard/app/utils/projectStatusConfig.ts     | 50 +++++++++++++++++
 packages/dashboard/vitest.config.ts                |  2 +
 11 files changed, 282 insertions(+), 135 deletions(-)

Fusion-Task-Id: FN-5894

Fusion-Task-Lineage: 3626a4af-b62b-456d-82d7-cf47bbf55c21
2026-06-02 14:14:24 -07:00

65 lines
1.7 KiB
TypeScript

import { AlertCircle } from "lucide-react";
import type { ReactNode } from "react";
import { getErrorMessage } from "@fusion/core";
import { AgentEmptyState } from "./AgentEmptyState";
import { ProjectGridSkeleton } from "./ProjectGridSkeleton";
export interface DataBoundaryProps {
isEmpty: boolean;
isLoading?: boolean;
hasFetched?: boolean;
error?: unknown;
loadingFallback?: ReactNode;
emptyFallback?: ReactNode;
errorFallback?: ReactNode;
children: ReactNode;
}
function DefaultErrorFallback({ error }: { error: unknown }) {
return (
<div className="agent-empty" data-testid="data-boundary-error">
<AlertCircle className="agent-empty-state__icon" size={48} opacity={0.3} />
<p className="agent-empty-state__title">Unable to load data</p>
<p className="agent-empty-state__description text-secondary">
{getErrorMessage(error) || "Something went wrong while loading this view."}
</p>
</div>
);
}
export function DataBoundary({
isEmpty,
isLoading = false,
hasFetched = false,
error,
loadingFallback,
emptyFallback,
errorFallback,
children,
}: DataBoundaryProps) {
if (error) {
return <>{errorFallback ?? <DefaultErrorFallback error={error} />}</>;
}
const shouldShowLoading = isLoading || (!hasFetched && !error);
if (shouldShowLoading) {
return <>{loadingFallback ?? <ProjectGridSkeleton />}</>;
}
if (hasFetched && isEmpty) {
return (
<>
{emptyFallback ?? (
<AgentEmptyState
title="No data available"
description="There is nothing to show yet."
ctaLabel=""
/>
)}
</>
);
}
return <>{children}</>;
}