1ㅊㅏ완료
This commit is contained in:
@@ -2,62 +2,154 @@
|
||||
<div>
|
||||
<AppHeader />
|
||||
|
||||
<div class="container py-4">
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0">
|
||||
<i class="bi bi-journal-text me-2"></i>내 주간보고
|
||||
<i class="bi bi-journal-text me-2"></i>주간보고
|
||||
</h4>
|
||||
<NuxtLink to="/report/weekly/write" class="btn btn-primary">
|
||||
<i class="bi bi-plus me-1"></i>작성하기
|
||||
</NuxtLink>
|
||||
<div class="d-flex gap-2">
|
||||
<NuxtLink v-if="isAdmin" to="/report/summary" class="btn btn-outline-primary">
|
||||
<i class="bi bi-collection me-1"></i>취합하기
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/report/weekly/write" class="btn btn-primary">
|
||||
<i class="bi bi-plus me-1"></i>작성하기
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필터 영역 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<!-- 전체보기 (관리자만) -->
|
||||
<div class="col-auto" v-if="isAdmin">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="viewAll" v-model="filters.viewAll" @change="loadReports">
|
||||
<label class="form-check-label" for="viewAll">전체 보기</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작성자 -->
|
||||
<div class="col-md-2" v-if="isAdmin">
|
||||
<label class="form-label small text-muted">작성자</label>
|
||||
<select class="form-select form-select-sm" v-model="filters.authorId" @change="loadReports">
|
||||
<option value="">전체</option>
|
||||
<option v-for="emp in employees" :key="emp.employeeId" :value="emp.employeeId">
|
||||
{{ emp.employeeName }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 -->
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small text-muted">프로젝트</label>
|
||||
<select class="form-select form-select-sm" v-model="filters.projectId" @change="loadReports">
|
||||
<option value="">전체</option>
|
||||
<option v-for="proj in projects" :key="proj.projectId" :value="proj.projectId">
|
||||
{{ proj.projectName }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 연도 -->
|
||||
<div class="col-md-1">
|
||||
<label class="form-label small text-muted">연도</label>
|
||||
<select class="form-select form-select-sm" v-model="filters.year" @change="loadReports">
|
||||
<option value="">전체</option>
|
||||
<option v-for="y in yearOptions" :key="y" :value="y">{{ y }}년</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 기간 -->
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small text-muted">시작일</label>
|
||||
<input type="date" class="form-control form-control-sm" v-model="filters.startDate" @change="loadReports">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small text-muted">종료일</label>
|
||||
<input type="date" class="form-control form-control-sm" v-model="filters.endDate" @change="loadReports">
|
||||
</div>
|
||||
|
||||
<!-- 상태 -->
|
||||
<div class="col-md-1">
|
||||
<label class="form-label small text-muted">상태</label>
|
||||
<select class="form-select form-select-sm" v-model="filters.status" @change="loadReports">
|
||||
<option value="">전체</option>
|
||||
<option value="DRAFT">작성중</option>
|
||||
<option value="SUBMITTED">제출완료</option>
|
||||
<option value="AGGREGATED">취합완료</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 초기화 -->
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-secondary btn-sm" @click="resetFilters">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i>초기화
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 목록 -->
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 150px">주차</th>
|
||||
<th style="width: 200px">기간</th>
|
||||
<th style="width: 120px">주차</th>
|
||||
<th style="width: 180px">기간</th>
|
||||
<th v-if="isAdmin" style="width: 120px">작성자</th>
|
||||
<th>프로젝트</th>
|
||||
<th style="width: 100px">상태</th>
|
||||
<th style="width: 150px">작성일</th>
|
||||
<th style="width: 90px">상태</th>
|
||||
<th style="width: 100px">제출일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="5" class="text-center py-4">
|
||||
<td :colspan="isAdmin ? 6 : 5" class="text-center py-4">
|
||||
<span class="spinner-border spinner-border-sm me-2"></span>로딩 중...
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="reports.length === 0">
|
||||
<td colspan="5" class="text-center py-5 text-muted">
|
||||
<td :colspan="isAdmin ? 6 : 5" class="text-center py-5 text-muted">
|
||||
<i class="bi bi-inbox display-4"></i>
|
||||
<p class="mt-2 mb-0">작성한 주간보고가 없습니다.</p>
|
||||
<p class="mt-2 mb-0">조회된 주간보고가 없습니다.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else v-for="r in reports" :key="r.reportId"
|
||||
@click="router.push(`/report/weekly/${r.reportId}`)"
|
||||
style="cursor: pointer;">
|
||||
<td>
|
||||
<strong>{{ r.reportYear }}년 {{ r.reportWeek }}주차</strong>
|
||||
<strong>{{ r.reportYear }}년 {{ r.reportWeek }}주</strong>
|
||||
</td>
|
||||
<td class="small">
|
||||
{{ formatDate(r.weekStartDate) }} ~ {{ formatDate(r.weekEndDate) }}
|
||||
</td>
|
||||
<td v-if="isAdmin">
|
||||
<span class="badge bg-secondary">{{ r.authorName }}</span>
|
||||
</td>
|
||||
<td>{{ formatDate(r.weekStartDate) }} ~ {{ formatDate(r.weekEndDate) }}</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">{{ r.projectCount }}개 프로젝트</span>
|
||||
<span class="text-truncate d-inline-block" style="max-width: 400px;" :title="r.projectNames">
|
||||
{{ r.projectNames || '-' }}
|
||||
</span>
|
||||
<span class="badge bg-light text-dark ms-1">{{ r.projectCount }}건</span>
|
||||
</td>
|
||||
<td>
|
||||
<span :class="getStatusBadgeClass(r.reportStatus)">
|
||||
{{ getStatusText(r.reportStatus) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(r.createdAt) }}</td>
|
||||
<td class="small">{{ formatDateTime(r.submittedAt || r.createdAt) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-muted small" v-if="reports.length > 0">
|
||||
총 {{ reports.length }}건
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +160,23 @@ const { fetchCurrentUser } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
const reports = ref<any[]>([])
|
||||
const employees = ref<any[]>([])
|
||||
const projects = ref<any[]>([])
|
||||
const isLoading = ref(true)
|
||||
const isAdmin = ref(false)
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
const yearOptions = [currentYear, currentYear - 1, currentYear - 2]
|
||||
|
||||
const filters = ref({
|
||||
viewAll: false,
|
||||
authorId: '',
|
||||
projectId: '',
|
||||
year: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const user = await fetchCurrentUser()
|
||||
@@ -76,14 +184,52 @@ onMounted(async () => {
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin.value = user.employeeEmail === 'coziny@gmail.com'
|
||||
|
||||
// 직원, 프로젝트 목록 로드 (관리자용)
|
||||
if (isAdmin.value) {
|
||||
await loadFilterOptions()
|
||||
}
|
||||
|
||||
loadReports()
|
||||
})
|
||||
|
||||
async function loadFilterOptions() {
|
||||
try {
|
||||
// 직원 목록
|
||||
const empRes = await $fetch<any>('/api/employee/list')
|
||||
employees.value = empRes.employees || []
|
||||
|
||||
// 프로젝트 목록
|
||||
const projRes = await $fetch<any>('/api/project/list')
|
||||
projects.value = projRes.projects || []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReports() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res = await $fetch<any>('/api/report/weekly/list')
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (filters.value.viewAll) params.append('viewAll', 'true')
|
||||
if (filters.value.authorId) params.append('authorId', filters.value.authorId)
|
||||
if (filters.value.projectId) params.append('projectId', filters.value.projectId)
|
||||
if (filters.value.year) params.append('year', filters.value.year)
|
||||
if (filters.value.startDate) params.append('startDate', filters.value.startDate)
|
||||
if (filters.value.endDate) params.append('endDate', filters.value.endDate)
|
||||
if (filters.value.status) params.append('status', filters.value.status)
|
||||
|
||||
const res = await $fetch<any>(`/api/report/weekly/list?${params.toString()}`)
|
||||
reports.value = res.reports || []
|
||||
|
||||
// 일반 사용자도 프로젝트 필터 사용할 수 있도록
|
||||
if (!isAdmin.value && projects.value.length === 0) {
|
||||
const projRes = await $fetch<any>('/api/project/list')
|
||||
projects.value = projRes.projects || []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
@@ -91,15 +237,28 @@ async function loadReports() {
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.value = {
|
||||
viewAll: false,
|
||||
authorId: '',
|
||||
projectId: '',
|
||||
year: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
status: ''
|
||||
}
|
||||
loadReports()
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return ''
|
||||
return dateStr.split('T')[0]
|
||||
return dateStr.split('T')[0].replace(/-/g, '.')
|
||||
}
|
||||
|
||||
function formatDateTime(dateStr: string) {
|
||||
if (!dateStr) return ''
|
||||
if (!dateStr) return '-'
|
||||
const d = new Date(dateStr)
|
||||
return d.toLocaleDateString('ko-KR')
|
||||
return `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status: string) {
|
||||
@@ -120,3 +279,9 @@ function getStatusText(status: string) {
|
||||
return texts[status] || status
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-label {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user