feat(proxy-input): 대리입력 리뉴얼 — 2단계 UI + UPSERT + 부적합

프론트엔드:
- Step 1: 날짜 선택 → 전체 작업자 목록 (완료/미입력/휴가 구분)
- Step 2: 선택 작업자 일괄 편집 (프로젝트/공종/시간/부적합/비고)
- 연차=선택불가, 반차=4h, 반반차=6h 기본값

백엔드:
- POST /api/proxy-input UPSERT 방식 (409 제거)
- 신규: TBM 세션 자동 생성 + 작업보고서 INSERT
- 기존: 작업보고서 UPDATE
- 부적합: work_report_defects INSERT (기존 defect 있으면 SKIP)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-31 14:50:04 +09:00
parent b8d3a516e1
commit 3cc38791c8
4 changed files with 386 additions and 773 deletions

View File

@@ -7,7 +7,7 @@
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="/static/css/tkfb.css?v=2026033108">
<link rel="stylesheet" href="/css/proxy-input.css?v=2026033102">
<link rel="stylesheet" href="/css/proxy-input.css?v=2026033201">
</head>
<body class="bg-gray-50">
<header class="bg-orange-700 text-white sticky top-0 z-50">
@@ -32,93 +32,66 @@
<nav id="sideNav" class="hidden lg:flex flex-col gap-1 w-52 flex-shrink-0 pt-2 fixed lg:static z-40 bg-white lg:bg-transparent p-4 lg:p-0 rounded-lg lg:rounded-none shadow-lg lg:shadow-none top-14 left-0 bottom-0 overflow-y-auto"></nav>
<div class="flex-1 min-w-0">
<!-- Header -->
<div class="pi-header">
<div class="pi-header-row">
<button type="button" onclick="history.back()" class="pi-back-btn"><i class="fas fa-arrow-left"></i></button>
<h1>대리입력</h1>
<div class="pi-header-date" id="headerDate">-</div>
<!-- ═══ STEP 1: 작업자 선택 ═══ -->
<div id="step1">
<div class="pi-title-row">
<h2 class="pi-title">대리입력</h2>
<div class="pi-date-group">
<input type="date" id="dateInput" class="pi-date-input" onchange="loadWorkers()">
<button class="pi-refresh-btn" onclick="loadWorkers()"><i class="fas fa-sync-alt"></i></button>
</div>
</div>
<div class="pi-status-bar" id="statusBar">
<span>전체 <strong id="totalNum">0</strong></span>
<span>완료 <strong id="doneNum" class="text-green-600">0</strong></span>
<span>미입력 <strong id="missingNum" class="text-red-500">0</strong></span>
<span>휴가 <strong id="vacNum" class="text-blue-500">0</strong></span>
</div>
<div class="pi-select-all">
<label><input type="checkbox" id="selectAll" onchange="toggleSelectAll(this.checked)"> 전체 선택</label>
</div>
<div class="pi-worker-list" id="workerList">
<div class="pi-skeleton"></div>
<div class="pi-skeleton"></div>
<div class="pi-skeleton"></div>
</div>
<div class="pi-bottom-bar" id="editBar">
<button class="pi-edit-btn" id="editBtn" onclick="openEditMode()" disabled>
<i class="fas fa-pen mr-2"></i><span id="editBtnText">작업자를 선택하세요</span>
</button>
</div>
</div>
<!-- Date + Status -->
<div class="pi-date-bar">
<div class="pi-date-info">
<i class="fas fa-calendar-alt text-blue-500"></i>
<input type="date" id="dateInput" class="pi-date-input" onchange="onDateChange(this.value)">
<button type="button" class="pi-refresh-btn" onclick="loadWorkers()"><i class="fas fa-sync-alt"></i></button>
<!-- ═══ STEP 2: 일괄 편집 ═══ -->
<div id="step2" class="hidden">
<div class="pi-title-row">
<button class="pi-back-btn" onclick="closeEditMode()"><i class="fas fa-arrow-left"></i></button>
<h2 class="pi-title" id="editTitle">일괄 편집</h2>
</div>
<div class="pi-status-badge" id="statusBadge">
<i class="fas fa-exclamation-triangle text-amber-500"></i>
<span>미입력 <strong id="missingNum">0</strong></span>
<div class="pi-edit-list" id="editList"></div>
<div class="pi-bottom-bar">
<button class="pi-save-btn" id="saveBtn" onclick="saveAll()">
<i class="fas fa-save mr-2"></i><span id="saveBtnText">전체 저장</span>
</button>
</div>
</div>
<!-- Holiday Banner -->
<div class="pi-holiday-banner hidden" id="holidayBanner"></div>
<!-- Bulk Actions -->
<div class="pi-bulk hidden" id="bulkBar">
<span class="pi-bulk-label"><i class="fas fa-layer-group mr-1"></i>일괄 설정 (<span id="selectedCount">0</span>명)</span>
<div class="pi-bulk-actions">
<button type="button" class="pi-bulk-btn" onclick="bulkSet('project')"><i class="fas fa-folder mr-1"></i>프로젝트</button>
<button type="button" class="pi-bulk-btn" onclick="bulkSet('workType')"><i class="fas fa-wrench mr-1"></i>공종</button>
<button type="button" class="pi-bulk-btn" onclick="bulkSet('hours')"><i class="fas fa-clock mr-1"></i>시간</button>
</div>
</div>
<!-- Worker Cards -->
<div class="pi-cards" id="workerCards">
<div class="ds-skeleton"></div>
<div class="ds-skeleton"></div>
</div>
<!-- Empty State -->
<div class="pi-empty hidden" id="emptyState">
<i class="fas fa-check-circle text-3xl text-green-300"></i>
<p>미입력 작업자가 없습니다</p>
<a href="/pages/work/daily-status.html" class="ds-link">현황으로 돌아가기</a>
</div>
<!-- No Permission -->
<div class="ds-empty hidden" id="noPermission">
<i class="fas fa-lock text-3xl text-gray-300"></i>
<p>접근 권한이 없습니다</p>
<a href="/pages/dashboard.html" class="ds-link">대시보드로 이동</a>
</div>
<!-- Bottom Save -->
<div class="pi-bottom" id="bottomSave">
<button type="button" class="pi-save-btn" id="saveBtn" onclick="saveProxyInput()" disabled>
<i class="fas fa-spinner fa-spin mr-2" style="display:none"></i><i class="fas fa-save mr-2"></i><span id="saveBtnText">저장할 작업자를 선택하세요</span>
</button>
</div>
<!-- Toast -->
<div id="toastContainer" class="toast-container"></div>
<!-- Bulk Select Modal -->
<div class="pi-modal-overlay hidden" id="bulkModal">
<div class="pi-modal">
<div class="pi-modal-header">
<span id="bulkModalTitle">일괄 설정</span>
<button type="button" onclick="closeBulkModal()"><i class="fas fa-times"></i></button>
</div>
<div class="pi-modal-body" id="bulkModalBody"></div>
<div class="pi-modal-footer">
<button type="button" class="pi-modal-cancel" onclick="closeBulkModal()">취소</button>
<button type="button" class="pi-modal-confirm" id="bulkConfirmBtn">적용</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/js/tkfb-core.js?v=2026033108"></script>
<script src="/js/api-base.js?v=2026031701"></script>
<script src="/js/proxy-input.js?v=2026033102"></script>
<script src="/js/proxy-input.js?v=2026033201"></script>
<script>initAuth();</script>
</body>
</html>