diff --git a/system1-factory/api/models/proxyInputModel.js b/system1-factory/api/models/proxyInputModel.js index 2d739ea..d679bda 100644 --- a/system1-factory/api/models/proxyInputModel.js +++ b/system1-factory/api/models/proxyInputModel.js @@ -14,7 +14,7 @@ const ProxyInputModel = { SELECT ta.user_id, w.worker_name, ta.session_id FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id - JOIN workers w ON ta.user_id = w.worker_id + JOIN workers w ON ta.user_id = w.user_id WHERE s.session_date = ? AND ta.user_id IN (${placeholders}) AND s.status != 'cancelled' `, [sessionDate, ...userIds]); return rows; @@ -27,9 +27,9 @@ const ProxyInputModel = { if (!userIds.length) return []; const placeholders = userIds.map(() => '?').join(','); const [rows] = await conn.query(` - SELECT worker_id FROM workers WHERE worker_id IN (${placeholders}) AND status = 'active' + SELECT user_id FROM workers WHERE user_id IN (${placeholders}) AND status = 'active' `, [...userIds]); - return rows.map(r => r.worker_id); + return rows.map(r => r.user_id); }, /** @@ -73,11 +73,11 @@ const ProxyInputModel = { // 1. 활성 작업자 const [workers] = await db.query(` - SELECT w.worker_id AS user_id, w.worker_name, w.job_type, + SELECT w.user_id, w.worker_name, w.job_type, COALESCE(d.department_name, '미배정') AS department_name FROM workers w LEFT JOIN departments d ON w.department_id = d.department_id - WHERE w.status = 'active' + WHERE w.status = 'active' AND w.user_id IS NOT NULL ORDER BY w.worker_name `); @@ -87,7 +87,7 @@ const ProxyInputModel = { lu.worker_name AS leader_name, s.is_proxy_input FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id - LEFT JOIN workers lu ON s.leader_user_id = lu.worker_id + LEFT JOIN workers lu ON s.leader_user_id = lu.user_id WHERE s.session_date = ? AND s.status != 'cancelled' `, [date]); @@ -191,11 +191,11 @@ const ProxyInputModel = { // 작업자 정보 const [workerRows] = await db.query(` - SELECT w.worker_id AS user_id, w.worker_name, w.job_type, + SELECT w.user_id, w.worker_name, w.job_type, COALESCE(d.department_name, '미배정') AS department_name FROM workers w LEFT JOIN departments d ON w.department_id = d.department_id - WHERE w.worker_id = ? + WHERE w.user_id = ? `, [userId]); // TBM 세션 @@ -206,7 +206,7 @@ const ProxyInputModel = { p.project_name, wt.work_type_name, ta.work_hours FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id - LEFT JOIN workers lu ON s.leader_user_id = lu.worker_id + LEFT JOIN workers lu ON s.leader_user_id = lu.user_id LEFT JOIN sso_users pu ON s.proxy_input_by = pu.user_id LEFT JOIN projects p ON ta.project_id = p.project_id LEFT JOIN work_types wt ON ta.work_type_id = wt.work_type_id diff --git a/system1-factory/web/css/proxy-input.css b/system1-factory/web/css/proxy-input.css index 29334ee..e553bc8 100644 --- a/system1-factory/web/css/proxy-input.css +++ b/system1-factory/web/css/proxy-input.css @@ -60,6 +60,24 @@ .pi-skeleton { height: 52px; border-radius: 10px; background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%); background-size: 200% 100%; animation: pi-shimmer 1.5s infinite; } @keyframes pi-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } +/* Department Label */ +.pi-dept-label { font-size: 11px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; padding: 8px 2px 4px; } + +/* Bulk Form */ +.pi-bulk-form { background: white; border-radius: 12px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); display: flex; flex-direction: column; gap: 8px; } +.pi-edit-row { display: flex; gap: 8px; } +.pi-select { flex: 1; padding: 8px 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; background: white; } +.pi-field { display: flex; flex-direction: column; gap: 2px; flex: 1; } +.pi-field span { font-size: 11px; color: #6b7280; font-weight: 600; } +.pi-input { padding: 8px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 15px; text-align: center; font-weight: 600; } +.pi-note-input { width: 100%; padding: 8px 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 13px; } + +/* Target Section */ +.pi-target-section { background: white; border-radius: 12px; padding: 12px; margin-bottom: 80px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); } +.pi-target-label { font-size: 12px; font-weight: 700; color: #6b7280; margin-bottom: 8px; } +.pi-target-list { display: flex; flex-wrap: wrap; gap: 6px; } +.pi-target-chip { font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 20px; background: #dbeafe; color: #1e40af; } + /* Empty */ .pi-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 48px 16px; color: #9ca3af; font-size: 14px; } diff --git a/system1-factory/web/js/proxy-input.js b/system1-factory/web/js/proxy-input.js index 81b8bf4..1508bf3 100644 --- a/system1-factory/web/js/proxy-input.js +++ b/system1-factory/web/js/proxy-input.js @@ -1,7 +1,7 @@ /** * proxy-input.js — 대리입력 리뉴얼 * Step 1: 날짜 선택 → 작업자 목록 (체크박스) - * Step 2: 선택 작업자 일괄 편집 → 저장 (TBM 자동 생성) + * Step 2: 공통 입력 1개 → 선택된 전원 일괄 적용 */ let currentDate = ''; @@ -9,14 +9,11 @@ let allWorkers = []; let selectedIds = new Set(); let projects = []; let workTypes = []; -let editData = {}; // { userId: { project_id, work_type_id, work_hours, defect_hours, note } } // ===== Init ===== document.addEventListener('DOMContentLoaded', () => { - const now = new Date(); - currentDate = now.toISOString().substring(0, 10); + currentDate = new Date().toISOString().substring(0, 10); document.getElementById('dateInput').value = currentDate; - setTimeout(async () => { await loadDropdownData(); await loadWorkers(); @@ -50,7 +47,7 @@ async function loadWorkers() { allWorkers = res.data.workers || []; const s = res.data.summary || {}; - document.getElementById('totalNum').textContent = s.total_active_workers || 0; + document.getElementById('totalNum').textContent = s.total_active_workers || allWorkers.length; document.getElementById('doneNum').textContent = s.report_completed || 0; document.getElementById('missingNum').textContent = s.report_missing || 0; document.getElementById('vacNum').textContent = allWorkers.filter(w => w.vacation_type_code === 'ANNUAL_FULL').length; @@ -68,27 +65,38 @@ function renderWorkerList() { return; } - list.innerHTML = allWorkers.map(w => { - const isFullVac = w.vacation_type_code === 'ANNUAL_FULL'; - const hasVac = !!w.vacation_type_code; - const vacBadge = isFullVac ? '연차' - : hasVac ? `${esc(w.vacation_type_name)}` : ''; - const doneBadge = w.has_report ? `${w.total_report_hours}h` : '미입력'; + // 부서별 그룹핑 + const byDept = {}; + allWorkers.forEach(w => { + const dept = w.department_name || '미배정'; + if (!byDept[dept]) byDept[dept] = []; + byDept[dept].push(w); + }); - return ` - `; - }).join(''); + let html = ''; + Object.keys(byDept).sort().forEach(dept => { + html += `