diff --git a/tksafety/api/controllers/visitRequestController.js b/tksafety/api/controllers/visitRequestController.js index fc2fd3a..ebff1b4 100644 --- a/tksafety/api/controllers/visitRequestController.js +++ b/tksafety/api/controllers/visitRequestController.js @@ -280,6 +280,19 @@ exports.updateTrainingRecord = async (req, res) => { } }; +exports.deleteTrainingRecord = async (req, res) => { + try { + const result = await visitRequestModel.deleteTrainingRecord(req.params.id); + if (result.affectedRows === 0) { + return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' }); + } + res.json({ success: true, message: '안전교육 기록이 삭제되었습니다.' }); + } catch (err) { + console.error('안전교육 기록 삭제 오류:', err); + res.status(500).json({ success: false, message: '안전교육 기록 삭제 중 오류가 발생했습니다.' }); + } +}; + exports.completeTraining = async (req, res) => { try { const trainingId = req.params.id; diff --git a/tksafety/api/models/visitRequestModel.js b/tksafety/api/models/visitRequestModel.js index 6a1b649..66e249b 100644 --- a/tksafety/api/models/visitRequestModel.js +++ b/tksafety/api/models/visitRequestModel.js @@ -350,6 +350,42 @@ const completeTraining = async (trainingId, signatureData) => { return result; }; +const deleteTrainingRecord = async (trainingId) => { + const db = getPool(); + const connection = await db.getConnection(); + try { + await connection.beginTransaction(); + + // 1. training record 조회 → request_id 획득 + const [rows] = await connection.query( + 'SELECT training_id, request_id FROM safety_training_records WHERE training_id = ?', + [trainingId] + ); + if (!rows.length) { + await connection.rollback(); + return { affectedRows: 0 }; + } + const requestId = rows[0].request_id; + + // 2. training record 삭제 + await connection.query('DELETE FROM safety_training_records WHERE training_id = ?', [trainingId]); + + // 3. 출입 신청 상태를 approved로 복원 + await connection.query( + "UPDATE workplace_visit_requests SET status = 'approved', updated_at = NOW() WHERE request_id = ?", + [requestId] + ); + + await connection.commit(); + return { affectedRows: 1 }; + } catch (err) { + await connection.rollback(); + throw err; + } finally { + connection.release(); + } +}; + const getTrainingRecords = async (filters = {}) => { const db = getPool(); let query = ` @@ -581,6 +617,7 @@ module.exports = { createTrainingRecord, getTrainingRecordByRequestId, updateTrainingRecord, + deleteTrainingRecord, completeTraining, getTrainingRecords, getAllCategories, diff --git a/tksafety/api/routes/visitRequestRoutes.js b/tksafety/api/routes/visitRequestRoutes.js index e02d6e2..f70fa6d 100644 --- a/tksafety/api/routes/visitRequestRoutes.js +++ b/tksafety/api/routes/visitRequestRoutes.js @@ -41,6 +41,7 @@ router.post('/training', requireAdmin, visitRequestController.createTrainingReco router.get('/training', visitRequestController.getTrainingRecords); router.get('/training/request/:requestId', visitRequestController.getTrainingRecordByRequestId); router.put('/training/:id', requireAdmin, visitRequestController.updateTrainingRecord); +router.delete('/training/:id', requireAdmin, visitRequestController.deleteTrainingRecord); router.post('/training/:id/complete', requireAdmin, visitRequestController.completeTraining); module.exports = router; diff --git a/tksafety/web/static/js/tksafety-training.js b/tksafety/web/static/js/tksafety-training.js index 7f49a7d..49b2156 100644 --- a/tksafety/web/static/js/tksafety-training.js +++ b/tksafety/web/static/js/tksafety-training.js @@ -36,6 +36,12 @@ function renderPendingTraining() { + + `).join(''); } @@ -70,6 +76,12 @@ function renderCompletedTraining() { ${escapeHtml(t.trainer_full_name || t.trainer_name || '-')} ${t.completed_at ? '완료' : '진행중'} + + `; }).join(''); @@ -217,6 +229,112 @@ function clearSignature() { hasSignature = false; } +/* ===== 대기 목록: 출입 신청 수정/삭제 ===== */ +function openEditRequest(requestId) { + const r = pendingRequests.find(x => x.request_id === requestId); + if (!r) return; + document.getElementById('editRequestId').value = requestId; + document.getElementById('editReqCompany').value = r.visitor_company || ''; + document.getElementById('editReqCount').value = r.visitor_count || 1; + document.getElementById('editReqDate').value = r.visit_date ? r.visit_date.substring(0, 10) : ''; + document.getElementById('editReqTime').value = r.visit_time ? String(r.visit_time).substring(0, 5) : ''; + document.getElementById('editReqNotes').value = r.notes || ''; + document.getElementById('editRequestModal').classList.remove('hidden'); +} + +function closeEditRequestModal() { + document.getElementById('editRequestModal').classList.add('hidden'); +} + +async function submitEditRequest(e) { + e.preventDefault(); + const id = document.getElementById('editRequestId').value; + const r = pendingRequests.find(x => x.request_id === parseInt(id)); + if (!r) return; + + const data = { + visitor_company: document.getElementById('editReqCompany').value, + visitor_count: parseInt(document.getElementById('editReqCount').value), + category_id: r.category_id, + workplace_id: r.workplace_id, + visit_date: document.getElementById('editReqDate').value, + visit_time: document.getElementById('editReqTime').value, + purpose_id: r.purpose_id, + notes: document.getElementById('editReqNotes').value || null + }; + + try { + await api('/visit-requests/requests/' + id, { + method: 'PUT', body: JSON.stringify(data) + }); + showToast('출입 신청이 수정되었습니다'); + closeEditRequestModal(); + await loadPendingTraining(); + } catch (e) { + showToast(e.message, 'error'); + } +} + +async function doDeleteRequest(requestId) { + if (!confirm('이 출입 신청을 삭제하시겠습니까?')) return; + try { + await api('/visit-requests/requests/' + requestId, { method: 'DELETE' }); + showToast('출입 신청이 삭제되었습니다'); + await loadPendingTraining(); + } catch (e) { + showToast(e.message, 'error'); + } +} + +/* ===== 완료 이력: 교육 기록 수정/삭제 ===== */ +function openEditTraining(trainingId) { + const t = completedTrainings.find(x => x.training_id === trainingId); + if (!t) return; + document.getElementById('editTrainingId').value = trainingId; + document.getElementById('editTrainDate').value = t.training_date ? t.training_date.substring(0, 10) : ''; + document.getElementById('editTrainStartTime').value = t.training_start_time ? String(t.training_start_time).substring(0, 5) : ''; + document.getElementById('editTrainEndTime').value = t.training_end_time ? String(t.training_end_time).substring(0, 5) : ''; + document.getElementById('editTrainTopics').value = t.training_topics || ''; + document.getElementById('editTrainingModal').classList.remove('hidden'); +} + +function closeEditTrainingModal() { + document.getElementById('editTrainingModal').classList.add('hidden'); +} + +async function submitEditTraining(e) { + e.preventDefault(); + const id = document.getElementById('editTrainingId').value; + const data = { + training_date: document.getElementById('editTrainDate').value, + training_start_time: document.getElementById('editTrainStartTime').value, + training_end_time: document.getElementById('editTrainEndTime').value || null, + training_topics: document.getElementById('editTrainTopics').value || null + }; + + try { + await api('/visit-requests/training/' + id, { + method: 'PUT', body: JSON.stringify(data) + }); + showToast('교육 기록이 수정되었습니다'); + closeEditTrainingModal(); + await loadCompletedTraining(); + } catch (e) { + showToast(e.message, 'error'); + } +} + +async function doDeleteTraining(trainingId) { + if (!confirm('이 교육 기록을 삭제하시겠습니까?\n삭제 시 해당 출입 신청이 대기 목록으로 복원됩니다.')) return; + try { + await api('/visit-requests/training/' + trainingId, { method: 'DELETE' }); + showToast('교육 기록이 삭제되었습니다'); + await Promise.all([loadPendingTraining(), loadCompletedTraining()]); + } catch (e) { + showToast(e.message, 'error'); + } +} + /* ===== Init ===== */ function initTrainingPage() { if (!initAuth()) return; @@ -233,6 +351,8 @@ function initTrainingPage() { } document.getElementById('trainingForm').addEventListener('submit', submitTraining); + document.getElementById('editRequestForm').addEventListener('submit', submitEditRequest); + document.getElementById('editTrainingForm').addEventListener('submit', submitEditTraining); initSignaturePad(); loadPendingTraining(); loadCompletedTraining(); diff --git a/tksafety/web/training.html b/tksafety/web/training.html index 6eab0e1..a5619f2 100644 --- a/tksafety/web/training.html +++ b/tksafety/web/training.html @@ -133,6 +133,84 @@ + + + + + +