From 344ad356516fb33f6e3fdceb92e19cf19579914a Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 19 Jan 2026 08:35:36 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=91=EC=97=85=EC=9E=90/=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EA=B4=80=EB=A6=AC=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=AA=A8=EB=93=88=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EB=B0=8F=20DB=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 수정 내용 ### 1. JavaScript 모듈 로딩 문제 수정 - ES6 import 사용 파일에 type="module" 속성 추가 - api-config.js, load-navbar.js, worker-management.js, project-management.js ### 2. DB 스키마 불일치 해결 - workers 테이블 실제 구조에 맞게 코드 수정 - 존재하지 않는 컬럼 제거: phone_number, email, hire_date, department, notes - 실제 컬럼 사용: join_date, salary, annual_leave ### 3. 백엔드 수정 - workerModel.js: create, update 함수를 실제 테이블 구조에 맞게 수정 - workerController.js: 상세 로깅 추가 ### 4. 프론트엔드 수정 - worker-management.js: 데이터 전송 구조 수정 - api-config.js: 에러 로깅 개선 - HTML 파일: 스크립트 type="module" 추가 및 버전 업데이트 ### 5. 개발 문서 - 개발로그 추가: 2026-01-19_작업자관리_스키마_동기화.md ## 영향 범위 - 작업자 관리 페이지: 상태 변경 기능 정상화 - 프로젝트 관리 페이지: 모듈 로딩 오류 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../controllers/workerController.js | 11 +- api.hyungi.net/models/workerModel.js | 91 +++-- web-ui/js/api-config.js | 17 +- web-ui/js/worker-management.js | 62 ++-- .../pages/management/project-management.html | 6 +- .../pages/management/worker-management.html | 6 +- .../2026-01-19_작업자관리_스키마_동기화.md | 351 ++++++++++++++++++ 7 files changed, 471 insertions(+), 73 deletions(-) create mode 100644 개발로그/2026-01-19_작업자관리_스키마_동기화.md diff --git a/api.hyungi.net/controllers/workerController.js b/api.hyungi.net/controllers/workerController.js index b356073..c8bdce5 100644 --- a/api.hyungi.net/controllers/workerController.js +++ b/api.hyungi.net/controllers/workerController.js @@ -116,9 +116,18 @@ exports.updateWorker = asyncHandler(async (req, res) => { const workerData = { ...req.body, worker_id: id }; + console.log('🔧 작업자 수정 요청:', { + worker_id: id, + 받은데이터: req.body, + 처리할데이터: workerData + }); + const changes = await new Promise((resolve, reject) => { workerModel.update(workerData, (err, affected) => { - if (err) reject(new DatabaseError('작업자 수정 중 오류가 발생했습니다')); + if (err) { + console.error('❌ workerModel.update 에러:', err); + reject(new DatabaseError(`작업자 수정 중 오류가 발생했습니다: ${err.message}`)); + } else resolve(affected); }); }); diff --git a/api.hyungi.net/models/workerModel.js b/api.hyungi.net/models/workerModel.js index 96554dd..ca519a0 100644 --- a/api.hyungi.net/models/workerModel.js +++ b/api.hyungi.net/models/workerModel.js @@ -15,24 +15,23 @@ const create = async (worker, callback) => { const db = await getDb(); const { worker_name, - job_type = 'worker', - phone_number = null, - email = null, - hire_date = null, - department = null, - notes = null, + job_type = null, + join_date = null, + salary = null, + annual_leave = null, status = 'active' } = worker; const [result] = await db.query( `INSERT INTO workers - (worker_name, job_type, phone_number, email, hire_date, department, notes, status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - [worker_name, job_type, phone_number, email, formatDate(hire_date), department, notes, status] + (worker_name, job_type, join_date, salary, annual_leave, status) + VALUES (?, ?, ?, ?, ?, ?)`, + [worker_name, job_type, formatDate(join_date), salary, annual_leave, status] ); callback(null, result.insertId); } catch (err) { + console.error('❌ create 함수 에러:', err); callback(err); } }; @@ -80,39 +79,57 @@ const update = async (worker, callback) => { worker_name, job_type, status, - phone_number, - email, - hire_date, - department, - notes + join_date, + salary, + annual_leave } = worker; - const [result] = await db.query( - `UPDATE workers - SET worker_name = ?, - job_type = ?, - status = ?, - phone_number = ?, - email = ?, - hire_date = ?, - department = ?, - notes = ? - WHERE worker_id = ?`, - [ - worker_name, - job_type, - status, - phone_number, - email, - formatDate(hire_date), - department, - notes, - worker_id - ] - ); + // 업데이트할 필드만 동적으로 구성 + const updates = []; + const values = []; + + if (worker_name !== undefined) { + updates.push('worker_name = ?'); + values.push(worker_name); + } + if (job_type !== undefined) { + updates.push('job_type = ?'); + values.push(job_type); + } + if (status !== undefined) { + updates.push('status = ?'); + values.push(status); + } + if (join_date !== undefined) { + updates.push('join_date = ?'); + values.push(formatDate(join_date)); + } + if (salary !== undefined) { + updates.push('salary = ?'); + values.push(salary); + } + if (annual_leave !== undefined) { + updates.push('annual_leave = ?'); + values.push(annual_leave); + } + + if (updates.length === 0) { + callback(new Error('업데이트할 필드가 없습니다')); + return; + } + + values.push(worker_id); // WHERE 조건용 + + const query = `UPDATE workers SET ${updates.join(', ')} WHERE worker_id = ?`; + + console.log('🔍 실행할 SQL:', query); + console.log('🔍 SQL 파라미터:', values); + + const [result] = await db.query(query, values); callback(null, result.affectedRows); } catch (err) { + console.error('❌ update 함수 에러:', err); callback(new Error(err.message || String(err))); } }; diff --git a/web-ui/js/api-config.js b/web-ui/js/api-config.js index 96730fc..3a195bd 100644 --- a/web-ui/js/api-config.js +++ b/web-ui/js/api-config.js @@ -120,8 +120,16 @@ async function apiCall(url, method = 'GET', data = null) { try { const errorData = await response.json(); errorMessage = errorData.error || errorData.message || errorMessage; + console.error('📋 서버 에러 상세:', errorData); } catch (e) { - // JSON 파싱 실패시 기본 메시지 사용 + // JSON 파싱 실패시 텍스트로 시도 + try { + const errorText = await response.text(); + console.error('📋 서버 에러 텍스트:', errorText); + errorMessage = errorText || errorMessage; + } catch (e2) { + console.error('📋 에러 파싱 실패'); + } } throw new Error(errorMessage); } @@ -129,15 +137,16 @@ async function apiCall(url, method = 'GET', data = null) { const result = await response.json(); console.log(`✅ API 성공: ${fullUrl}`); return result; - + } catch (error) { console.error(`❌ API 오류 (${fullUrl}):`, error); - + console.error('❌ 에러 전체 내용:', JSON.stringify(error, null, 2)); + // 네트워크 오류 vs 서버 오류 구분 if (error.name === 'TypeError' && error.message.includes('fetch')) { throw new Error('네트워크 연결 오류입니다. 인터넷 연결을 확인해주세요.'); } - + throw error; } } diff --git a/web-ui/js/worker-management.js b/web-ui/js/worker-management.js index 63f530a..46d187f 100644 --- a/web-ui/js/worker-management.js +++ b/web-ui/js/worker-management.js @@ -429,24 +429,30 @@ function openWorkerModal(worker = null) { // 수정 모드 modalTitle.textContent = '작업자 정보 수정'; deleteBtn.style.display = 'inline-flex'; - - // 폼에 데이터 채우기 + + // 폼에 데이터 채우기 (실제 테이블 구조에 맞게) document.getElementById('workerId').value = worker.worker_id; document.getElementById('workerName').value = worker.worker_name || ''; - document.getElementById('jobType').value = worker.job_type || 'worker'; - document.getElementById('phoneNumber').value = worker.phone_number || ''; - document.getElementById('email').value = worker.email || ''; - document.getElementById('hireDate').value = worker.hire_date || ''; - document.getElementById('department').value = worker.department || ''; - document.getElementById('notes').value = worker.notes || ''; - + document.getElementById('jobType').value = worker.job_type || ''; + + // 옵션 필드들 - 존재하는 경우에만 설정 + const joinDateElem = document.getElementById('joinDate'); + if (joinDateElem) joinDateElem.value = worker.join_date || ''; + + const salaryElem = document.getElementById('salary'); + if (salaryElem) salaryElem.value = worker.salary || ''; + + const annualLeaveElem = document.getElementById('annualLeave'); + if (annualLeaveElem) annualLeaveElem.value = worker.annual_leave || ''; + // is_active 값 처리 (DB에서 0/1로 오는 경우 대비) const isActiveValue = worker.status !== 'inactive' && worker.is_active !== 0 && worker.is_active !== false; document.getElementById('isActive').checked = isActiveValue; - + console.log('🔧 작업자 로드:', { worker_id: worker.worker_id, worker_name: worker.worker_name, + job_type: worker.job_type, status: worker.status, is_active_raw: worker.is_active, is_active_processed: isActiveValue @@ -455,7 +461,7 @@ function openWorkerModal(worker = null) { // 신규 등록 모드 modalTitle.textContent = '새 작업자 등록'; deleteBtn.style.display = 'none'; - + // 폼 초기화 document.getElementById('workerForm').reset(); document.getElementById('workerId').value = ''; @@ -496,19 +502,18 @@ function editWorker(workerId) { async function saveWorker() { try { const form = document.getElementById('workerForm'); - + + // 실제 테이블 구조에 맞는 필드만 사용 const workerData = { worker_name: document.getElementById('workerName').value.trim(), - job_type: document.getElementById('jobType').value || 'worker', - phone_number: document.getElementById('phoneNumber').value.trim() || null, - email: document.getElementById('email').value.trim() || null, - hire_date: document.getElementById('hireDate').value || null, - department: document.getElementById('department').value.trim() || null, - notes: document.getElementById('notes').value.trim() || null, + job_type: document.getElementById('jobType').value || null, + join_date: document.getElementById('joinDate')?.value || null, + salary: document.getElementById('salary')?.value || null, + annual_leave: document.getElementById('annualLeave')?.value || null, status: document.getElementById('isActive').checked ? 'active' : 'inactive' }; - - console.log('💾 저장할 작업자 데이터:', workerData); + + console.log('💾 저장할 작업자 데이터:', JSON.stringify(workerData, null, 2)); // 필수 필드 검증 if (!workerData.worker_name) { @@ -560,15 +565,21 @@ async function toggleWorkerStatus(workerId) { } console.log(`🔄 작업자 상태 변경: ${worker.worker_name} → ${newStatus}`); - + try { + // 실제 테이블 구조에 맞는 필드만 전송 const updateData = { - ...worker, + worker_name: worker.worker_name, + job_type: worker.job_type || null, status: newStatus, - is_active: newStatus === 'active' ? 1 : 0 + join_date: worker.join_date || null, + salary: worker.salary || null, + annual_leave: worker.annual_leave || null }; - - const response = await window.apiCall(`${window.API}/workers/${workerId}`, 'PUT', updateData); + + console.log('📤 전송 데이터:', JSON.stringify(updateData, null, 2)); + + const response = await window.apiCall(`/workers/${workerId}`, 'PUT', updateData); if (response) { // 로컬 데이터 업데이트 @@ -732,3 +743,4 @@ window.filterWorkers = filterWorkers; window.sortWorkers = sortWorkers; window.refreshWorkerList = refreshWorkerList; window.filterByStatus = filterByStatus; +window.toggleWorkerStatus = toggleWorkerStatus; diff --git a/web-ui/pages/management/project-management.html b/web-ui/pages/management/project-management.html index 7661c72..5393643 100644 --- a/web-ui/pages/management/project-management.html +++ b/web-ui/pages/management/project-management.html @@ -207,8 +207,8 @@ - - - + + + diff --git a/web-ui/pages/management/worker-management.html b/web-ui/pages/management/worker-management.html index 62d1a33..8c9870e 100644 --- a/web-ui/pages/management/worker-management.html +++ b/web-ui/pages/management/worker-management.html @@ -8,7 +8,7 @@ - +
@@ -195,7 +195,7 @@
- - + + diff --git a/개발로그/2026-01-19_작업자관리_스키마_동기화.md b/개발로그/2026-01-19_작업자관리_스키마_동기화.md new file mode 100644 index 0000000..5d94f87 --- /dev/null +++ b/개발로그/2026-01-19_작업자관리_스키마_동기화.md @@ -0,0 +1,351 @@ +# 개발로그 - 2026-01-19: 작업자 관리 스키마 동기화 + +## 작업 요약 +작업자 관리 페이지에서 발생하던 500 에러를 수정하였습니다. 문제의 원인은 **DB 테이블 스키마와 코드 간의 불일치**였습니다. + +## 발견된 문제 + +### 1. JavaScript 모듈 로딩 에러 +- **증상**: `SyntaxError: Unexpected token '{'` 에러 +- **원인**: ES6 `import` 구문을 사용하는 파일들이 `type="module"` 없이 로드됨 +- **영향 파일**: + - `load-navbar.js` + - `api-config.js` + - `worker-management.js` + +### 2. 작업자 상태 변경 시 500 에러 +- **증상**: 작업자 활성화/비활성화 시 서버 500 에러 +- **에러 메시지**: `Unknown column 'phone_number' in 'field list'` +- **원인**: 코드에서 사용하는 컬럼과 실제 DB 테이블 컬럼 불일치 + +### 3. DB 스키마 불일치 상세 + +#### 실제 workers 테이블 컬럼: +```sql +- worker_id (int, PK, auto_increment) +- worker_name (varchar(100), NOT NULL) +- join_date (date, nullable) +- job_type (varchar(100), nullable) +- salary (decimal(10,2), nullable) +- annual_leave (int, nullable) +- status (text, default 'active') +- created_at (timestamp, default current_timestamp) +- updated_at (timestamp, default current_timestamp on update) +``` + +#### 코드에서 사용하려던 컬럼 (존재하지 않음): +``` +- phone_number ❌ +- email ❌ +- hire_date ❌ (join_date는 있음) +- department ❌ +- notes ❌ +``` + +## 수정 내용 + +### 1. HTML 파일 수정 +**파일**: `web-ui/pages/management/worker-management.html` + +```html + + + + + + + + + +``` + +### 2. 백엔드 Model 수정 +**파일**: `api.hyungi.net/models/workerModel.js` + +#### create 함수: +```javascript +// 수정 전 +const create = async (worker, callback) => { + const { worker_name, job_type = 'worker', phone_number = null, + email = null, hire_date = null, department = null, + notes = null, status = 'active' } = worker; + + await db.query( + `INSERT INTO workers (worker_name, job_type, phone_number, email, + hire_date, department, notes, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [worker_name, job_type, phone_number, email, formatDate(hire_date), + department, notes, status] + ); +}; + +// 수정 후 +const create = async (worker, callback) => { + const { worker_name, job_type = null, join_date = null, + salary = null, annual_leave = null, status = 'active' } = worker; + + await db.query( + `INSERT INTO workers (worker_name, job_type, join_date, salary, + annual_leave, status) + VALUES (?, ?, ?, ?, ?, ?)`, + [worker_name, job_type, formatDate(join_date), salary, annual_leave, status] + ); +}; +``` + +#### update 함수: +```javascript +// 수정 전: 고정된 컬럼 업데이트 +const update = async (worker, callback) => { + const { worker_id, worker_name, job_type, status, phone_number, + email, hire_date, department, notes } = worker; + + await db.query( + `UPDATE workers SET worker_name = ?, job_type = ?, status = ?, + phone_number = ?, email = ?, hire_date = ?, + department = ?, notes = ? + WHERE worker_id = ?`, + [worker_name, job_type, status, phone_number, email, + formatDate(hire_date), department, notes, worker_id] + ); +}; + +// 수정 후: 동적 컬럼 업데이트 +const update = async (worker, callback) => { + const { worker_id, worker_name, job_type, status, join_date, + salary, annual_leave } = worker; + + // 업데이트할 필드만 동적으로 구성 + const updates = []; + const values = []; + + if (worker_name !== undefined) { + updates.push('worker_name = ?'); + values.push(worker_name); + } + if (job_type !== undefined) { + updates.push('job_type = ?'); + values.push(job_type); + } + if (status !== undefined) { + updates.push('status = ?'); + values.push(status); + } + if (join_date !== undefined) { + updates.push('join_date = ?'); + values.push(formatDate(join_date)); + } + if (salary !== undefined) { + updates.push('salary = ?'); + values.push(salary); + } + if (annual_leave !== undefined) { + updates.push('annual_leave = ?'); + values.push(annual_leave); + } + + if (updates.length === 0) { + callback(new Error('업데이트할 필드가 없습니다')); + return; + } + + values.push(worker_id); + const query = `UPDATE workers SET ${updates.join(', ')} WHERE worker_id = ?`; + + await db.query(query, values); +}; +``` + +### 3. 프론트엔드 JavaScript 수정 +**파일**: `web-ui/js/worker-management.js` + +#### toggleWorkerStatus 함수: +```javascript +// 수정 전 +const updateData = { + ...worker, // 모든 필드 포함 (created_at, updated_at 등 포함) + status: newStatus, + is_active: newStatus === 'active' ? 1 : 0 +}; + +// 수정 후 +const updateData = { + worker_name: worker.worker_name, + job_type: worker.job_type || null, + status: newStatus, + join_date: worker.join_date || null, + salary: worker.salary || null, + annual_leave: worker.annual_leave || null +}; +``` + +#### saveWorker 함수: +```javascript +// 수정 전 +const workerData = { + worker_name: document.getElementById('workerName').value.trim(), + job_type: document.getElementById('jobType').value || 'worker', + phone_number: document.getElementById('phoneNumber').value.trim() || null, + email: document.getElementById('email').value.trim() || null, + hire_date: document.getElementById('hireDate').value || null, + department: document.getElementById('department').value.trim() || null, + notes: document.getElementById('notes').value.trim() || null, + status: document.getElementById('isActive').checked ? 'active' : 'inactive' +}; + +// 수정 후 +const workerData = { + worker_name: document.getElementById('workerName').value.trim(), + job_type: document.getElementById('jobType').value || null, + join_date: document.getElementById('joinDate')?.value || null, + salary: document.getElementById('salary')?.value || null, + annual_leave: document.getElementById('annualLeave')?.value || null, + status: document.getElementById('isActive').checked ? 'active' : 'inactive' +}; +``` + +#### openWorkerModal 함수: +```javascript +// 수정 전 +document.getElementById('phoneNumber').value = worker.phone_number || ''; +document.getElementById('email').value = worker.email || ''; +document.getElementById('hireDate').value = worker.hire_date || ''; +document.getElementById('department').value = worker.department || ''; +document.getElementById('notes').value = worker.notes || ''; + +// 수정 후 +const joinDateElem = document.getElementById('joinDate'); +if (joinDateElem) joinDateElem.value = worker.join_date || ''; + +const salaryElem = document.getElementById('salary'); +if (salaryElem) salaryElem.value = worker.salary || ''; + +const annualLeaveElem = document.getElementById('annualLeave'); +if (annualLeaveElem) annualLeaveElem.value = worker.annual_leave || ''; +``` + +### 4. 에러 로깅 개선 +**파일**: `web-ui/js/api-config.js` + +```javascript +// 수정 후: 상세한 에러 로깅 +if (!response.ok) { + let errorMessage = `HTTP ${response.status}`; + try { + const errorData = await response.json(); + errorMessage = errorData.error || errorData.message || errorMessage; + console.error('📋 서버 에러 상세:', errorData); + } catch (e) { + try { + const errorText = await response.text(); + console.error('📋 서버 에러 텍스트:', errorText); + errorMessage = errorText || errorMessage; + } catch (e2) { + console.error('📋 에러 파싱 실패'); + } + } + throw new Error(errorMessage); +} +``` + +**파일**: `api.hyungi.net/controllers/workerController.js` + +```javascript +// 작업자 수정 요청 로깅 추가 +console.log('🔧 작업자 수정 요청:', { + worker_id: id, + 받은데이터: req.body, + 처리할데이터: workerData +}); +``` + +## 테스트 방법 + +1. 브라우저에서 강력 새로고침 (Ctrl+Shift+R 또는 Cmd+Shift+R) +2. 작업자 관리 페이지 접속 +3. 작업자 상태 토글 (활성화/비활성화) 테스트 +4. 브라우저 콘솔에서 로그 확인: + - `📤 전송 데이터:` - 올바른 필드만 전송되는지 확인 + - `📋 서버 에러 상세:` - 에러 발생 시 상세 내용 확인 + +## 영향 범위 + +### 수정된 파일: +1. `web-ui/pages/management/worker-management.html` +2. `web-ui/js/api-config.js` +3. `web-ui/js/worker-management.js` +4. `api.hyungi.net/models/workerModel.js` +5. `api.hyungi.net/controllers/workerController.js` + +### 영향받는 기능: +- ✅ 작업자 목록 조회 +- ✅ 작업자 상태 변경 (활성화/비활성화) +- ⚠️ 작업자 추가/수정 (HTML 폼 필드도 수정 필요) + +## 향후 작업 필요 사항 + +### 1. HTML 폼 업데이트 +`worker-management.html`의 모달 폼을 실제 테이블 구조에 맞게 수정 필요: + +```html + + + + +