diff --git a/src/pages/UsersView.vue b/src/pages/UsersView.vue
index 67cc292..4a3faef 100644
--- a/src/pages/UsersView.vue
+++ b/src/pages/UsersView.vue
@@ -1,5 +1,5 @@
diff --git a/src/utils/menuUtils.js b/src/utils/menuUtils.js
index 946e950..e7421f2 100644
--- a/src/utils/menuUtils.js
+++ b/src/utils/menuUtils.js
@@ -12,12 +12,12 @@ export const menuUtils = {
value: "workflows",
icon: "mdi-code-braces",
},
- {
- title: "Workflow Step Config",
- path: "/workflow-step-config",
- value: "workflow-step-config",
- icon: "mdi-hammer-wrench",
- },
+ // {
+ // title: "Workflow Step Config",
+ // path: "/workflow-step-config",
+ // value: "workflow-step-config",
+ // icon: "mdi-hammer-wrench",
+ // },
{
title: "Run",
path: "/run",
diff --git a/src/views/Select.vue b/src/views/Select.vue
index ed6ce2b..4e7052c 100644
--- a/src/views/Select.vue
+++ b/src/views/Select.vue
@@ -5,7 +5,6 @@ import { useAutoflowStore } from "@/stores/autoflowStore";
import type {
UiProject,
- ApiProject,
Permission,
} from "@/components/models/project/Project";
import { ProjectService } from "@/components/service/project/projectService";
@@ -31,6 +30,11 @@ const menuX = ref(0);
const menuY = ref(0);
const selectedIndex = ref(null);
+// id별 원본 등록자(reg) 보관 (값 그대로, 가공/문자열화 X)
+const projectRegById = ref>(
+ {},
+);
+
const projects = ref([]);
type UserOption = { id: number | string; username: string };
const userOptions = ref([]);
@@ -43,7 +47,8 @@ const form = ref({
prjDesc: "",
selectedUsers: [] as string[],
});
-/** ===== 서버 응답 타입 ===== */
+
+/** ===== 롤 ===== */
const roles = ref([]);
const refreshRoles = () => {
const auth = storage.getAuth?.() ?? storage.get?.("vpp-Auth") ?? null;
@@ -52,27 +57,93 @@ const refreshRoles = () => {
};
const isAdmin = computed(() => roles.value.includes("ROLE_ADMIN"));
+/** ===== 페이지네이션 상태 ===== */
+const pager = ref({ pageNum: 1, pageSize: 8, total: 0, pageLength: 1 });
+
+const pagedProjects = computed(() => {
+ const total = projects.value.length;
+ pager.value.total = total;
+ pager.value.pageLength = Math.max(1, Math.ceil(total / pager.value.pageSize));
+ if (pager.value.pageNum > pager.value.pageLength)
+ pager.value.pageNum = pager.value.pageLength;
+ if (pager.value.pageNum < 1) pager.value.pageNum = 1;
+ const start = (pager.value.pageNum - 1) * pager.value.pageSize;
+ return projects.value.slice(start, start + pager.value.pageSize);
+});
+
+const changePageNum = (page: number) => {
+ pager.value.pageNum = page;
+};
+const changePageSize = (size: number) => {
+ pager.value.pageSize = size;
+ pager.value.pageNum = 1;
+};
+
/** ===== 서버 응답 타입 ===== */
interface ProjectSearchResponseItem {
id: number;
prjNm: string;
prjDesc: string;
prjStartDt?: string;
- regUserId?: string; // 화면의 "생성자"에 그대로 표시(콤마 구분 username들)
+ regUserId?: string;
+ regUserNm?: string;
+ modUserId?: string;
+ modUserNm?: string;
}
interface UserResponseItem {
id: number | string;
username: string;
}
+/** ===== 페이로드 타입(서비스 시그니처를 못 바꾼다는 가정에서 지역 타입 분리) ===== */
+// 생성 시 mod* 안 보냄
+type NewProjectPayload = {
+ id: null;
+ prjCd: string;
+ prjNm: string;
+ prjDesc: string;
+ prjStartDt: string;
+ prjEndDt: string;
+ delYn: string;
+ regDate: string;
+ regUserId?: string;
+ regUserNm?: string;
+};
+// 수정 시 reg*는 기존 그대로 유지, mod*만 채움
+type UpdateProjectPayload = {
+ id: number;
+ prjCd: string;
+ prjNm: string;
+ prjDesc: string;
+ prjStartDt: string;
+ prjEndDt: string;
+ delYn: string;
+ regDate: string;
+ regUserId?: string;
+ regUserNm?: string;
+ modDate: string;
+ modUserId?: string;
+ modUserNm?: string;
+};
+
/** ===== 유틸 ===== */
-const buildApiProjectPayload = (): ApiProject => {
+function buildCreatePayload(): NewProjectPayload {
const today = new Date().toISOString().slice(0, 10);
const nowIso = new Date().toISOString();
- const namesCsv = form.value.selectedUsers.join(",");
+ const names = form.value.selectedUsers;
+ const namesCsv = names.join(",");
+ const idsCsv = names
+ .map((name) => userOptions.value.find((u) => u.username === name)?.id)
+ .filter(
+ (v): v is number | string => v !== undefined && v !== null && v !== "",
+ )
+ .map(String)
+ .join(","); // "6,5"
+
+ // mod* 없음 (생성)
return {
- id: modalMode.value === "edit" ? editingProjectId.value! : null,
+ id: null,
prjCd: form.value.prjCd,
prjNm: form.value.prjNm,
prjDesc: form.value.prjDesc,
@@ -80,13 +151,50 @@ const buildApiProjectPayload = (): ApiProject => {
prjEndDt: today,
delYn: "N",
regDate: nowIso,
- regUserId: namesCsv,
+ regUserId: idsCsv,
regUserNm: namesCsv,
+ };
+}
+
+function buildUpdatePayload(): UpdateProjectPayload {
+ const today = new Date().toISOString().slice(0, 10);
+ const nowIso = new Date().toISOString();
+
+ const names = form.value.selectedUsers;
+ const namesCsv = names.join(",");
+ const idsCsv = names
+ .map((name) => userOptions.value.find((u) => u.username === name)?.id)
+ .filter(
+ (v): v is number | string => v !== undefined && v !== null && v !== "",
+ )
+ .map(String)
+ .join(",");
+
+ const id = editingProjectId.value!;
+ const kept = projectRegById.value[id] || {};
+
+ // reg*는 DB 원본 유지, mod*만 현재 선택으로 반영
+ return {
+ id,
+ prjCd: form.value.prjCd,
+ prjNm: form.value.prjNm,
+ prjDesc: form.value.prjDesc,
+ prjStartDt: today,
+ prjEndDt: today,
+ delYn: "N",
+ regDate: nowIso,
+ regUserId: kept.regId,
+ regUserNm: kept.regNm,
modDate: nowIso,
- modUserId: namesCsv,
+ modUserId: idsCsv,
modUserNm: namesCsv,
};
-};
+}
+
+const fillerCount = computed(() =>
+ Math.max(0, pager.value.pageSize - pagedProjects.value.length),
+);
+const fillers = computed(() => Array.from({ length: fillerCount.value }));
const resetForm = () => {
form.value.prjCd = `PRJ${Date.now()}`;
@@ -99,13 +207,33 @@ const loadProjects = async () => {
try {
const { data } = await ProjectService.search();
const rawList = data as ProjectSearchResponseItem[];
- projects.value = rawList.map((p) => ({
- id: p.id,
- title: p.prjNm,
- creator: p.regUserId ?? "",
- date: p.prjStartDt ?? "",
- description: p.prjDesc,
- }));
+
+ const sorted = [...rawList].sort((a, b) => (b.id ?? 0) - (a.id ?? 0));
+
+ projectRegById.value = {};
+ projects.value = sorted.map((p) => {
+ // 원본 reg 값은 따로 보관(수정 시 reg* 유지용)
+ projectRegById.value[p.id] = { regId: p.regUserId, regNm: p.regUserNm };
+
+ // ★ 화면/모달에 보여줄 선택값은 mod_user_nm이 있으면 그걸로, 없으면 reg_user_nm
+ const displayNm =
+ p.modUserNm && p.modUserNm.length > 0 ? p.modUserNm : p.regUserNm || "";
+
+ return {
+ id: p.id,
+ title: p.prjNm,
+ creator: displayNm, // ← 이 값이 모달 v-select v-model에 들어감
+ date: p.prjStartDt, // fallback 없이 그대로
+ description: p.prjDesc,
+ };
+ });
+
+ if (
+ pager.value.pageNum >
+ Math.max(1, Math.ceil(projects.value.length / pager.value.pageSize))
+ ) {
+ pager.value.pageNum = 1;
+ }
} catch (e) {
console.error("프로젝트 조회 실패:", e);
}
@@ -136,30 +264,27 @@ const closeDialog = () => {
};
const selectProject = (index: number) => {
- const selected = projects.value[index];
+ const selected = pagedProjects.value[index];
autoflowStore.setProjectId(selected.id);
autoflowStore.setProjectName(selected.title);
router.push("/home");
};
-/** ===== 프로젝트 저장 & 권한 부여 ===== */
const grantDefaultPermissions = async (
projectId: number,
usernames: string[],
) => {
if (!usernames?.length) return;
-
const nameSet = new Set(usernames);
const numericIds = userOptions.value
.filter((u) => nameSet.has(u.username))
.map((u) => Number(u.id))
.filter((n) => Number.isFinite(n));
-
await Promise.all(
numericIds.map((uid) =>
ProjectService.projectAuthority(projectId, {
projectId,
- userId: uid, // number로 보장
+ userId: uid,
permissions: DEFAULT_PERMISSIONS,
}),
),
@@ -171,20 +296,17 @@ const saveProject = async () => {
alert("권한이 없습니다. (ROLE_ADMIN 전용)");
return;
}
-
- const payload = buildApiProjectPayload();
-
try {
let projectId: number;
-
if (modalMode.value === "create") {
- const createRes = await ProjectService.add(payload);
+ const createPayload = buildCreatePayload(); // mod* 없음
+ const createRes = await ProjectService.add(createPayload); // ← 오타 수정
projectId = createRes.data.id;
} else {
- await ProjectService.update(editingProjectId.value!, payload);
+ const updatePayload = buildUpdatePayload(); // reg* 유지, mod* 반영
+ await ProjectService.update(editingProjectId.value!, updatePayload); // ← non-null 보장
projectId = editingProjectId.value!;
}
-
await grantDefaultPermissions(projectId, form.value.selectedUsers);
await loadProjects();
closeDialog();
@@ -197,8 +319,7 @@ const saveProject = async () => {
const deleteProject = async () => {
try {
if (selectedIndex.value === null) return;
- const target = projects.value[selectedIndex.value];
-
+ const target = pagedProjects.value[selectedIndex.value];
await ProjectService.delete(target.id);
await loadProjects();
} catch (e: any) {
@@ -209,43 +330,6 @@ const deleteProject = async () => {
}
};
-// 프로젝트 권한있을 때
-// const deleteProject = async (): Promise => {
-// try {
-// if (selectedIndex.value === null) return;
-// const target = projects.value[selectedIndex.value];
-// const projectId = target.id;
-
-// // 1) 프로젝트에 연결된 username들 뽑기
-// const usernames = (target.creator || "")
-// .split(",")
-// .map((s) => s.trim())
-// .filter(Boolean);
-
-// // 2) username -> userId 매핑
-// if (usernames.length) {
-// const ids = userOptions.value
-// .filter((u) => usernames.includes(u.username))
-// .map((u) => u.id);
-
-// // 3) 각 사용자 권한/매핑 제거
-// await Promise.all(
-// ids.map((uid) => ProjectService.deleteProjectAuthority(projectId, uid)),
-// );
-// }
-
-// // 4) 마지막에 프로젝트 삭제
-// await ProjectService.delete(projectId);
-
-// await loadProjects();
-// } catch (e: any) {
-// console.error("삭제 실패:", e?.response?.status, e?.response?.data || e);
-// alert("삭제 실패: " + (e?.response?.data?.message || e.message || ""));
-// } finally {
-// contextMenu.value = false;
-// }
-// };
-
const onAddProject = () => {
if (!isAdmin.value) {
alert("권한이 없습니다. (ROLE_ADMIN 전용)");
@@ -260,19 +344,19 @@ const onAddProject = () => {
const modifyProject = () => {
contextMenu.value = false;
if (selectedIndex.value === null) return;
+ const selected = pagedProjects.value[selectedIndex.value];
- const selected = projects.value[selectedIndex.value];
modalMode.value = "edit";
editingProjectId.value = selected.id;
-
form.value.prjCd = selected.title;
form.value.prjNm = selected.title;
form.value.prjDesc = selected.description;
+
+ // creator는 reg_user_nm 그대로 들어오므로 그대로 분해. 값 없으면 []
form.value.selectedUsers =
- selected.creator
- ?.split(",")
- .map((s) => s.trim())
- .filter(Boolean) ?? [];
+ typeof selected.creator === "string" && selected.creator.length
+ ? selected.creator.split(",").map((s) => s.trim())
+ : [];
dialog.value = true;
};
@@ -301,7 +385,6 @@ onBeforeUnmount(() => {
-
{
+
+
+
{
class="d-flex"
>
{
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
- Cancel
{{ modalMode === "create" ? "Create" : "Save" }}
+ Cancel
-
+