Refactor renamed the component groups to something less obtuse:
Elements -> Groups -> Screens -> Views
This commit is contained in:
parent
ca6de91a96
commit
dd46acba87
2 changed files with 151 additions and 144 deletions
|
|
@ -0,0 +1,94 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { TaskCard } from "./TaskCard";
|
||||
|
||||
const meta: Meta<typeof TaskCard> = {
|
||||
title: "Groups/TaskCard",
|
||||
component: TaskCard,
|
||||
args: {
|
||||
onViewDetails: fn(),
|
||||
onViewResult: fn(),
|
||||
onCancel: fn(),
|
||||
onDismiss: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TaskCard>;
|
||||
|
||||
export const Running: Story = {
|
||||
args: {
|
||||
taskId: "task-abc-123",
|
||||
status: "running",
|
||||
method: "tools/call",
|
||||
target: "generate_report",
|
||||
progress: 80,
|
||||
progressDescription: "Generating report section 4 of 5...",
|
||||
startedAt: "2026-03-17T10:30:00Z",
|
||||
elapsed: "2m 15s",
|
||||
},
|
||||
};
|
||||
|
||||
export const RunningWithProgress: Story = {
|
||||
args: {
|
||||
taskId: "task-def-456",
|
||||
status: "running",
|
||||
method: "tools/call",
|
||||
target: "analyze_data",
|
||||
progress: 45,
|
||||
progressDescription: "Analyzing dataset...",
|
||||
startedAt: "2026-03-17T10:32:00Z",
|
||||
elapsed: "30s",
|
||||
},
|
||||
};
|
||||
|
||||
export const Waiting: Story = {
|
||||
args: {
|
||||
taskId: "task-ghi-789",
|
||||
status: "waiting",
|
||||
method: "resources/read",
|
||||
target: "file:///data/report.csv",
|
||||
progress: 0,
|
||||
startedAt: "2026-03-17T10:35:00Z",
|
||||
},
|
||||
};
|
||||
|
||||
export const Completed: Story = {
|
||||
args: {
|
||||
taskId: "task-jkl-012",
|
||||
status: "completed",
|
||||
method: "tools/call",
|
||||
target: "generate_summary",
|
||||
progress: 100,
|
||||
startedAt: "2026-03-17T10:20:00Z",
|
||||
completedAt: "2026-03-17T10:25:00Z",
|
||||
elapsed: "5m 00s",
|
||||
},
|
||||
};
|
||||
|
||||
export const Failed: Story = {
|
||||
args: {
|
||||
taskId: "task-mno-345",
|
||||
status: "failed",
|
||||
method: "tools/call",
|
||||
target: "fetch_remote_data",
|
||||
progress: 45,
|
||||
startedAt: "2026-03-17T10:28:00Z",
|
||||
completedAt: "2026-03-17T10:29:30Z",
|
||||
elapsed: "1m 30s",
|
||||
error: "Connection refused: upstream server not responding after 3 retries",
|
||||
},
|
||||
};
|
||||
|
||||
export const Cancelled: Story = {
|
||||
args: {
|
||||
taskId: "task-pqr-678",
|
||||
status: "cancelled",
|
||||
method: "tools/call",
|
||||
target: "long_running_analysis",
|
||||
progress: 30,
|
||||
startedAt: "2026-03-17T10:15:00Z",
|
||||
completedAt: "2026-03-17T10:18:00Z",
|
||||
elapsed: "3m 00s",
|
||||
},
|
||||
};
|
||||
159
clients/web/src/components/groups/TaskCard/TaskCard.tsx
Normal file
159
clients/web/src/components/groups/TaskCard/TaskCard.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { Badge, Button, Card, Group, Stack, Text } from "@mantine/core";
|
||||
import { ProgressDisplay } from "../../elements/ProgressDisplay/ProgressDisplay";
|
||||
|
||||
export type TaskStatus =
|
||||
| "waiting"
|
||||
| "running"
|
||||
| "completed"
|
||||
| "failed"
|
||||
| "cancelled";
|
||||
|
||||
export interface TaskCardProps {
|
||||
taskId: string;
|
||||
status: TaskStatus;
|
||||
method: string;
|
||||
target?: string;
|
||||
progress?: number;
|
||||
progressDescription?: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
elapsed?: string;
|
||||
error?: string;
|
||||
onViewDetails: () => void;
|
||||
onViewResult: () => void;
|
||||
onCancel: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<TaskStatus, string> = {
|
||||
waiting: "gray",
|
||||
running: "blue",
|
||||
completed: "green",
|
||||
failed: "red",
|
||||
cancelled: "yellow",
|
||||
};
|
||||
|
||||
const TaskContainer = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "md",
|
||||
});
|
||||
|
||||
const TaskIdText = Text.withProps({
|
||||
size: "sm",
|
||||
ff: "monospace",
|
||||
});
|
||||
|
||||
const DetailText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const ValueSpan = Text.withProps({
|
||||
component: "span",
|
||||
fw: 600,
|
||||
});
|
||||
|
||||
const ProgressNote = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
fs: "italic",
|
||||
});
|
||||
|
||||
const SubtleButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
size: "xs",
|
||||
});
|
||||
|
||||
const CancelButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
color: "red",
|
||||
size: "xs",
|
||||
});
|
||||
|
||||
function formatTaskId(taskId: string): string {
|
||||
return `Task: ${taskId}`;
|
||||
}
|
||||
|
||||
function formatTimeline(
|
||||
startedAt: string,
|
||||
completedAt?: string,
|
||||
elapsed?: string,
|
||||
): string {
|
||||
let result = `Started: ${startedAt}`;
|
||||
if (completedAt) result += ` | Completed: ${completedAt}`;
|
||||
if (elapsed) result += ` | Elapsed: ${elapsed}`;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function TaskCard({
|
||||
taskId,
|
||||
status,
|
||||
method,
|
||||
target,
|
||||
progress,
|
||||
progressDescription,
|
||||
startedAt,
|
||||
completedAt,
|
||||
elapsed,
|
||||
error,
|
||||
onViewDetails,
|
||||
onViewResult,
|
||||
onCancel,
|
||||
onDismiss,
|
||||
}: TaskCardProps) {
|
||||
const isActive = status === "waiting" || status === "running";
|
||||
|
||||
return (
|
||||
<TaskContainer>
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<TaskIdText>{formatTaskId(taskId)}</TaskIdText>
|
||||
<Badge color={STATUS_COLORS[status]}>{status}</Badge>
|
||||
</Group>
|
||||
{progress !== undefined && <ProgressDisplay progress={progress} />}
|
||||
</Group>
|
||||
|
||||
<DetailText>
|
||||
Method: <ValueSpan>{method}</ValueSpan>
|
||||
</DetailText>
|
||||
|
||||
{target && (
|
||||
<DetailText>
|
||||
Tool/Resource: <ValueSpan>{target}</ValueSpan>
|
||||
</DetailText>
|
||||
)}
|
||||
|
||||
{startedAt && (
|
||||
<DetailText>
|
||||
{formatTimeline(startedAt, completedAt, elapsed)}
|
||||
</DetailText>
|
||||
)}
|
||||
|
||||
{progressDescription && (
|
||||
<ProgressNote>{progressDescription}</ProgressNote>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Text size="sm" c="red">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
{isActive ? (
|
||||
<>
|
||||
<SubtleButton onClick={onViewDetails}>View Details</SubtleButton>
|
||||
<CancelButton onClick={onCancel}>Cancel</CancelButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SubtleButton onClick={onViewResult}>View Result</SubtleButton>
|
||||
<SubtleButton onClick={onDismiss}>Dismiss</SubtleButton>
|
||||
</>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</TaskContainer>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue