Active Menu Attendance Monthly

Penambahan Modal rekapitulasi kehadiran perbulan
This commit is contained in:
2026-06-02 08:07:14 +07:00
parent 442980a2f1
commit 77a65abdb5
5 changed files with 520 additions and 10 deletions
@@ -0,0 +1,390 @@
<style>
.calendar-grid{
display:grid;
grid-template-columns:repeat(7,1fr);
gap:8px;
}
.calendar-card{
min-height:60px;
font-size:12px;
}
.calendar-card.empty{
visibility:hidden;
}
.calendar-card .badge{
font-size:10px;
}
</style>
<div class="container mt-4">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4">
<div class="d-flex justify-content-end align-items-center mb-3">
<h7 class="mb-0">Tanggal</h7>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">
Monitoring Absensi Bulanan
</h5>
<input type="month"
id="filterMonth"
class="form-control"
style="max-width:220px"
value="<?= date('Y-m') ?>">
</div>
<div class="table-responsive">
<table id="tableAttendance"
class="table modern-table align-middle w-100">
<thead class="table-light">
<tr>
<th>No</th>
<th>Kode</th>
<th>Nama</th>
<th>Periode</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<!-- MODAL DETAIL -->
<div class="modal fade" id="modalDetail">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
<h5>Detail Absensi Bulanan</h5>
<button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<div class="col-6">
<table class="table mb-3">
<tbody>
<tr>
<th width="180">No</th>
<td><span id="d_id"></span></td>
</tr>
<tr>
<th>Kode Karyawan</th>
<td><span id="d_employee_code"></span></td>
</tr>
<tr>
<th>Nama Karyawan</th>
<td><span id="d_full_name"></span></td>
</tr>
<tr>
<th>Periode</th>
<td><span id="d_month"></span></td>
</tr>
<tr>
<th>Status</th>
<td><span id="d_status"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-3">
<table class="table mb-3">
<tbody>
<tr>
<th width="100"><span class="badge bg-success">Hadir</span></th>
<td id="d_present_count"></td>
</tr>
<tr>
<th><span class="badge bg-info">Sakit</span></th>
<td id="d_sick_count"></td>
</tr>
<tr>
<th><span class="badge bg-primary">Izin</span></th>
<td id="d_leave_count"></td>
</tr>
<tr>
<th><span class="badge bg-danger">Alpha</span></th>
<td id="d_absent_count"></td>
</tr>
<tr>
<th><span class="badge bg-warning">Terlambat</span></th>
<td id="d_late_count"></td>
</tr>
</tbody>
</table>
</div>
<div class="col-3">
<table class="table mb-3">
<tbody>
<tr>
<th width="100"><span class="badge bg-success">Total Hari Kerja</span></th>
<td id="d_total_hari_kerja"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-dark">Cuti</span></th>
<td id="d_total_permit"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-secondary">Total Jam Kerja</span></th>
<td id="d_total_work_hours"></td>
<th width="180">Jam</th>
</tr>
<tr>
<th><span class="badge bg-danger">Total Overtime Hours</span></th>
<td id="d_overtime_hours"></td>
<th width="180">Jam</th>
</tr>
<tr>
<th><span class="badge bg-warning">Total Terlambat / Menit</span></th>
<td id="d_total_late_minutes"></td>
<th width="180">Menit</th>
</tr>
</tbody>
</table>
</div>
<div class="border-top my-3"></div>
<div id="detailContent"> // VIEW DETAIL ABSENSI BULANAN (DI-GENERATE DARI JS)
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function(){
let table = $('#tableAttendance').DataTable({
ajax:{
url:`<?= base_url(); ?>attendancemonthly/get_data`,
data:function(d){
d.date = $('#filterMonth').val();
},
dataSrc:'data'
},
columns:[
{data:0},
{data:1},
{data:2},
{data:3},
{data:4},
{data:5}
]
});
$('#filterMonth').change(function(){
table.ajax.reload();
});
});
$(function () {
$(document).on('click', '.btn-detail', function () {
let id = $(this).data('id');
let month = $(this).data('month');
$.get(
`<?= base_url(); ?>attendancemonthly/detail/${id}/${month}`,
function (res) {
// Header
$('#d_id').text(res.header.id);
$('#d_employee_code').text(res.header.employee_code);
$('#d_full_name').text(res.header.full_name);
$('#d_month').text(res.header.month);
$('#d_status').text(res.header.status);
// Header - perhitungan
$('#d_total_hari_kerja').text(res.detail.length);
$('#d_present_count').text(res.detail.filter(d => d.attendance_status === 'present').length);
$('#d_total_hadir').text(res.detail.filter(d => d.attendance_status === 'present').length);
$('#d_late_count').text(res.detail.filter(d => d.attendance_status === 'late').length);
$('#d_total_permit').text(res.detail.filter(d => d.attendance_status === 'permit').length);
$('#d_sick_count').text(res.detail.filter(d => d.attendance_status === 'sick').length);
$('#d_total_work_hours').text(res.detail.filter(d => d.attendance_status === 'present').length * 8); // Asumsi 8 jam kerja per hari
$('#d_late_minutes').text(res.detail.reduce((total, d) => {
if (d.attendance_status === 'late') {
return total + parseInt(d.late_minutes);
}
return total;
}, 0));
const totalLateMinutes = res.detail
.filter(d => d.attendance_status === 'late')
.reduce((sum, d) => sum + (Number(d.total_late_minutes) || 0), 0);
$('#d_total_late_minutes').text(totalLateMinutes);
$('#d_overtime_hours').text(res.detail.filter(d => d.attendance_status === 'present' && d.overtime_hours)
.reduce((total, d) => total + parseFloat(d.overtime_hours || 0), 0)
);
$('#d_leave_count').text(res.detail.filter(d => d.attendance_status === 'permit').length);
$('#d_absent_count').text(res.detail.filter(d => d.attendance_status === 'alpha').length);
$('#d_late_count').text(res.detail.filter(d => d.attendance_status === 'late').length);
// Mapping data absensi berdasarkan tanggal
let attendanceMap = {};
$.each(res.detail, function (i, row) {
attendanceMap[row.attendance_date] = row;
});
// Ambil bulan dari header
let monthDate = new Date(res.header.month);
let year = monthDate.getFullYear();
let month = monthDate.getMonth();
// Hari pertama bulan
let firstDay = new Date(year, month, 1);
// Jumlah hari dalam bulan
let daysInMonth = new Date(year, month + 1, 0).getDate();
// Konversi agar minggu dimulai Senin
let startDay = firstDay.getDay();
startDay = startDay === 0 ? 6 : startDay - 1;
let html = `
<div class="calendar-wrapper">
<div class="row g-1 fw-bold text-center mb-2">
<div class="col">Sen</div>
<div class="col">Sel</div>
<div class="col">Rab</div>
<div class="col">Kam</div>
<div class="col">Jum</div>
<div class="col">Sab</div>
<div class="col">Min</div>
</div>
<div class="calendar-grid">
`;
// Kosong sebelum tanggal 1
for (let i = 0; i < startDay; i++) {
html += `<div class="calendar-card empty"></div>`;
}
// Isi tanggal
for (let day = 1; day <= daysInMonth; day++) {
let dateStr =
`${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
let data = attendanceMap[dateStr];
let badge = '';
let cardClass = '';
if (data) {
switch (data.attendance_status) {
case 'present':
badge = '<span class="badge bg-success">Hadir</span>';
cardClass = 'border-success';
break;
case 'late':
badge = '<span class="badge bg-warning">Terlambat</span>';
cardClass = 'border-warning';
break;
case 'alpha':
badge = '<span class="badge bg-danger">Alpha</span>';
cardClass = 'border-danger';
break;
case 'sick':
badge = '<span class="badge bg-info">Sakit</span>';
cardClass = 'border-info';
break;
case 'permit':
badge = '<span class="badge bg-secondary">Izin</span>';
cardClass = 'border-secondary';
break;
}
html += `
<div class="calendar-card card ${cardClass}">
<div class="card-body p-2">
<div class="row gx-0">
<div class="col-auto">
<div class="fw-bold" style="font-size: 16px;">${day}</div>
</div>
<div class="col text-end">
<div class="m-0">${badge}</div>
</div>
</div>
<small class="text-muted d-block mt-1" style="font-size:15px;">
${data.shift_name}
</small>
</div>
</div>
`;
} else {
html += `
<div class="calendar-card card">
<div class="card-body p-2">
<div class="fw-bold" style="font-size: 16px;">${day}</div>
</div>
</div>
`;
}
}
html += `
</div>
</div>
`;
$('#detailContent').html(html);
// Bootstrap 5
let modal = new bootstrap.Modal(document.getElementById('modalDetail'));
modal.show();
},
'json'
);
});
});
</script>
+10
View File
@@ -402,6 +402,16 @@ $('#btnSimpan').click(function(){
Swal.fire('Warning','Harap Isi Semua Kolom!','warning'); // ✅ FIX add all collumn must be filled
return;
}
if($('#keterangan').val().trim() === ''){
Swal.fire('Warning','Keterangan tidak boleh kosong!','warning');
return;
}
if(!$('#tanggal').val()){
Swal.fire('Warning','Tanggal tidak boleh kosong!','warning');
return;
}
// lanjut simpan
$.post("<?= base_url('jurnal/save'); ?>", {
+10 -3
View File
@@ -258,17 +258,24 @@
</li>
<li>
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>"
href="<?= base_url('attendancemonitoring'); ?>">
<i class="bi bi-fingerprint me-2"></i>
Monitoring Absensi
</a>
</li>
<li>
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monthly') ? 'active' : '' ?>"
href="<?= base_url('attendancemonthly'); ?>">
<i class="bi bi-calendar2-date me-2"></i>
Absensi Bulanan
</a>
</li>
<hr class="dropdown-divider">
<li>