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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user