From efc6d0651ecff442014a47615d777d901960df82 Mon Sep 17 00:00:00 2001 From: jschoi Date: Fri, 19 Sep 2025 09:55:30 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20workflow,=20workflowstep,=20project=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 2 +- .../atoms/organisms/WorkflowsBaseDialog.vue | 53 ++++-- .../organisms/WorklfowStepBaseDialog.vue | 178 +++++++++++------- .../service/management/workflowService.ts | 1 - .../{ => templates}/home/ListComponent.vue | 58 +++++- .../{Project => projects}/ListComponent.vue | 0 .../templates/stepconfig/ListComponent.vue | 32 ++-- .../templates/stepconfig/ViewComponent.vue | 139 +++++++++++--- src/pages/HomeView.vue | 2 +- src/pages/ProjectView.vue | 2 +- src/utils/menuUtils.js | 6 - 11 files changed, 337 insertions(+), 136 deletions(-) rename src/components/{ => templates}/home/ListComponent.vue (83%) rename src/components/templates/{Project => projects}/ListComponent.vue (100%) diff --git a/components.d.ts b/components.d.ts index bc7634c..41a80f1 100644 --- a/components.d.ts +++ b/components.d.ts @@ -25,7 +25,7 @@ declare module 'vue' { IconModifyBtn: typeof import('./src/components/atoms/button/IconModifyBtn.vue')['default'] IconSettingBtn: typeof import('./src/components/atoms/button/IconSettingBtn.vue')['default'] LayoutComponent: typeof import('./src/components/common/LayoutComponent.vue')['default'] - ListComponent: typeof import('./src/components/home/ListComponent.vue')['default'] + ListComponent: typeof import('./src/components/templates/Datasets/ListComponent.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SidebarHeader: typeof import('./src/components/common/SidebarHeader.vue')['default'] diff --git a/src/components/atoms/organisms/WorkflowsBaseDialog.vue b/src/components/atoms/organisms/WorkflowsBaseDialog.vue index 2b4c3c3..317a652 100644 --- a/src/components/atoms/organisms/WorkflowsBaseDialog.vue +++ b/src/components/atoms/organisms/WorkflowsBaseDialog.vue @@ -74,8 +74,8 @@ const nowLocalIso = (): string => { async function submit() { errorMsg.value = ""; - const name = form.value.name.trim(); + const name = form.value.name.trim(); if (!name) { errorMsg.value = "Workflow Name은 필수입니다."; return; @@ -85,7 +85,6 @@ async function submit() { const authObj = (typeof storage?.getAuth === "function" ? storage.getAuth() : null) ?? JSON.parse(localStorage.getItem("autoflow-auth") || "{}"); - const regUserId = authObj?.userInfo?.username ?? authObj?.userinfo?.username ?? @@ -98,36 +97,58 @@ async function submit() { return; } + if (!projectId.value) { + errorMsg.value = "프로젝트가 선택되지 않았습니다."; + return; + } + const now = nowLocalIso(); - const payload: Workflow = { - workflowName: name, - workflowDescription: form.value.description?.trim() || "", - uploadYn: "Y", - regUserId, - regDt: now, - modDt: now, - projectId: projectId.value, - }; try { saving.value = true; if (isEdit.value) { - // 수정: id 추출(행에서 넘겨준 deviceKey 또는 id 지원) + // ===== 수정 ===== const rawId = props.editData?.id ?? props.editData?.deviceKey; const id = Number(rawId); - if (!id) { errorMsg.value = "수정할 ID가 없습니다."; return; } - const { data } = await WorkflowService.update(id, payload); + // 기존 레코드 먼저 조회해서 보존해야 할 필드 채우기 + const viewRes = await WorkflowService.view(id); + const current = (viewRes?.data ?? viewRes) || {}; + + const updatePayload: any = { + id, + workflowName: name, + workflowDescription: form.value.description?.trim() || "", + uploadYn: current.uploadYn ?? "Y", + // ← 기존 값 유지 (null로 덮어쓰지 않도록) + regUserId: current.regUserId ?? regUserId, + regDt: current.regDt ?? now, + projectId: current.projectId ?? projectId.value, + // ← 수정 시각만 갱신 + modDt: now, + }; + + const { data } = await WorkflowService.update(id, updatePayload); emit("saved", data); emit("close-modal"); } else { - // 생성 - const { data } = await WorkflowService.add(payload); + // ===== 생성 ===== + const createPayload: any = { + workflowName: name, + workflowDescription: form.value.description?.trim() || "", + uploadYn: "Y", + regUserId, // 생성자 + regDt: now, // 생성 시각 + projectId: projectId.value, + // modDt는 보내지 않아도 됨 (서버에서 필요하면 세팅) + }; + + const { data } = await WorkflowService.add(createPayload); emit("saved", data); emit("close-modal"); } diff --git a/src/components/atoms/organisms/WorklfowStepBaseDialog.vue b/src/components/atoms/organisms/WorklfowStepBaseDialog.vue index ea509cd..de23b2c 100644 --- a/src/components/atoms/organisms/WorklfowStepBaseDialog.vue +++ b/src/components/atoms/organisms/WorklfowStepBaseDialog.vue @@ -5,12 +5,9 @@ import { storeToRefs } from "pinia"; import { useAutoflowStore } from "@/stores/autoflowStore"; import type { AxiosError } from "axios"; import { WorkflowStepService } from "@/components/service/management/workflowStepService"; +import { WorkflowService } from "@/components/service/management/workflowService"; // ⬅️ 추가 -const props = defineProps<{ - editData: any; - mode: "create" | "edit"; -}>(); - +const props = defineProps<{ editData: any; mode: "create" | "edit" }>(); const emit = defineEmits<{ (e: "close-modal"): void; (e: "saved", value: any): void; @@ -22,21 +19,71 @@ const { projectId } = storeToRefs(useAutoflowStore()); const saving = ref(false); const errorMsg = ref(""); -// 폼 상태(필수 UI만: stepName, status) +// ===== 워크플로우 드롭다운 상태 ===== +const workflowsLoading = ref(false); +const workflowOptions = ref>([]); +const selectedWorkflowId = ref(null); + +async function fetchWorkflows() { + try { + workflowsLoading.value = true; + // 1) /api/workflows (배열) or 2) search 결과(content) 모두 대응 + const res = + (await (WorkflowService as any).list?.()) ?? + (await (WorkflowService as any).search?.({ page: 0, size: 1000 })); + const raw = res?.data ?? res; + const list = Array.isArray(raw?.content) + ? raw.content + : Array.isArray(raw) + ? raw + : []; + + const filtered = projectId.value + ? list.filter((w: any) => Number(w.projectId) === Number(projectId.value)) + : list; + + workflowOptions.value = filtered.map((w: any) => ({ + title: + (w.workflowName || w.name || `Workflow #${w.id}`) + + (w.version ? ` (v${w.version})` : ""), + value: Number(w.id), + })); + + // 수정 모드에서 값 복원 + if (isEdit.value && selectedWorkflowId.value == null) { + const wid = + props.editData?.workflowStepId ?? + props.editData?.workflowId ?? + props.editData?.workflow?.id ?? + null; + if (wid != null) selectedWorkflowId.value = Number(wid); + } + } catch (e) { + console.error("[Dialog] 워크플로우 목록 조회 실패:", e); + workflowOptions.value = []; + } finally { + workflowsLoading.value = false; + } +} + +// ===== 폼 ===== type StepStatus = "Running" | "Success" | "Fail"; -const form = ref({ - stepName: "", - status: "Running" as StepStatus, -}); +const form = ref({ stepName: "", status: "Running" as StepStatus }); function hydrateFormFromEdit(data: any) { if (!data) return; form.value.stepName = data?.stepName ?? data?.name ?? ""; form.value.status = (data?.status as StepStatus) ?? "Running"; + + // 드롭다운 값 복원(가능한 키 모두 시도) + const wid = + data?.workflowStepId ?? data?.workflowId ?? data?.workflow?.id ?? null; + selectedWorkflowId.value = wid != null ? Number(wid) : null; } onMounted(() => { if (isEdit.value) hydrateFormFromEdit(props.editData); + fetchWorkflows(); }); watch( () => props.editData, @@ -44,10 +91,11 @@ watch( if (isEdit.value) hydrateFormFromEdit(v); }, ); +watch(projectId, () => fetchWorkflows()); const nowLocalIso = (): string => { const t = new Date(Date.now() - new Date().getTimezoneOffset() * 60000); - return t.toISOString().slice(0, 23); // 'YYYY-MM-DDTHH:mm:ss.SSS' + return t.toISOString().slice(0, 23); }; const regUserId = (() => { @@ -55,7 +103,6 @@ const regUserId = (() => { const authObj = (typeof storage?.getAuth === "function" ? storage.getAuth() : null) ?? JSON.parse(localStorage.getItem("autoflow-auth") || "{}"); - return ( authObj?.userInfo?.username ?? authObj?.userinfo?.username ?? @@ -71,62 +118,42 @@ const regUserId = (() => { async function submit() { errorMsg.value = ""; - // 기본 검증 + // 검증 const stepName = form.value.stepName.trim(); - if (!stepName) { - errorMsg.value = "Step Name은 필수입니다."; - return; - } - if (!regUserId) { - errorMsg.value = "로그인 사용자 정보를 찾을 수 없습니다."; - return; - } - if (!projectId.value) { - errorMsg.value = "프로젝트가 선택되지 않았습니다."; - return; - } - - // 백엔드 엔티티에 맞춘 payload - const now = nowLocalIso(); - const payload = { + if (!stepName) return (errorMsg.value = "Step Name은 필수입니다."); + if (!regUserId) + return (errorMsg.value = "로그인 사용자 정보를 찾을 수 없습니다."); + if (!projectId.value) + return (errorMsg.value = "프로젝트가 선택되지 않았습니다."); + if (!selectedWorkflowId.value) + return (errorMsg.value = "Workflow를 선택해주세요."); // ⬅️ 중요 + + // 백엔드 스키마에 맞춤 (NOT NULL 컬럼들 포함) + const payload: any = { stepName, - status: form.value.status, // not null - regUserId, // not null - regDt: now, // not null (스웨거 요구) - version: 1, // not null (초기값) - projectId: projectId.value, // not null - - // 선택 항목은 이번 버전에서 제외(pipelineId 없음 = 빈값/누락) - // pipelineId: undefined, - // startTime: undefined, - // endTime: undefined, - // logPath: undefined, + status: form.value.status, + regUserId, + regDt: nowLocalIso(), + version: 1, + projectId: projectId.value, + workflowStepId: selectedWorkflowId.value, // ⬅️ 이게 없어서 500났었음 }; try { saving.value = true; - if (isEdit.value) { - // 수정: id 추출(행에서 넘어온 deviceKey 또는 id 허용) const rawId = props.editData?.id ?? props.editData?.deviceKey; const id = Number(rawId); - - if (!id) { - errorMsg.value = "수정할 ID가 없습니다."; - return; - } - - const { data } = await WorkflowStepService.update(id, payload as any); + if (!id) return (errorMsg.value = "수정할 ID가 없습니다."); + const { data } = await WorkflowStepService.update(id, payload); emit("saved", data); - emit("close-modal"); } else { - const { data } = await WorkflowStepService.add(payload as any); + const { data } = await WorkflowStepService.add(payload); emit("saved", data); - emit("close-modal"); } + emit("close-modal"); } catch (e) { - const err = e as AxiosError; - console.error("워크플로우 스텝 저장 실패:", err); + console.error("워크플로우 스텝 저장 실패:", e as AxiosError); errorMsg.value = "저장에 실패했습니다. 잠시 후 다시 시도하세요."; } finally { saving.value = false; @@ -142,7 +169,6 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onEsc)); diff --git a/src/components/service/management/workflowService.ts b/src/components/service/management/workflowService.ts index 2c728d9..b0cf7c8 100644 --- a/src/components/service/management/workflowService.ts +++ b/src/components/service/management/workflowService.ts @@ -10,7 +10,6 @@ export const WorkflowService = { getAll: () => { return request.get("/api/workflows", {}); }, - delete: (id: Number) => { return request.delete(`/api/workflows/${id}`, {}); }, diff --git a/src/components/home/ListComponent.vue b/src/components/templates/home/ListComponent.vue similarity index 83% rename from src/components/home/ListComponent.vue rename to src/components/templates/home/ListComponent.vue index 800484a..4ef56ea 100644 --- a/src/components/home/ListComponent.vue +++ b/src/components/templates/home/ListComponent.vue @@ -1,8 +1,11 @@