feat: 알림 시스템 및 시설설비 관리 기능 구현
- 알림 시스템 구축 (navbar 알림 아이콘, 드롭다운) - 알림 수신자 설정 기능 (계정관리 페이지) - 시설설비 관리 페이지 추가 (수리 워크플로우) - 수리 신청 → 접수 → 처리중 → 완료 상태 관리 - 사이드바 메뉴 구조 개선 (공장 관리 카테고리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,114 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 알림 버튼 이벤트
|
||||
const notificationBtn = document.getElementById('notificationBtn');
|
||||
const notificationDropdown = document.getElementById('notificationDropdown');
|
||||
const notificationWrapper = document.getElementById('notificationWrapper');
|
||||
|
||||
if (notificationBtn && notificationDropdown) {
|
||||
notificationBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
notificationDropdown.classList.toggle('show');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (notificationWrapper && !notificationWrapper.contains(e.target)) {
|
||||
notificationDropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 알림 로드 =====
|
||||
async function loadNotifications() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
|
||||
const response = await fetch(`${window.API_BASE_URL}/notifications/unread`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (!response.ok) return;
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
const notifications = result.data || [];
|
||||
updateNotificationBadge(notifications.length);
|
||||
renderNotificationList(notifications);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('알림 로드 오류:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updateNotificationBadge(count) {
|
||||
const badge = document.getElementById('notificationBadge');
|
||||
const btn = document.getElementById('notificationBtn');
|
||||
if (!badge) return;
|
||||
|
||||
if (count > 0) {
|
||||
badge.textContent = count > 99 ? '99+' : count;
|
||||
badge.style.display = 'flex';
|
||||
btn?.classList.add('has-notifications');
|
||||
} else {
|
||||
badge.style.display = 'none';
|
||||
btn?.classList.remove('has-notifications');
|
||||
}
|
||||
}
|
||||
|
||||
function renderNotificationList(notifications) {
|
||||
const list = document.getElementById('notificationList');
|
||||
if (!list) return;
|
||||
|
||||
if (notifications.length === 0) {
|
||||
list.innerHTML = '<div class="notification-empty">새 알림이 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const icons = { repair: '🔧', safety: '⚠️', system: '📢', equipment: '🔩', maintenance: '🛠️' };
|
||||
|
||||
list.innerHTML = notifications.slice(0, 5).map(n => `
|
||||
<div class="notification-item ${n.is_read ? '' : 'unread'}" data-id="${n.notification_id}" data-url="${n.link_url || ''}">
|
||||
<div class="notification-item-icon ${n.type || 'repair'}">${icons[n.type] || '🔔'}</div>
|
||||
<div class="notification-item-content">
|
||||
<div class="notification-item-title">${escapeHtml(n.title)}</div>
|
||||
<div class="notification-item-desc">${escapeHtml(n.message || '')}</div>
|
||||
</div>
|
||||
<div class="notification-item-time">${formatTimeAgo(n.created_at)}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
list.querySelectorAll('.notification-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const url = item.dataset.url;
|
||||
// 수리 알림은 클릭해도 읽음 처리 안함 (수리 처리 페이지에서 확인 처리해야 함)
|
||||
window.location.href = url || '/pages/admin/notifications.html';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatTimeAgo(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
if (diffMins < 1) return '방금 전';
|
||||
if (diffMins < 60) return `${diffMins}분 전`;
|
||||
if (diffHours < 24) return `${diffHours}시간 전`;
|
||||
if (diffDays < 7) return `${diffDays}일 전`;
|
||||
return date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// ===== 날짜/시간 업데이트 =====
|
||||
@@ -387,6 +495,10 @@
|
||||
// 8. 날씨 (백그라운드)
|
||||
setTimeout(updateWeather, 100);
|
||||
|
||||
// 9. 알림 로드 (30초마다 갱신)
|
||||
setTimeout(loadNotifications, 200);
|
||||
setInterval(loadNotifications, 30000);
|
||||
|
||||
console.log('✅ app-init 완료');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user