Files
websiteplannera/src/components/flow/CustomNode.tsx
2026-01-09 18:52:15 +01:00

150 lines
5.6 KiB
TypeScript

"use client";
import { memo, useMemo } from "react";
import { Handle, Position, NodeProps } from "@xyflow/react";
import { Badge } from "@/components/ui/badge";
export interface PageNodeData {
title: string;
status: "draft" | "ready" | "review";
content?: string;
notes?: string;
hasAttachments?: boolean;
hasLinks?: boolean;
[key: string]: unknown;
}
const statusConfig = {
draft: {
label: "Szkic",
className: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
},
ready: {
label: "Gotowe",
className: "bg-green-500/20 text-green-400 border-green-500/30",
},
review: {
label: "Do poprawki",
className: "bg-red-500/20 text-red-400 border-red-500/30",
},
};
// Different styles for node types
const nodeTypeConfig = {
page: {
bgClass: "bg-slate-800/90",
borderClass: "border-slate-600",
selectedBorderClass: "border-blue-500",
accentColor: "blue",
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
label: "Strona",
},
note: {
bgClass: "bg-amber-900/40",
borderClass: "border-amber-600/50",
selectedBorderClass: "border-amber-400",
accentColor: "amber",
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
),
label: "Notatka",
},
section: {
bgClass: "bg-purple-900/40",
borderClass: "border-purple-600/50",
selectedBorderClass: "border-purple-400",
accentColor: "purple",
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
),
label: "Sekcja",
},
};
function CustomNode({ data, selected, type }: NodeProps) {
const nodeData = data as PageNodeData;
const status = statusConfig[nodeData.status] || statusConfig.draft;
const nodeType = nodeTypeConfig[type as keyof typeof nodeTypeConfig] || nodeTypeConfig.page;
const hasContent = useMemo(() => {
return nodeData.hasAttachments || nodeData.hasLinks;
}, [nodeData.hasAttachments, nodeData.hasLinks]);
const handleColorClass = nodeType.accentColor === "amber"
? "!bg-amber-500"
: nodeType.accentColor === "purple"
? "!bg-purple-500"
: "!bg-blue-500";
return (
<div
className={`
${nodeType.bgClass} backdrop-blur-sm border-2 rounded-xl shadow-xl
min-w-[180px] max-w-[240px] transition-all duration-200
${selected ? `${nodeType.selectedBorderClass} shadow-${nodeType.accentColor}-500/25` : `${nodeType.borderClass} hover:border-opacity-80`}
`}
>
{/* Top Handle */}
<Handle
type="target"
position={Position.Top}
className={`!w-3 !h-3 ${handleColorClass} !border-2 !border-slate-800`}
/>
{/* Type indicator */}
<div className={`px-3 py-1.5 border-b border-white/10 flex items-center gap-2 text-${nodeType.accentColor}-400`}>
{nodeType.icon}
<span className="text-xs font-medium opacity-70">{nodeType.label}</span>
</div>
{/* Content */}
<div className="p-4">
<div className="flex items-start justify-between gap-2 mb-2">
<h3 className="font-semibold text-white text-sm leading-tight line-clamp-2">
{nodeData.title || "Nowa strona"}
</h3>
{hasContent && (
<div className="flex-shrink-0">
<svg
className={`w-4 h-4 text-${nodeType.accentColor}-400`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
/>
</svg>
</div>
)}
</div>
<Badge variant="outline" className={`text-xs ${status.className}`}>
{status.label}
</Badge>
</div>
{/* Bottom Handle */}
<Handle
type="source"
position={Position.Bottom}
className={`!w-3 !h-3 ${handleColorClass} !border-2 !border-slate-800`}
/>
</div>
);
}
export default memo(CustomNode);