fix(tkfb): 대시보드 콘솔 에러 수정 (notifications, attendance, repair-requests)
- notifications/unread 호출 제거 → tkuser 링크로 대체 - attendance/today-summary → daily-status 엔드포인트로 변경 - GET /equipments/repair-requests 엔드포인트 신규 구현 - 캐시 버스팅 tkfb-dashboard.js?v=2026031701 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -558,6 +558,19 @@ const EquipmentController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getRepairRequests: async (req, res) => {
|
||||||
|
try {
|
||||||
|
const results = await EquipmentModel.getRepairRequests(req.query.status || null);
|
||||||
|
res.json({ success: true, data: results });
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('수리 요청 목록 조회 오류:', err);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: '수리 요청 목록 조회 중 오류가 발생했습니다.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getRepairCategories: async (req, res) => {
|
getRepairCategories: async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const results = await EquipmentModel.getRepairCategories();
|
const results = await EquipmentModel.getRepairCategories();
|
||||||
|
|||||||
@@ -710,6 +710,33 @@ const EquipmentModel = {
|
|||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getRepairRequests: async (status) => {
|
||||||
|
const db = await getDb();
|
||||||
|
let query = `
|
||||||
|
SELECT wir.report_id, wir.status, wir.additional_description, wir.created_at,
|
||||||
|
e.equipment_name, irc.category_name, iri.item_name,
|
||||||
|
u_rep.name AS reported_by_name, w.workplace_name
|
||||||
|
FROM work_issue_reports wir
|
||||||
|
INNER JOIN issue_report_categories irc ON wir.issue_category_id = irc.category_id
|
||||||
|
LEFT JOIN issue_report_items iri ON wir.issue_item_id = iri.item_id
|
||||||
|
LEFT JOIN equipments e ON wir.equipment_id = e.equipment_id
|
||||||
|
LEFT JOIN users u_rep ON wir.reporter_id = u_rep.user_id
|
||||||
|
LEFT JOIN workplaces w ON wir.workplace_id = w.workplace_id
|
||||||
|
WHERE irc.category_name = '설비 수리'
|
||||||
|
`;
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
query += ' AND wir.status = ?';
|
||||||
|
values.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ' ORDER BY wir.created_at DESC';
|
||||||
|
|
||||||
|
const [rows] = await db.query(query, values);
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
|
||||||
getRepairCategories: async () => {
|
getRepairCategories: async () => {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
const [rows] = await db.query(
|
const [rows] = await db.query(
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ router.get('/repair-categories', equipmentController.getRepairCategories);
|
|||||||
// 새 수리 항목 추가
|
// 새 수리 항목 추가
|
||||||
router.post('/repair-categories', equipmentController.addRepairCategory);
|
router.post('/repair-categories', equipmentController.addRepairCategory);
|
||||||
|
|
||||||
|
// 수리 요청 목록 조회 (?status=pending)
|
||||||
|
router.get('/repair-requests', equipmentController.getRepairRequests);
|
||||||
|
|
||||||
// ==================== 사진 관리 ====================
|
// ==================== 사진 관리 ====================
|
||||||
|
|
||||||
// 사진 삭제 (설비 ID 없이 photo_id만으로)
|
// 사진 삭제 (설비 ID 없이 photo_id만으로)
|
||||||
|
|||||||
@@ -65,8 +65,8 @@
|
|||||||
<div class="stat-label">수리 요청</div>
|
<div class="stat-label">수리 요청</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value text-purple-600" id="statNotifications">-</div>
|
<div class="stat-value text-purple-600" id="statNotifications"><i class="fas fa-external-link-alt text-base"></i></div>
|
||||||
<div class="stat-label">미확인 알림</div>
|
<div class="stat-label">알림 관리</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -139,6 +139,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/tkfb-core.js?v=2026031601"></script>
|
<script src="/static/js/tkfb-core.js?v=2026031601"></script>
|
||||||
<script src="/static/js/tkfb-dashboard.js"></script>
|
<script src="/static/js/tkfb-dashboard.js?v=2026031701"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -16,30 +16,28 @@ async function loadDashboard() {
|
|||||||
|
|
||||||
const results = await Promise.allSettled([
|
const results = await Promise.allSettled([
|
||||||
api('/tbm/sessions/date/' + today).catch(() => ({ data: [] })),
|
api('/tbm/sessions/date/' + today).catch(() => ({ data: [] })),
|
||||||
api('/notifications/unread').catch(() => ({ data: [] })),
|
|
||||||
api('/equipments/repair-requests?status=pending').catch(() => ({ data: [] })),
|
api('/equipments/repair-requests?status=pending').catch(() => ({ data: [] })),
|
||||||
api('/attendance/today-summary').catch(() => ({ data: {} })),
|
api('/attendance/daily-status?date=' + today).catch(() => ({ data: [] })),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tbmData = results[0].status === 'fulfilled' ? results[0].value : { data: [] };
|
const tbmData = results[0].status === 'fulfilled' ? results[0].value : { data: [] };
|
||||||
const notifData = results[1].status === 'fulfilled' ? results[1].value : { data: [] };
|
const repairData = results[1].status === 'fulfilled' ? results[1].value : { data: [] };
|
||||||
const repairData = results[2].status === 'fulfilled' ? results[2].value : { data: [] };
|
const attendData = results[2].status === 'fulfilled' ? results[2].value : { data: [] };
|
||||||
const attendData = results[3].status === 'fulfilled' ? results[3].value : { data: {} };
|
|
||||||
|
|
||||||
const tbmSessions = tbmData.data || [];
|
const tbmSessions = tbmData.data || [];
|
||||||
const notifications = notifData.data || [];
|
|
||||||
const repairs = repairData.data || [];
|
const repairs = repairData.data || [];
|
||||||
const attendance = attendData.data || {};
|
const attendList = Array.isArray(attendData.data) ? attendData.data : [];
|
||||||
|
const checkedInCount = attendList.filter(d => d.status !== 'incomplete').length;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
document.getElementById('statTbm').textContent = tbmSessions.length;
|
document.getElementById('statTbm').textContent = tbmSessions.length;
|
||||||
document.getElementById('statWorkers').textContent = attendance.checked_in_count || 0;
|
document.getElementById('statWorkers').textContent = checkedInCount;
|
||||||
document.getElementById('statRepairs').textContent = repairs.length;
|
document.getElementById('statRepairs').textContent = repairs.length;
|
||||||
document.getElementById('statNotifications').textContent = notifications.length;
|
document.getElementById('statNotifications').textContent = '-';
|
||||||
|
|
||||||
// TBM list
|
// TBM list
|
||||||
renderTbmList(tbmSessions);
|
renderTbmList(tbmSessions);
|
||||||
renderNotificationList(notifications);
|
renderNotificationPanel();
|
||||||
renderRepairList(repairs);
|
renderRepairList(repairs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,23 +62,15 @@ function renderTbmList(sessions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNotificationList(notifications) {
|
function renderNotificationPanel() {
|
||||||
const el = document.getElementById('notificationList');
|
const el = document.getElementById('notificationList');
|
||||||
if (!notifications.length) {
|
el.innerHTML = `<div class="flex flex-col items-center gap-3 py-6">
|
||||||
el.innerHTML = '<p class="text-gray-400 text-sm text-center py-4">새 알림이 없습니다</p>';
|
<i class="fas fa-bell text-gray-300 text-3xl"></i>
|
||||||
return;
|
<p class="text-gray-400 text-sm">알림은 사용자관리에서 확인하세요</p>
|
||||||
}
|
<a href="${_tkuserBase}/?tab=notificationRecipients" class="text-sm text-orange-600 hover:text-orange-700 font-medium">
|
||||||
const icons = { repair: 'fa-wrench text-amber-500', safety: 'fa-shield-alt text-red-500', system: 'fa-bell text-blue-500', equipment: 'fa-cog text-gray-500', maintenance: 'fa-tools text-green-500' };
|
<i class="fas fa-external-link-alt mr-1"></i>알림 관리 바로가기
|
||||||
el.innerHTML = notifications.slice(0, 5).map(n => {
|
</a>
|
||||||
const iconClass = icons[n.type] || 'fa-bell text-gray-400';
|
</div>`;
|
||||||
return `<div class="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100" onclick="location.href='${_tkuserBase}/?tab=notificationRecipients'">
|
|
||||||
<i class="fas ${iconClass} mt-0.5"></i>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<div class="text-sm font-medium text-gray-800 truncate">${escapeHtml(n.title)}</div>
|
|
||||||
<div class="text-xs text-gray-500 mt-0.5">${formatTimeAgo(n.created_at)}</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRepairList(repairs) {
|
function renderRepairList(repairs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user