2025-10-24 15:40:08 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 검증 기능이 포함된 노드 래퍼
|
2026-03-10 18:30:18 +09:00
|
|
|
*
|
2025-10-24 15:40:08 +09:00
|
|
|
* 모든 노드에 경고/에러 아이콘을 표시하는 공통 래퍼
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { memo, ReactNode } from "react";
|
|
|
|
|
import { AlertTriangle, AlertCircle, Info } from "lucide-react";
|
|
|
|
|
import type { FlowValidation } from "@/lib/utils/flowValidation";
|
2026-03-10 18:30:18 +09:00
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
2025-10-24 15:40:08 +09:00
|
|
|
|
|
|
|
|
interface NodeWithValidationProps {
|
|
|
|
|
nodeId: string;
|
|
|
|
|
validations: FlowValidation[];
|
|
|
|
|
children: ReactNode;
|
|
|
|
|
onClick?: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
export const NodeWithValidation = memo(({ nodeId, validations, children, onClick }: NodeWithValidationProps) => {
|
|
|
|
|
// 이 노드와 관련된 검증 결과 필터링
|
|
|
|
|
const nodeValidations = validations.filter((v) => v.nodeId === nodeId || v.affectedNodes?.includes(nodeId));
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
// 가장 높은 심각도 결정
|
|
|
|
|
const hasError = nodeValidations.some((v) => v.severity === "error");
|
|
|
|
|
const hasWarning = nodeValidations.some((v) => v.severity === "warning");
|
|
|
|
|
const hasInfo = nodeValidations.some((v) => v.severity === "info");
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
if (nodeValidations.length === 0) {
|
|
|
|
|
return <>{children}</>;
|
|
|
|
|
}
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
// 심각도별 아이콘 및 색상
|
|
|
|
|
const getIconAndColor = () => {
|
|
|
|
|
if (hasError) {
|
|
|
|
|
return {
|
|
|
|
|
Icon: AlertCircle,
|
|
|
|
|
bgColor: "bg-red-500",
|
|
|
|
|
textColor: "text-red-700",
|
|
|
|
|
borderColor: "border-red-500",
|
|
|
|
|
hoverBgColor: "hover:bg-red-600",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (hasWarning) {
|
2025-10-24 15:40:08 +09:00
|
|
|
return {
|
2026-03-10 18:30:18 +09:00
|
|
|
Icon: AlertTriangle,
|
|
|
|
|
bgColor: "bg-yellow-500",
|
|
|
|
|
textColor: "text-yellow-700",
|
|
|
|
|
borderColor: "border-yellow-500",
|
|
|
|
|
hoverBgColor: "hover:bg-yellow-600",
|
2025-10-24 15:40:08 +09:00
|
|
|
};
|
2026-03-10 18:30:18 +09:00
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
Icon: Info,
|
|
|
|
|
bgColor: "bg-blue-500",
|
|
|
|
|
textColor: "text-blue-700",
|
|
|
|
|
borderColor: "border-blue-500",
|
|
|
|
|
hoverBgColor: "hover:bg-blue-600",
|
2025-10-24 15:40:08 +09:00
|
|
|
};
|
2026-03-10 18:30:18 +09:00
|
|
|
};
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
const { Icon, bgColor, textColor, borderColor, hoverBgColor } = getIconAndColor();
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
return (
|
|
|
|
|
<div className="relative" onClick={onClick}>
|
|
|
|
|
{children}
|
2025-10-24 15:40:08 +09:00
|
|
|
|
2026-03-10 18:30:18 +09:00
|
|
|
{/* 경고 배지 */}
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<div
|
|
|
|
|
className={`absolute -top-2 -right-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full ${bgColor} ${hoverBgColor} shadow-lg transition-all hover:scale-110`}
|
2025-10-24 15:40:08 +09:00
|
|
|
>
|
2026-03-10 18:30:18 +09:00
|
|
|
<Icon className="h-3.5 w-3.5 text-white" />
|
|
|
|
|
{nodeValidations.length > 1 && (
|
|
|
|
|
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-white text-[10px] font-bold shadow-sm">
|
|
|
|
|
{nodeValidations.length}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent side="right" className="max-w-xs border-0 p-0">
|
|
|
|
|
<div className={`rounded-lg border-2 ${borderColor} bg-white p-3 shadow-lg`}>
|
|
|
|
|
<div className="mb-2 flex items-center gap-2">
|
|
|
|
|
<Icon className={`h-4 w-4 ${textColor}`} />
|
|
|
|
|
<span className="font-semibold text-gray-900">
|
|
|
|
|
{hasError ? "오류" : hasWarning ? "경고" : "정보"} ({nodeValidations.length})
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
{nodeValidations.map((validation, index) => (
|
|
|
|
|
<div key={index} className="rounded border-l-2 border-gray-300 bg-gray-50 p-2">
|
|
|
|
|
<div className="mb-1 text-xs font-medium text-gray-500">{validation.type}</div>
|
|
|
|
|
<div className="text-sm text-gray-700">{validation.message}</div>
|
|
|
|
|
{validation.affectedNodes && validation.affectedNodes.length > 1 && (
|
|
|
|
|
<div className="mt-1 text-xs text-gray-500">
|
|
|
|
|
영향받는 노드: {validation.affectedNodes.length}개
|
2025-10-24 15:40:08 +09:00
|
|
|
</div>
|
2026-03-10 18:30:18 +09:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2025-10-24 15:40:08 +09:00
|
|
|
</div>
|
2026-03-10 18:30:18 +09:00
|
|
|
</div>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|
2025-10-24 15:40:08 +09:00
|
|
|
|
|
|
|
|
NodeWithValidation.displayName = "NodeWithValidation";
|