feat: 알림 시스템 및 시설설비 관리 기능 구현
- 알림 시스템 구축 (navbar 알림 아이콘, 드롭다운) - 알림 수신자 설정 기능 (계정관리 페이지) - 시설설비 관리 페이지 추가 (수리 워크플로우) - 수리 신청 → 접수 → 처리중 → 완료 상태 관리 - 사이드바 메뉴 구조 개선 (공장 관리 카테고리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1074,3 +1074,190 @@ async function unlinkWorker() {
|
||||
}
|
||||
}
|
||||
window.unlinkWorker = unlinkWorker;
|
||||
|
||||
// ========== 알림 수신자 관리 ========== //
|
||||
let notificationRecipients = {};
|
||||
let allUsersForRecipient = [];
|
||||
let currentNotificationType = null;
|
||||
|
||||
const NOTIFICATION_TYPE_CONFIG = {
|
||||
repair: { name: '설비 수리', icon: '🔧', description: '설비 수리 신청 시 알림을 받을 사용자' },
|
||||
safety: { name: '안전 신고', icon: '⚠️', description: '안전 관련 신고 시 알림을 받을 사용자' },
|
||||
nonconformity: { name: '부적합 신고', icon: '🚫', description: '부적합 사항 신고 시 알림을 받을 사용자' },
|
||||
equipment: { name: '설비 관련', icon: '🔩', description: '설비 관련 알림을 받을 사용자' },
|
||||
maintenance: { name: '정기점검', icon: '🛠️', description: '정기점검 알림을 받을 사용자' },
|
||||
system: { name: '시스템', icon: '📢', description: '시스템 알림을 받을 사용자' }
|
||||
};
|
||||
|
||||
// 알림 수신자 목록 로드
|
||||
async function loadNotificationRecipients() {
|
||||
try {
|
||||
const response = await window.apiCall('/notification-recipients');
|
||||
if (response.success) {
|
||||
notificationRecipients = response.data || {};
|
||||
renderNotificationTypeCards();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('알림 수신자 로드 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 알림 유형 카드 렌더링
|
||||
function renderNotificationTypeCards() {
|
||||
const container = document.getElementById('notificationTypeCards');
|
||||
if (!container) return;
|
||||
|
||||
let html = '';
|
||||
|
||||
Object.keys(NOTIFICATION_TYPE_CONFIG).forEach(type => {
|
||||
const config = NOTIFICATION_TYPE_CONFIG[type];
|
||||
const recipients = notificationRecipients[type]?.recipients || [];
|
||||
|
||||
html += `
|
||||
<div class="notification-type-card ${type}">
|
||||
<div class="notification-type-header">
|
||||
<div class="notification-type-title">
|
||||
<span class="notification-type-icon">${config.icon}</span>
|
||||
<span>${config.name}</span>
|
||||
</div>
|
||||
<button class="edit-recipients-btn" onclick="openRecipientModal('${type}')">
|
||||
편집
|
||||
</button>
|
||||
</div>
|
||||
<div class="recipient-list">
|
||||
${recipients.length > 0
|
||||
? recipients.map(r => `
|
||||
<span class="recipient-tag">
|
||||
<span class="tag-icon">👤</span>
|
||||
${r.user_name || r.username}
|
||||
</span>
|
||||
`).join('')
|
||||
: '<span class="no-recipients">지정된 수신자 없음</span>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// 수신자 편집 모달 열기
|
||||
async function openRecipientModal(notificationType) {
|
||||
currentNotificationType = notificationType;
|
||||
const config = NOTIFICATION_TYPE_CONFIG[notificationType];
|
||||
|
||||
// 모달 정보 업데이트
|
||||
document.getElementById('recipientModalTitle').textContent = config.name + ' 알림 수신자';
|
||||
document.getElementById('recipientModalDesc').textContent = config.description;
|
||||
|
||||
// 사용자 목록 로드 (users가 이미 로드되어 있으면 사용)
|
||||
if (users.length === 0) {
|
||||
await loadUsers();
|
||||
}
|
||||
allUsersForRecipient = users.filter(u => u.is_active);
|
||||
|
||||
// 현재 수신자 목록
|
||||
const currentRecipients = notificationRecipients[notificationType]?.recipients || [];
|
||||
const currentRecipientIds = currentRecipients.map(r => r.user_id);
|
||||
|
||||
// 사용자 목록 렌더링
|
||||
renderRecipientUserList(currentRecipientIds);
|
||||
|
||||
// 검색 이벤트
|
||||
const searchInput = document.getElementById('recipientSearchInput');
|
||||
searchInput.value = '';
|
||||
searchInput.oninput = (e) => {
|
||||
renderRecipientUserList(currentRecipientIds, e.target.value);
|
||||
};
|
||||
|
||||
// 모달 표시
|
||||
document.getElementById('notificationRecipientModal').style.display = 'flex';
|
||||
}
|
||||
window.openRecipientModal = openRecipientModal;
|
||||
|
||||
// 수신자 사용자 목록 렌더링
|
||||
function renderRecipientUserList(selectedIds, searchTerm = '') {
|
||||
const container = document.getElementById('recipientUserList');
|
||||
if (!container) return;
|
||||
|
||||
let filteredUsers = allUsersForRecipient;
|
||||
|
||||
if (searchTerm) {
|
||||
const term = searchTerm.toLowerCase();
|
||||
filteredUsers = filteredUsers.filter(u =>
|
||||
(u.name && u.name.toLowerCase().includes(term)) ||
|
||||
(u.username && u.username.toLowerCase().includes(term))
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredUsers.length === 0) {
|
||||
container.innerHTML = '<div style="padding: 2rem; text-align: center; color: #6c757d;">사용자가 없습니다</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = filteredUsers.map(user => {
|
||||
const isSelected = selectedIds.includes(user.user_id);
|
||||
return `
|
||||
<div class="recipient-user-item ${isSelected ? 'selected' : ''}" onclick="toggleRecipientUser(${user.user_id}, this)">
|
||||
<input type="checkbox" ${isSelected ? 'checked' : ''} data-user-id="${user.user_id}">
|
||||
<div class="user-avatar-small">${(user.name || user.username).charAt(0)}</div>
|
||||
<div class="recipient-user-info">
|
||||
<div class="recipient-user-name">${user.name || user.username}</div>
|
||||
<div class="recipient-user-role">${getRoleName(user.role)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 수신자 토글
|
||||
function toggleRecipientUser(userId, element) {
|
||||
const checkbox = element.querySelector('input[type="checkbox"]');
|
||||
checkbox.checked = !checkbox.checked;
|
||||
element.classList.toggle('selected', checkbox.checked);
|
||||
}
|
||||
window.toggleRecipientUser = toggleRecipientUser;
|
||||
|
||||
// 수신자 모달 닫기
|
||||
function closeRecipientModal() {
|
||||
document.getElementById('notificationRecipientModal').style.display = 'none';
|
||||
currentNotificationType = null;
|
||||
}
|
||||
window.closeRecipientModal = closeRecipientModal;
|
||||
|
||||
// 알림 수신자 저장
|
||||
async function saveNotificationRecipients() {
|
||||
if (!currentNotificationType) {
|
||||
showToast('알림 유형이 선택되지 않았습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const checkboxes = document.querySelectorAll('#recipientUserList input[type="checkbox"]:checked');
|
||||
const userIds = Array.from(checkboxes).map(cb => parseInt(cb.dataset.userId));
|
||||
|
||||
const response = await window.apiCall(`/notification-recipients/${currentNotificationType}`, 'PUT', {
|
||||
user_ids: userIds
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
showToast('알림 수신자가 저장되었습니다.', 'success');
|
||||
closeRecipientModal();
|
||||
await loadNotificationRecipients();
|
||||
} else {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('알림 수신자 저장 오류:', error);
|
||||
showToast(`저장 중 오류가 발생했습니다: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
window.saveNotificationRecipients = saveNotificationRecipients;
|
||||
|
||||
// 초기화 시 알림 수신자 로드
|
||||
const originalInitializePage = initializePage;
|
||||
initializePage = async function() {
|
||||
await originalInitializePage();
|
||||
await loadNotificationRecipients();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user