feat(ui): add review empty states

This commit is contained in:
Saravana Achu Mac 2026-05-06 13:18:52 -07:00
parent 192a2aafde
commit 6472a58ad1
3 changed files with 62 additions and 9 deletions

View File

@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { AppShell } from "@/components/AppShell";
import {
Badge,
EmptyState,
LoadingSpinner,
Panel,
} from "@/components/ui/Primitives";
import {
@ -33,6 +35,7 @@ export default function ReviewsPage() {
const [selectedApprovalId, setSelectedApprovalId] = useState<string | null>(null);
const [selectedBatchIds, setSelectedBatchIds] = useState<Set<string>>(new Set());
const [reviewNote, setReviewNote] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
@ -50,6 +53,8 @@ export default function ReviewsPage() {
setTimeline(nextTimeline);
} catch (err) {
setError(err instanceof Error ? err.message : "Unable to load review queue");
} finally {
setIsLoading(false);
}
})();
}, []);
@ -281,8 +286,18 @@ export default function ReviewsPage() {
onClear={clearBatch}
onBatchDecision={(decision) => void handleBatchDecision(decision)}
/>
{error ? <div style={{ color: "var(--nl-text-secondary)" }}>{error}</div> : null}
{isLoading ? (
<LoadingSpinner label="Loading review queue" className="py-8" />
) : error && approvalQueue.length === 0 ? (
<EmptyState
title="Unable to load review queue"
description={error}
actionLabel="Retry"
onAction={() => window.location.reload()}
/>
) : (
<>
{error ? <EmptyState title="Review update failed" description={error} /> : null}
<ReviewQueueList
items={approvalQueue}
batchMode={batchMode}
@ -290,6 +305,8 @@ export default function ReviewsPage() {
selectedApprovalId={featuredProposal?.id ?? null}
onSelectItem={(id) => batchMode ? toggleBatchItem(id) : setSelectedApprovalId(id)}
/>
</>
)}
</Panel>
</section>

View File

@ -4,6 +4,7 @@ import { ProposalReviewCard } from "@/components/ProposalReviewCard";
import {
Badge,
Button,
EmptyState,
ListItemButton,
Panel,
PanelBody,
@ -126,6 +127,15 @@ export function ReviewQueueList({
selectedApprovalId: string | null;
onSelectItem: (id: string) => void;
}) {
if (items.length === 0) {
return (
<EmptyState
title="No proposals need review"
description="Agent-mediated changes that need human approval will appear here."
/>
);
}
return (
<div style={{ display: "grid", gap: "var(--nl-space-3)" }}>
{items.map((item) => (
@ -212,5 +222,19 @@ export function ProposalDiffCard({
}
export function ReviewTimeline({ items }: { items: AgentTimelineItem[] }) {
if (items.length === 0) {
return (
<Panel>
<PanelHeader>
<PanelTitle>Agent activity timeline</PanelTitle>
</PanelHeader>
<EmptyState
title="No review activity yet"
description="Approvals, rejections, and agent changes will be listed as they happen."
/>
</Panel>
);
}
return <AgentTimeline items={items} />;
}

View File

@ -23,10 +23,12 @@ import {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
EmptyState as BytelystEmptyState,
IconButton as BytelystIconButton,
Input as BytelystInput,
Label as BytelystLabel,
ListItemButton as BytelystListItemButton,
LoadingSpinner as BytelystLoadingSpinner,
Panel as BytelystPanel,
PanelBody as BytelystPanelBody,
PanelDescription as BytelystPanelDescription,
@ -57,10 +59,12 @@ import {
type DataListItemProps,
type DataListProps,
type DiffCardProps,
type EmptyStateProps,
type IconButtonProps,
type InputProps,
type LabelProps,
type ListItemButtonProps,
type LoadingSpinnerProps,
type PanelBodyProps,
type PanelDescriptionProps,
type PanelHeaderProps,
@ -297,3 +301,11 @@ export function DataListItem({ className, ...props }: DataListItemProps) {
export const OperationalList = DataList;
export const OperationalListItem = DataListItem;
export function EmptyState({ className, ...props }: EmptyStateProps) {
return <BytelystEmptyState className={mergeClassNames("rounded-[var(--nl-radius-md)]", className)} {...props} />;
}
export function LoadingSpinner({ className, ...props }: LoadingSpinnerProps) {
return <BytelystLoadingSpinner className={className} {...props} />;
}