|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ApiProject, UiProject } from "@/components/models/project/Project";
|
|
|
|
|
import { UserManagerService } from "@/components/service/management/userManagerService";
|
|
|
|
|
import { ProjectService } from "@/components/service/project/projectService";
|
|
|
|
|
import { onMounted, ref } from "vue";
|
|
|
|
|
import { useRouter } from "vue-router";
|
|
|
|
|
|
|
|
|
|
const dialog = ref(false);
|
|
|
|
|
const contextMenu = ref(false);
|
|
|
|
|
const menuX = ref(0);
|
|
|
|
|
const menuY = ref(0);
|
|
|
|
|
const selectedIndex = ref<number | null>(null);
|
|
|
|
|
const projects = ref<UiProject[]>([]);
|
|
|
|
|
const userOptions = ref<string[]>([]);
|
|
|
|
|
const modalMode = ref<"create" | "edit">("create");
|
|
|
|
|
const editingProjectId = ref<number | null>(null);
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const form = ref({
|
|
|
|
|
prjCd: "",
|
|
|
|
|
prjNm: "",
|
|
|
|
|
prjDesc: "",
|
|
|
|
|
selectedUsers: [] as string[],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const loadProjects = async (): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await ProjectService.search();
|
|
|
|
|
projects.value = res.data.map((p) => ({
|
|
|
|
|
id: p.id!,
|
|
|
|
|
title: p.prjNm,
|
|
|
|
|
creator: p.regUserId,
|
|
|
|
|
date: p.prjStartDt,
|
|
|
|
|
description: p.prjDesc,
|
|
|
|
|
}));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("프로젝트 조회 실패:", error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadUsers = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await UserManagerService.getAll();
|
|
|
|
|
// res.data 가 [{id, username, …}, …] 이라면
|
|
|
|
|
userOptions.value = res.data.map((u: any) => u.username);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("사용자 조회 실패:", err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectProject = (idx: number): void => {
|
|
|
|
|
const p = projects.value[idx];
|
|
|
|
|
router.push("/home");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openContextMenu = (e: MouseEvent, idx: number): void => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
selectedIndex.value = idx;
|
|
|
|
|
menuX.value = e.pageX;
|
|
|
|
|
menuY.value = e.pageY;
|
|
|
|
|
contextMenu.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const saveProject = async () => {
|
|
|
|
|
const payload: ApiProject = {
|
|
|
|
|
id: modalMode.value === "edit" ? editingProjectId.value! : null,
|
|
|
|
|
prjCd: form.value.prjCd,
|
|
|
|
|
prjNm: form.value.prjNm,
|
|
|
|
|
prjDesc: form.value.prjDesc,
|
|
|
|
|
prjStartDt: new Date().toISOString().slice(0, 10),
|
|
|
|
|
prjEndDt: new Date().toISOString().slice(0, 10),
|
|
|
|
|
delYn: "N",
|
|
|
|
|
regDate: new Date().toISOString(),
|
|
|
|
|
regUserId: form.value.selectedUsers.join(","),
|
|
|
|
|
regUserNm: form.value.selectedUsers.join(","),
|
|
|
|
|
modDate: new Date().toISOString(),
|
|
|
|
|
modUserId: form.value.selectedUsers.join(","),
|
|
|
|
|
modUserNm: form.value.selectedUsers.join(","),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (modalMode.value === "create") {
|
|
|
|
|
await ProjectService.add(payload);
|
|
|
|
|
} else {
|
|
|
|
|
await ProjectService.update(editingProjectId.value!, payload);
|
|
|
|
|
}
|
|
|
|
|
await loadProjects();
|
|
|
|
|
closeDialog();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(`${modalMode.value} 실패:`, err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deleteProject = async (): Promise<void> => {
|
|
|
|
|
contextMenu.value = false;
|
|
|
|
|
const idx = selectedIndex.value;
|
|
|
|
|
if (idx === null) return;
|
|
|
|
|
const p = projects.value[idx];
|
|
|
|
|
try {
|
|
|
|
|
await ProjectService.delete(p.id);
|
|
|
|
|
await loadProjects();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("삭제 실패:", error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onAddProject = () => {
|
|
|
|
|
modalMode.value = "create";
|
|
|
|
|
editingProjectId.value = null;
|
|
|
|
|
form.value.prjCd = `PRJ${Date.now()}`;
|
|
|
|
|
form.value.prjNm = "";
|
|
|
|
|
form.value.prjDesc = "";
|
|
|
|
|
form.value.selectedUsers = [];
|
|
|
|
|
dialog.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const modifyProject = () => {
|
|
|
|
|
contextMenu.value = false;
|
|
|
|
|
const idx = selectedIndex.value;
|
|
|
|
|
if (idx === null) return;
|
|
|
|
|
|
|
|
|
|
const p = projects.value[idx];
|
|
|
|
|
modalMode.value = "edit";
|
|
|
|
|
editingProjectId.value = p.id;
|
|
|
|
|
// 기존 데이터를 폼에 채워넣기
|
|
|
|
|
form.value.prjCd = p.title; // 만약 코드가 title 과 다르면 실제 prjCd 로 교체
|
|
|
|
|
form.value.prjNm = p.title;
|
|
|
|
|
form.value.prjDesc = p.description;
|
|
|
|
|
form.value.selectedUsers = p.creator.split(","); // 필요에 따라 분할
|
|
|
|
|
dialog.value = true;
|
|
|
|
|
};
|
|
|
|
|
const cancel = () => {
|
|
|
|
|
dialog.value = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeDialog = () => {
|
|
|
|
|
dialog.value = false;
|
|
|
|
|
contextMenu.value = false;
|
|
|
|
|
selectedIndex.value = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadProjects();
|
|
|
|
|
loadUsers();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<v-container class="mt-12" style="max-width: 1600px">
|
|
|
|
|
<v-row class="mb-6" align="center" justify="space-between">
|
|
|
|
|
<v-col cols="auto">
|
|
|
|
|
<h2 class="font-weight-bold text-h5">Project Selection</h2>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="auto">
|
|
|
|
|
<v-btn
|
|
|
|
|
color="secondary"
|
|
|
|
|
variant="flat"
|
|
|
|
|
class="text-white font-weight-bold"
|
|
|
|
|
@click="onAddProject"
|
|
|
|
|
>
|
|
|
|
|
<v-icon left icon="mdi-plus" size="20" />
|
|
|
|
|
Create Project
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
|
|
|
|
|
<v-row dense>
|
|
|
|
|
<v-col
|
|
|
|
|
v-for="(project, index) in projects"
|
|
|
|
|
:key="index"
|
|
|
|
|
cols="12"
|
|
|
|
|
sm="6"
|
|
|
|
|
md="6"
|
|
|
|
|
lg="6"
|
|
|
|
|
class="d-flex"
|
|
|
|
|
>
|
|
|
|
|
<v-card
|
|
|
|
|
class="pa-4 flex-grow-1 d-flex flex-column"
|
|
|
|
|
color="primary"
|
|
|
|
|
variant="elevated"
|
|
|
|
|
elevation="6"
|
|
|
|
|
rounded="lg"
|
|
|
|
|
@click="selectProject(index)"
|
|
|
|
|
@contextmenu.prevent="(e) => openContextMenu(e, index)"
|
|
|
|
|
>
|
|
|
|
|
<v-card-title class="d-flex align-center">
|
|
|
|
|
<v-icon color="#6EC1E4" icon="mdi-file" start size="18" />
|
|
|
|
|
<h4>{{ project.title }}</h4>
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
|
|
|
|
<v-card-subtitle
|
|
|
|
|
class="text-white text-caption d-flex justify-space-between"
|
|
|
|
|
>
|
|
|
|
|
<span>생성자: {{ project.creator }}</span>
|
|
|
|
|
<span>등록일: {{ project.date }}</span>
|
|
|
|
|
</v-card-subtitle>
|
|
|
|
|
|
|
|
|
|
<v-card-text
|
|
|
|
|
class="text-white mt-3 text-body-2 flex-grow-1"
|
|
|
|
|
style="white-space: normal"
|
|
|
|
|
>
|
|
|
|
|
{{ project.description }}
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
<v-menu
|
|
|
|
|
v-model="contextMenu"
|
|
|
|
|
absolute
|
|
|
|
|
:style="{ top: menuY + 'px', left: menuX + 'px' }"
|
|
|
|
|
max-width="180"
|
|
|
|
|
width="130"
|
|
|
|
|
>
|
|
|
|
|
<v-list>
|
|
|
|
|
<v-list-item @click="modifyProject">
|
|
|
|
|
<v-list-item-icon
|
|
|
|
|
><v-icon>mdi-square-edit-outline</v-icon></v-list-item-icon
|
|
|
|
|
>
|
|
|
|
|
<v-list-item-title>Modify</v-list-item-title>
|
|
|
|
|
</v-list-item>
|
|
|
|
|
<v-list-item @click="deleteProject">
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
|
|
|
|
|
<v-list-item-title>Delete</v-list-item-title>
|
|
|
|
|
</v-list-item>
|
|
|
|
|
</v-list>
|
|
|
|
|
</v-menu>
|
|
|
|
|
<v-dialog v-model="dialog" max-width="500">
|
|
|
|
|
<v-card>
|
|
|
|
|
<!-- 모드에 따라 타이틀 변경 -->
|
|
|
|
|
<v-card-title class="headline">
|
|
|
|
|
{{ modalMode === "create" ? "Create Project" : "Modify Project" }}
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
|
|
|
|
<v-card-text>
|
|
|
|
|
<v-form>
|
|
|
|
|
<v-text-field label="Project Name" v-model="form.prjNm" required />
|
|
|
|
|
<v-textarea
|
|
|
|
|
label="Description"
|
|
|
|
|
v-model="form.prjDesc"
|
|
|
|
|
rows="3"
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
<v-select
|
|
|
|
|
label="Select Users"
|
|
|
|
|
v-model="form.selectedUsers"
|
|
|
|
|
:items="userOptions"
|
|
|
|
|
multiple
|
|
|
|
|
chips
|
|
|
|
|
closable-chips
|
|
|
|
|
/>
|
|
|
|
|
</v-form>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
|
|
|
|
<v-card-actions>
|
|
|
|
|
<v-spacer />
|
|
|
|
|
<v-btn text @click="closeDialog">Cancel</v-btn>
|
|
|
|
|
<!-- 클릭 핸들러도 공용 saveProject -->
|
|
|
|
|
<v-btn color="primary" @click="saveProject">
|
|
|
|
|
{{ modalMode === "create" ? "Create" : "Save" }}
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-card-actions>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-dialog>
|
|
|
|
|
</v-container>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped></style>
|