135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import React from "react";
|
|
import { Link } from "react-router-dom";
|
|
import { ArrowRightIcon } from "../../icons";
|
|
import Button from "../ui/button/Button";
|
|
|
|
export interface RelationshipData {
|
|
taskId: number;
|
|
taskTitle: string;
|
|
clusterId?: number | null;
|
|
clusterName?: string | null;
|
|
ideaId?: number | null;
|
|
ideaTitle?: string | null;
|
|
keywordIds?: number[];
|
|
keywordNames?: string[];
|
|
}
|
|
|
|
interface RelationshipMapProps {
|
|
task: RelationshipData;
|
|
onNavigate?: (type: "cluster" | "idea" | "keyword", id: number) => void;
|
|
className?: string;
|
|
}
|
|
|
|
const RelationshipMap: React.FC<RelationshipMapProps> = ({
|
|
task,
|
|
onNavigate,
|
|
className = "",
|
|
}) => {
|
|
const hasRelationships = task.clusterId || task.ideaId || (task.keywordIds && task.keywordIds.length > 0);
|
|
|
|
if (!hasRelationships) {
|
|
return (
|
|
<div className={`text-xs text-gray-500 dark:text-gray-400 ${className}`}>
|
|
No relationships mapped
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`space-y-2 ${className}`}>
|
|
{/* Keywords → Cluster → Idea → Task Flow */}
|
|
<div className="flex flex-wrap items-center gap-2 text-xs">
|
|
{/* Keywords */}
|
|
{task.keywordIds && task.keywordIds.length > 0 && (
|
|
<>
|
|
<div className="flex flex-wrap items-center gap-1">
|
|
{task.keywordNames?.slice(0, 3).map((keyword, idx) => (
|
|
<Button
|
|
key={idx}
|
|
onClick={() => onNavigate?.("keyword", task.keywordIds![idx])}
|
|
variant="ghost"
|
|
tone="neutral"
|
|
size="xs"
|
|
>
|
|
{keyword}
|
|
</Button>
|
|
))}
|
|
{task.keywordIds.length > 3 && (
|
|
<span className="px-2 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400">
|
|
+{task.keywordIds.length - 3}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{task.clusterId && <ArrowRightIcon className="size-3 text-gray-400" />}
|
|
</>
|
|
)}
|
|
|
|
{/* Cluster */}
|
|
{task.clusterId && (
|
|
<>
|
|
<Link
|
|
to={`/planner/clusters?highlight=${task.clusterId}`}
|
|
onClick={(e) => {
|
|
if (onNavigate) {
|
|
e.preventDefault();
|
|
onNavigate("cluster", task.clusterId!);
|
|
}
|
|
}}
|
|
className="px-2 py-0.5 rounded bg-brand-50 dark:bg-brand-500/15 text-brand-600 dark:text-brand-400 hover:bg-brand-100 dark:hover:bg-brand-500/25 transition-colors font-medium"
|
|
>
|
|
{task.clusterName || `Cluster #${task.clusterId}`}
|
|
</Link>
|
|
{task.ideaId && <ArrowRightIcon className="size-3 text-gray-400" />}
|
|
</>
|
|
)}
|
|
|
|
{/* Idea */}
|
|
{task.ideaId && (
|
|
<>
|
|
<Link
|
|
to={`/planner/ideas?highlight=${task.ideaId}`}
|
|
onClick={(e) => {
|
|
if (onNavigate) {
|
|
e.preventDefault();
|
|
onNavigate("idea", task.ideaId!);
|
|
}
|
|
}}
|
|
className="px-2 py-0.5 rounded bg-success-50 dark:bg-success-500/15 text-success-600 dark:text-success-400 hover:bg-success-100 dark:hover:bg-success-500/25 transition-colors font-medium"
|
|
>
|
|
{task.ideaTitle || `Idea #${task.ideaId}`}
|
|
</Link>
|
|
<ArrowRightIcon className="size-3 text-gray-400" />
|
|
</>
|
|
)}
|
|
|
|
{/* Task (current) */}
|
|
<span className="px-2 py-0.5 rounded bg-warning-50 dark:bg-warning-500/15 text-warning-600 dark:text-warning-400 font-medium">
|
|
Task
|
|
</span>
|
|
</div>
|
|
|
|
{/* Relationship Summary */}
|
|
<div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400 pt-1 border-t border-gray-200 dark:border-gray-800">
|
|
{task.clusterId && (
|
|
<span>
|
|
Cluster: <span className="font-medium text-gray-700 dark:text-gray-300">{task.clusterName || `#${task.clusterId}`}</span>
|
|
</span>
|
|
)}
|
|
{task.ideaId && (
|
|
<span>
|
|
Idea: <span className="font-medium text-gray-700 dark:text-gray-300">{task.ideaTitle || `#${task.ideaId}`}</span>
|
|
</span>
|
|
)}
|
|
{task.keywordIds && task.keywordIds.length > 0 && (
|
|
<span>
|
|
Keywords: <span className="font-medium text-gray-700 dark:text-gray-300">{task.keywordIds.length}</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RelationshipMap;
|
|
|