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 + + + + +