feat(monthly-comparison): detail 모드 근태 인라인 편집
- 각 일별 카드에 편집 버튼 (detail 모드 전용) - 인라인 폼: 근무시간 + 휴가유형 선택 - 저장 → POST /attendance/records (upsert + vacation balance 자동 연동) - 휴가유형 선택 시 시간 자동 조정 (연차→0, 반차→4, 반반차→6) - attendance_type_id 자동 결정 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -303,3 +303,18 @@
|
||||
.ds-link { color: #2563eb; font-size: 0.8rem; text-decoration: underline; }
|
||||
|
||||
@media (max-width: 480px) { body { max-width: 480px; margin: 0 auto; } }
|
||||
|
||||
/* Inline Edit */
|
||||
.mc-edit-btn { background: none; border: none; color: #9ca3af; cursor: pointer; font-size: 12px; padding: 2px 6px; margin-left: auto; }
|
||||
.mc-edit-btn:hover { color: #2563eb; }
|
||||
.mc-attend-row { display: flex; align-items: center; }
|
||||
.mc-edit-form { display: flex; flex-direction: column; gap: 6px; padding: 4px 0; }
|
||||
.mc-edit-row { display: flex; align-items: center; gap: 6px; font-size: 13px; }
|
||||
.mc-edit-row label { width: 36px; font-weight: 600; color: #6b7280; font-size: 12px; }
|
||||
.mc-edit-input { width: 60px; padding: 4px 6px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; text-align: center; }
|
||||
.mc-edit-select { padding: 4px 6px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; flex: 1; }
|
||||
.mc-edit-actions { display: flex; gap: 6px; margin-top: 2px; }
|
||||
.mc-edit-save { padding: 4px 12px; background: #10b981; color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; }
|
||||
.mc-edit-save:hover { background: #059669; }
|
||||
.mc-edit-cancel { padding: 4px 12px; background: #e5e7eb; color: #374151; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; }
|
||||
.mc-edit-cancel:hover { background: #d1d5db; }
|
||||
|
||||
@@ -253,9 +253,11 @@ function renderDailyList(records) {
|
||||
|
||||
if (r.attendance) {
|
||||
const vacInfo = r.attendance.vacation_type ? ` (${r.attendance.vacation_type})` : '';
|
||||
attendLine = `<div class="mc-daily-row">근태관리: <strong>${r.attendance.total_work_hours}h</strong> <span>(${escHtml(r.attendance.attendance_type)}${vacInfo})</span></div>`;
|
||||
const editBtn = currentMode === 'detail' ? `<button class="mc-edit-btn" onclick="editAttendance('${r.date}', ${r.attendance.total_work_hours}, ${r.attendance.vacation_type_id || 'null'})" title="근태 수정"><i class="fas fa-pen"></i></button>` : '';
|
||||
attendLine = `<div class="mc-daily-row mc-attend-row" id="attend-${r.date}">근태관리: <strong>${r.attendance.total_work_hours}h</strong> <span>(${escHtml(r.attendance.attendance_type)}${vacInfo})</span>${editBtn}</div>`;
|
||||
} else if (r.status !== 'holiday') {
|
||||
attendLine = '<div class="mc-daily-row" style="color:#9ca3af">근태관리: 미입력</div>';
|
||||
const addBtn = currentMode === 'detail' ? `<button class="mc-edit-btn" onclick="editAttendance('${r.date}', 0, null)" title="근태 입력"><i class="fas fa-plus"></i></button>` : '';
|
||||
attendLine = `<div class="mc-daily-row mc-attend-row" id="attend-${r.date}" style="color:#9ca3af">근태관리: 미입력${addBtn}</div>`;
|
||||
}
|
||||
|
||||
if (r.status === 'mismatch' && r.hours_diff) {
|
||||
@@ -549,6 +551,71 @@ function showToast(msg, type) {
|
||||
setTimeout(() => t.remove(), 3000);
|
||||
}
|
||||
|
||||
// ===== Inline Attendance Edit (detail mode) =====
|
||||
function getAttendanceTypeId(hours, vacTypeId) {
|
||||
if (vacTypeId) return 4; // VACATION
|
||||
if (hours >= 8) return 1; // REGULAR
|
||||
if (hours > 0) return 3; // PARTIAL
|
||||
return 0;
|
||||
}
|
||||
|
||||
function editAttendance(date, currentHours, currentVacTypeId) {
|
||||
const el = document.getElementById('attend-' + date);
|
||||
if (!el) return;
|
||||
const vacTypeId = currentVacTypeId === 'null' || currentVacTypeId === null ? '' : currentVacTypeId;
|
||||
el.innerHTML = `
|
||||
<div class="mc-edit-form">
|
||||
<div class="mc-edit-row">
|
||||
<label>시간</label>
|
||||
<input type="number" id="editHours-${date}" value="${currentHours}" step="0.5" min="0" max="24" class="mc-edit-input">
|
||||
<span>h</span>
|
||||
</div>
|
||||
<div class="mc-edit-row">
|
||||
<label>휴가</label>
|
||||
<select id="editVacType-${date}" class="mc-edit-select" onchange="onVacTypeChange('${date}')">
|
||||
<option value="">없음</option>
|
||||
<option value="1" ${vacTypeId == 1 ? 'selected' : ''}>연차</option>
|
||||
<option value="2" ${vacTypeId == 2 ? 'selected' : ''}>반차</option>
|
||||
<option value="3" ${vacTypeId == 3 ? 'selected' : ''}>반반차</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mc-edit-actions">
|
||||
<button class="mc-edit-save" onclick="saveAttendance('${date}')"><i class="fas fa-check"></i> 저장</button>
|
||||
<button class="mc-edit-cancel" onclick="loadData()">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function onVacTypeChange(date) {
|
||||
const vacType = document.getElementById('editVacType-' + date).value;
|
||||
const hoursInput = document.getElementById('editHours-' + date);
|
||||
if (vacType === '1') hoursInput.value = '0'; // 연차 → 0시간
|
||||
else if (vacType === '2') hoursInput.value = '4'; // 반차 → 4시간
|
||||
else if (vacType === '3') hoursInput.value = '6'; // 반반차 → 6시간
|
||||
}
|
||||
|
||||
async function saveAttendance(date) {
|
||||
const hours = parseFloat(document.getElementById('editHours-' + date).value) || 0;
|
||||
const vacTypeVal = document.getElementById('editVacType-' + date).value;
|
||||
const vacTypeId = vacTypeVal ? parseInt(vacTypeVal) : null;
|
||||
const attTypeId = getAttendanceTypeId(hours, vacTypeId);
|
||||
|
||||
try {
|
||||
await window.apiCall('/attendance/records', 'POST', {
|
||||
record_date: date,
|
||||
user_id: currentUserId,
|
||||
total_work_hours: hours,
|
||||
vacation_type_id: vacTypeId,
|
||||
attendance_type_id: attTypeId
|
||||
});
|
||||
showToast('근태 수정 완료', 'success');
|
||||
await loadData(); // 전체 새로고침
|
||||
} catch (e) {
|
||||
showToast('저장 실패: ' + (e.message || e), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ESC로 모달 닫기
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') closeRejectModal();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<script>tailwind.config = { corePlugins: { preflight: false } }</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=2026033001">
|
||||
<link rel="stylesheet" href="/css/monthly-comparison.css?v=2026033001">
|
||||
<link rel="stylesheet" href="/css/monthly-comparison.css?v=2026033107">
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<header class="bg-orange-700 text-white sticky top-0 z-50">
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
<script src="/static/js/tkfb-core.js?v=2026033106"></script>
|
||||
<script src="/js/api-base.js?v=2026031701"></script>
|
||||
<script src="/js/monthly-comparison.js?v=2026033106"></script>
|
||||
<script src="/js/monthly-comparison.js?v=2026033107"></script>
|
||||
<script>initAuth();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user