You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
autoflow-web-console/src/components/atoms/organisms/WorklfowStepBaseDialog.vue

208 lines
5.5 KiB

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { storage } from "@/utils/storage";
import { storeToRefs } from "pinia";
import { useAutoflowStore } from "@/stores/autoflowStore";
import type { AxiosError } from "axios";
import { WorkflowStepService } from "@/components/service/management/workflowStepService";
const props = defineProps<{
editData: any;
mode: "create" | "edit";
}>();
const emit = defineEmits<{
(e: "close-modal"): void;
(e: "saved", value: any): void;
}>();
const isEdit = computed(() => props.mode === "edit");
const { projectId } = storeToRefs(useAutoflowStore());
const saving = ref(false);
const errorMsg = ref("");
// 폼 상태(필수 UI만: stepName, status)
type StepStatus = "Running" | "Success" | "Fail";
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";
}
onMounted(() => {
if (isEdit.value) hydrateFormFromEdit(props.editData);
});
watch(
() => props.editData,
(v) => {
if (isEdit.value) hydrateFormFromEdit(v);
},
);
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'
};
const regUserId = (() => {
try {
const authObj =
(typeof storage?.getAuth === "function" ? storage.getAuth() : null) ??
JSON.parse(localStorage.getItem("autoflow-auth") || "{}");
return (
authObj?.userInfo?.username ??
authObj?.userinfo?.username ??
authObj?.username ??
authObj?.userId ??
""
);
} catch {
return "";
}
})();
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 = {
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,
};
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);
emit("saved", data);
emit("close-modal");
} else {
const { data } = await WorkflowStepService.add(payload as any);
emit("saved", data);
emit("close-modal");
}
} catch (e) {
const err = e as AxiosError;
console.error("워크플로우 스텝 저장 실패:", err);
errorMsg.value = "저장에 실패했습니다. 잠시 후 다시 시도하세요.";
} finally {
saving.value = false;
}
}
function onEsc(e: KeyboardEvent) {
if (e.key === "Escape") emit("close-modal");
}
onMounted(() => window.addEventListener("keydown", onEsc));
onBeforeUnmount(() => window.removeEventListener("keydown", onEsc));
</script>
<template>
<v-card>
<!-- 타이틀: 동일 스타일 -->
<v-card-title
class="text-white font-weight-bold text-h6"
style="background-color: #1976d2"
>
{{ isEdit ? "Edit Workflow Step" : "Create Workflow Step" }}
</v-card-title>
<!-- 영역 -->
<v-card-text class="pa-6">
<div class="text-subtitle-1 font-weight-medium mb-4">
Workflow Step Information
</div>
<v-form @submit.prevent="submit">
<div class="mb-5">
<label class="text-subtitle-2 font-weight-medium mb-1 d-block">
Step Name
</label>
<v-text-field
v-model="form.stepName"
variant="outlined"
:disabled="saving"
dense
hide-details
required
/>
</div>
<div class="mb-5">
<label class="text-subtitle-2 font-weight-medium mb-1 d-block">
Status
</label>
<v-select
v-model="form.status"
:items="['Running', 'Success', 'Fail']"
variant="outlined"
:disabled="saving"
dense
hide-details
/>
</div>
<div v-if="errorMsg" class="mt-3 text-error">{{ errorMsg }}</div>
</v-form>
</v-card-text>
<!-- 액션 영역: 동일 버튼 배치/텍스트 -->
<v-card-actions class="justify-end" style="padding: 16px 24px">
<v-btn color="success" :loading="saving" @click="submit">
{{ isEdit ? "Update" : "Save" }}
</v-btn>
<v-btn
text
class="white--text"
:disabled="saving"
@click="$emit('close-modal')"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</template>