|
|
|
|
@ -3,15 +3,31 @@ import IconArrowDown from "@/components/atoms/button/IconArrowDown.vue";
|
|
|
|
|
import IconArrowUp from "@/components/atoms/button/IconArrowUp.vue";
|
|
|
|
|
import IconDeleteBtn from "@/components/atoms/button/IconDeleteBtn.vue";
|
|
|
|
|
import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
|
|
|
|
|
import { ref, watch } from "vue";
|
|
|
|
|
import { computed, onBeforeUnmount, onMounted, watch, ref } from "vue";
|
|
|
|
|
import { AutoflowService } from "@/components/service/management/AutoflowService";
|
|
|
|
|
import { storage } from "@/utils/storage";
|
|
|
|
|
import { Workflow } from "@/components/models/management/Autoflow";
|
|
|
|
|
import type { Workflow } from "@/components/models/management/Autoflow";
|
|
|
|
|
import { storeToRefs } from "pinia";
|
|
|
|
|
import { useAutoflowStore } from "@/stores/autoflowStore";
|
|
|
|
|
|
|
|
|
|
const { projectId } = storeToRefs(useAutoflowStore());
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
editData?: any;
|
|
|
|
|
mode?: "create" | "edit";
|
|
|
|
|
userOption?: any[];
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
(e: "close-modal"): void;
|
|
|
|
|
(e: "saved", value: any): void;
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const isEdit = computed(() => props.mode === "edit");
|
|
|
|
|
|
|
|
|
|
const saving = ref(false);
|
|
|
|
|
const errorMsg = ref("");
|
|
|
|
|
const { projectId } = storeToRefs(useAutoflowStore());
|
|
|
|
|
|
|
|
|
|
const steps = ref([
|
|
|
|
|
{ order: 1, stepName: "Data Load", type: "DataPrep", status: "Configured" },
|
|
|
|
|
{
|
|
|
|
|
@ -28,38 +44,49 @@ const steps = ref([
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
editData: Object,
|
|
|
|
|
mode: String,
|
|
|
|
|
userOption: Array,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(["handle-data", "close-modal", "saved"]);
|
|
|
|
|
|
|
|
|
|
const visible = ref(true);
|
|
|
|
|
|
|
|
|
|
/** form state */
|
|
|
|
|
const form = ref({
|
|
|
|
|
name: "",
|
|
|
|
|
description: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** props.editData -> form 바인딩 */
|
|
|
|
|
function hydrateFormFromEdit(data: any) {
|
|
|
|
|
if (!data) return;
|
|
|
|
|
form.value.name = data.workflowName ?? data.name ?? "";
|
|
|
|
|
form.value.description = data.workflowDescription ?? data.description ?? "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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); // 밀리초까지 포함, 끝의 'Z' 제거
|
|
|
|
|
return t.toISOString().slice(0, 23);
|
|
|
|
|
};
|
|
|
|
|
const now = nowLocalIso();
|
|
|
|
|
const submit = async () => {
|
|
|
|
|
|
|
|
|
|
async function submit() {
|
|
|
|
|
errorMsg.value = "";
|
|
|
|
|
if (!form.value.name.trim()) {
|
|
|
|
|
const name = form.value.name.trim();
|
|
|
|
|
|
|
|
|
|
if (!name) {
|
|
|
|
|
errorMsg.value = "Workflow Name은 필수입니다.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 로그인 사용자
|
|
|
|
|
const authObj =
|
|
|
|
|
(typeof storage?.getAuth === "function" ? storage.getAuth() : null) ??
|
|
|
|
|
JSON.parse(localStorage.getItem("autoflow-auth") || "{}");
|
|
|
|
|
|
|
|
|
|
// 2) username 추출 (userinfo / userInfo 모두 대응)
|
|
|
|
|
const regUserId =
|
|
|
|
|
authObj?.userInfo?.username ??
|
|
|
|
|
authObj?.userinfo?.username ??
|
|
|
|
|
@ -72,8 +99,9 @@ const submit = async () => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const now = nowLocalIso();
|
|
|
|
|
const payload: Workflow = {
|
|
|
|
|
workflowName: form.value.name.trim(),
|
|
|
|
|
workflowName: name,
|
|
|
|
|
workflowDescription: form.value.description?.trim() || "",
|
|
|
|
|
uploadYn: "Y",
|
|
|
|
|
regUserId,
|
|
|
|
|
@ -84,32 +112,57 @@ const submit = async () => {
|
|
|
|
|
|
|
|
|
|
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 AutoflowService.update(id, payload);
|
|
|
|
|
emit("saved", data);
|
|
|
|
|
emit("close-modal");
|
|
|
|
|
} else {
|
|
|
|
|
// 생성
|
|
|
|
|
const { data } = await AutoflowService.add(payload);
|
|
|
|
|
emit("saved", data);
|
|
|
|
|
emit("close-modal");
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("워크플로우 저장 실패:", e);
|
|
|
|
|
errorMsg.value = "저장에 실패했습니다. 잠시 후 다시 시도하세요.";
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** ESC로 닫기 */
|
|
|
|
|
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 class="rounded-lg overflow-hidden">
|
|
|
|
|
<!-- 타이틀 영역 -->
|
|
|
|
|
<v-card>
|
|
|
|
|
<!-- 타이틀 -->
|
|
|
|
|
<v-card-title
|
|
|
|
|
class="text-white font-weight-bold text-h6"
|
|
|
|
|
style="background-color: #1976d2"
|
|
|
|
|
>
|
|
|
|
|
Create Workflow
|
|
|
|
|
{{ isEdit ? "Edit Workflow" : "Create Workflow" }}
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
|
|
|
|
<v-card-text class="pa-6">
|
|
|
|
|
<div class="text-subtitle-1 font-weight-medium mb-4">
|
|
|
|
|
Workflow Information
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<v-form @submit.prevent="submit">
|
|
|
|
|
<div class="mb-5">
|
|
|
|
|
<label class="text-subtitle-2 font-weight-medium mb-1 d-block"
|
|
|
|
|
@ -118,6 +171,7 @@ const submit = async () => {
|
|
|
|
|
<v-text-field
|
|
|
|
|
v-model="form.name"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
:disabled="saving"
|
|
|
|
|
dense
|
|
|
|
|
hide-details
|
|
|
|
|
required
|
|
|
|
|
@ -131,29 +185,40 @@ const submit = async () => {
|
|
|
|
|
<v-textarea
|
|
|
|
|
v-model="form.description"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
:disabled="saving"
|
|
|
|
|
rows="3"
|
|
|
|
|
dense
|
|
|
|
|
hide-details
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="errorMsg" class="mt-3 text-error">{{ errorMsg }}</div>
|
|
|
|
|
</v-form>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
|
|
|
|
<v-card-text class="pt-6 pb-4 px-6">
|
|
|
|
|
<div class="text-subtitle-1 font-weight-medium mb-4">Workflow Steps</div>
|
|
|
|
|
|
|
|
|
|
<v-row class="align-center mb-4">
|
|
|
|
|
<v-col cols="auto">
|
|
|
|
|
<v-btn color="primary" small>
|
|
|
|
|
<v-icon left>mdi-plus</v-icon>
|
|
|
|
|
Add Step
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="auto">
|
|
|
|
|
<v-btn color="success" small>Save</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="auto">
|
|
|
|
|
<v-btn color="grey" small>Cancel</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="auto"
|
|
|
|
|
><v-btn color="primary" small :disabled="saving"
|
|
|
|
|
><v-icon left>mdi-plus</v-icon> Add Step</v-btn
|
|
|
|
|
></v-col
|
|
|
|
|
>
|
|
|
|
|
<v-col cols="auto"
|
|
|
|
|
><v-btn color="success" small :loading="saving" @click="submit">{{
|
|
|
|
|
isEdit ? "Update" : "Save"
|
|
|
|
|
}}</v-btn></v-col
|
|
|
|
|
>
|
|
|
|
|
<v-col cols="auto"
|
|
|
|
|
><v-btn
|
|
|
|
|
color="grey"
|
|
|
|
|
small
|
|
|
|
|
:disabled="saving"
|
|
|
|
|
@click="$emit('close-modal')"
|
|
|
|
|
>Cancel</v-btn
|
|
|
|
|
></v-col
|
|
|
|
|
>
|
|
|
|
|
<v-spacer />
|
|
|
|
|
</v-row>
|
|
|
|
|
|
|
|
|
|
@ -169,7 +234,7 @@ const submit = async () => {
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr
|
|
|
|
|
v-for="(step, i) in steps"
|
|
|
|
|
v-for="step in steps"
|
|
|
|
|
:key="step.order"
|
|
|
|
|
style="border: 1px solid #ccc"
|
|
|
|
|
>
|
|
|
|
|
@ -188,7 +253,6 @@ const submit = async () => {
|
|
|
|
|
<td class="text-center">
|
|
|
|
|
<IconArrowUp />
|
|
|
|
|
<IconArrowDown />
|
|
|
|
|
|
|
|
|
|
<IconModifyBtn />
|
|
|
|
|
<IconDeleteBtn />
|
|
|
|
|
</td>
|
|
|
|
|
@ -198,8 +262,14 @@ const submit = async () => {
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
|
|
|
|
<v-card-actions class="justify-end" style="padding: 16px 24px">
|
|
|
|
|
<v-btn color="success" @click="submit">Save</v-btn>
|
|
|
|
|
<v-btn text class="white--text" @click="$emit('close-modal')"
|
|
|
|
|
<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>
|