restapi 버튼 동작

This commit is contained in:
kjs
2025-09-29 12:17:10 +09:00
parent cedb5e3ec3
commit c9afdec09f
19 changed files with 1910 additions and 81 deletions

View File

@@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
import { ComponentData } from "@/types/screen";
import { apiClient } from "@/lib/api/client";
import { ButtonDataflowConfigPanel } from "./ButtonDataflowConfigPanel";
import { ImprovedButtonControlConfigPanel } from "./ImprovedButtonControlConfigPanel";
interface ButtonConfigPanelProps {
component: ComponentData;
@@ -526,7 +527,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({ component,
<p className="mt-1 text-sm text-gray-600"> </p>
</div>
<ButtonDataflowConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
</div>
</div>
);

View File

@@ -37,7 +37,7 @@ interface RelationshipOption {
* 🔥 버튼 제어관리 설정 패널 (Phase 1: 간편 모드만)
*
* 성능 최적화를 위해 간편 모드만 구현:
* - 기존 관계 선택
* - 기존 관계 선택
* - "after" 타이밍만 지원
* - 복잡한 고급 모드는 Phase 2에서
*/
@@ -57,14 +57,14 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
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);
@@ -72,12 +72,12 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
}, [dataflowConfig.selectedDiagramId]);
/**
* 🔥 관계 목록 로딩 (캐시 활용)
* 🔥 관계 목록 로딩 (캐시 활용)
*/
const loadDiagrams = async () => {
try {
setDiagramsLoading(true);
console.log("🔍 데이터플로우 관계 목록 로딩...");
console.log("🔍 데이터플로우 관계 목록 로딩...");
const response = await apiClient.get("/test-button-dataflow/diagrams");
@@ -90,10 +90,10 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
}));
setDiagrams(diagramList);
console.log(`✅ 관계 ${diagramList.length}개 로딩 완료`);
console.log(`✅ 관계 ${diagramList.length}개 로딩 완료`);
}
} catch (error) {
console.error("❌ 관계 목록 로딩 실패:", error);
console.error("❌ 관계 목록 로딩 실패:", error);
setDiagrams([]);
} finally {
setDiagramsLoading(false);
@@ -106,7 +106,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
const loadRelationships = async (diagramId: number) => {
try {
setRelationshipsLoading(true);
console.log(`🔍 관계 ${diagramId} 관계 목록 로딩...`);
console.log(`🔍 관계 ${diagramId} 관계 목록 로딩...`);
const response = await apiClient.get(`/test-button-dataflow/diagrams/${diagramId}/relationships`);
@@ -216,7 +216,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
}
};
// 선택된 관계 정보
// 선택된 관계 정보
const selectedDiagram = diagrams.find((d) => d.id === dataflowConfig.selectedDiagramId);
const selectedRelationship = relationships.find((r) => r.id === dataflowConfig.selectedRelationshipId);
@@ -324,7 +324,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
<SelectValue placeholder="제어 모드를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="simple"> ( )</SelectItem>
<SelectItem value="simple"> ( )</SelectItem>
<SelectItem value="advanced" disabled>
()
</SelectItem>
@@ -335,11 +335,11 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
{/* 간편 모드 설정 */}
{(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>
<h4 className="text-sm font-medium text-gray-700"> </h4>
{/* 관계 선택 */}
{/* 관계 선택 */}
<div>
<Label className="text-xs"></Label>
<Label className="text-xs"></Label>
<Popover open={diagramOpen} onOpenChange={setDiagramOpen}>
<PopoverTrigger asChild>
<Button
@@ -357,7 +357,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
</Badge>
</div>
) : (
"관계를 선택하세요"
"관계를 선택하세요"
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
@@ -365,9 +365,9 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
<PopoverContent className="w-80 p-0">
<div className="p-2">
{diagramsLoading ? (
<div className="p-4 text-center text-sm text-gray-500"> ...</div>
<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="p-4 text-center text-sm text-gray-500"> </div>
) : (
<div className="max-h-60 overflow-y-auto">
{diagrams.map((diagram) => (
@@ -377,7 +377,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
className="h-auto w-full justify-start p-2"
onClick={() => {
onUpdateProperty("webTypeConfig.dataflowConfig.selectedDiagramId", diagram.id);
// 관계 변경 시 기존 관계 선택 초기화
// 관계 변경 시 기존 관계 선택 초기화
onUpdateProperty("webTypeConfig.dataflowConfig.selectedRelationshipId", null);
setDiagramOpen(false);
}}
@@ -435,7 +435,7 @@ export const ButtonDataflowConfigPanel: React.FC<ButtonDataflowConfigPanelProps>
<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">

View File

@@ -0,0 +1,281 @@
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
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 { Separator } from "@/components/ui/separator";
import {
Settings,
GitBranch,
Clock,
Zap,
Info
} from "lucide-react";
import { ComponentData, ButtonDataflowConfig } from "@/types/screen";
import { apiClient } from "@/lib/api/client";
interface ImprovedButtonControlConfigPanelProps {
component: ComponentData;
onUpdateProperty: (path: string, value: any) => void;
}
interface RelationshipOption {
id: string;
name: string;
sourceTable: string;
targetTable: string;
category: string;
}
/**
* 🔥 단순화된 버튼 제어 설정 패널
*
* 관계 실행만 지원:
* - 관계 선택 및 실행 타이밍 설정
* - 관계 내부에 데이터 저장/외부호출 로직 포함
*/
export const ImprovedButtonControlConfigPanel: React.FC<ImprovedButtonControlConfigPanelProps> = ({
component,
onUpdateProperty,
}) => {
const config = component.webTypeConfig || {};
const dataflowConfig = config.dataflowConfig || {};
// 🔥 State 관리
const [relationships, setRelationships] = useState<RelationshipOption[]>([]);
const [loading, setLoading] = useState(false);
// 🔥 관계 목록 로딩
useEffect(() => {
if (config.enableDataflowControl) {
loadRelationships();
}
}, [config.enableDataflowControl]);
/**
* 🔥 전체 관계 목록 로드 (관계도별 구분 없이)
*/
const loadRelationships = async () => {
try {
setLoading(true);
console.log("🔍 전체 관계 목록 로딩...");
const response = await apiClient.get("/test-button-dataflow/relationships/all");
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 || "데이터 흐름",
}));
setRelationships(relationshipList);
console.log(`✅ 관계 ${relationshipList.length}개 로딩 완료`);
}
} catch (error) {
console.error("❌ 관계 목록 로딩 실패:", error);
setRelationships([]);
} finally {
setLoading(false);
}
};
/**
* 🔥 관계 선택 핸들러
*/
const handleRelationshipSelect = (relationshipId: string) => {
const selectedRelationship = relationships.find(r => r.id === relationshipId);
if (selectedRelationship) {
onUpdateProperty("webTypeConfig.dataflowConfig.relationshipConfig", {
relationshipId: selectedRelationship.id,
relationshipName: selectedRelationship.name,
executionTiming: "after", // 기본값
contextData: {},
});
}
};
/**
* 🔥 제어 타입 변경 핸들러
*/
const handleControlTypeChange = (controlType: string) => {
// 기존 설정 초기화
onUpdateProperty("webTypeConfig.dataflowConfig", {
controlMode: controlType,
relationshipConfig: controlType === "relationship" ? undefined : null,
});
};
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 && (
<Card>
<CardHeader>
<CardTitle> </CardTitle>
</CardHeader>
<CardContent>
<Tabs
value={dataflowConfig.controlMode || "none"}
onValueChange={handleControlTypeChange}
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="none"> </TabsTrigger>
<TabsTrigger value="relationship"> </TabsTrigger>
</TabsList>
<TabsContent value="none" className="mt-4">
<div className="text-center py-8 text-gray-500">
<Zap className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p> .</p>
</div>
</TabsContent>
<TabsContent value="relationship" className="mt-4">
<RelationshipSelector
relationships={relationships}
selectedRelationshipId={dataflowConfig.relationshipConfig?.relationshipId}
onSelect={handleRelationshipSelect}
loading={loading}
/>
{dataflowConfig.relationshipConfig && (
<div className="mt-4 space-y-4">
<Separator />
<ExecutionTimingSelector
value={dataflowConfig.relationshipConfig.executionTiming}
onChange={(timing) =>
onUpdateProperty("webTypeConfig.dataflowConfig.relationshipConfig.executionTiming", timing)
}
/>
<div className="rounded bg-blue-50 p-3">
<div className="flex items-start space-x-2">
<Info className="h-4 w-4 text-blue-600 mt-0.5" />
<div className="text-xs text-blue-800">
<p className="font-medium"> :</p>
<p className="mt-1"> , .</p>
</div>
</div>
</div>
</div>
)}
</TabsContent>
</Tabs>
</CardContent>
</Card>
)}
</div>
);
};
/**
* 🔥 관계 선택 컴포넌트
*/
const RelationshipSelector: React.FC<{
relationships: RelationshipOption[];
selectedRelationshipId?: string;
onSelect: (relationshipId: string) => void;
loading: boolean;
}> = ({ relationships, selectedRelationshipId, onSelect, loading }) => {
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
<GitBranch className="h-4 w-4 text-blue-600" />
<Label> </Label>
</div>
<Select value={selectedRelationshipId || ""} onValueChange={onSelect}>
<SelectTrigger>
<SelectValue placeholder="관계를 선택하세요" />
</SelectTrigger>
<SelectContent>
{loading ? (
<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>
) : (
relationships.map((rel) => (
<SelectItem key={rel.id} value={rel.id}>
<div className="flex flex-col">
<span className="font-medium">{rel.name}</span>
<span className="text-xs text-muted-foreground">
{rel.sourceTable} {rel.targetTable}
</span>
</div>
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
);
};
/**
* 🔥 실행 타이밍 선택 컴포넌트
*/
const ExecutionTimingSelector: React.FC<{
value: string;
onChange: (timing: "before" | "after" | "replace") => void;
}> = ({ value, onChange }) => {
return (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Clock className="h-4 w-4 text-orange-600" />
<Label> </Label>
</div>
<Select value={value} onValueChange={onChange}>
<SelectTrigger>
<SelectValue placeholder="실행 타이밍을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="before">
<div className="flex flex-col">
<span className="font-medium">Before ( )</span>
<span className="text-xs text-muted-foreground"> </span>
</div>
</SelectItem>
<SelectItem value="after">
<div className="flex flex-col">
<span className="font-medium">After ( )</span>
<span className="text-xs text-muted-foreground"> </span>
</div>
</SelectItem>
<SelectItem value="replace">
<div className="flex flex-col">
<span className="font-medium">Replace ( )</span>
<span className="text-xs text-muted-foreground"> </span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
);
};