feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현
- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성 - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동 - common/ → attendance/: 근태/휴가 관련 페이지 이동 - admin/ 정리: safety-* 파일들을 safety/로 이동 - 사이드바 네비게이션 메뉴 구현 - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리 - 접기/펼치기 기능 및 상태 저장 - 관리자 전용 메뉴 자동 표시/숨김 - 날씨 API 연동 (기상청 단기예보) - TBM 및 navbar에 현재 날씨 표시 - weatherService.js 추가 - 안전 체크리스트 확장 - 기본/날씨별/작업별 체크 유형 추가 - checklist-manage.html 페이지 추가 - 이슈 신고 시스템 구현 - workIssueController, workIssueModel, workIssueRoutes 추가 - DB 마이그레이션 파일 추가 (실행 대기) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ let editingWorkId = null; // 수정 중인 작업 ID
|
||||
let incompleteTbms = []; // 미완료 TBM 작업 목록
|
||||
let currentTab = 'tbm'; // 현재 활성 탭
|
||||
|
||||
// 부적합 원인 관리
|
||||
let currentDefectIndex = null; // 현재 편집 중인 행 인덱스
|
||||
let tempDefects = {}; // 임시 부적합 원인 저장 { index: [{ error_type_id, defect_hours, note }] }
|
||||
|
||||
// 작업장소 지도 관련 변수
|
||||
let mapCanvas = null;
|
||||
let mapCtx = null;
|
||||
@@ -156,9 +160,8 @@ function renderTbmWorkList() {
|
||||
<th>공정</th>
|
||||
<th>작업</th>
|
||||
<th>작업장소</th>
|
||||
<th>작업시간<br>(시간)</th>
|
||||
<th>부적합<br>(시간)</th>
|
||||
<th>부적합 원인</th>
|
||||
<th>작업시간</th>
|
||||
<th>부적합</th>
|
||||
<th>제출</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -190,9 +193,8 @@ function renderTbmWorkList() {
|
||||
<th>공정</th>
|
||||
<th>작업</th>
|
||||
<th>작업장소</th>
|
||||
<th>작업시간<br>(시간)</th>
|
||||
<th>부적합<br>(시간)</th>
|
||||
<th>부적합 원인</th>
|
||||
<th>작업시간</th>
|
||||
<th>부적합</th>
|
||||
<th>제출</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -226,18 +228,13 @@ function renderTbmWorkList() {
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" id="errorHours_${index}" value="0">
|
||||
<div class="time-input-trigger has-value"
|
||||
id="errorHoursDisplay_${index}"
|
||||
onclick="openTimePicker(${index}, 'error')">
|
||||
0시간
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-input-compact" id="errorType_${index}" style="display: none;">
|
||||
<option value="">선택</option>
|
||||
${errorTypes.map(et => `<option value="${et.id}">${et.name}</option>`).join('')}
|
||||
</select>
|
||||
<span id="errorTypeNone_${index}">-</span>
|
||||
<input type="hidden" id="errorType_${index}" value="">
|
||||
<button type="button"
|
||||
class="btn-defect-toggle"
|
||||
id="defectToggle_${index}"
|
||||
onclick="toggleDefectArea(${index})">
|
||||
<span id="defectSummary_${index}">없음</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button"
|
||||
@@ -247,6 +244,18 @@ function renderTbmWorkList() {
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="defect-row" id="defectRow_${index}" style="display: none;">
|
||||
<td colspan="8" style="padding: 0; background: #fef3c7;">
|
||||
<div class="defect-inline-area" id="defectArea_${index}">
|
||||
<div class="defect-list" id="defectList_${index}">
|
||||
<!-- 부적합 원인 목록 -->
|
||||
</div>
|
||||
<button type="button" class="btn-add-defect-inline" onclick="addInlineDefect(${index})">
|
||||
+ 부적합 추가
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
@@ -293,8 +302,11 @@ window.submitTbmWorkReport = async function(index) {
|
||||
const tbm = incompleteTbms[index];
|
||||
|
||||
const totalHours = parseFloat(document.getElementById(`totalHours_${index}`).value);
|
||||
const errorHours = parseFloat(document.getElementById(`errorHours_${index}`).value) || 0;
|
||||
const errorTypeId = document.getElementById(`errorType_${index}`).value;
|
||||
const defects = tempDefects[index] || [];
|
||||
|
||||
// 총 부적합 시간 계산
|
||||
const errorHours = defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0);
|
||||
const errorTypeId = defects.length > 0 && defects[0].error_type_id ? defects[0].error_type_id : null;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!totalHours || totalHours <= 0) {
|
||||
@@ -307,8 +319,10 @@ window.submitTbmWorkReport = async function(index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorHours > 0 && !errorTypeId) {
|
||||
showMessage('부적합 처리 시간이 있는 경우 원인을 선택해주세요.', 'error');
|
||||
// 부적합 원인 유효성 검사
|
||||
const invalidDefects = defects.filter(d => d.defect_hours > 0 && !d.error_type_id);
|
||||
if (invalidDefects.length > 0) {
|
||||
showMessage('부적합 시간이 있는 항목은 원인을 선택해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -330,12 +344,12 @@ window.submitTbmWorkReport = async function(index) {
|
||||
end_time: null,
|
||||
total_hours: totalHours,
|
||||
error_hours: errorHours,
|
||||
error_type_id: errorTypeId || null,
|
||||
error_type_id: errorTypeId,
|
||||
work_status_id: errorHours > 0 ? 2 : 1
|
||||
};
|
||||
|
||||
console.log('🔍 TBM 제출 데이터:', JSON.stringify(reportData, null, 2));
|
||||
console.log('🔍 tbm 객체:', tbm);
|
||||
console.log('🔍 부적합 원인:', defects);
|
||||
|
||||
try {
|
||||
const response = await window.apiCall('/daily-work-reports/from-tbm', 'POST', reportData);
|
||||
@@ -344,6 +358,16 @@ window.submitTbmWorkReport = async function(index) {
|
||||
throw new Error(response.message || '작업보고서 제출 실패');
|
||||
}
|
||||
|
||||
// 부적합 원인이 있으면 저장
|
||||
if (defects.length > 0 && response.data?.report_id) {
|
||||
const validDefects = defects.filter(d => d.error_type_id && d.defect_hours > 0);
|
||||
if (validDefects.length > 0) {
|
||||
await window.apiCall(`/daily-work-reports/${response.data.report_id}/defects`, 'PUT', {
|
||||
defects: validDefects
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showSaveResultModal(
|
||||
'success',
|
||||
'작업보고서 제출 완료',
|
||||
@@ -353,6 +377,9 @@ window.submitTbmWorkReport = async function(index) {
|
||||
response.data.completion_status
|
||||
);
|
||||
|
||||
// 임시 부적합 데이터 삭제
|
||||
delete tempDefects[index];
|
||||
|
||||
// 목록 새로고침
|
||||
await loadIncompleteTbms();
|
||||
} catch (error) {
|
||||
@@ -576,18 +603,13 @@ window.addManualWorkRow = function() {
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" id="errorHours_${manualIndex}" value="0">
|
||||
<div class="time-input-trigger has-value"
|
||||
id="errorHoursDisplay_${manualIndex}"
|
||||
onclick="openTimePicker('${manualIndex}', 'error')">
|
||||
0시간
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-input-compact" id="errorType_${manualIndex}" style="display: none;">
|
||||
<option value="">선택</option>
|
||||
${errorTypes.map(et => `<option value="${et.id}">${et.name}</option>`).join('')}
|
||||
</select>
|
||||
<span id="errorTypeNone_${manualIndex}">-</span>
|
||||
<input type="hidden" id="errorType_${manualIndex}" value="">
|
||||
<button type="button"
|
||||
class="btn-defect-toggle"
|
||||
id="defectToggle_${manualIndex}"
|
||||
onclick="toggleDefectArea('${manualIndex}')">
|
||||
<span id="defectSummary_${manualIndex}">없음</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn-submit-compact" onclick="submitManualWorkReport('${manualIndex}')">
|
||||
@@ -600,6 +622,26 @@ window.addManualWorkRow = function() {
|
||||
`;
|
||||
|
||||
tbody.appendChild(newRow);
|
||||
|
||||
// 부적합 인라인 영역 행 추가
|
||||
const defectRow = document.createElement('tr');
|
||||
defectRow.className = 'defect-row';
|
||||
defectRow.id = `defectRow_${manualIndex}`;
|
||||
defectRow.style.display = 'none';
|
||||
defectRow.innerHTML = `
|
||||
<td colspan="9" style="padding: 0; background: #fef3c7;">
|
||||
<div class="defect-inline-area" id="defectArea_${manualIndex}">
|
||||
<div class="defect-list" id="defectList_${manualIndex}">
|
||||
<!-- 부적합 원인 목록 -->
|
||||
</div>
|
||||
<button type="button" class="btn-add-defect-inline" onclick="addInlineDefect('${manualIndex}')">
|
||||
+ 부적합 추가
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(defectRow);
|
||||
|
||||
showMessage('새 작업 행이 추가되었습니다. 정보를 입력하고 제출하세요.', 'info');
|
||||
};
|
||||
|
||||
@@ -608,9 +650,15 @@ window.addManualWorkRow = function() {
|
||||
*/
|
||||
window.removeManualWorkRow = function(manualIndex) {
|
||||
const row = document.querySelector(`tr[data-index="${manualIndex}"]`);
|
||||
const defectRow = document.getElementById(`defectRow_${manualIndex}`);
|
||||
if (row) {
|
||||
row.remove();
|
||||
}
|
||||
if (defectRow) {
|
||||
defectRow.remove();
|
||||
}
|
||||
// 임시 부적합 데이터도 삭제
|
||||
delete tempDefects[manualIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -976,8 +1024,11 @@ window.submitManualWorkReport = async function(manualIndex) {
|
||||
const workplaceCategoryId = document.getElementById(`workplaceCategory_${manualIndex}`).value;
|
||||
const workplaceId = document.getElementById(`workplace_${manualIndex}`).value;
|
||||
const totalHours = parseFloat(document.getElementById(`totalHours_${manualIndex}`).value);
|
||||
const errorHours = parseFloat(document.getElementById(`errorHours_${manualIndex}`).value) || 0;
|
||||
const errorTypeId = document.getElementById(`errorType_${manualIndex}`).value;
|
||||
|
||||
// 부적합 원인 가져오기
|
||||
const defects = tempDefects[manualIndex] || [];
|
||||
const errorHours = defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0);
|
||||
const errorTypeId = defects.length > 0 && defects[0].error_type_id ? defects[0].error_type_id : null;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!workerId) {
|
||||
@@ -1014,8 +1065,10 @@ window.submitManualWorkReport = async function(manualIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorHours > 0 && !errorTypeId) {
|
||||
showMessage('부적합 처리 시간이 있는 경우 원인을 선택해주세요.', 'error');
|
||||
// 부적합 원인 유효성 검사
|
||||
const invalidDefects = defects.filter(d => d.defect_hours > 0 && !d.error_type_id);
|
||||
if (invalidDefects.length > 0) {
|
||||
showMessage('부적합 시간이 있는 항목은 원인을 선택해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1042,13 +1095,23 @@ window.submitManualWorkReport = async function(manualIndex) {
|
||||
throw new Error(response.message || '작업보고서 제출 실패');
|
||||
}
|
||||
|
||||
// 부적합 원인이 있으면 저장
|
||||
if (defects.length > 0 && response.data?.workReport_ids?.[0]) {
|
||||
const validDefects = defects.filter(d => d.error_type_id && d.defect_hours > 0);
|
||||
if (validDefects.length > 0) {
|
||||
await window.apiCall(`/daily-work-reports/${response.data.workReport_ids[0]}/defects`, 'PUT', {
|
||||
defects: validDefects
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showSaveResultModal(
|
||||
'success',
|
||||
'작업보고서 제출 완료',
|
||||
'작업보고서가 성공적으로 제출되었습니다.'
|
||||
);
|
||||
|
||||
// 행 제거
|
||||
// 행 제거 (부적합 임시 데이터도 함께 삭제됨)
|
||||
removeManualWorkRow(manualIndex);
|
||||
|
||||
// 목록 새로고침
|
||||
@@ -2438,17 +2501,37 @@ function updateTimeDisplay() {
|
||||
*/
|
||||
window.confirmTimeSelection = function() {
|
||||
if (!currentEditingField) return;
|
||||
|
||||
const { index, type } = currentEditingField;
|
||||
|
||||
const { index, type, defectIndex } = currentEditingField;
|
||||
|
||||
// 부적합 시간 선택인 경우
|
||||
if (type === 'defect') {
|
||||
if (tempDefects[index] && tempDefects[index][defectIndex] !== undefined) {
|
||||
tempDefects[index][defectIndex].defect_hours = currentTimeValue;
|
||||
|
||||
// 시간 표시 업데이트
|
||||
const timeDisplay = document.getElementById(`defectTime_${index}_${defectIndex}`);
|
||||
if (timeDisplay) {
|
||||
timeDisplay.textContent = currentTimeValue;
|
||||
}
|
||||
|
||||
// 요약 및 hidden 필드 업데이트
|
||||
updateDefectSummary(index);
|
||||
}
|
||||
closeTimePicker();
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 total/error 시간 선택
|
||||
const inputId = type === 'total' ? `totalHours_${index}` : `errorHours_${index}`;
|
||||
const displayId = type === 'total' ? `totalHoursDisplay_${index}` : `errorHoursDisplay_${index}`;
|
||||
|
||||
|
||||
// hidden input 값 설정
|
||||
const hiddenInput = document.getElementById(inputId);
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = currentTimeValue;
|
||||
}
|
||||
|
||||
|
||||
// 표시 영역 업데이트
|
||||
const displayDiv = document.getElementById(displayId);
|
||||
if (displayDiv) {
|
||||
@@ -2456,8 +2539,8 @@ window.confirmTimeSelection = function() {
|
||||
displayDiv.classList.remove('placeholder');
|
||||
displayDiv.classList.add('has-value');
|
||||
}
|
||||
|
||||
// 부적합 시간 입력 시 에러 타입 토글
|
||||
|
||||
// 부적합 시간 입력 시 에러 타입 토글 (기존 방식 - 이제 사용안함)
|
||||
if (type === 'error') {
|
||||
if (index.toString().startsWith('manual_')) {
|
||||
toggleManualErrorType(index);
|
||||
@@ -2465,7 +2548,7 @@ window.confirmTimeSelection = function() {
|
||||
calculateRegularHours(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
closeTimePicker();
|
||||
};
|
||||
|
||||
@@ -2477,10 +2560,200 @@ window.closeTimePicker = function() {
|
||||
if (overlay) {
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
currentEditingField = null;
|
||||
currentTimeValue = 0;
|
||||
|
||||
|
||||
// ESC 키 리스너 제거
|
||||
document.removeEventListener('keydown', handleEscapeKey);
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// 부적합 원인 관리 (인라인 방식)
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* 부적합 영역 토글
|
||||
*/
|
||||
window.toggleDefectArea = function(index) {
|
||||
const defectRow = document.getElementById(`defectRow_${index}`);
|
||||
if (!defectRow) return;
|
||||
|
||||
const isVisible = defectRow.style.display !== 'none';
|
||||
|
||||
if (isVisible) {
|
||||
// 숨기기
|
||||
defectRow.style.display = 'none';
|
||||
} else {
|
||||
// 보이기 - 부적합 원인이 없으면 자동으로 하나 추가
|
||||
if (!tempDefects[index] || tempDefects[index].length === 0) {
|
||||
tempDefects[index] = [{
|
||||
error_type_id: '',
|
||||
defect_hours: 0,
|
||||
note: ''
|
||||
}];
|
||||
}
|
||||
renderInlineDefectList(index);
|
||||
defectRow.style.display = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 인라인 부적합 목록 렌더링
|
||||
*/
|
||||
function renderInlineDefectList(index) {
|
||||
const listContainer = document.getElementById(`defectList_${index}`);
|
||||
if (!listContainer) return;
|
||||
|
||||
const defects = tempDefects[index] || [];
|
||||
|
||||
listContainer.innerHTML = defects.map((defect, i) => `
|
||||
<div class="defect-inline-item" data-defect-index="${i}">
|
||||
<select class="defect-select"
|
||||
onchange="updateInlineDefect('${index}', ${i}, 'error_type_id', this.value)">
|
||||
<option value="">원인 선택</option>
|
||||
${errorTypes.map(et => `<option value="${et.id}" ${defect.error_type_id == et.id ? 'selected' : ''}>${et.name}</option>`).join('')}
|
||||
</select>
|
||||
<div class="defect-time-input"
|
||||
onclick="openDefectTimePicker('${index}', ${i})">
|
||||
<span class="defect-time-value" id="defectTime_${index}_${i}">${defect.defect_hours || 0}</span>
|
||||
<span class="defect-time-unit">시간</span>
|
||||
</div>
|
||||
<button type="button" class="btn-remove-defect" onclick="removeInlineDefect('${index}', ${i})">−</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
updateDefectSummary(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 인라인 부적합 추가
|
||||
*/
|
||||
window.addInlineDefect = function(index) {
|
||||
if (!tempDefects[index]) {
|
||||
tempDefects[index] = [];
|
||||
}
|
||||
|
||||
tempDefects[index].push({
|
||||
error_type_id: '',
|
||||
defect_hours: 0,
|
||||
note: ''
|
||||
});
|
||||
|
||||
renderInlineDefectList(index);
|
||||
};
|
||||
|
||||
/**
|
||||
* 인라인 부적합 수정
|
||||
*/
|
||||
window.updateInlineDefect = function(index, defectIndex, field, value) {
|
||||
if (tempDefects[index] && tempDefects[index][defectIndex]) {
|
||||
if (field === 'defect_hours') {
|
||||
tempDefects[index][defectIndex][field] = parseFloat(value) || 0;
|
||||
} else {
|
||||
tempDefects[index][defectIndex][field] = value;
|
||||
}
|
||||
updateDefectSummary(index);
|
||||
updateHiddenDefectFields(index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 인라인 부적합 삭제
|
||||
*/
|
||||
window.removeInlineDefect = function(index, defectIndex) {
|
||||
if (tempDefects[index]) {
|
||||
tempDefects[index].splice(defectIndex, 1);
|
||||
|
||||
// 모든 부적합이 삭제되면 영역 숨기기
|
||||
if (tempDefects[index].length === 0) {
|
||||
const defectRow = document.getElementById(`defectRow_${index}`);
|
||||
if (defectRow) {
|
||||
defectRow.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
renderInlineDefectList(index);
|
||||
}
|
||||
|
||||
updateDefectSummary(index);
|
||||
updateHiddenDefectFields(index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 부적합 시간 선택기 열기 (시간 선택 팝오버 재사용)
|
||||
*/
|
||||
window.openDefectTimePicker = function(index, defectIndex) {
|
||||
currentEditingField = { index, type: 'defect', defectIndex };
|
||||
|
||||
// 현재 값 가져오기
|
||||
const defects = tempDefects[index] || [];
|
||||
currentTimeValue = defects[defectIndex]?.defect_hours || 0;
|
||||
|
||||
// 팝오버 표시
|
||||
const overlay = document.getElementById('timePickerOverlay');
|
||||
const title = document.getElementById('timePickerTitle');
|
||||
|
||||
title.textContent = '부적합 시간 선택';
|
||||
updateTimeDisplay();
|
||||
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
// ESC 키로 닫기
|
||||
document.addEventListener('keydown', handleEscapeKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* hidden input 필드 업데이트
|
||||
*/
|
||||
function updateHiddenDefectFields(index) {
|
||||
const defects = tempDefects[index] || [];
|
||||
|
||||
// 총 부적합 시간 계산
|
||||
const totalErrorHours = defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0);
|
||||
|
||||
// hidden input에 대표 error_type_id 저장 (첫 번째 값)
|
||||
const errorTypeInput = document.getElementById(`errorType_${index}`);
|
||||
if (errorTypeInput && defects.length > 0 && defects[0].error_type_id) {
|
||||
errorTypeInput.value = defects[0].error_type_id;
|
||||
} else if (errorTypeInput) {
|
||||
errorTypeInput.value = '';
|
||||
}
|
||||
|
||||
// 부적합 시간 input 업데이트
|
||||
const errorHoursInput = document.getElementById(`errorHours_${index}`);
|
||||
if (errorHoursInput) {
|
||||
errorHoursInput.value = totalErrorHours;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 부적합 요약 텍스트 업데이트
|
||||
*/
|
||||
function updateDefectSummary(index) {
|
||||
const summaryEl = document.getElementById(`defectSummary_${index}`);
|
||||
const toggleBtn = document.getElementById(`defectToggle_${index}`);
|
||||
if (!summaryEl) return;
|
||||
|
||||
const defects = tempDefects[index] || [];
|
||||
const validDefects = defects.filter(d => d.error_type_id && d.defect_hours > 0);
|
||||
|
||||
if (validDefects.length === 0) {
|
||||
summaryEl.textContent = '없음';
|
||||
summaryEl.style.color = '#6b7280';
|
||||
if (toggleBtn) toggleBtn.classList.remove('has-defect');
|
||||
} else {
|
||||
const totalHours = validDefects.reduce((sum, d) => sum + d.defect_hours, 0);
|
||||
if (validDefects.length === 1) {
|
||||
const typeName = errorTypes.find(et => et.id == validDefects[0].error_type_id)?.name || '부적합';
|
||||
summaryEl.textContent = `${typeName} ${totalHours}h`;
|
||||
} else {
|
||||
summaryEl.textContent = `${validDefects.length}건 ${totalHours}h`;
|
||||
}
|
||||
summaryEl.style.color = '#dc2626';
|
||||
if (toggleBtn) toggleBtn.classList.add('has-defect');
|
||||
}
|
||||
|
||||
// hidden 필드도 업데이트
|
||||
updateHiddenDefectFields(index);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user