feat: TBM 모바일 시스템 + 작업 분할/이동 + 권한 통합

TBM 시스템:
- 4단계 워크플로우 (draft→세부편집→완료→작업보고)
- 모바일 전용 TBM 페이지 (tbm-mobile.html) + 3단계 생성 위자드
- 작업자 작업 분할 (work_hours + split_seq)
- 작업자 이동 보내기/빼오기 (tbm_transfers 테이블)
- 생성 시 중복 배정 방지 (당일 배정 현황 조회)
- 데스크탑 TBM 페이지 세부편집 기능 추가

작업보고서:
- 모바일 전용 작업보고서 페이지 (report-create-mobile.html)
- TBM에서 사전 등록된 work_hours 자동 반영

권한 시스템:
- tkuser user_page_permissions 테이블과 system1 페이지 접근 연동
- pageAccessRoutes를 userRoutes보다 먼저 등록 (라우트 우선순위 수정)
- TKUSER_DEFAULT_ACCESS 폴백 추가 (개인→부서→기본값 3단계)
- 권한 캐시키 갱신 (userPageAccess_v2)

기타:
- app-init.js 캐시 버스팅 (v=5)
- iOS Safari touch-action: manipulation 적용
- KST 타임존 날짜 버그 수정 (toISOString UTC 이슈)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-25 07:46:21 +09:00
parent d36303101e
commit 7637be33f3
65 changed files with 9470 additions and 240 deletions

View File

@@ -213,6 +213,46 @@ function getUser() {
return user ? JSON.parse(user) : null;
}
/**
* 근태 유형에 따른 기본 작업시간 반환
*/
function getDefaultHoursFromAttendance(tbm) {
// work_hours가 있으면 (분할 배정) 해당 값 우선 사용
if (tbm.work_hours != null && parseFloat(tbm.work_hours) > 0) {
return parseFloat(tbm.work_hours);
}
switch (tbm.attendance_type) {
case 'overtime': return 8 + (parseFloat(tbm.attendance_hours) || 0);
case 'regular': return 8;
case 'half': return 4;
case 'quarter': return 6;
case 'early': return parseFloat(tbm.attendance_hours) || 0;
default: return 0;
}
}
/**
* 근태 유형 뱃지 HTML 반환
*/
function getAttendanceBadgeHtml(type) {
const labels = { overtime: '연장근무', regular: '정시근로', annual: '연차', half: '반차', quarter: '반반차', early: '조퇴' };
const colors = { overtime: '#7c3aed', regular: '#2563eb', annual: '#ef4444', half: '#f59e0b', quarter: '#f97316', early: '#6b7280' };
if (!type || !labels[type]) return '';
return ` <span style="display:inline-block; padding:0.125rem 0.375rem; border-radius:0.25rem; font-size:0.625rem; font-weight:700; color:white; background:${colors[type]}; vertical-align:middle; margin-left:0.25rem;">${labels[type]}</span>`;
}
/**
* 시간 표시 포맷
*/
function formatHoursDisplay(val) {
if (!val || val <= 0) return '시간 선택';
val = parseFloat(val);
if (val === Math.floor(val)) return val + '시간';
const hours = Math.floor(val);
const mins = Math.round((val - hours) * 60);
return hours > 0 ? hours + '시간 ' + mins + '분' : mins + '분';
}
/**
* TBM 작업 목록 렌더링 (날짜별 > 세션별 그룹화)
* - 날짜별로 접기/펼치기 가능
@@ -422,11 +462,15 @@ function renderTbmWorkList() {
}
return false;
});
// 근태 기반 자동 시간 채움
const defaultHours = tbm.attendance_type ? getDefaultHoursFromAttendance(tbm) : 0;
const hasDefaultHours = defaultHours > 0;
const attendanceBadgeHtml = tbm.attendance_type ? getAttendanceBadgeHtml(tbm.attendance_type) : '';
return `
<tr data-index="${index}" data-type="tbm" data-session-key="${key}">
<td>
<div class="worker-cell">
<strong>${tbm.worker_name || '작업자'}</strong>
<strong>${tbm.worker_name || '작업자'}</strong>${attendanceBadgeHtml}
<div class="worker-job-type">${tbm.job_type || '-'}</div>
</div>
</td>
@@ -440,11 +484,12 @@ function renderTbmWorkList() {
</div>
</td>
<td>
<input type="hidden" id="totalHours_${index}" value="" required>
<div class="time-input-trigger placeholder"
<input type="hidden" id="totalHours_${index}" value="${hasDefaultHours ? defaultHours : ''}" required>
<div class="time-input-trigger ${hasDefaultHours ? '' : 'placeholder'}"
id="totalHoursDisplay_${index}"
onclick="openTimePicker(${index}, 'total')">
시간 선택
onclick="openTimePicker(${index}, 'total')"
style="${hasDefaultHours ? 'color:#1f2937; font-weight:600;' : ''}">
${hasDefaultHours ? formatHoursDisplay(defaultHours) : '시간 선택'}
</div>
</td>
<td>