Replace plain text dependency indicators with visual, status-aware components: - New DependencyChip/PhaseNumberBadge components with status-colored styling - Sidebar shows compact numbered circles for phase deps instead of text - Detail panel uses bordered cards with phase badges and status indicators - Task dependency callout bars with resolved/total counters - Collapse mechanism for tasks with 3+ dependencies (+N more button) - Full dark mode support via semantic status tokens
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import { X } from "lucide-react";
|
|
import { StatusDot, mapEntityStatus, type StatusVariant } from "@/components/StatusDot";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PhaseNumberBadge — small numbered circle colored by phase status
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const badgeStyles: Record<StatusVariant, string> = {
|
|
active: "bg-status-active-bg text-status-active-fg border-status-active-border",
|
|
success: "bg-status-success-bg text-status-success-fg border-status-success-border",
|
|
warning: "bg-status-warning-bg text-status-warning-fg border-status-warning-border",
|
|
error: "bg-status-error-bg text-status-error-fg border-status-error-border",
|
|
neutral: "bg-muted text-muted-foreground border-border",
|
|
urgent: "bg-status-urgent-bg text-status-urgent-fg border-status-urgent-border",
|
|
};
|
|
|
|
interface PhaseNumberBadgeProps {
|
|
index: number;
|
|
status: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function PhaseNumberBadge({ index, status, className }: PhaseNumberBadgeProps) {
|
|
const variant = mapEntityStatus(status);
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full border px-1 font-mono text-[9px] font-bold leading-none",
|
|
badgeStyles[variant],
|
|
className,
|
|
)}
|
|
title={`Phase ${index} (${status})`}
|
|
>
|
|
{index}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DependencyChip — compact pill showing dependency name + status dot
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const chipBorderColor: Record<StatusVariant, string> = {
|
|
active: "border-status-active-border/60",
|
|
success: "border-status-success-border/60",
|
|
warning: "border-status-warning-border/60",
|
|
error: "border-status-error-border/60",
|
|
neutral: "border-border",
|
|
urgent: "border-status-urgent-border/60",
|
|
};
|
|
|
|
interface DependencyChipProps {
|
|
name: string;
|
|
status: string;
|
|
size?: "xs" | "sm";
|
|
className?: string;
|
|
onRemove?: () => void;
|
|
}
|
|
|
|
export function DependencyChip({
|
|
name,
|
|
status,
|
|
size = "sm",
|
|
className,
|
|
onRemove,
|
|
}: DependencyChipProps) {
|
|
const variant = mapEntityStatus(status);
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"inline-flex items-center gap-1 rounded-full border bg-background/80",
|
|
chipBorderColor[variant],
|
|
size === "xs" ? "px-1.5 py-px text-[10px]" : "px-2 py-0.5 text-xs",
|
|
className,
|
|
)}
|
|
>
|
|
<StatusDot status={status} size="sm" />
|
|
<span className={cn("truncate", size === "xs" ? "max-w-[80px]" : "max-w-[140px]")}>
|
|
{name}
|
|
</span>
|
|
{onRemove && (
|
|
<button
|
|
className="ml-0.5 rounded-full p-px text-muted-foreground transition-colors hover:text-destructive"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onRemove();
|
|
}}
|
|
title="Remove dependency"
|
|
>
|
|
<X className="h-2.5 w-2.5" />
|
|
</button>
|
|
)}
|
|
</span>
|
|
);
|
|
}
|