feat(tksafety): 체크리스트 작업별 항목에 tkuser 작업(task) 참조 연동
- getAllChecks: tasks/work_types/weather_conditions JOIN + 프론트엔드 필드명 alias - createCheck/updateCheck: item_type→check_type 등 프론트-DB 필드 매핑 - 모달에 작업(task) 드롭다운 추가, 공정 선택 시 동적 로드 - renderWorktypeItems: work_type → task 2단 그룹핑 - openEditItem: async/await로 task 목록 로드 후 값 설정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -122,11 +122,18 @@
|
||||
</div>
|
||||
<!-- 작업유형 (work_type only) -->
|
||||
<div id="workTypeField" class="hidden">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">작업 유형 <span class="text-red-400">*</span></label>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">공정 <span class="text-red-400">*</span></label>
|
||||
<select id="itemWorkType" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 작업 선택 (work_type 선택 시 표시) -->
|
||||
<div id="taskField" class="hidden">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">작업 <span class="text-red-400">*</span></label>
|
||||
<select id="itemTask" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
||||
<option value="">공정을 먼저 선택하세요</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">카테고리</label>
|
||||
<input type="text" id="itemCategory" class="input-field w-full px-3 py-2 rounded-lg text-sm" placeholder="예: 개인보호구, 작업환경">
|
||||
@@ -155,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/static/js/tksafety-core.js?v=3"></script>
|
||||
<script src="/static/js/tksafety-checklist.js"></script>
|
||||
<script src="/static/js/tksafety-checklist.js?v=2"></script>
|
||||
<script>initChecklistPage();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -53,6 +53,32 @@ async function loadLookupData() {
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Load tasks by work type ===== */
|
||||
async function loadTasksByWorkType(workTypeId) {
|
||||
const taskSelect = document.getElementById('itemTask');
|
||||
if (!workTypeId) {
|
||||
taskSelect.innerHTML = '<option value="">공정을 먼저 선택하세요</option>';
|
||||
document.getElementById('taskField').classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await api('/checklist/tasks/' + workTypeId);
|
||||
const tasks = res.data || [];
|
||||
taskSelect.innerHTML = '<option value="">작업 선택</option>' +
|
||||
tasks.map(t => `<option value="${t.task_id}">${escapeHtml(t.task_name)}</option>`).join('');
|
||||
document.getElementById('taskField').classList.remove('hidden');
|
||||
} catch (e) {
|
||||
console.error('Task load error:', e);
|
||||
taskSelect.innerHTML = '<option value="">로드 실패</option>';
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Work type change handler ===== */
|
||||
function onWorkTypeChange() {
|
||||
const workTypeId = document.getElementById('itemWorkType').value;
|
||||
loadTasksByWorkType(workTypeId);
|
||||
}
|
||||
|
||||
/* ===== Render basic items ===== */
|
||||
function renderBasicItems() {
|
||||
const items = checklistItems.filter(i => i.item_type === 'basic');
|
||||
@@ -109,7 +135,7 @@ function renderWeatherItems() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
/* ===== Render worktype items ===== */
|
||||
/* ===== Render worktype items (2-level: work_type → task) ===== */
|
||||
function renderWorktypeItems() {
|
||||
const items = checklistItems.filter(i => i.item_type === 'work_type');
|
||||
const container = document.getElementById('worktypeItemsList');
|
||||
@@ -118,22 +144,31 @@ function renderWorktypeItems() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Group by work type
|
||||
const groups = {};
|
||||
// 2-level grouping: work_type → task
|
||||
const wtGroups = {};
|
||||
items.forEach(i => {
|
||||
const wt = i.work_type_name || '미지정';
|
||||
if (!groups[wt]) groups[wt] = [];
|
||||
groups[wt].push(i);
|
||||
if (!wtGroups[wt]) wtGroups[wt] = {};
|
||||
const task = i.task_name || '미지정';
|
||||
if (!wtGroups[wt][task]) wtGroups[wt][task] = [];
|
||||
wtGroups[wt][task].push(i);
|
||||
});
|
||||
|
||||
container.innerHTML = Object.entries(groups).map(([wt, items]) => `
|
||||
container.innerHTML = Object.entries(wtGroups).map(([wt, taskGroups]) => `
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<div class="bg-amber-50 px-4 py-2 font-medium text-sm text-amber-700">
|
||||
<i class="fas fa-hard-hat mr-1"></i>${escapeHtml(wt)}
|
||||
</div>
|
||||
<div class="divide-y">
|
||||
${items.map(i => renderItemRow(i)).join('')}
|
||||
</div>
|
||||
${Object.entries(taskGroups).map(([task, items]) => `
|
||||
<div class="border-t">
|
||||
<div class="bg-amber-50/50 px-4 py-1.5 text-xs font-medium text-amber-600 pl-8">
|
||||
<i class="fas fa-wrench mr-1"></i>${escapeHtml(task)}
|
||||
</div>
|
||||
<div class="divide-y">
|
||||
${items.map(i => renderItemRow(i)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
@@ -180,7 +215,7 @@ function openAddItem(tab) {
|
||||
document.getElementById('itemModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function openEditItem(id) {
|
||||
async function openEditItem(id) {
|
||||
const item = checklistItems.find(i => i.item_id === id);
|
||||
if (!item) return;
|
||||
editingItemId = id;
|
||||
@@ -196,6 +231,11 @@ function openEditItem(id) {
|
||||
}
|
||||
if (item.work_type_id) {
|
||||
document.getElementById('itemWorkType').value = item.work_type_id;
|
||||
// Load tasks first, then set value after load completes
|
||||
await loadTasksByWorkType(item.work_type_id);
|
||||
if (item.task_id) {
|
||||
document.getElementById('itemTask').value = item.task_id;
|
||||
}
|
||||
}
|
||||
|
||||
toggleTypeFields();
|
||||
@@ -204,6 +244,7 @@ function openEditItem(id) {
|
||||
|
||||
function closeItemModal() {
|
||||
document.getElementById('itemModal').classList.add('hidden');
|
||||
document.getElementById('taskField').classList.add('hidden');
|
||||
editingItemId = null;
|
||||
}
|
||||
|
||||
@@ -211,6 +252,16 @@ function toggleTypeFields() {
|
||||
const type = document.getElementById('itemType').value;
|
||||
document.getElementById('weatherConditionField').classList.toggle('hidden', type !== 'weather');
|
||||
document.getElementById('workTypeField').classList.toggle('hidden', type !== 'work_type');
|
||||
// Hide task field when type changes away from work_type
|
||||
if (type !== 'work_type') {
|
||||
document.getElementById('taskField').classList.add('hidden');
|
||||
} else {
|
||||
// Show task field if work type is already selected
|
||||
const workTypeId = document.getElementById('itemWorkType').value;
|
||||
if (workTypeId) {
|
||||
document.getElementById('taskField').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Submit item ===== */
|
||||
@@ -231,8 +282,9 @@ async function submitItem(e) {
|
||||
if (!data.weather_condition_id) { showToast('날씨 조건을 선택해주세요', 'error'); return; }
|
||||
}
|
||||
if (data.item_type === 'work_type') {
|
||||
data.work_type_id = parseInt(document.getElementById('itemWorkType').value) || null;
|
||||
if (!data.work_type_id) { showToast('작업 유형을 선택해주세요', 'error'); return; }
|
||||
const taskId = parseInt(document.getElementById('itemTask').value) || null;
|
||||
if (!taskId) { showToast('작업을 선택해주세요', 'error'); return; }
|
||||
data.task_id = taskId;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -279,6 +331,7 @@ function initChecklistPage() {
|
||||
|
||||
// Type change handler
|
||||
document.getElementById('itemType').addEventListener('change', toggleTypeFields);
|
||||
document.getElementById('itemWorkType').addEventListener('change', onWorkTypeChange);
|
||||
document.getElementById('itemForm').addEventListener('submit', submitItem);
|
||||
|
||||
loadLookupData();
|
||||
|
||||
Reference in New Issue
Block a user