Active Menu Attendance Monthly
Penambahan Modal rekapitulasi kehadiran perbulan
This commit is contained in:
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||||
|
|
||||||
|
class Attendancemonthly extends CI_Controller {
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->load->database();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
"active_menu" => "attendance_monthly"
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->load->view('partials/header', $data);
|
||||||
|
$this->load->view('employees/attendance_monthly');
|
||||||
|
$this->load->view('partials/footer');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_data()
|
||||||
|
{
|
||||||
|
$date = $this->input->get('date') . '-01';
|
||||||
|
|
||||||
|
if(!$date){
|
||||||
|
$date = date('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->db->query("
|
||||||
|
SELECT
|
||||||
|
a.*,
|
||||||
|
e.employee_code,
|
||||||
|
e.full_name
|
||||||
|
FROM k_attendance_monthly a
|
||||||
|
LEFT JOIN k_employees e ON e.id = a.employee_id
|
||||||
|
WHERE a.month = '$date'
|
||||||
|
ORDER BY a.updated_at DESC
|
||||||
|
")->result();
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$no = 1;
|
||||||
|
|
||||||
|
foreach($data as $row){
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
$no++,
|
||||||
|
$row->employee_code,
|
||||||
|
$row->full_name,
|
||||||
|
tanggal_indo($row->month,'F Y'),
|
||||||
|
$row->status,
|
||||||
|
'
|
||||||
|
<button class="btn btn-sm btn-info btn-detail"
|
||||||
|
data-id="'.$row->id.'" data-month="'.$row->month.'">Detail</button>
|
||||||
|
'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'data' => $rows
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function detail($id)
|
||||||
|
// {
|
||||||
|
// echo json_encode(
|
||||||
|
// $this->db->get_where('k_attendance_monthly',['id'=>$id])->row()
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function detail($id, $month)
|
||||||
|
{
|
||||||
|
$header = $this->db
|
||||||
|
->select('a.*, e.full_name, e.employee_code')
|
||||||
|
->from('k_attendance_monthly a')
|
||||||
|
->join('k_employees e','e.id = a.employee_id', 'left')
|
||||||
|
->where('a.id',$id)
|
||||||
|
->where('a.month',$month)
|
||||||
|
->get()
|
||||||
|
->row();
|
||||||
|
|
||||||
|
$startDate = $month;
|
||||||
|
$endDate = substr($month, 0, 7) . date('-t');
|
||||||
|
|
||||||
|
$detail = $this->db
|
||||||
|
->select('a.attendance_date, a.attendance_status, b.shift_name')
|
||||||
|
->from('k_attendances a')
|
||||||
|
->join('k_shifts b', 'b.id = a.shift_id', 'left')
|
||||||
|
->where('a.employee_id', $header->employee_id)
|
||||||
|
->where('a.attendance_date >=', $startDate)
|
||||||
|
->where('a.attendance_date <=', $endDate)
|
||||||
|
->get()
|
||||||
|
->result();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'header'=>$header,
|
||||||
|
'detail'=>$detail
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -141,13 +141,13 @@ class Jurnal extends CI_Controller {
|
|||||||
$debit = $this->input->post('debit');
|
$debit = $this->input->post('debit');
|
||||||
$kredit = $this->input->post('kredit');
|
$kredit = $this->input->post('kredit');
|
||||||
|
|
||||||
if (array_sum($debit) != array_sum($kredit)) {
|
if (array_sum($debit) != array_sum($kredit)) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'status'=>false,
|
'status'=>false,
|
||||||
'message'=>'Debit & Kredit tidak balance!'
|
'message'=>'Debit & Kredit tidak balance!'
|
||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->db->trans_start();
|
$this->db->trans_start();
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -403,6 +403,16 @@ $('#btnSimpan').click(function(){
|
|||||||
return;
|
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
|
// lanjut simpan
|
||||||
$.post("<?= base_url('jurnal/save'); ?>", {
|
$.post("<?= base_url('jurnal/save'); ?>", {
|
||||||
tanggal: $('#tanggal').val(),
|
tanggal: $('#tanggal').val(),
|
||||||
|
|||||||
@@ -258,17 +258,24 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>"
|
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>"
|
||||||
href="<?= base_url('attendancemonitoring'); ?>">
|
href="<?= base_url('attendancemonitoring'); ?>">
|
||||||
|
|
||||||
<i class="bi bi-fingerprint me-2"></i>
|
<i class="bi bi-fingerprint me-2"></i>
|
||||||
Monitoring Absensi
|
Monitoring Absensi
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</li>
|
</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">
|
<hr class="dropdown-divider">
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user