ERP-node/frontend/components/screen/config-panels/ButtonDataflowConfigPanel.tsx

436 lines
17 KiB
TypeScript
Raw Normal View History

2025-09-18 10:05:50 +09:00
"use client";
import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Check, ChevronsUpDown, Search, Info, Settings } from "lucide-react";
import { cn } from "@/lib/utils";
import { ComponentData, ButtonDataflowConfig } from "@/types/screen";
import { apiClient } from "@/lib/api/client";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
interface ButtonDataflowConfigPanelProps {
component: ComponentData;
onUpdateProperty: (path: string, value: any) => void;
}
interface DiagramOption {
id: number;
name: string;
description?: string;
relationshipCount: number;
}
interface RelationshipOption {
id: string;
name: string;
sourceTable: string;
targetTable: string;
category: string;
}
/**
* 🔥 (Phase 1: 간편 )
*
* :
* -
* - "after"
* - Phase 2
*/
export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps> = ({
component,
onUpdateProperty,
}) => {
const config = component.webTypeConfig || {};
const dataflowConfig = config.dataflowConfig || {};
// 🔥 State 관리
const [diagrams, setDiagrams] = useState<DiagramOption[]>([]);
const [relationships, setRelationships] = useState<RelationshipOption[]>([]);
const [diagramsLoading, setDiagramsLoading] = useState(false);
const [relationshipsLoading, setRelationshipsLoading] = useState(false);
const [diagramOpen, setDiagramOpen] = useState(false);
const [relationshipOpen, setRelationshipOpen] = useState(false);
const [previewData, setPreviewData] = useState<any>(null);
// 🔥 관계도 목록 로딩
useEffect(() => {
if (config.enableDataflowControl) {
loadDiagrams();
}
}, [config.enableDataflowControl]);
// 🔥 관계도 변경 시 관계 목록 로딩
useEffect(() => {
if (dataflowConfig.selectedDiagramId) {
loadRelationships(dataflowConfig.selectedDiagramId);
}
}, [dataflowConfig.selectedDiagramId]);
/**
* 🔥 ( )
*/
const loadDiagrams = async () => {
try {
setDiagramsLoading(true);
console.log("🔍 데이터플로우 관계도 목록 로딩...");
const response = await apiClient.get("/test-button-dataflow/diagrams");
if (response.data.success && Array.isArray(response.data.data)) {
const diagramList = response.data.data.map((diagram: any) => ({
id: diagram.diagram_id,
name: diagram.diagram_name,
description: diagram.description,
relationshipCount: diagram.relationships?.relationships?.length || 0,
}));
setDiagrams(diagramList);
console.log(`✅ 관계도 ${diagramList.length}개 로딩 완료`);
}
} catch (error) {
console.error("❌ 관계도 목록 로딩 실패:", error);
setDiagrams([]);
} finally {
setDiagramsLoading(false);
}
};
/**
* 🔥
*/
const loadRelationships = async (diagramId: number) => {
try {
setRelationshipsLoading(true);
console.log(`🔍 관계도 ${diagramId} 관계 목록 로딩...`);
const response = await apiClient.get(`/test-button-dataflow/diagrams/${diagramId}/relationships`);
if (response.data.success && Array.isArray(response.data.data)) {
const relationshipList = response.data.data.map((rel: any) => ({
id: rel.id,
name: rel.name || `${rel.sourceTable}${rel.targetTable}`,
sourceTable: rel.sourceTable,
targetTable: rel.targetTable,
category: rel.category || "data-save",
}));
setRelationships(relationshipList);
console.log(`✅ 관계 ${relationshipList.length}개 로딩 완료`);
}
} catch (error) {
console.error("❌ 관계 목록 로딩 실패:", error);
setRelationships([]);
} finally {
setRelationshipsLoading(false);
}
};
/**
* 🔥
*/
const loadRelationshipPreview = async () => {
if (!dataflowConfig.selectedDiagramId || !dataflowConfig.selectedRelationshipId) {
return;
}
try {
const response = await apiClient.get(
`/test-button-dataflow/diagrams/${dataflowConfig.selectedDiagramId}/relationships/${dataflowConfig.selectedRelationshipId}/preview`,
);
if (response.data.success) {
setPreviewData(response.data.data);
}
} catch (error) {
console.error("❌ 관계 미리보기 로딩 실패:", error);
}
};
// 선택된 관계가 변경되면 미리보기 로딩
useEffect(() => {
if (dataflowConfig.selectedRelationshipId) {
loadRelationshipPreview();
}
}, [dataflowConfig.selectedRelationshipId]);
/**
*
*/
const getActionDisplayName = (actionType: string): string => {
const displayNames: Record<string, string> = {
save: "저장",
delete: "삭제",
edit: "수정",
add: "추가",
search: "검색",
reset: "초기화",
submit: "제출",
close: "닫기",
popup: "팝업",
navigate: "페이지 이동",
};
return displayNames[actionType] || actionType;
};
/**
* ()
*/
const getTimingDescription = (timing: string): string => {
switch (timing) {
case "before":
return "액션 실행 전 제어관리";
case "after":
return "액션 실행 후 제어관리";
case "replace":
return "제어관리로 완전 대체";
default:
return "";
}
};
// 선택된 관계도 정보
const selectedDiagram = diagrams.find((d) => d.id === dataflowConfig.selectedDiagramId);
const selectedRelationship = relationships.find((r) => r.id === dataflowConfig.selectedRelationshipId);
return (
<div className="space-y-6">
{/* 🔥 제어관리 활성화 스위치 */}
<div className="flex items-center justify-between rounded-lg border bg-blue-50 p-4">
<div className="flex items-center space-x-2">
<Settings className="h-4 w-4 text-blue-600" />
<div>
<Label className="text-sm font-medium">📊 </Label>
<p className="mt-1 text-xs text-gray-600"> </p>
</div>
</div>
<Switch
checked={config.enableDataflowControl || false}
onCheckedChange={(checked) => onUpdateProperty("webTypeConfig.enableDataflowControl", checked)}
/>
</div>
{/* 🔥 제어관리가 활성화된 경우에만 설정 표시 */}
{config.enableDataflowControl && (
<div className="space-y-6 border-l-2 border-blue-200 pl-4">
{/* 현재 액션 정보 (간소화) */}
<div className="rounded bg-gray-100 p-2">
<p className="text-xs text-gray-600">
<strong>{getActionDisplayName(config.actionType || "save")}</strong>
</p>
</div>
{/* 실행 타이밍 선택 (Phase 1: after만 지원) */}
<div>
<Label className="text-sm font-medium"> </Label>
<Select
value={config.dataflowTiming || "after"}
onValueChange={(value) => onUpdateProperty("webTypeConfig.dataflowTiming", value)}
>
<SelectTrigger className="mt-2">
<SelectValue placeholder="실행 타이밍을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="after"> ()</SelectItem>
<SelectItem value="before" disabled>
()
</SelectItem>
<SelectItem value="replace" disabled>
()
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 제어 모드 선택 (Phase 1: simple만 지원) */}
<div>
<Label className="text-sm font-medium"> </Label>
<Select
value={dataflowConfig.controlMode || "simple"}
onValueChange={(value) => onUpdateProperty("webTypeConfig.dataflowConfig.controlMode", value)}
>
<SelectTrigger className="mt-2">
<SelectValue placeholder="제어 모드를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="simple"> ( )</SelectItem>
<SelectItem value="advanced" disabled>
()
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 간편 모드 설정 */}
{(dataflowConfig.controlMode === "simple" || !dataflowConfig.controlMode) && (
<div className="space-y-3 rounded border bg-gray-50 p-3">
<h4 className="text-sm font-medium text-gray-700"> </h4>
{/* 관계도 선택 */}
<div>
<Label className="text-xs"></Label>
<Popover open={diagramOpen} onOpenChange={setDiagramOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={diagramOpen}
className="mt-2 w-full justify-between"
disabled={diagramsLoading}
>
{selectedDiagram ? (
<div className="flex items-center space-x-2">
<span>{selectedDiagram.name}</span>
<Badge variant="secondary" className="text-xs">
{selectedDiagram.relationshipCount}
</Badge>
</div>
) : (
"관계도를 선택하세요"
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 p-0">
<div className="p-2">
{diagramsLoading ? (
<div className="p-4 text-center text-sm text-gray-500"> ...</div>
) : diagrams.length === 0 ? (
<div className="p-4 text-center text-sm text-gray-500"> </div>
) : (
<div className="max-h-60 overflow-y-auto">
{diagrams.map((diagram) => (
<Button
key={diagram.id}
variant="ghost"
className="h-auto w-full justify-start p-2"
onClick={() => {
onUpdateProperty("webTypeConfig.dataflowConfig.selectedDiagramId", diagram.id);
// 관계도 변경 시 기존 관계 선택 초기화
onUpdateProperty("webTypeConfig.dataflowConfig.selectedRelationshipId", null);
setDiagramOpen(false);
}}
>
<div className="flex w-full items-center space-x-2">
<Check
className={cn(
"h-4 w-4",
selectedDiagram?.id === diagram.id ? "opacity-100" : "opacity-0",
)}
/>
<div className="flex-1 text-left">
<div className="font-medium">{diagram.name}</div>
<div className="text-xs text-gray-500">{diagram.relationshipCount} </div>
</div>
</div>
</Button>
))}
</div>
)}
</div>
</PopoverContent>
</Popover>
</div>
{/* 관계 선택 */}
{dataflowConfig.selectedDiagramId && (
<div>
<Label className="text-xs"></Label>
<Popover open={relationshipOpen} onOpenChange={setRelationshipOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={relationshipOpen}
className="mt-2 w-full justify-between"
disabled={relationshipsLoading}
>
{selectedRelationship ? (
<div className="flex items-center space-x-2">
<span>{selectedRelationship.name}</span>
<Badge variant="outline" className="text-xs">
{selectedRelationship.category}
</Badge>
</div>
) : (
"관계를 선택하세요"
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-96 p-0">
<div className="p-2">
{relationshipsLoading ? (
<div className="p-4 text-center text-sm text-gray-500"> ...</div>
) : relationships.length === 0 ? (
<div className="p-4 text-center text-sm text-gray-500">
</div>
) : (
<div className="max-h-60 overflow-y-auto">
{relationships.map((relationship) => (
<Button
key={relationship.id}
variant="ghost"
className="h-auto w-full justify-start p-2"
onClick={() => {
onUpdateProperty(
"webTypeConfig.dataflowConfig.selectedRelationshipId",
relationship.id,
);
setRelationshipOpen(false);
}}
>
<div className="flex w-full items-center space-x-2">
<Check
className={cn(
"h-4 w-4",
selectedRelationship?.id === relationship.id ? "opacity-100" : "opacity-0",
)}
/>
<div className="flex-1 text-left">
<div className="font-medium">{relationship.name}</div>
<div className="text-xs text-gray-500">
{relationship.sourceTable} {relationship.targetTable}
</div>
<Badge variant="outline" className="mt-1 text-xs">
{relationship.category}
</Badge>
</div>
</div>
</Button>
))}
</div>
)}
</div>
</PopoverContent>
</Popover>
</div>
)}
{/* 선택된 관계 간단 정보 */}
{selectedRelationship && (
<div className="mt-2 rounded border bg-blue-50 p-2">
<p className="text-xs text-blue-700">
<strong>{selectedRelationship.sourceTable}</strong> {" "}
<strong>{selectedRelationship.targetTable}</strong>
{previewData && (
<span className="ml-2">
( {previewData.conditionsCount || 0}, {previewData.actionsCount || 0})
</span>
)}
</p>
</div>
)}
</div>
)}
</div>
)}
</div>
);
};