제어관리 개선판
This commit is contained in:
@@ -4,13 +4,12 @@ import React, { useEffect, useState } from "react";
|
||||
import { FlowComponent } from "@/types/screen-management";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AlertCircle, Loader2, ChevronDown, ChevronUp, History } from "lucide-react";
|
||||
import { getFlowById, getAllStepCounts, getStepDataList, moveBatchData, getFlowAuditLogs } from "@/lib/api/flow";
|
||||
import { AlertCircle, Loader2, ChevronUp, History } from "lucide-react";
|
||||
import { getFlowById, getAllStepCounts, getStepDataList, getFlowAuditLogs } from "@/lib/api/flow";
|
||||
import type { FlowDefinition, FlowStep, FlowAuditLog } from "@/types/flow";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { toast } from "sonner";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -55,8 +54,6 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
const [stepDataColumns, setStepDataColumns] = useState<string[]>([]);
|
||||
const [stepDataLoading, setStepDataLoading] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
||||
const [movingData, setMovingData] = useState(false);
|
||||
const [selectedNextStepId, setSelectedNextStepId] = useState<number | null>(null); // 선택된 다음 단계
|
||||
|
||||
// 오딧 로그 상태
|
||||
const [auditLogs, setAuditLogs] = useState<FlowAuditLog[]>([]);
|
||||
@@ -303,84 +300,6 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
onSelectedDataChange?.(selectedData, selectedStepId);
|
||||
};
|
||||
|
||||
// 현재 단계에서 가능한 다음 단계들 찾기
|
||||
const getNextSteps = (currentStepId: number) => {
|
||||
return connections
|
||||
.filter((conn) => conn.fromStepId === currentStepId)
|
||||
.map((conn) => steps.find((s) => s.id === conn.toStepId))
|
||||
.filter((step) => step !== undefined);
|
||||
};
|
||||
|
||||
// 다음 단계로 이동
|
||||
const handleMoveToNext = async (targetStepId?: number) => {
|
||||
if (!flowId || !selectedStepId || selectedRows.size === 0) return;
|
||||
|
||||
// 다음 단계 결정
|
||||
let nextStepId = targetStepId || selectedNextStepId;
|
||||
|
||||
if (!nextStepId) {
|
||||
const nextSteps = getNextSteps(selectedStepId);
|
||||
if (nextSteps.length === 0) {
|
||||
toast.error("다음 단계가 없습니다");
|
||||
return;
|
||||
}
|
||||
if (nextSteps.length === 1) {
|
||||
nextStepId = nextSteps[0].id;
|
||||
} else {
|
||||
toast.error("다음 단계를 선택해주세요");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedData = Array.from(selectedRows).map((index) => stepData[index]);
|
||||
|
||||
try {
|
||||
setMovingData(true);
|
||||
|
||||
// Primary Key 컬럼 추출 (첫 번째 컬럼 가정)
|
||||
const primaryKeyColumn = stepDataColumns[0];
|
||||
const dataIds = selectedData.map((data) => String(data[primaryKeyColumn]));
|
||||
|
||||
// 배치 이동 API 호출
|
||||
const response = await moveBatchData({
|
||||
flowId,
|
||||
fromStepId: selectedStepId,
|
||||
toStepId: nextStepId,
|
||||
dataIds,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(response.message || "데이터 이동에 실패했습니다");
|
||||
}
|
||||
|
||||
const nextStepName = steps.find((s) => s.id === nextStepId)?.stepName;
|
||||
toast.success(`${selectedRows.size}건의 데이터를 "${nextStepName}"(으)로 이동했습니다`);
|
||||
|
||||
// 선택 초기화
|
||||
setSelectedNextStepId(null);
|
||||
setSelectedRows(new Set());
|
||||
// 선택 초기화 전달
|
||||
onSelectedDataChange?.([], selectedStepId);
|
||||
|
||||
// 데이터 새로고침
|
||||
await handleStepClick(selectedStepId, steps.find((s) => s.id === selectedStepId)?.stepName || "");
|
||||
|
||||
// 건수 새로고침
|
||||
const countsResponse = await getAllStepCounts(flowId);
|
||||
if (countsResponse.success && countsResponse.data) {
|
||||
const countsMap: Record<number, number> = {};
|
||||
countsResponse.data.forEach((item: any) => {
|
||||
countsMap[item.stepId] = item.count;
|
||||
});
|
||||
setStepCounts(countsMap);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Failed to move data:", err);
|
||||
toast.error(err.message || "데이터 이동 중 오류가 발생했습니다");
|
||||
} finally {
|
||||
setMovingData(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 오딧 로그 로드
|
||||
const loadAuditLogs = async () => {
|
||||
@@ -716,93 +635,18 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
{selectedStepId !== null && (
|
||||
<div className="bg-muted/30 mt-4 w-full rounded-lg p-4 sm:mt-6 sm:rounded-xl sm:p-5 lg:mt-8 lg:p-6">
|
||||
{/* 헤더 */}
|
||||
<div className="mb-4 flex flex-col items-start justify-between gap-3 sm:mb-6 sm:flex-row sm:items-center">
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<div className="flex-1">
|
||||
<h4 className="text-foreground text-base font-semibold sm:text-lg">
|
||||
{steps.find((s) => s.id === selectedStepId)?.stepName}
|
||||
</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">총 {stepData.length}건의 데이터</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
|
||||
총 {stepData.length}건의 데이터
|
||||
{selectedRows.size > 0 && (
|
||||
<span className="text-primary ml-2 font-medium">({selectedRows.size}건 선택됨)</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{allowDataMove &&
|
||||
selectedRows.size > 0 &&
|
||||
(() => {
|
||||
const nextSteps = getNextSteps(selectedStepId);
|
||||
return nextSteps.length > 1 ? (
|
||||
// 다음 단계가 여러 개인 경우: 선택 UI 표시
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
|
||||
<Select
|
||||
value={selectedNextStepId?.toString() || ""}
|
||||
onValueChange={(value) => setSelectedNextStepId(Number(value))}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-full text-xs sm:h-10 sm:w-[180px] sm:text-sm">
|
||||
<SelectValue placeholder="이동할 단계 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{nextSteps.map((step) => (
|
||||
<SelectItem key={step.id} value={step.id.toString()}>
|
||||
{step.stepName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
onClick={() => handleMoveToNext()}
|
||||
disabled={movingData || !selectedNextStepId}
|
||||
className="h-8 gap-1 px-3 text-xs sm:h-10 sm:gap-2 sm:px-4 sm:text-sm"
|
||||
>
|
||||
{movingData ? (
|
||||
<>
|
||||
<Loader2 className="h-3 w-3 animate-spin sm:h-4 sm:w-4" />
|
||||
<span>이동 중...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="h-3 w-3 sm:h-4 sm:w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
||||
/>
|
||||
</svg>
|
||||
<span>이동 ({selectedRows.size})</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
// 다음 단계가 하나인 경우: 바로 이동 버튼만 표시
|
||||
<Button
|
||||
onClick={() => handleMoveToNext()}
|
||||
disabled={movingData}
|
||||
className="h-8 w-full gap-1 px-3 text-xs sm:h-10 sm:w-auto sm:gap-2 sm:px-4 sm:text-sm"
|
||||
>
|
||||
{movingData ? (
|
||||
<>
|
||||
<Loader2 className="h-3 w-3 animate-spin sm:h-4 sm:w-4" />
|
||||
<span className="hidden sm:inline">이동 중...</span>
|
||||
<span className="sm:hidden">이동중</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="h-3 w-3 sm:h-4 sm:w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
||||
/>
|
||||
</svg>
|
||||
<span className="hidden sm:inline">
|
||||
{nextSteps.length > 0 ? `${nextSteps[0].stepName}(으)로 이동` : "다음 단계로 이동"} (
|
||||
{selectedRows.size})
|
||||
</span>
|
||||
<span className="sm:hidden">다음 ({selectedRows.size})</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* 데이터 테이블 */}
|
||||
|
||||
Reference in New Issue
Block a user