feat(training): 안전교육 실시 페이지 수정/삭제 기능 추가
대기 목록·완료 이력 양쪽에 수정/삭제 버튼 추가. 교육 기록 삭제 시 트랜잭션으로 출입 신청 상태를 approved로 복원. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,12 @@ function renderPendingTraining() {
|
||||
<button onclick="openTrainingModal(${r.request_id})" class="text-blue-600 hover:text-blue-800 text-xs px-2 py-1 border border-blue-200 rounded hover:bg-blue-50">
|
||||
<i class="fas fa-chalkboard-teacher mr-1"></i>교육실시
|
||||
</button>
|
||||
<button onclick="openEditRequest(${r.request_id})" class="text-gray-600 hover:text-gray-800 text-xs px-1.5 py-1 border border-gray-200 rounded hover:bg-gray-50 ml-1" title="수정">
|
||||
<i class="fas fa-pen"></i>
|
||||
</button>
|
||||
<button onclick="doDeleteRequest(${r.request_id})" class="text-red-500 hover:text-red-700 text-xs px-1.5 py-1 border border-red-200 rounded hover:bg-red-50 ml-1" title="삭제">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
@@ -70,6 +76,12 @@ function renderCompletedTraining() {
|
||||
<td>${escapeHtml(t.trainer_full_name || t.trainer_name || '-')}</td>
|
||||
<td class="text-right">
|
||||
${t.completed_at ? '<span class="badge badge-green">완료</span>' : '<span class="badge badge-amber">진행중</span>'}
|
||||
<button onclick="openEditTraining(${t.training_id})" class="text-gray-600 hover:text-gray-800 text-xs px-1.5 py-1 border border-gray-200 rounded hover:bg-gray-50 ml-1" title="수정">
|
||||
<i class="fas fa-pen"></i>
|
||||
</button>
|
||||
<button onclick="doDeleteTraining(${t.training_id})" class="text-red-500 hover:text-red-700 text-xs px-1.5 py-1 border border-red-200 rounded hover:bg-red-50 ml-1" title="삭제">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).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();
|
||||
|
||||
@@ -133,6 +133,84 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 출입 신청 수정 모달 -->
|
||||
<div id="editRequestModal" class="hidden modal-overlay" onclick="if(event.target===this)closeEditRequestModal()">
|
||||
<div class="modal-content p-6" style="max-width: 32rem;">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold">출입 신청 수정</h3>
|
||||
<button onclick="closeEditRequestModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<form id="editRequestForm">
|
||||
<input type="hidden" id="editRequestId">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">업체 <span class="text-red-400">*</span></label>
|
||||
<input type="text" id="editReqCompany" class="input-field w-full px-3 py-2 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">인원 <span class="text-red-400">*</span></label>
|
||||
<input type="number" id="editReqCount" class="input-field w-full px-3 py-2 rounded-lg text-sm" min="1" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">방문일 <span class="text-red-400">*</span></label>
|
||||
<input type="date" id="editReqDate" class="input-field w-full px-3 py-2 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">방문시간 <span class="text-red-400">*</span></label>
|
||||
<input type="time" id="editReqTime" class="input-field w-full px-3 py-2 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">비고</label>
|
||||
<textarea id="editReqNotes" class="input-field w-full px-3 py-2 rounded-lg text-sm" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4 gap-2">
|
||||
<button type="button" onclick="closeEditRequestModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
||||
<i class="fas fa-save mr-1"></i>저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 교육 기록 수정 모달 -->
|
||||
<div id="editTrainingModal" class="hidden modal-overlay" onclick="if(event.target===this)closeEditTrainingModal()">
|
||||
<div class="modal-content p-6" style="max-width: 32rem;">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold">교육 기록 수정</h3>
|
||||
<button onclick="closeEditTrainingModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<form id="editTrainingForm">
|
||||
<input type="hidden" id="editTrainingId">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">교육일 <span class="text-red-400">*</span></label>
|
||||
<input type="date" id="editTrainDate" class="input-field w-full px-3 py-2 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">시작시간 <span class="text-red-400">*</span></label>
|
||||
<input type="time" id="editTrainStartTime" class="input-field w-full px-3 py-2 rounded-lg text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">종료시간</label>
|
||||
<input type="time" id="editTrainEndTime" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">교육 내용</label>
|
||||
<textarea id="editTrainTopics" class="input-field w-full px-3 py-2 rounded-lg text-sm" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4 gap-2">
|
||||
<button type="button" onclick="closeEditTrainingModal()" class="px-4 py-2 border rounded-lg text-sm hover:bg-gray-50">취소</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
||||
<i class="fas fa-save mr-1"></i>저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/tksafety-core.js?v=3"></script>
|
||||
<script src="/static/js/tksafety-training.js"></script>
|
||||
<script>initTrainingPage();</script>
|
||||
|
||||
Reference in New Issue
Block a user