TTK_E32222585_laravel/resources/views/dashboard/report.blade.php

663 lines
28 KiB
PHP

@extends('dashboard.base')
@section('title', 'Laporan')
@push('css')
<style>
.nav-tabs .nav-link {
color: #6c7293;
border: none;
border-bottom: 2px solid transparent;
background: transparent;
padding: 0.75rem 1.5rem;
}
.nav-tabs .nav-link.active {
color: #7da0fa;
border-bottom-color: #7da0fa;
background: transparent;
}
.nav-tabs .nav-link:hover {
border-bottom-color: #7da0fa;
color: #7da0fa;
}
.filter-card {
border: 1px solid #ebedf2;
border-radius: 0.375rem;
background: #fff;
}
.summary-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
}
.summary-card-alt {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border-radius: 0.5rem;
}
.table-responsive {
border-radius: 0.375rem;
overflow: hidden;
}
.badge-status {
font-size: 0.75rem;
font-weight: 500;
padding: 0.375rem 0.75rem;
border-radius: 0.25rem;
}
.btn-export {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.btn-export:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
color: white;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner-border-custom {
width: 3rem;
height: 3rem;
border-width: 0.3em;
position: absolute;
top: 50%;
left: 50%;
}
</style>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
@endpush
@section('content')
<div class="content-wrapper">
<div class="row">
<div class="col-md-12 grid-margin">
<div class="row">
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
<h3 class="font-weight-bold">Laporan</h3>
<h6 class="font-weight-normal mb-0">Kelola dan unduh laporan absensi dan izin karyawan</h6>
</div>
</div>
</div>
</div>
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner-border spinner-border-custom text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs" id="reportTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="attendance-tab" data-toggle="tab" href="#attendance" role="tab" aria-controls="attendance" aria-selected="true">
<span class="material-icons align-middle">calendar_today</span>
Laporan Absensi
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="permission-tab" data-toggle="tab" href="#permission" role="tab" aria-controls="permission" aria-selected="false">
<span class="material-icons align-middle">assignment_turned_in</span>
Laporan Izin
</a>
</li>
</ul>
<div class="tab-content mt-4" id="reportTabsContent">
<div class="tab-pane fade show active" id="attendance" role="tabpanel" aria-labelledby="attendance-tab">
<div class="row">
<div class="col-md-12 mb-4">
<div class="filter-card p-4">
<h5 class="mb-3">Filter Laporan Absensi</h5>
<form id="attendanceFilterForm">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label for="attendance_start_date">Tanggal Mulai</label>
<input type="date" class="form-control" id="attendance_start_date" name="start_date" required>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="attendance_end_date">Tanggal Akhir</label>
<input type="date" class="form-control" id="attendance_end_date" name="end_date" required>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="attendance_user_id">Karyawan</label>
<select class="form-control" id="attendance_user_id" name="user_id">
<option value="">Semua Karyawan</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="attendance_status">Status</label>
<select class="form-control" id="attendance_status" name="status">
<option value="">Semua Status</option>
<option value="pending">Pending</option>
<option value="accepted">Diterima</option>
<option value="rejected">Ditolak</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-export">
<span class="material-icons align-middle">search</span> Filter
</button>
<button type="button" class="btn btn-export" id="exportAttendanceExcel" title="Export Excel">
<span class="material-icons align-middle">file_download</span> Export Excel
</button>
{{-- <button type="button" class="btn btn-export" id="exportAttendancePdf" title="Export PDF">
<span class="material-icons align-middle">print</span> Export PDF
</button> --}}
</div>
</div>
</form>
</div>
</div>
<div class="col-md-12 mb-4" id="attendanceSummarySection" style="display: none;">
<h5 class="mb-3">Ringkasan Absensi</h5>
<div class="row" id="attendanceSummaryCards">
</div>
</div>
<div class="col-md-12" id="attendanceDetailsSection" style="display: none;">
<h5 class="mb-3">Detail Absensi</h5>
<div class="table-responsive" style="overflow-x: scroll;">
<table class="table table-striped table-bordered" id="attendanceTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>No</th>
<th>Tanggal</th>
<th>Nama</th>
<th>Waktu</th>
<th>Tipe</th>
<th>Status</th>
<th>Lokasi</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="permission" role="tabpanel" aria-labelledby="permission-tab">
<div class="row">
<div class="col-md-12 mb-4">
<div class="filter-card p-4">
<h5 class="mb-3">Filter Laporan Izin</h5>
<form id="permissionFilterForm">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label for="permission_start_date">Tanggal Mulai</label>
<input type="date" class="form-control" id="permission_start_date" name="start_date" required>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="permission_end_date">Tanggal Akhir</label>
<input type="date" class="form-control" id="permission_end_date" name="end_date" required>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label for="permission_user_id">Karyawan</label>
<select class="form-control" id="permission_user_id" name="user_id">
<option value="">Semua Karyawan</option>
</select>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label for="permission_status">Status</label>
<select class="form-control" id="permission_status" name="status">
<option value="">Semua Status</option>
<option value="pending">Pending</option>
<option value="accepted">Diterima</option>
<option value="rejected">Ditolak</option>
</select>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label for="permission_category">Kategori</label>
<select class="form-control" id="permission_category" name="category">
<option value="">Semua Kategori</option>
<option value="sakit">Sakit</option>
<option value="cuti">Cuti</option>
<option value="izin">Izin</option>
<option value="lainnya">Lainnya</option>
</select>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<button type="submit" class="btn btn-export">
<span class="material-icons align-middle">search</span> Filter
</button>
<button type="button" class="btn btn-export" id="exportPermissionExcel" title="Export Excel">
<span class="material-icons align-middle">file_download</span> Export Excel
</button>
{{-- <button type="button" class="btn btn-export" id="exportPermissionPdf" title="Export PDF">
<span class="material-icons align-middle">print</span> Export PDF
</button> --}}
</div>
</div>
</form>
</div>
</div>
<div class="col-md-12 mb-4" id="permissionSummarySection" style="display: none;">
<h5 class="mb-3">Ringkasan Izin per Kategori</h5>
<div class="row" id="permissionSummaryCards">
</div>
</div>
<div class="col-md-12" id="permissionDetailsSection" style="display: none;">
<h5 class="mb-3">Detail Izin</h5>
<div class="table-responsive" style="overflow-x: scroll;">
<table class="table table-striped table-bordered" id="permissionTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>No</th>
<th>Tanggal Pengajuan</th>
<th>Nama</th>
<th>Kategori</th>
<th>Tanggal Mulai</th>
<th>Tanggal Akhir</th>
<th>Durasi</th>
<th>Status</th>
<th>Approver</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('script')
<script>
$(document).ready(function() {
const today = new Date();
const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate());
$('#attendance_start_date, #permission_start_date').val(formatDate(lastMonth));
$('#attendance_end_date, #permission_end_date').val(formatDate(today));
loadUsers();
initDataTables();
$('#attendanceFilterForm').on('submit', handleAttendanceFilter);
$('#permissionFilterForm').on('submit', handlePermissionFilter);
$('#exportAttendanceExcel').on('click', () => exportReport('attendance', 'excel'));
$('#exportAttendancePdf').on('click', () => exportReport('attendance', 'pdf'));
$('#exportPermissionExcel').on('click', () => exportReport('permission', 'excel'));
$('#exportPermissionPdf').on('click', () => exportReport('permission', 'pdf'));
});
function formatDate(date) {
return date.toISOString().split('T')[0];
}
function showLoading() {
$('#loadingOverlay').show();
}
function hideLoading() {
$('#loadingOverlay').hide();
}
function loadUsers() {
$.ajax({
url: '/api/admin/users',
method: 'GET',
headers: {
'Authorization': getAuthorizationHeader(),
'Accept': 'application/json'
},
success: function(response) {
const employees = response.data.filter(user => user.role === 'karyawan');
const options = employees.map(user =>
`<option value="${user.id}">${user.profile?.name || user.email}</option>`
).join('');
$('#attendance_user_id, #permission_user_id').append(options);
},
error: function(xhr) {
console.error('Failed to load users:', xhr);
}
});
}
function initDataTables() {
if ($.fn.dataTable.isDataTable('#attendanceTable')) {
$('#attendanceTable').DataTable().clear().destroy();
}
if ($.fn.dataTable.isDataTable('#permissionTable')) {
$('#permissionTable').DataTable().clear().destroy();
}
$('#attendanceTable').DataTable({
searching: false,
paging: true,
info: true,
lengthChange: true,
pageLength: 25,
});
$('#permissionTable').DataTable({
searching: false,
paging: true,
info: true,
lengthChange: true,
pageLength: 25,
});
}
function handleAttendanceFilter(e) {
e.preventDefault();
const formData = new FormData(e.target);
const params = new URLSearchParams(formData);
showLoading();
$.ajax({
url: '/api/admin/reports/attendance?' + params.toString(),
method: 'GET',
headers: {
'Authorization': getAuthorizationHeader(),
'Accept': 'application/json'
},
success: function(response) {
displayAttendanceReport(response.data);
hideLoading();
},
error: function(xhr) {
hideLoading();
Swal.fire('Error', xhr.responseJSON?.message || 'Gagal memuat laporan', 'error');
}
});
}
function handlePermissionFilter(e) {
e.preventDefault();
const formData = new FormData(e.target);
const params = new URLSearchParams(formData);
showLoading();
$.ajax({
url: '/api/admin/reports/permission?' + params.toString(),
method: 'GET',
headers: {
'Authorization': getAuthorizationHeader(),
'Accept': 'application/json'
},
success: function(response) {
displayPermissionReport(response.data);
hideLoading();
},
error: function(xhr) {
hideLoading();
Swal.fire('Error', xhr.responseJSON?.message || 'Gagal memuat laporan', 'error');
}
});
}
function displayAttendanceReport(data) {
displayAttendanceSummary(data.summary);
displayAttendanceDetails(data.details);
$('#attendanceSummarySection, #attendanceDetailsSection').show();
}
function displayAttendanceSummary(summary) {
let html = '';
summary.forEach((item, index) => {
const cardClass = index % 2 === 0 ? 'summary-card' : 'summary-card-alt';
html += `
<div class="col-md-4 mb-3">
<div class="card ${cardClass}">
<div class="card-body">
<h6 class="mb-2">${item.user.profile?.name || item.user.email}</h6>
<div class="row">
<div class="col-6">
<div class="text-center">
<h4 class="mb-0">${item.total_days}</h4>
<small>Total Hari</small>
</div>
</div>
<div class="col-6">
<div class="text-center">
<h4 class="mb-0">${item.present_days}</h4>
<small>Hadir</small>
</div>
</div>
</div>
</div>
</div>
</div>
`;
});
$('#attendanceSummaryCards').html(html);
}
function displayAttendanceDetails(details) {
const table = $('#attendanceTable').DataTable();
table.clear();
details.forEach((item, index) => {
const statusBadge = getStatusBadge(item.status);
const typeBadge = item.type === 'in' ? '<span class="badge badge-success">Masuk</span>' : '<span class="badge badge-warning">Keluar</span>';
table.row.add([
index + 1,
formatDateIndonesia(item.date),
item.user.profile?.name || item.user.email,
item.time,
typeBadge,
statusBadge,
item.latitude && item.longitude ? `${item.latitude}, ${item.longitude}` : '-'
]);
});
table.draw();
}
function displayPermissionReport(data) {
displayPermissionSummary(data.summary);
displayPermissionDetails(data.details);
$('#permissionSummarySection, #permissionDetailsSection').show();
}
function displayPermissionSummary(summary) {
let html = '';
summary.forEach((item, index) => {
const cardClass = index % 2 === 0 ? 'summary-card' : 'summary-card-alt';
html += `
<div class="col-md-3 mb-3">
<div class="card ${cardClass} h-100 shadow-sm">
<div class="card-body">
<h6 class="mb-3 text-capitalize text-wrap">${item.category}</h6>
<div class="row text-center mb-3">
<div class="col-6">
<h5 class="mb-0">${item.total}</h5>
<small>Total</small>
</div>
<div class="col-6">
<h5 class="mb-0">${item.total_days}</h5>
<small>Hari</small>
</div>
</div>
<ul class="list-unstyled text-center mb-0 small">
<li><span class="fw-bold">${item.approved}</span> Approved</li>
<li><span class="fw-bold">${item.pending}</span> Pending</li>
<li><span class="fw-bold">${item.rejected}</span> Rejected</li>
</ul>
</div>
</div>
</div>
`;
});
$('#permissionSummaryCards').html(html);
}
function displayPermissionDetails(details) {
const table = $('#permissionTable').DataTable();
table.clear();
details.forEach((item, index) => {
const statusBadge = getStatusBadge(item.status);
table.row.add([
index + 1,
formatDateIndonesia(item.created_at),
item.user.profile?.name || item.user.email,
item.category.charAt(0).toUpperCase() + item.category.slice(1),
formatDateIndonesia(item.start_date),
formatDateIndonesia(item.end_date),
item.duration + ' hari',
statusBadge,
item.approver?.profile?.name || '-'
]);
});
table.draw();
}
function getStatusBadge(status) {
const badges = {
'pending': '<span class="badge badge-warning">Pending</span>',
'accepted': '<span class="badge badge-success">Diterima</span>',
'rejected': '<span class="badge badge-danger">Ditolak</span>'
};
return badges[status] || '<span class="badge badge-secondary">Unknown</span>';
}
function formatDateIndonesia(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
function exportReport(type, format) {
const formId = type === 'attendance' ? '#attendanceFilterForm' : '#permissionFilterForm';
const formData = new FormData(document.querySelector(formId));
const params = new URLSearchParams(formData);
const startDate = formData.get('start_date');
const endDate = formData.get('end_date');
if (!startDate || !endDate) {
Swal.fire('Error', 'Tanggal mulai dan akhir harus diisi', 'error');
return;
}
showLoading();
let endpoint = '';
if (type === 'attendance') {
endpoint = `/api/admin/reports/attendance/export/${format}?${params.toString()}`;
} else if (type === 'permission') {
endpoint = `/api/admin/reports/permission/export/${format}?${params.toString()}`;
}
fetch(endpoint, {
method: 'GET',
headers: {
'Authorization': getAuthorizationHeader(),
'Accept': 'application/json'
}
})
.then(async response => {
hideLoading();
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(errorData?.message || 'Gagal mengunduh file');
}
const blob = await response.blob();
const disposition = response.headers.get('content-disposition');
let filename = `laporan_${type}_${startDate}_${endDate}.${format === 'excel' ? 'xlsx' : 'pdf'}`;
if (disposition && disposition.indexOf('filename=') !== -1) {
filename = disposition.split('filename=')[1].replace(/"/g, '');
}
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
Swal.fire('Berhasil', 'File berhasil diunduh', 'success');
})
.catch(error => {
hideLoading();
Swal.fire('Error', error.message, 'error');
});
}
</script>
@endpush