feat: 수신함 상세 정보 표시 개선 및 읽음 처리 기능 제거

📋 Enhanced Information Display:
- 업로드 시간 상세 표시 (날짜 + 시간)
- 사진 정보 개선 (사진 개수 표시: '사진 2장', '사진 없음')
- 신고 ID 표시로 식별성 향상
- 공수 정보 및 상세 메모 표시
- 4개 정보 그리드 레이아웃 (신고자, 카테고리, 사진, 시간)

🎨 Visual Improvements:
- 색상별 아이콘으로 정보 구분 (👤🏷️📷)
- 중요 정보 강조 표시 (굵은 글씨)
- 업로드 정보 배경 구분 (회색 배경)
- 좌측 파란색 테두리로 카드 구분
- 호버 효과 및 클릭 가능한 제목

🗑️ Removed Read Status Features:
- 읽음 상태 변수 및 Set 제거
- 읽음 처리 버튼 완전 제거
- 모두 읽음 처리 버튼 제거
- localStorage 읽음 상태 로직 제거
- 읽음/안읽음 구분 UI 제거

📊 New Dashboard Statistics:
- 전체: 수신함에 남아있는 목록 개수
- 금일 신규: 오늘 올라온 목록 숫자 (확인된 것 포함)
- 금일 처리: 오늘 처리된 건수
- 미해결: 오늘꺼 제외한 남아있는 것들

🔧 Code Improvements:
- 통계 계산 로직 개선 (클라이언트 기반)
- 날짜/시간 처리 개선
- 사진 개수 동적 계산
- 불필요한 읽음 상태 관련 코드 정리

Expected Result:
 더 상세하고 유용한 정보 표시
 깔끔하고 직관적인 카드 레이아웃
 불필요한 읽음 처리 기능 제거로 단순화
 새로운 기준의 의미있는 통계 제공
This commit is contained in:
Hyungi Ahn
2025-10-25 13:11:29 +09:00
parent aacf05d05c
commit 73240d425a

View File

@@ -137,10 +137,6 @@
<p class="text-gray-600 mt-1">새로 등록된 신고 사항을 확인하고 처리하세요</p>
</div>
<div class="flex items-center space-x-3">
<button onclick="markAllAsRead()" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
<i class="fas fa-check-double mr-2"></i>
모두 읽음 처리
</button>
<button onclick="refreshInbox()" class="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors">
<i class="fas fa-sync-alt mr-2"></i>
새로고침
@@ -152,19 +148,19 @@
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-envelope text-blue-500 text-xl mr-3"></i>
<i class="fas fa-list text-blue-500 text-xl mr-3"></i>
<div>
<p class="text-sm text-blue-600">새 부적합</p>
<p class="text-2xl font-bold text-blue-700" id="newIssuesCount">0</p>
<p class="text-sm text-blue-600">전체</p>
<p class="text-2xl font-bold text-blue-700" id="totalCount">0</p>
</div>
</div>
</div>
<div class="bg-yellow-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-clock text-yellow-500 text-xl mr-3"></i>
<i class="fas fa-plus-circle text-yellow-500 text-xl mr-3"></i>
<div>
<p class="text-sm text-yellow-600">처리 대기</p>
<p class="text-2xl font-bold text-yellow-700" id="pendingIssuesCount">0</p>
<p class="text-sm text-yellow-600">금일 신규</p>
<p class="text-2xl font-bold text-yellow-700" id="todayNewCount">0</p>
</div>
</div>
</div>
@@ -172,17 +168,17 @@
<div class="flex items-center">
<i class="fas fa-check-circle text-green-500 text-xl mr-3"></i>
<div>
<p class="text-sm text-green-600">오늘 처리</p>
<p class="text-sm text-green-600">금일 처리</p>
<p class="text-2xl font-bold text-green-700" id="todayProcessedCount">0</p>
</div>
</div>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<div class="bg-red-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-chart-line text-purple-500 text-xl mr-3"></i>
<i class="fas fa-exclamation-triangle text-red-500 text-xl mr-3"></i>
<div>
<p class="text-sm text-purple-600">전체</p>
<p class="text-2xl font-bold text-purple-700" id="totalIssuesCount">0</p>
<p class="text-sm text-red-600">미해결</p>
<p class="text-2xl font-bold text-red-700" id="unresolvedCount">0</p>
</div>
</div>
</div>
@@ -390,7 +386,6 @@
let issues = [];
let projects = [];
let filteredIssues = [];
let readStatus = new Set(); // 읽은 부적합 ID 저장
// 애니메이션 함수들
function animateHeaderAppearance() {
@@ -580,11 +575,6 @@
if (response.ok) {
issues = await response.json();
// 읽음 상태 로드 (localStorage에서)
const savedReadStatus = localStorage.getItem('inbox_read_status');
if (savedReadStatus) {
readStatus = new Set(JSON.parse(savedReadStatus));
}
filterIssues();
await loadStatistics();
@@ -627,10 +617,6 @@
case 'priority':
const priorityOrder = { 'high': 3, 'medium': 2, 'low': 1 };
return (priorityOrder[b.priority] || 1) - (priorityOrder[a.priority] || 1);
case 'unread':
const aRead = readStatus.has(a.id) ? 1 : 0;
const bRead = readStatus.has(b.id) ? 1 : 0;
return aRead - bRead;
default:
return new Date(b.created_at) - new Date(a.created_at);
}
@@ -651,30 +637,69 @@
emptyState.classList.add('hidden');
container.innerHTML = filteredIssues.map(issue => {
const isUnread = !readStatus.has(issue.id);
const project = projects.find(p => p.id === issue.project_id);
const createdDate = new Date(issue.created_at).toLocaleDateString('ko-KR');
const timeAgo = getTimeAgo(new Date(issue.created_at));
const reportDate = new Date(issue.report_date);
const createdDate = reportDate.toLocaleDateString('ko-KR');
const createdTime = reportDate.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' });
const timeAgo = getTimeAgo(reportDate);
// 사진 정보 처리
const photoCount = [issue.photo_path, issue.photo_path2].filter(Boolean).length;
const photoInfo = photoCount > 0 ? `사진 ${photoCount}` : '사진 없음';
return `
<div class="issue-card p-6 hover:bg-gray-50 ${isUnread ? 'unread' : ''}"
<div class="issue-card p-6 hover:bg-gray-50 border-l-4 border-blue-500"
data-issue-id="${issue.id}">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-2">
${isUnread ? '<div class="w-2 h-2 bg-blue-500 rounded-full"></div>' : '<div class="w-2 h-2"></div>'}
<span class="badge badge-new">검토 대기</span>
${project ? `<span class="text-sm text-gray-500">${project.project_name}</span>` : ''}
<span class="text-sm text-gray-400">${timeAgo}</span>
<!-- 상단 정보 -->
<div class="flex items-center justify-between mb-3">
<div class="flex items-center space-x-3">
<span class="badge badge-new">검토 대기</span>
${project ? `<span class="text-sm font-medium text-blue-600">${project.project_name}</span>` : '<span class="text-sm text-gray-400">프로젝트 미지정</span>'}
</div>
<span class="text-xs text-gray-400">ID: ${issue.id}</span>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2 cursor-pointer" onclick="viewIssueDetail(${issue.id})">${issue.description}</h3>
<!-- 제목 -->
<h3 class="text-lg font-semibold text-gray-900 mb-3 cursor-pointer hover:text-blue-600 transition-colors" onclick="viewIssueDetail(${issue.id})">${issue.description}</h3>
<div class="flex items-center text-sm text-gray-500 space-x-4 mb-3">
<span><i class="fas fa-user mr-1"></i>${issue.reporter?.username || '알 수 없음'}</span>
<span><i class="fas fa-calendar mr-1"></i>${createdDate}</span>
${issue.category ? `<span><i class="fas fa-tag mr-1"></i>${getCategoryText(issue.category)}</span>` : ''}
${issue.photo_path ? '<span><i class="fas fa-camera mr-1"></i>사진 첨부</span>' : ''}
<!-- 상세 정보 그리드 -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4 text-sm">
<div class="flex items-center text-gray-600">
<i class="fas fa-user mr-2 text-blue-500"></i>
<span class="font-medium">${issue.reporter?.username || '알 수 없음'}</span>
</div>
<div class="flex items-center text-gray-600">
<i class="fas fa-tag mr-2 text-green-500"></i>
<span>${getCategoryText(issue.category)}</span>
</div>
<div class="flex items-center text-gray-600">
<i class="fas fa-camera mr-2 text-purple-500"></i>
<span class="${photoCount > 0 ? 'text-purple-600 font-medium' : ''}">${photoInfo}</span>
</div>
<div class="flex items-center text-gray-600">
<i class="fas fa-clock mr-2 text-orange-500"></i>
<span class="font-medium">${timeAgo}</span>
</div>
</div>
<!-- 업로드 시간 정보 -->
<div class="bg-gray-50 rounded-lg p-3 mb-4">
<div class="flex items-center justify-between text-sm">
<div class="flex items-center text-gray-600">
<i class="fas fa-calendar-alt mr-2"></i>
<span>업로드: <strong>${createdDate} ${createdTime}</strong></span>
</div>
${issue.work_hours > 0 ? `<div class="flex items-center text-gray-600">
<i class="fas fa-hourglass-half mr-2"></i>
<span>공수: <strong>${issue.work_hours}시간</strong></span>
</div>` : ''}
</div>
${issue.detail_notes ? `<div class="mt-2 text-sm text-gray-600">
<i class="fas fa-sticky-note mr-2"></i>
<span class="italic">"${issue.detail_notes}"</span>
</div>` : ''}
</div>
<!-- 워크플로우 액션 버튼들 -->
@@ -694,65 +719,82 @@
</div>
</div>
<div class="flex items-center space-x-2 ml-4">
<button onclick="markAsRead(${issue.id})"
class="p-2 text-gray-400 hover:text-blue-600 transition-colors"
title="${isUnread ? '읽음 처리' : '읽음'}">
<i class="fas fa-${isUnread ? 'envelope' : 'envelope-open'}"></i>
</button>
</div>
</div>
</div>
`;
}).join('');
}
// 수신함 통계 로드 (실제 API 연동)
// 통계 로드 (새로운 기준)
async function loadStatistics() {
try {
const response = await fetch('/api/inbox/statistics', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
'Content-Type': 'application/json'
// 현재 수신함 이슈들을 기반으로 통계 계산
const today = new Date();
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
// 전체: 수신함에 남아있는 목록 개수 (pending_review 상태)
const totalCount = issues.length;
// 금일 신규: 오늘 올라온 목록 숫자 (확인된 것 포함)
const todayNewCount = issues.filter(issue => {
const reportDate = new Date(issue.report_date);
return reportDate >= todayStart;
}).length;
// 금일 처리: 오늘 처리된 건수 (API에서 가져와야 함)
let todayProcessedCount = 0;
try {
const processedResponse = await fetch('/api/inbox/statistics', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
'Content-Type': 'application/json'
}
});
if (processedResponse.ok) {
const stats = await processedResponse.json();
todayProcessedCount = stats.today_processed || 0;
}
});
if (response.ok) {
const stats = await response.json();
document.getElementById('newIssuesCount').textContent = stats.pending_review || 0;
document.getElementById('pendingIssuesCount').textContent = stats.total_in_management || 0;
document.getElementById('todayProcessedCount').textContent = stats.today_processed || 0;
document.getElementById('totalIssuesCount').textContent = stats.total_issues || 0;
} else {
console.error('통계 로드 실패');
} catch (e) {
console.log('처리된 건수 조회 실패:', e);
}
// 미해결: 오늘꺼 제외한 남아있는 것들
const unresolvedCount = issues.filter(issue => {
const reportDate = new Date(issue.report_date);
return reportDate < todayStart;
}).length;
// 통계 업데이트
document.getElementById('totalCount').textContent = totalCount;
document.getElementById('todayNewCount').textContent = todayNewCount;
document.getElementById('todayProcessedCount').textContent = todayProcessedCount;
document.getElementById('unresolvedCount').textContent = unresolvedCount;
console.log('📊 통계 업데이트:', {
전체: totalCount,
금일신규: todayNewCount,
금일처리: todayProcessedCount,
미해결: unresolvedCount
});
} catch (error) {
console.error('통계 로드 오류:', error);
// 오류 시 기본값 설정
document.getElementById('totalCount').textContent = '0';
document.getElementById('todayNewCount').textContent = '0';
document.getElementById('todayProcessedCount').textContent = '0';
document.getElementById('unresolvedCount').textContent = '0';
}
}
// 읽음 처리
function markAsRead(issueId) {
readStatus.add(issueId);
localStorage.setItem('inbox_read_status', JSON.stringify([...readStatus]));
displayIssues();
}
// 모두 읽음 처리
function markAllAsRead() {
filteredIssues.forEach(issue => readStatus.add(issue.id));
localStorage.setItem('inbox_read_status', JSON.stringify([...readStatus]));
displayIssues();
}
// 새로고침
function refreshInbox() {
loadIssues();
}
// 부적합 상세 보기
// 신고 상세 보기
function viewIssueDetail(issueId) {
markAsRead(issueId);
window.location.href = `/issue-view.html#detail-${issueId}`;
}
@@ -1063,3 +1105,4 @@
</script>
</body>
</html>