Files
Groupware/Project/Web/wwwroot/Kuntae/index.html
backuppc f0d46b7cb1 perf: WebView2 HostObject 프록시 캐싱으로 성능 개선
- 각 HTML 파일에서 machine 프록시를 전역 변수로 한 번만 초기화
- 매 함수 호출마다 hostObjects.machine 접근하던 오버헤드 제거
- 네비게이션 클릭 시 콘솔 로그 추가 (디버깅용)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 15:58:39 +09:00

791 lines
39 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>근태입력 조회</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
success: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
},
warning: {
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f',
},
danger: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
}
},
animation: {
'fade-in': 'fadeIn 0.5s ease-in-out',
'slide-up': 'slideUp 0.3s ease-out',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
}
}
}
}
}
</script>
<style>
.glass-effect {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid #ffffff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.table-container {
max-height: 600px;
overflow-y: auto;
}
/* 스크롤바 스타일링 */
.custom-scrollbar::-webkit-scrollbar {
width: var(--scrollbar-width, 16px);
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.1);
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
<div class="container mx-auto px-4 py-8">
<!-- 개발중 경고 메시지 -->
<div class="bg-orange-500 rounded-lg p-4 mb-6 border-l-4 border-orange-700 animate-slide-up shadow-lg">
<div class="flex items-center">
<svg class="w-5 h-5 text-orange-900 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
</svg>
<div>
<p class="text-white font-bold text-base">🚧 개발중인 기능입니다</p>
<p class="text-orange-100 text-sm font-medium">일부 기능이 정상적으로 동작하지 않을 수 있습니다.</p>
</div>
</div>
</div>
<!-- 검색 및 필터 섹션 -->
<div class="glass-effect rounded-lg p-6 mb-6 animate-slide-up">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="startDate" class="block text-sm font-medium text-white/80 mb-2">시작일</label>
<input type="date" id="startDate" class="w-full px-3 py-2 bg-white/20 border border-white/30 rounded-md text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent">
</div>
<div>
<label for="endDate" class="block text-sm font-medium text-white/80 mb-2">종료일</label>
<input type="date" id="endDate" class="w-full px-3 py-2 bg-white/20 border border-white/30 rounded-md text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent">
</div>
<div class="flex items-end">
<button id="searchBtn" class="w-full glass-effect text-white px-4 py-2 rounded-md hover:bg-white/30 transition-colors duration-200 flex items-center justify-center">
<span id="searchBtnText">조회</span>
<div id="searchBtnLoading" class="loading ml-2 hidden"></div>
</button>
</div>
</div>
</div>
<!-- 통계 카드 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6 animate-slide-up">
<div class="glass-effect rounded-lg p-6 card-hover">
<div class="flex items-center">
<div class="p-3 bg-primary-500/20 rounded-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-white/80">휴가사용</p>
<p id="totalDays" class="text-2xl font-bold text-white">0</p>
</div>
</div>
</div>
<div class="glass-effect rounded-lg p-6 card-hover">
<div class="flex items-center">
<div class="p-3 bg-success-500/20 rounded-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-white/80">대체사용</p>
<p id="normalDays" class="text-2xl font-bold text-white">0</p>
</div>
</div>
</div>
<div class="glass-effect rounded-lg p-6 card-hover">
<div class="flex items-center">
<div class="p-3 bg-warning-500/20 rounded-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-white/80">잔량(년차)</p>
<p id="lateDays" class="text-2xl font-bold text-white">0</p>
</div>
</div>
</div>
<div class="glass-effect rounded-lg p-6 card-hover">
<div class="flex items-center">
<div class="p-3 bg-danger-500/20 rounded-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-white/80">잔량(대체)</p>
<p id="absentDays" class="text-2xl font-bold text-white">0</p>
</div>
</div>
</div>
</div>
<!-- 데이터 테이블 -->
<div class="glass-effect rounded-lg animate-slide-up custom-scrollbar">
<div class="px-6 py-4 border-b border-white/20 flex justify-between items-center">
<h3 class="text-lg font-medium text-white">근태 상세 내역</h3>
<button id="addBtn" class="glass-effect text-white px-4 py-2 rounded-md hover:bg-white/30 transition-colors duration-200 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
근태 추가
</button>
</div>
<div class="table-container">
<table class="min-w-full divide-y divide-white/20">
<thead class="bg-white/10 sticky top-0">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">구분</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">시작일</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">종료일</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">사번</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">성명</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">사용(일)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">사용(H)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">발생(일)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">발생(H)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">내용</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">#</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">잔량(일)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">잔량(H)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">전일(일)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">전일(H)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">소스</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">등록자</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">등록일</th>
</tr>
</thead>
<tbody id="dataTableBody" class="divide-y divide-white/10">
<tr id="loadingRow" class="hidden">
<td colspan="18" class="px-6 py-4 text-center">
<div class="flex items-center justify-center">
<div class="loading mr-2"></div>
<span class="text-white/80">데이터를 불러오는 중...</span>
</div>
</td>
</tr>
<tr id="noDataRow" class="hidden">
<td colspan="18" class="px-6 py-4 text-center text-white/70">
조회된 데이터가 없습니다.
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 근태 추가/편집 모달 -->
<div id="kuntaeModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex justify-between items-center mb-4">
<h3 id="modalTitle" class="text-lg font-medium text-gray-900">근태 추가</h3>
<button id="closeModal" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<form id="kuntaeForm">
<input type="hidden" id="editId" name="id">
<div class="mb-4">
<label for="modalDate" class="block text-sm font-medium text-gray-700 mb-2">날짜</label>
<input type="date" id="modalDate" name="pdate" required
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label for="modalInTime" class="block text-sm font-medium text-gray-700 mb-2">출근시간</label>
<input type="time" id="modalInTime" name="intime"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<div>
<label for="modalOutTime" class="block text-sm font-medium text-gray-700 mb-2">퇴근시간</label>
<input type="time" id="modalOutTime" name="outtime"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
</div>
<div class="mb-4">
<label for="modalMemo" class="block text-sm font-medium text-gray-700 mb-2">비고</label>
<textarea id="modalMemo" name="memo" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="비고사항을 입력하세요"></textarea>
</div>
<div class="flex justify-end space-x-3">
<button type="button" id="cancelBtn"
class="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors duration-200">
취소
</button>
<button type="submit" id="saveBtn"
class="px-4 py-2 bg-primary text-white rounded-md hover:bg-blue-600 transition-colors duration-200">
저장
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 삭제 확인 모달 -->
<div id="deleteModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex items-center mb-4">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<svg class="h-6 w-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
</svg>
</div>
</div>
<div class="text-center">
<h3 class="text-lg font-medium text-gray-900 mb-2">근태 삭제</h3>
<p class="text-sm text-gray-500 mb-4">선택한 근태 데이터를 삭제하시겠습니까?</p>
<p id="deleteConfirmText" class="text-sm text-gray-700 mb-6"></p>
<div class="flex justify-center space-x-3">
<button id="cancelDeleteBtn"
class="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors duration-200">
취소
</button>
<button id="confirmDeleteBtn"
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors duration-200">
삭제
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 공통 네비게이션 -->
<script src="/js/common-navigation.js"></script>
<script>
// 전역 변수
let currentData = [];
let currentEditId = null;
// 비동기 프록시 캐싱 (한 번만 초기화)
const machine = window.chrome.webview.hostObjects.machine;
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', function() {
// 네비게이션 초기화
initNavigation('kuntae');
initializeDates();
loadData();
setupEventListeners();
setupModalEvents();
});
// 날짜 초기화 (현재 월)
function initializeDates() {
const now = new Date();
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
document.getElementById('startDate').value = startOfMonth.toISOString().split('T')[0];
document.getElementById('endDate').value = endOfMonth.toISOString().split('T')[0];
}
// 이벤트 리스너 설정
function setupEventListeners() {
document.getElementById('searchBtn').addEventListener('click', loadData);
document.getElementById('addBtn').addEventListener('click', showAddModal);
// Enter 키로 검색
document.getElementById('startDate').addEventListener('keypress', function(e) {
if (e.key === 'Enter') loadData();
});
document.getElementById('endDate').addEventListener('keypress', function(e) {
if (e.key === 'Enter') loadData();
});
}
// 모달 이벤트 설정
function setupModalEvents() {
// 근태 모달 이벤트
document.getElementById('closeModal').addEventListener('click', hideKuntaeModal);
document.getElementById('cancelBtn').addEventListener('click', hideKuntaeModal);
document.getElementById('kuntaeForm').addEventListener('submit', saveKuntae);
// 삭제 모달 이벤트
document.getElementById('cancelDeleteBtn').addEventListener('click', hideDeleteModal);
document.getElementById('confirmDeleteBtn').addEventListener('click', confirmDelete);
// 모달 외부 클릭 시 닫기
document.getElementById('kuntaeModal').addEventListener('click', function(e) {
if (e.target === this) hideKuntaeModal();
});
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) hideDeleteModal();
});
}
// 데이터 로드
async function loadData() {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
if (!startDate || !endDate) {
alert('시작일과 종료일을 모두 입력해주세요.');
return;
}
if (new Date(startDate) > new Date(endDate)) {
alert('시작일은 종료일보다 늦을 수 없습니다.');
return;
}
showLoading(true);
try {
const jsonStr = await machine.Kuntae_GetList(startDate, endDate);
const data = JSON.parse(jsonStr);
if (data) {
currentData = data;
renderTable();
updateStatistics();
} else {
currentData = [];
renderTable();
}
} catch (error) {
console.error('데이터 로드 중 오류 발생:', error);
alert('데이터를 불러오는 중 오류가 발생했습니다.');
currentData = [];
renderTable();
} finally {
showLoading(false);
}
}
// 로딩 상태 표시
function showLoading(show) {
const searchBtn = document.getElementById('searchBtn');
const searchBtnText = document.getElementById('searchBtnText');
const searchBtnLoading = document.getElementById('searchBtnLoading');
const loadingRow = document.getElementById('loadingRow');
if (show) {
searchBtn.disabled = true;
searchBtnText.textContent = '조회 중...';
searchBtnLoading.classList.remove('hidden');
loadingRow.classList.remove('hidden');
} else {
searchBtn.disabled = false;
searchBtnText.textContent = '조회';
searchBtnLoading.classList.add('hidden');
loadingRow.classList.add('hidden');
}
}
// 테이블 렌더링
function renderTable() {
const tbody = document.getElementById('dataTableBody');
const noDataRow = document.getElementById('noDataRow');
// 기존 데이터 행 제거 (로딩, 노데이터 행 제외)
const existingRows = tbody.querySelectorAll('tr:not(#loadingRow):not(#noDataRow)');
existingRows.forEach(row => row.remove());
if (currentData.length === 0) {
noDataRow.classList.remove('hidden');
return;
}
noDataRow.classList.add('hidden');
currentData.forEach(item => {
const row = document.createElement('tr');
row.className = 'hover:bg-white/10 cursor-pointer transition-colors';
row.setAttribute('data-id', item.idx);
const startDate = item.sdate ? new Date(item.sdate) : null;
const endDate = item.edate ? new Date(item.edate) : null;
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.cate || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${startDate ? formatDate(startDate) : '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${endDate ? formatDate(endDate) : '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.uid || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.uname || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.term || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.termdr || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.drtime || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.crtime || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white/80 max-w-xs truncate" title="${item.contents || ''}">${item.contents || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.tag || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.extcate || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.wuid || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.wdate || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white/80">
<div class="flex space-x-2">
<button class="text-blue-400 hover:text-blue-300 edit-btn transition-colors" data-id="${item.idx}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button class="text-red-400 hover:text-red-300 delete-btn transition-colors" data-id="${item.idx}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
</td>
`;
tbody.appendChild(row);
});
// 행 클릭 이벤트 추가
tbody.querySelectorAll('tr[data-id]').forEach(row => {
row.addEventListener('click', function(e) {
if (!e.target.closest('.edit-btn') && !e.target.closest('.delete-btn')) {
const id = this.getAttribute('data-id');
showEditModal(id);
}
});
});
// 편집 버튼 이벤트
tbody.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const id = this.getAttribute('data-id');
showEditModal(id);
});
});
// 삭제 버튼 이벤트
tbody.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const id = this.getAttribute('data-id');
showDeleteModal(id);
});
});
}
// 날짜 포맷팅
function formatDate(date) {
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
// 근무시간 계산
function calculateWorkHours(item) {
if (!item.intime || !item.outtime) return '-';
try {
const inTime = new Date(`2000-01-01 ${item.intime}`);
const outTime = new Date(`2000-01-01 ${item.outtime}`);
if (outTime < inTime) {
outTime.setDate(outTime.getDate() + 1);
}
const diffMs = outTime - inTime;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
return `${diffHours}시간 ${diffMinutes}`;
} catch (error) {
return '-';
}
}
// 상태 판단
function getStatus(item) {
if (!item.intime) return '결근';
if (!item.outtime) return '출근';
// 지각 판단 (예: 9시 이후 출근)
const inTime = new Date(`2000-01-01 ${item.intime}`);
const lateThreshold = new Date(`2000-01-01 09:00:00`);
if (inTime > lateThreshold) return '지각';
return '정상';
}
// 상태별 CSS 클래스
function getStatusClass(status) {
switch (status) {
case '정상': return 'bg-green-100 text-green-800';
case '지각': return 'bg-yellow-100 text-yellow-800';
case '결근': return 'bg-red-100 text-red-800';
case '출근': return 'bg-blue-100 text-blue-800';
default: return 'bg-gray-100 text-gray-800';
}
}
// 통계 업데이트
function updateStatistics() {
const totalDays = currentData.length;
const normalDays = currentData.filter(item => getStatus(item) === '정상').length;
const lateDays = currentData.filter(item => getStatus(item) === '지각').length;
const absentDays = currentData.filter(item => getStatus(item) === '결근').length;
document.getElementById('totalDays').textContent = totalDays;
document.getElementById('normalDays').textContent = normalDays;
document.getElementById('lateDays').textContent = lateDays;
document.getElementById('absentDays').textContent = absentDays;
}
// 근태 추가 모달 표시
function showAddModal() {
currentEditId = null;
document.getElementById('modalTitle').textContent = '근태 추가';
document.getElementById('editId').value = '';
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
document.getElementById('modalInTime').value = '';
document.getElementById('modalOutTime').value = '';
document.getElementById('modalMemo').value = '';
document.getElementById('kuntaeModal').classList.remove('hidden');
}
// 근태 편집 모달 표시
function showEditModal(id) {
const item = currentData.find(data => (data.id || data.wdate) == id);
if (!item) {
alert('데이터를 찾을 수 없습니다.');
return;
}
currentEditId = id;
document.getElementById('modalTitle').textContent = '근태 편집';
document.getElementById('editId').value = id;
document.getElementById('modalDate').value = item.pdate ? new Date(item.pdate).toISOString().split('T')[0] : '';
document.getElementById('modalInTime').value = item.intime || '';
document.getElementById('modalOutTime').value = item.outtime || '';
document.getElementById('modalMemo').value = item.memo || '';
document.getElementById('kuntaeModal').classList.remove('hidden');
}
// 근태 모달 숨기기
function hideKuntaeModal() {
document.getElementById('kuntaeModal').classList.add('hidden');
currentEditId = null;
}
// 근태 저장
async function saveKuntae(e) {
e.preventDefault();
const formData = new FormData(e.target);
const data = {
id: formData.get('id'),
pdate: formData.get('pdate'),
intime: formData.get('intime'),
outtime: formData.get('outtime'),
memo: formData.get('memo')
};
if (!data.pdate) {
alert('날짜를 입력해주세요.');
return;
}
try {
const url = currentEditId ? '/Kuntae/Update' : '/Kuntae/Insert';
const method = currentEditId ? 'PUT' : 'POST';
const response = await axios({
method: method,
url: url,
data: data,
headers: {
'Content-Type': 'application/json'
}
});
if (response.data && response.data.success) {
alert(currentEditId ? '근태가 수정되었습니다.' : '근태가 추가되었습니다.');
hideKuntaeModal();
loadData(); // 목록 새로고침
} else {
alert(response.data?.message || '저장 중 오류가 발생했습니다.');
}
} catch (error) {
console.error('저장 중 오류 발생:', error);
alert('저장 중 오류가 발생했습니다.');
}
}
// 삭제 모달 표시
function showDeleteModal(id) {
const item = currentData.find(data => (data.id || data.wdate) == id);
if (!item) {
alert('데이터를 찾을 수 없습니다.');
return;
}
const date = new Date(item.pdate);
document.getElementById('deleteConfirmText').textContent =
`${formatDate(date)} 근태 데이터를 삭제하시겠습니까?`;
document.getElementById('confirmDeleteBtn').setAttribute('data-id', id);
document.getElementById('deleteModal').classList.remove('hidden');
}
// 삭제 모달 숨기기
function hideDeleteModal() {
document.getElementById('deleteModal').classList.add('hidden');
}
// 삭제 확인
async function confirmDelete() {
const id = document.getElementById('confirmDeleteBtn').getAttribute('data-id');
try {
const jsonStr = await machine.Kuntae_Delete(id);
const result = JSON.parse(jsonStr);
if (result && result.success) {
alert('근태가 삭제되었습니다.');
hideDeleteModal();
loadData(); // 목록 새로고침
} else {
alert(result?.message || '삭제 중 오류가 발생했습니다.');
}
} catch (error) {
console.error('삭제 중 오류 발생:', error);
alert('삭제 중 오류가 발생했습니다.');
}
}
</script>
</body>
</html>