fix: [attendance] 근태관리 승인 탭 제거
- 결재관리에서 처리하므로 승인 탭 불필요 - 탭 네비게이션, 승인 탭 콘텐츠, 승인 신청 모달 제거 - 승인/반려 JS 함수 및 탭 전환 로직 제거
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800">근태관리</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">근태 등록, 수정, 삭제 및 승인 관리</p>
|
||||
<p class="text-sm text-gray-500 mt-1">근태 등록, 수정, 삭제 관리</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<button type="button" onclick="openBulkModal()"
|
||||
@@ -28,21 +28,8 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 네비게이션 --}}
|
||||
<div class="flex items-center gap-1 mb-4 border-b border-gray-200">
|
||||
<button type="button" onclick="switchTab('manage')" id="tab-manage"
|
||||
class="px-4 py-2.5 text-sm font-medium border-b-2 transition-colors border-blue-600 text-blue-600">
|
||||
등록/관리
|
||||
</button>
|
||||
<button type="button" onclick="switchTab('requests')" id="tab-requests"
|
||||
class="px-4 py-2.5 text-sm font-medium border-b-2 transition-colors border-transparent text-gray-500 hover:text-gray-700">
|
||||
승인
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 탭 콘텐츠 영역 --}}
|
||||
{{-- 콘텐츠 영역 --}}
|
||||
<div id="manage-content">
|
||||
{{-- 등록/관리 탭 --}}
|
||||
<div id="content-manage">
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
{{-- 필터 + 일괄 삭제 --}}
|
||||
@@ -118,17 +105,6 @@ class="min-h-[200px]">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 승인 탭 --}}
|
||||
<div id="content-requests" class="hidden">
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div id="attendance-requests-container">
|
||||
<div class="flex justify-center items-center p-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,102 +283,10 @@ class="px-4 py-2 text-sm text-white bg-purple-600 hover:bg-purple-700 rounded-lg
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 승인 신청 모달 --}}
|
||||
<div id="requestModal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="fixed inset-0 bg-black/40" onclick="closeRequestModal()"></div>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-md relative">
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200">
|
||||
<h3 id="requestModalTitle" class="text-lg font-semibold text-gray-800">근태 신청</h3>
|
||||
<button type="button" onclick="closeRequestModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-5 h-5" 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"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-6 py-4 space-y-4">
|
||||
<div id="requestModalMessage" class="hidden rounded-lg px-4 py-3 text-sm"></div>
|
||||
<input type="hidden" id="req_id" value="">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">신청자</label>
|
||||
<select id="req_user_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="">사원 선택</option>
|
||||
@foreach($employees as $emp)
|
||||
<option value="{{ $emp->user_id }}">{{ $emp->display_name ?? $emp->user?->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">신청 유형</label>
|
||||
<select id="req_type" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="vacation">휴가</option>
|
||||
<option value="businessTrip">출장</option>
|
||||
<option value="remote">재택</option>
|
||||
<option value="fieldWork">외근</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div style="flex: 1;">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">시작일</label>
|
||||
<input type="date" id="req_start_date" value="{{ now()->toDateString() }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">종료일</label>
|
||||
<input type="date" id="req_end_date" value="{{ now()->toDateString() }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">사유</label>
|
||||
<textarea id="req_reason" rows="3" placeholder="신청 사유를 입력하세요..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200">
|
||||
<button type="button" onclick="closeRequestModal()"
|
||||
class="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
취소
|
||||
</button>
|
||||
<button type="button" onclick="submitRequest()"
|
||||
class="px-4 py-2 text-sm text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
|
||||
신청
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// ===== 현재 탭 상태 =====
|
||||
let currentTab = 'manage';
|
||||
const tabLoaded = { manage: true, requests: false };
|
||||
|
||||
function switchTab(tab) {
|
||||
document.getElementById('tab-' + currentTab).className = 'px-4 py-2.5 text-sm font-medium border-b-2 transition-colors border-transparent text-gray-500 hover:text-gray-700';
|
||||
document.getElementById('content-' + currentTab).classList.add('hidden');
|
||||
|
||||
currentTab = tab;
|
||||
document.getElementById('tab-' + tab).className = 'px-4 py-2.5 text-sm font-medium border-b-2 transition-colors border-blue-600 text-blue-600';
|
||||
document.getElementById('content-' + tab).classList.remove('hidden');
|
||||
|
||||
if (!tabLoaded[tab]) {
|
||||
tabLoaded[tab] = true;
|
||||
loadTabData(tab);
|
||||
}
|
||||
}
|
||||
|
||||
function loadTabData(tab) {
|
||||
if (tab === 'requests') {
|
||||
htmx.ajax('GET', '{{ route("api.admin.hr.attendances.requests.index") }}', {
|
||||
target: '#attendance-requests-container',
|
||||
swap: 'innerHTML',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 일괄 선택 관리 =====
|
||||
const selectedAttendanceIds = new Set();
|
||||
|
||||
@@ -713,99 +597,5 @@ function showBulkMessage(message, isError) {
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// ===== 승인 신청 모달 =====
|
||||
function openRequestModal() {
|
||||
document.getElementById('req_id').value = '';
|
||||
document.getElementById('req_user_id').value = '';
|
||||
document.getElementById('req_type').value = 'vacation';
|
||||
document.getElementById('req_start_date').value = '{{ now()->toDateString() }}';
|
||||
document.getElementById('req_end_date').value = '{{ now()->toDateString() }}';
|
||||
document.getElementById('req_reason').value = '';
|
||||
document.getElementById('requestModalTitle').textContent = '근태 신청';
|
||||
document.getElementById('requestModalMessage').classList.add('hidden');
|
||||
document.getElementById('requestModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeRequestModal() {
|
||||
document.getElementById('requestModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function submitRequest() {
|
||||
const userId = document.getElementById('req_user_id').value;
|
||||
if (!userId) { showRequestMessage('사원을 선택해주세요.', true); return; }
|
||||
|
||||
const body = {
|
||||
user_id: parseInt(userId),
|
||||
request_type: document.getElementById('req_type').value,
|
||||
start_date: document.getElementById('req_start_date').value,
|
||||
end_date: document.getElementById('req_end_date').value,
|
||||
reason: document.getElementById('req_reason').value || null,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('{{ route("api.admin.hr.attendances.requests.store") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
showRequestMessage(data.message, false);
|
||||
if (tabLoaded.requests) loadTabData('requests');
|
||||
setTimeout(() => closeRequestModal(), 800);
|
||||
} else {
|
||||
showRequestMessage(data.message || '오류가 발생했습니다.', true);
|
||||
}
|
||||
} catch(e) {
|
||||
showRequestMessage('서버 통신 중 오류가 발생했습니다.', true);
|
||||
}
|
||||
}
|
||||
|
||||
function showRequestMessage(message, isError) {
|
||||
const el = document.getElementById('requestModalMessage');
|
||||
el.textContent = message;
|
||||
el.className = 'rounded-lg px-4 py-3 text-sm ' + (isError ? 'bg-red-50 text-red-700' : 'bg-emerald-50 text-emerald-700');
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// ===== 승인/반려 처리 =====
|
||||
async function approveRequest(id) {
|
||||
if (!confirm('승인하시겠습니까?')) return;
|
||||
try {
|
||||
const res = await fetch('{{ url("/api/admin/hr/attendance-requests") }}/' + id + '/approve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
loadTabData('requests');
|
||||
refreshTable();
|
||||
} else {
|
||||
alert(data.message || '승인 처리 중 오류');
|
||||
}
|
||||
} catch(e) { alert('서버 통신 중 오류'); }
|
||||
}
|
||||
|
||||
async function rejectRequest(id) {
|
||||
const reason = prompt('반려 사유를 입력하세요:');
|
||||
if (reason === null) return;
|
||||
try {
|
||||
const res = await fetch('{{ url("/api/admin/hr/attendance-requests") }}/' + id + '/reject', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
|
||||
body: JSON.stringify({ reject_reason: reason }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
loadTabData('requests');
|
||||
} else {
|
||||
alert(data.message || '반려 처리 중 오류');
|
||||
}
|
||||
} catch(e) { alert('서버 통신 중 오류'); }
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
Reference in New Issue
Block a user