205 lines
7.7 KiB
Vue
205 lines
7.7 KiB
Vue
<template>
|
|
<div>
|
|
<AppHeader />
|
|
|
|
<div class="container-fluid py-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h4 class="mb-0">
|
|
<NuxtLink to="/business-report" class="text-decoration-none text-muted me-2"><i class="bi bi-arrow-left"></i></NuxtLink>
|
|
<i class="bi bi-file-earmark-text me-2"></i>{{ report?.businessName }} - {{ report?.reportYear }}-W{{ String(report?.reportWeek || 0).padStart(2, '0') }}
|
|
</h4>
|
|
<div v-if="report">
|
|
<span :class="report.status === 'confirmed' ? 'badge bg-success me-3' : 'badge bg-warning me-3'">
|
|
{{ report.status === 'confirmed' ? '확정' : '작성중' }}
|
|
</span>
|
|
<button class="btn btn-success" @click="confirmReport" v-if="report.status !== 'confirmed'" :disabled="isConfirming">
|
|
<i class="bi bi-check-lg me-1"></i>확정
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="isLoading" class="text-center py-5">
|
|
<div class="spinner-border text-primary"></div>
|
|
</div>
|
|
|
|
<div v-else-if="report" class="row">
|
|
<div class="col-md-8">
|
|
<!-- 요약 -->
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<strong>취합 요약</strong>
|
|
<button class="btn btn-sm btn-outline-primary" @click="toggleEdit" v-if="report.status !== 'confirmed'">
|
|
{{ isEditing ? '취소' : '수정' }}
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
<div v-if="!isEditing">
|
|
<div class="mb-3" v-if="report.manualSummary">
|
|
<label class="text-muted small">최종 요약 (수정됨)</label>
|
|
<div style="white-space: pre-wrap;">{{ report.manualSummary }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-muted small">AI 생성 요약</label>
|
|
<div style="white-space: pre-wrap;" :class="{ 'text-muted': report.manualSummary }">{{ report.aiSummary }}</div>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<label class="form-label">요약 수정</label>
|
|
<textarea class="form-control" v-model="editSummary" rows="8" placeholder="AI 요약을 수정하세요..."></textarea>
|
|
<div class="mt-2">
|
|
<button class="btn btn-primary btn-sm" @click="saveSummary" :disabled="isSaving">
|
|
<span v-if="isSaving"><span class="spinner-border spinner-border-sm me-1"></span></span>
|
|
저장
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 실적 목록 -->
|
|
<div class="card">
|
|
<div class="card-header"><strong>포함된 실적 목록</strong></div>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-bordered mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width: 120px">프로젝트</th>
|
|
<th style="width: 80px">담당자</th>
|
|
<th>실적 내용</th>
|
|
<th style="width: 60px" class="text-center">시간</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="t in tasks" :key="t.taskId">
|
|
<td>{{ t.projectName }}</td>
|
|
<td>{{ t.employeeName }}</td>
|
|
<td>{{ t.taskDescription }}</td>
|
|
<td class="text-center">{{ t.taskHours || '-' }}</td>
|
|
</tr>
|
|
<tr v-if="tasks.length === 0">
|
|
<td colspan="4" class="text-center py-3 text-muted">실적이 없습니다.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<!-- 정보 -->
|
|
<div class="card">
|
|
<div class="card-header"><strong>보고서 정보</strong></div>
|
|
<ul class="list-group list-group-flush">
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">사업</span>
|
|
<NuxtLink :to="`/business/${report.businessId}`">{{ report.businessName }}</NuxtLink>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">주차</span>
|
|
<span>{{ report.reportYear }}-W{{ String(report.reportWeek).padStart(2, '0') }}</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">기간</span>
|
|
<span>{{ formatDate(report.weekStartDate) }} ~ {{ formatDate(report.weekEndDate) }}</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">실적 건수</span>
|
|
<span>{{ tasks.length }}건</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">생성자</span>
|
|
<span>{{ report.createdByName || '-' }}</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span class="text-muted">생성일</span>
|
|
<span>{{ formatDateTime(report.createdAt) }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const { fetchCurrentUser } = useAuth()
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
const report = ref<any>(null)
|
|
const tasks = ref<any[]>([])
|
|
const isLoading = ref(true)
|
|
const isEditing = ref(false)
|
|
const isSaving = ref(false)
|
|
const isConfirming = ref(false)
|
|
const editSummary = ref('')
|
|
|
|
onMounted(async () => {
|
|
const user = await fetchCurrentUser()
|
|
if (!user) { router.push('/login'); return }
|
|
await loadReport()
|
|
})
|
|
|
|
async function loadReport() {
|
|
isLoading.value = true
|
|
try {
|
|
const res = await $fetch<{ report: any; tasks: any[] }>(`/api/business-report/${route.params.id}/detail`)
|
|
report.value = res.report
|
|
tasks.value = res.tasks || []
|
|
} catch (e: any) {
|
|
alert('보고서를 불러올 수 없습니다.')
|
|
router.push('/business-report')
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function toggleEdit() {
|
|
if (!isEditing.value) {
|
|
editSummary.value = report.value.manualSummary || report.value.aiSummary || ''
|
|
}
|
|
isEditing.value = !isEditing.value
|
|
}
|
|
|
|
async function saveSummary() {
|
|
isSaving.value = true
|
|
try {
|
|
await $fetch(`/api/business-report/${route.params.id}/update`, {
|
|
method: 'PUT',
|
|
body: { manualSummary: editSummary.value }
|
|
})
|
|
isEditing.value = false
|
|
await loadReport()
|
|
} catch (e: any) {
|
|
alert(e.data?.message || '저장에 실패했습니다.')
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
async function confirmReport() {
|
|
if (!confirm('보고서를 확정하시겠습니까? 확정 후에는 수정할 수 없습니다.')) return
|
|
isConfirming.value = true
|
|
try {
|
|
await $fetch(`/api/business-report/${route.params.id}/confirm`, { method: 'PUT' })
|
|
await loadReport()
|
|
} catch (e: any) {
|
|
alert(e.data?.message || '확정에 실패했습니다.')
|
|
} finally {
|
|
isConfirming.value = false
|
|
}
|
|
}
|
|
|
|
function formatDate(d: string) {
|
|
if (!d) return '-'
|
|
return d.split('T')[0]
|
|
}
|
|
|
|
function formatDateTime(d: string) {
|
|
if (!d) return '-'
|
|
const dt = new Date(d)
|
|
return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')} ${String(dt.getHours()).padStart(2,'0')}:${String(dt.getMinutes()).padStart(2,'0')}`
|
|
}
|
|
</script>
|