feat: implement StatusDot component for status indicators in approvals and agents
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { Markdown } from "@/components/atoms/Markdown";
|
import { Markdown } from "@/components/atoms/Markdown";
|
||||||
|
import { StatusDot } from "@/components/atoms/StatusDot";
|
||||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||||
import { TaskBoard } from "@/components/organisms/TaskBoard";
|
import { TaskBoard } from "@/components/organisms/TaskBoard";
|
||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
@@ -2425,14 +2426,12 @@ export default function BoardDetailPage() {
|
|||||||
>
|
>
|
||||||
<div className="relative flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-xs font-semibold text-slate-700">
|
<div className="relative flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-xs font-semibold text-slate-700">
|
||||||
{agentAvatarLabel(agent)}
|
{agentAvatarLabel(agent)}
|
||||||
<span
|
<StatusDot
|
||||||
|
status={agent.status}
|
||||||
|
variant="agent"
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute -right-0.5 -bottom-0.5 h-2.5 w-2.5 rounded-full border-2 border-white",
|
"absolute -right-0.5 -bottom-0.5 h-2.5 w-2.5 rounded-full border-2 border-white",
|
||||||
isWorking
|
isWorking && "ring-2 ring-emerald-200",
|
||||||
? "bg-emerald-500"
|
|
||||||
: agent.status === "online"
|
|
||||||
? "bg-green-500"
|
|
||||||
: "bg-slate-300",
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
useUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
|
useUpdateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
|
||||||
} from "@/api/generated/approvals/approvals";
|
} from "@/api/generated/approvals/approvals";
|
||||||
import type { ApprovalRead } from "@/api/generated/model";
|
import type { ApprovalRead } from "@/api/generated/model";
|
||||||
|
import { StatusDot } from "@/components/atoms/StatusDot";
|
||||||
import {
|
import {
|
||||||
ChartContainer,
|
ChartContainer,
|
||||||
ChartTooltip,
|
ChartTooltip,
|
||||||
@@ -85,12 +86,6 @@ const humanizeAction = (value: string) =>
|
|||||||
const formatStatusLabel = (status: string) =>
|
const formatStatusLabel = (status: string) =>
|
||||||
status.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
status.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
||||||
|
|
||||||
const statusDotClass = (status: string) => {
|
|
||||||
if (status === "approved") return "bg-emerald-500";
|
|
||||||
if (status === "rejected") return "bg-rose-500";
|
|
||||||
return "bg-amber-500";
|
|
||||||
};
|
|
||||||
|
|
||||||
const rubricColors = [
|
const rubricColors = [
|
||||||
"#0f172a",
|
"#0f172a",
|
||||||
"#1d4ed8",
|
"#1d4ed8",
|
||||||
@@ -584,10 +579,11 @@ export function BoardApprovalsPanel({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
|
<div className="flex items-center gap-3 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
|
||||||
<span
|
<StatusDot
|
||||||
|
status={selectedApproval.status}
|
||||||
|
variant="approval"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-2 w-2 rounded-full",
|
"h-2 w-2 rounded-full",
|
||||||
statusDotClass(selectedApproval.status),
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
77
frontend/src/components/atoms/StatusDot.tsx
Normal file
77
frontend/src/components/atoms/StatusDot.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type StatusDotVariant = "agent" | "approval" | "task";
|
||||||
|
|
||||||
|
const AGENT_STATUS_DOT_CLASS_BY_STATUS: Record<string, string> = {
|
||||||
|
online: "bg-emerald-500",
|
||||||
|
busy: "bg-amber-500",
|
||||||
|
provisioning: "bg-amber-500",
|
||||||
|
updating: "bg-sky-500",
|
||||||
|
deleting: "bg-rose-500",
|
||||||
|
offline: "bg-slate-400",
|
||||||
|
};
|
||||||
|
|
||||||
|
const APPROVAL_STATUS_DOT_CLASS_BY_STATUS: Record<string, string> = {
|
||||||
|
approved: "bg-emerald-500",
|
||||||
|
rejected: "bg-rose-500",
|
||||||
|
pending: "bg-amber-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
const TASK_STATUS_DOT_CLASS_BY_STATUS: Record<string, string> = {
|
||||||
|
inbox: "bg-slate-400",
|
||||||
|
in_progress: "bg-purple-500",
|
||||||
|
review: "bg-indigo-500",
|
||||||
|
done: "bg-emerald-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_DOT_CLASS_BY_VARIANT: Record<
|
||||||
|
StatusDotVariant,
|
||||||
|
Record<string, string>
|
||||||
|
> = {
|
||||||
|
agent: AGENT_STATUS_DOT_CLASS_BY_STATUS,
|
||||||
|
approval: APPROVAL_STATUS_DOT_CLASS_BY_STATUS,
|
||||||
|
task: TASK_STATUS_DOT_CLASS_BY_STATUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_STATUS_DOT_CLASS: Record<StatusDotVariant, string> = {
|
||||||
|
agent: "bg-slate-300",
|
||||||
|
approval: "bg-amber-500",
|
||||||
|
task: "bg-slate-300",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const statusDotClass = (
|
||||||
|
status: string | null | undefined,
|
||||||
|
variant: StatusDotVariant = "agent",
|
||||||
|
) => {
|
||||||
|
const normalized = (status ?? "").trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return DEFAULT_STATUS_DOT_CLASS[variant];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
STATUS_DOT_CLASS_BY_VARIANT[variant][normalized] ??
|
||||||
|
DEFAULT_STATUS_DOT_CLASS[variant]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type StatusDotProps = {
|
||||||
|
status?: string | null;
|
||||||
|
variant?: StatusDotVariant;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function StatusDot({
|
||||||
|
status,
|
||||||
|
variant = "agent",
|
||||||
|
className,
|
||||||
|
}: StatusDotProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"inline-block h-2.5 w-2.5 rounded-full",
|
||||||
|
statusDotClass(status, variant),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user