기능구현중

This commit is contained in:
2026-01-11 13:47:33 +09:00
parent f5bf084afc
commit d56154d5d2
13 changed files with 948 additions and 46 deletions

View File

@@ -527,6 +527,43 @@
</div>
</div>
<div class="modal-backdrop fade show" v-if="showMaintenanceModal"></div>
<!-- TODO 연계 확인 모달 -->
<div class="modal fade" :class="{ show: showTodoLinkModal }" :style="{ display: showTodoLinkModal ? 'block' : 'none' }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-check2-square me-2 text-success"></i>TODO 완료 확인</h5>
<button type="button" class="btn-close" @click="closeTodoLinkModal"></button>
</div>
<div class="modal-body">
<p class="text-muted small">작성한 실적과 유사한 TODO가 발견되었습니다. 완료 처리할 항목을 선택하세요.</p>
<div class="list-group">
<label v-for="todo in similarTodos" :key="todo.todoId" class="list-group-item list-group-item-action">
<div class="d-flex align-items-start">
<input type="checkbox" class="form-check-input me-2 mt-1" v-model="todo.selected" />
<div class="flex-grow-1">
<div class="fw-bold">{{ todo.todoTitle }}</div>
<small class="text-muted">
{{ todo.projectName || '프로젝트 없음' }}
<span v-if="todo.dueDate"> · 마감: {{ todo.dueDate.split('T')[0] }}</span>
</small>
</div>
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="closeTodoLinkModal">건너뛰기</button>
<button type="button" class="btn btn-success" @click="completeTodos" :disabled="!hasSelectedTodos || isLinkingTodos">
<span v-if="isLinkingTodos" class="spinner-border spinner-border-sm me-1"></span>
<i v-else class="bi bi-check-lg me-1"></i>완료 처리
</button>
</div>
</div>
</div>
</div>
<div class="modal-backdrop fade show" v-if="showTodoLinkModal"></div>
</div>
</template>
@@ -599,6 +636,13 @@ const generatedTasks = ref<{ description: string; taskType: string; sourceTaskId
const selectedMaintenanceCount = computed(() => maintenanceTasks.value.filter(t => t.selected).length)
// TODO 연계 모달
const showTodoLinkModal = ref(false)
const similarTodos = ref<any[]>([])
const savedReportId = ref<number | null>(null)
const isLinkingTodos = ref(false)
const hasSelectedTodos = computed(() => similarTodos.value.some(t => t.selected))
const form = ref({
reportYear: new Date().getFullYear(),
reportWeek: 1,
@@ -958,7 +1002,7 @@ async function handleSubmit() {
isSaving.value = true
try {
await $fetch('/api/report/weekly/create', {
const res = await $fetch<{ reportId: number }>('/api/report/weekly/create', {
method: 'POST',
body: {
reportYear: form.value.reportYear,
@@ -977,8 +1021,17 @@ async function handleSubmit() {
remarkDescription: form.value.remarkDescription
}
})
toast.success('주간보고가 작성되었습니다.')
router.push('/report/weekly')
savedReportId.value = res.reportId
// 완료된 WORK 실적에 대해 유사 TODO 검색
const completedWorks = validTasks.filter(t => t.taskType === 'WORK' && t.isCompleted)
if (completedWorks.length > 0) {
await searchSimilarTodos(completedWorks)
} else {
router.push('/report/weekly')
}
} catch (e: any) {
toast.error(e.data?.message || '저장에 실패했습니다.')
} finally {
@@ -986,6 +1039,71 @@ async function handleSubmit() {
}
}
// === TODO 연계 기능 ===
async function searchSimilarTodos(completedWorks: TaskItem[]) {
try {
const allTodos: any[] = []
for (const work of completedWorks) {
const res = await $fetch<{ todos: any[] }>('/api/todo/report/similar', {
method: 'POST',
body: {
taskDescription: work.description,
projectId: work.projectId
}
})
if (res.todos && res.todos.length > 0) {
for (const todo of res.todos) {
if (!allTodos.find(t => t.todoId === todo.todoId)) {
allTodos.push({ ...todo, selected: true })
}
}
}
}
if (allTodos.length > 0) {
similarTodos.value = allTodos
showTodoLinkModal.value = true
} else {
router.push('/report/weekly')
}
} catch (e) {
console.error('TODO 검색 실패:', e)
router.push('/report/weekly')
}
}
function closeTodoLinkModal() {
showTodoLinkModal.value = false
router.push('/report/weekly')
}
async function completeTodos() {
const selected = similarTodos.value.filter(t => t.selected)
if (selected.length === 0) return
isLinkingTodos.value = true
try {
for (const todo of selected) {
await $fetch('/api/todo/report/link', {
method: 'POST',
body: {
todoId: todo.todoId,
weeklyReportId: savedReportId.value,
markCompleted: true
}
})
}
toast.success(`${selected.length}개의 TODO가 완료 처리되었습니다.`)
showTodoLinkModal.value = false
router.push('/report/weekly')
} catch (e: any) {
toast.error('TODO 완료 처리에 실패했습니다.')
} finally {
isLinkingTodos.value = false
}
}
// === textarea 자동 높이 조절 ===
function autoResize(e: Event) {
const textarea = e.target as HTMLTextAreaElement