diff --git a/gateway/html/shared/notification-bell.js b/gateway/html/shared/notification-bell.js index b8f0766..811940c 100644 --- a/gateway/html/shared/notification-bell.js +++ b/gateway/html/shared/notification-bell.js @@ -365,7 +365,7 @@ } function _typeLabel(type) { - var map = { system: '시스템', repair: '수리', safety: '안전', maintenance: '구매' }; + var map = { system: '시스템', repair: '설비수리', safety: '안전', nonconformity: '부적합', partner_work: '협력업체', day_labor: '일용공' }; return map[type] || type || '알림'; } diff --git a/system1-factory/web/pages/admin/notifications.html b/system1-factory/web/pages/admin/notifications.html index 649baa6..708ee30 100644 --- a/system1-factory/web/pages/admin/notifications.html +++ b/system1-factory/web/pages/admin/notifications.html @@ -187,6 +187,18 @@ background: var(--primary-100); } + .notification-icon.nonconformity { + background: #FEE2E2; + } + + .notification-icon.partner_work { + background: #DBEAFE; + } + + .notification-icon.day_labor { + background: #E0E7FF; + } + .notification-content { flex: 1; min-width: 0; @@ -456,8 +468,9 @@ repair: '🔧', safety: '⚠️', system: '📢', - equipment: '🔩', - maintenance: '🛠️' + nonconformity: '❗', + partner_work: '🏗️', + day_labor: '👷' }; return icons[type] || '🔔'; } diff --git a/system1-factory/web/static/js/tkfb-core.js b/system1-factory/web/static/js/tkfb-core.js index 0f08a19..16b6110 100644 --- a/system1-factory/web/static/js/tkfb-core.js +++ b/system1-factory/web/static/js/tkfb-core.js @@ -288,6 +288,6 @@ async function initAuth() { /* ===== 알림 벨 ===== */ function _loadNotificationBell() { const s = document.createElement('script'); - s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); } diff --git a/system2-report/web/js/api-base.js b/system2-report/web/js/api-base.js index 1175244..008e115 100644 --- a/system2-report/web/js/api-base.js +++ b/system2-report/web/js/api-base.js @@ -153,7 +153,7 @@ if ('serviceWorker' in navigator) { window._loadNotificationBell = function() { var h = window.location.hostname; var s = document.createElement('script'); - s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); }; diff --git a/system3-nonconformance/web/static/js/app.js b/system3-nonconformance/web/static/js/app.js index c201ff6..41df9ba 100644 --- a/system3-nonconformance/web/static/js/app.js +++ b/system3-nonconformance/web/static/js/app.js @@ -401,7 +401,7 @@ class App { _loadNotificationBell() { var h = window.location.hostname; var s = document.createElement('script'); - s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); } diff --git a/tkpurchase/api/controllers/dayLaborController.js b/tkpurchase/api/controllers/dayLaborController.js index f1593c3..0d2dd8a 100644 --- a/tkpurchase/api/controllers/dayLaborController.js +++ b/tkpurchase/api/controllers/dayLaborController.js @@ -51,7 +51,7 @@ async function create(req, res) { // 알림: 일용공 신청 notify.send({ - type: 'maintenance', + type: 'day_labor', title: '일용공 신청', message: `${work_date} ${worker_count}명 일용공 신청`, link_url: '/daylabor.html', diff --git a/tkpurchase/api/controllers/workReportController.js b/tkpurchase/api/controllers/workReportController.js index 87ad4f9..94f054c 100644 --- a/tkpurchase/api/controllers/workReportController.js +++ b/tkpurchase/api/controllers/workReportController.js @@ -94,7 +94,7 @@ async function create(req, res) { // 알림: 작업 보고서 제출 notify.send({ - type: 'maintenance', + type: 'partner_work', title: '작업 보고서 제출', message: `${report_date} 작업 보고서가 제출되었습니다.`, link_url: '/workreport.html', diff --git a/tkpurchase/web/static/js/tkpurchase-core.js b/tkpurchase/web/static/js/tkpurchase-core.js index e6d4f1e..aea9a9d 100644 --- a/tkpurchase/web/static/js/tkpurchase-core.js +++ b/tkpurchase/web/static/js/tkpurchase-core.js @@ -156,6 +156,6 @@ function initAuth() { /* ===== 알림 벨 ===== */ function _loadNotificationBell() { const s = document.createElement('script'); - s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); } diff --git a/tksafety/web/static/js/tksafety-core.js b/tksafety/web/static/js/tksafety-core.js index ca0982d..4a9ca41 100644 --- a/tksafety/web/static/js/tksafety-core.js +++ b/tksafety/web/static/js/tksafety-core.js @@ -147,6 +147,6 @@ function initAuth() { /* ===== 알림 벨 ===== */ function _loadNotificationBell() { const s = document.createElement('script'); - s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); } diff --git a/user-management/api/models/notificationRecipientModel.js b/user-management/api/models/notificationRecipientModel.js index 3315b80..2d31edf 100644 --- a/user-management/api/models/notificationRecipientModel.js +++ b/user-management/api/models/notificationRecipientModel.js @@ -2,18 +2,41 @@ const { getPool } = require('./userModel'); const NOTIFICATION_TYPES = { - repair: '설비 수리', - safety: '안전 신고', + repair: '설비수리', + safety: '안전신고', nonconformity: '부적합 신고', - equipment: '설비 관련', - maintenance: '정기점검', - system: '시스템' + system: '시스템', + partner_work: '협력업체 작업', + day_labor: '일용공 신청' +}; + +const NOTIFICATION_CATEGORIES = { + production: { + label: '생산', + icon: 'fa-industry', + types: ['repair', 'nonconformity'] + }, + safety: { + label: '안전', + icon: 'fa-shield-alt', + types: ['safety'] + }, + purchase: { + label: '구매', + icon: 'fa-shopping-cart', + types: ['partner_work', 'day_labor'] + }, + system: { + label: '시스템', + icon: 'fa-server', + types: ['system'] + } }; const notificationRecipientModel = { // 알림 유형 목록 가져오기 getTypes() { - return NOTIFICATION_TYPES; + return { types: NOTIFICATION_TYPES, categories: NOTIFICATION_CATEGORIES }; }, // 유형별 수신자 목록 조회 diff --git a/user-management/web/index.html b/user-management/web/index.html index 2a9b5f8..f30c8e1 100644 --- a/user-management/web/index.html +++ b/user-management/web/index.html @@ -1753,7 +1753,7 @@ - + diff --git a/user-management/web/static/js/tkuser-core.js b/user-management/web/static/js/tkuser-core.js index 5b67f15..2829eed 100644 --- a/user-management/web/static/js/tkuser-core.js +++ b/user-management/web/static/js/tkuser-core.js @@ -192,6 +192,6 @@ async function init() { /* ===== 알림 벨 ===== */ function _loadNotificationBell() { const s = document.createElement('script'); - s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=1'; + s.src = (location.hostname.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30000') + '/shared/notification-bell.js?v=2'; document.head.appendChild(s); } diff --git a/user-management/web/static/js/tkuser-notificationRecipients.js b/user-management/web/static/js/tkuser-notificationRecipients.js index a2d2c3f..80e183c 100644 --- a/user-management/web/static/js/tkuser-notificationRecipients.js +++ b/user-management/web/static/js/tkuser-notificationRecipients.js @@ -2,6 +2,7 @@ let nrLoaded = false; let nrData = {}; // { type: { label, recipients: [...] } } let nrTypes = {}; // { type: label } +let nrCategories = {}; // { category: { label, icon, types: [...] } } let nrAllUsers = []; // 사용자 목록 (수신자 추가용) async function loadNotificationRecipientsTab() { @@ -12,7 +13,8 @@ async function loadNotificationRecipientsTab() { api('/notification-recipients'), api('/users?status=active') ]); - nrTypes = typesRes.data || {}; + nrTypes = typesRes.data?.types || typesRes.data || {}; + nrCategories = typesRes.data?.categories || {}; nrData = allRes.data || {}; nrAllUsers = (usersRes.data || usersRes || []).filter(u => u.status !== 'inactive'); nrLoaded = true; @@ -31,45 +33,76 @@ function renderNrTab() { } let html = ''; - for (const [type, label] of Object.entries(nrTypes)) { - const recipients = nrData[type]?.recipients || []; - html += ` -
-
-

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

- -
-
- ${recipients.length === 0 - ? '수신자 없음' - : recipients.map(r => ` - - ${escapeHtml((r.user_name || r.username || '?')[0])} - ${escapeHtml(r.user_name || r.username)} - - - `).join('') - } -
-
`; + 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', - equipment: 'fa-cogs', - maintenance: 'fa-calendar-check', + partner_work: 'fa-hard-hat', + day_labor: 'fa-user-clock', system: 'fa-server' }; return icons[type] || 'fa-bell';