feat(ui): add review empty states
This commit is contained in:
parent
192a2aafde
commit
6472a58ad1
@ -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,15 +286,27 @@ export default function ReviewsPage() {
|
||||
onClear={clearBatch}
|
||||
onBatchDecision={(decision) => void handleBatchDecision(decision)}
|
||||
/>
|
||||
{error ? <div style={{ color: "var(--nl-text-secondary)" }}>{error}</div> : null}
|
||||
|
||||
<ReviewQueueList
|
||||
items={approvalQueue}
|
||||
batchMode={batchMode}
|
||||
selectedBatchIds={selectedBatchIds}
|
||||
selectedApprovalId={featuredProposal?.id ?? null}
|
||||
onSelectItem={(id) => batchMode ? toggleBatchItem(id) : setSelectedApprovalId(id)}
|
||||
/>
|
||||
{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}
|
||||
selectedBatchIds={selectedBatchIds}
|
||||
selectedApprovalId={featuredProposal?.id ?? null}
|
||||
onSelectItem={(id) => batchMode ? toggleBatchItem(id) : setSelectedApprovalId(id)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Panel>
|
||||
</section>
|
||||
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user