/* ===== 알림 수신자 관리 ===== */ let nrLoaded = false; let nrData = {}; // { type: { label, recipients: [...] } } let nrTypes = {}; // { type: label } let nrCategories = {}; // { category: { label, icon, types: [...] } } let nrAllUsers = []; // 사용자 목록 (수신자 추가용) async function loadNotificationRecipientsTab() { if (nrLoaded) return; try { const [typesRes, allRes, usersRes] = await Promise.all([ api('/notification-recipients/types'), api('/notification-recipients'), api('/users?status=active') ]); nrTypes = typesRes.data?.types || typesRes.data || {}; nrCategories = typesRes.data?.categories || {}; nrData = allRes.data || {}; nrAllUsers = (usersRes.data || usersRes || []).filter(u => u.status !== 'inactive'); nrLoaded = true; renderNrTab(); } catch (e) { document.getElementById('nrContent').innerHTML = `

데이터 로드 실패: ${escapeHtml(e.message)}

`; } } function renderNrTab() { const container = document.getElementById('nrContent'); if (!Object.keys(nrTypes).length) { container.innerHTML = '

등록된 알림 유형이 없습니다.

'; return; } let html = ''; const categoryKeys = Object.keys(nrCategories); if (categoryKeys.length > 0) { // 카테고리별 그룹 렌더링 for (const [catKey, cat] of Object.entries(nrCategories)) { html += `

${escapeHtml(cat.label)}

`; for (const type of cat.types) { const label = nrTypes[type]; if (!label) continue; const recipients = nrData[type]?.recipients || []; html += renderNrTypeCard(type, label, recipients); } html += `
`; } } else { // 폴백: 카테고리 없이 flat 리스트 for (const [type, label] of Object.entries(nrTypes)) { const recipients = nrData[type]?.recipients || []; html += renderNrTypeCard(type, label, recipients); } } container.innerHTML = html; } function renderNrTypeCard(type, label, recipients) { return `

${escapeHtml(label)} ${recipients.length}명

${recipients.length === 0 ? '수신자 없음' : recipients.map(r => ` ${escapeHtml((r.user_name || r.username || '?')[0])} ${escapeHtml(r.user_name || r.username)} `).join('') }
`; } function nrTypeIcon(type) { const icons = { repair: 'fa-wrench', safety: 'fa-shield-alt', nonconformity: 'fa-exclamation-triangle', partner_work: 'fa-hard-hat', day_labor: 'fa-user-clock', system: 'fa-server' }; return icons[type] || 'fa-bell'; } /* ===== 수신자 추가 모달 ===== */ let nrAddType = ''; function openNrAddModal(type) { nrAddType = type; const label = nrTypes[type] || type; document.getElementById('nrAddModalTitle').textContent = `${label} 수신자 추가`; const currentIds = (nrData[type]?.recipients || []).map(r => r.user_id); const available = nrAllUsers.filter(u => !currentIds.includes(u.user_id)); const listEl = document.getElementById('nrAddUserList'); if (available.length === 0) { listEl.innerHTML = '

추가 가능한 사용자가 없습니다.

'; } else { listEl.innerHTML = available.map(u => ` `).join(''); } document.getElementById('nrAddModal').classList.remove('hidden'); } function closeNrAddModal() { document.getElementById('nrAddModal').classList.add('hidden'); } async function submitNrAdd() { const checked = [...document.querySelectorAll('.nrAddCheck:checked')].map(c => Number(c.value)); if (checked.length === 0) { showToast('사용자를 선택해주세요.', 'error'); return; } try { for (const userId of checked) { await api('/notification-recipients', { method: 'POST', body: JSON.stringify({ notification_type: nrAddType, user_id: userId }) }); } showToast(`${checked.length}명 수신자 추가 완료`); closeNrAddModal(); nrLoaded = false; await loadNotificationRecipientsTab(); } catch (e) { showToast('수신자 추가 실패: ' + e.message, 'error'); } } async function removeNrRecipient(type, userId) { if (!confirm('이 수신자를 제거하시겠습니까?')) return; try { await api(`/notification-recipients/${type}/${userId}`, { method: 'DELETE' }); showToast('수신자 제거 완료'); nrLoaded = false; await loadNotificationRecipientsTab(); } catch (e) { showToast('수신자 제거 실패: ' + e.message, 'error'); } }