Files
accounting_dev/application/views/employees/generate_schedule.php
T
2026-05-26 08:07:45 +00:00

1059 lines
25 KiB
PHP

<!-- application/views/employees/generate_schedule.php -->
<style>
body{
background:#f5f7fb;
}
/* =======================================================
CALENDAR
======================================================= */
.calendar-grid{
display:grid;
grid-template-columns:repeat(7,minmax(0,1fr));
gap:10px;
}
.day-label{
background:#ff8a00;
color:#fff;
border-radius:12px;
padding:8px;
text-align:center;
font-size:12px;
font-weight:700;
}
.schedule-card{
background:#fff;
border-radius:14px;
overflow:hidden;
border:1px solid #edf0f2;
min-height:220px;
display:flex;
flex-direction:column;
}
.other-month{
opacity:.45;
}
.schedule-header{
background:linear-gradient(135deg,#ff8a00,#ffb347);
padding:10px;
color:#fff;
}
.schedule-date{
font-size:13px;
font-weight:600;
line-height:1;
}
.schedule-day{
font-size:11px;
margin-top:3px;
}
.schedule-body{
padding:8px;
flex:1;
}
.position-item{
background:#fff7ef;
border:1px solid #ffe1bf;
border-radius:10px;
padding:8px;
margin-bottom:6px;
}
.position-title{
font-size:11px;
font-weight:700;
color:#ff7a00;
}
.position-total{
font-size:10px;
color:#6c757d;
}
.employee-item{
border-top:1px dashed #e5e7eb;
padding-top:5px;
margin-top:5px;
}
.employee-name{
font-size:11px;
font-weight:600;
line-height:1.3;
}
.employee-shift{
font-size:10px;
color:#6c757d;
}
.time-box{
background:#fff;
border:1px solid #e9ecef;
border-radius:8px;
padding:4px 6px;
font-size:10px;
}
.empty-box{
min-height:120px;
display:flex;
align-items:center;
justify-content:center;
color:#adb5bd;
font-size:11px;
text-align:center;
}
.btn-orange{
background:#ff8a00;
border-color:#ff8a00;
color:#fff;
}
.btn-orange:hover{
background:#eb7d00;
border-color:#eb7d00;
color:#fff;
}
.modal-title{
font-size:16px;
}
.form-label{
font-size:11px;
font-weight:600;
}
.form-control,
.form-select{
font-size:12px;
}
.employee-check-item{
border:1px solid #ececec;
border-radius:10px;
padding:10px;
margin-bottom:8px;
cursor:pointer;
}
.employee-check-item:hover{
border-color:#ff8a00;
background:#fff8f0;
}
.small-text{
font-size:10px;
}
@media(max-width:1200px){
.calendar-grid{
grid-template-columns:repeat(4,minmax(0,1fr));
}
}
@media(max-width:768px){
.calendar-grid{
grid-template-columns:repeat(2,minmax(0,1fr));
}
}
@media(max-width:576px){
.calendar-grid{
grid-template-columns:repeat(1,minmax(0,1fr));
}
}
.employee-check-item{
border:1px solid #ececec;
border-radius:10px;
padding:8px;
cursor:pointer;
transition:.2s;
background:#fff;
height:100%;
}
.employee-check-item:hover{
border-color:#ff8a00;
background:#fff8f0;
}
.employee-check-name{
font-size:11px;
font-weight:600;
line-height:1.3;
}
.employee-check-id{
font-size:10px;
margin-bottom: -10px;
color:#6c757d;
}
</style>
<div class="container-fluid mt-3 mb-4 px-4">
<!-- HEADER -->
<div class="card border-0 shadow-sm rounded-4 mb-3">
<div class="card-body p-3">
<div class="row align-items-center">
<div class="col-md-6">
<div class="fw-bold" style="font-size:18px;">
Schedule Planner
</div>
<div class="text-muted small-text">
Jadwal kerja harian karyawan
</div>
</div>
<div class="col-md-6">
<div class="d-flex gap-2 justify-content-md-end mt-3 mt-md-0">
<select id="month" class="form-select">
<?php for($m=1; $m<=12; $m++): ?>
<option value="<?= $m ?>"
<?= $m == date('n') ? 'selected':'' ?>>
<?= date('F', mktime(0,0,0,$m,1)) ?>
</option>
<?php endfor; ?>
</select>
<select id="year" class="form-select">
<?php for($y=date('Y')-1; $y<=date('Y')+3; $y++): ?>
<option value="<?= $y ?>"
<?= $y == date('Y') ? 'selected':'' ?>>
<?= $y ?>
</option>
<?php endfor; ?>
</select>
<button class="btn btn-orange"
id="btnLoadCalendar">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- CALENDAR -->
<div class="calendar-grid"
id="scheduleWrapper"></div>
</div>
<!--MODAL-->
<!-- MODAL -->
<div class="modal fade"
id="modalSchedule"
tabindex="-1"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content border-0 rounded-4">
<div class="modal-header border-0 pb-0">
<div>
<div class="fw-bold" style="font-size:15px;">
Detail Schedule
</div>
<div class="small-text text-muted">
Tambah / edit jadwal employee
</div>
</div>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="schedule_date">
<!-- TOP FORM -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-body p-3">
<div class="row align-items-end">
<div class="col-md-4 mb-2">
<label class="form-label">
Position
</label>
<select id="position_id"
class="form-select">
<option value="">
Pilih Position
</option>
<?php foreach($positions as $p): ?>
<option value="<?= $p->id ?>">
<?= $p->position_name ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 mb-2">
<label class="form-label">
Shift
</label>
<select id="shift_id"
class="form-select">
<option value="">
Pilih Shift
</option>
<?php foreach($shifts as $s): ?>
<option value="<?= $s->id ?>">
<?= $s->shift_name ?>
(
<?= substr($s->checkin_time,0,5) ?>
-
<?= substr($s->checkout_time,0,5) ?>
)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 mb-2">
<button class="btn btn-orange w-100"
id="btnAddEmployeeSchedule">
<i class="bi bi-plus-circle me-1"></i>
Tambah Jadwal
</button>
</div>
</div>
<!-- EMPLOYEE LIST -->
<div id="employeeListWrapper"
class="mt-3"
style="display:none;">
<div class="card border-0 shadow-sm">
<div class="card-header bg-secondary text-light py-2">
<div class="d-flex justify-content-between align-items-center">
<div style="font-size:12px;font-weight:700;">
Pilih Employee
</div>
<div>
<button type="button"
class="btn btn-sm btn-light"
id="btnCheckAllEmployee">
Check All
</button>
<button type="button"
class="btn btn-sm btn-light"
id="btnUncheckAllEmployee">
Clear
</button>
</div>
</div>
</div>
<div class="card-body p-2">
<div class="row g-2"
id="employeeCheckboxList"></div>
</div>
</div>
</div>
</div>
</div>
<!-- DETAIL TABLE -->
<div id="scheduleTableWrapper"></div>
</div>
</div>
</div>
</div>
<script>
$(function(){
const base_url = "<?= base_url(); ?>";
// =====================================================
// INIT
// =====================================================
loadCalendar();
// =====================================================
// LOAD CALENDAR
// =====================================================
function loadCalendar()
{
$.ajax({
url : base_url + 'generateschedule/calendar_data',
type: 'GET',
data:{
month : $('#month').val(),
year : $('#year').val()
},
dataType:'json',
success:function(res){
renderCalendar(res.days || []);
}
});
}
// =====================================================
// RENDER CALENDAR
// =====================================================
function renderCalendar(days)
{
let html = '';
days.forEach(function(day){
html += `
<div class="schedule-card ${day.is_current_month ? '' : 'other-month'}">
<div class="schedule-header">
<div class="d-flex justify-content-between align-items-start">
<div>
<div class="schedule-date">
${day.day_name}
</div>
<div class="schedule-day">
${day.full_date}
</div>
</div>
<button class="btn btn-light btn-sm rounded-circle btnAddSchedule"
data-date="${day.full_date_raw}">
<i class="bi bi-pencil"></i>
</button>
</div>
</div>
<div class="schedule-body">
`;
if(day.shifts && day.shifts.length > 0){
day.shifts.forEach(function(shift){
html += `
<div class="position-item">
<div class="position-title mb-2">
${shift.shift_name}
</div>
`;
shift.positions.forEach(function(pos){
html += `
<div class="d-flex justify-content-between mb-1">
<div class="position-total">
${pos.position_name}
</div>
<div class="position-total">
${pos.total} Orang
</div>
</div>
`;
});
html += `
</div>
`;
});
} else {
html += `
<div class="empty-box">
<div>
<i class="bi bi-calendar-x fs-5"></i>
<div class="mt-1">
Belum ada jadwal
</div>
</div>
</div>
`;
}
html += `
</div>
</div>
`;
});
$('#scheduleWrapper').html(html);
}
// =====================================================
// RELOAD CALENDAR
// =====================================================
$('#btnLoadCalendar').click(function(){
loadCalendar();
});
// =====================================================
// OPEN MODAL
// =====================================================
$(document).on('click','.btnAddSchedule',function(){
let date = $(this).data('date');
$('#schedule_date').val(date);
$('#position_id').val('');
$('#employee_id').html(`
<option value="">
Pilih Employee
</option>
`);
$('#shift_id').val('');
loadScheduleDetail(date);
$('#modalSchedule').modal('show');
});
// =====================================================
// LOAD EMPLOYEE
// =====================================================
$('#position_id').change(function(){
let position_id = $(this).val();
$('#employeeCheckboxList').html('');
$('#employeeListWrapper').hide();
if(position_id == ''){
return;
}
$.ajax({
url : base_url + 'generateschedule/get_employees',
type:'GET',
data:{
position_id : position_id
},
dataType:'json',
success:function(res){
let html = '';
if(!res.data || res.data.length <= 0){
html = `
<div class="col-12">
<div class="text-center text-muted py-3"
style="font-size:11px;">
Employee tidak ditemukan
</div>
</div>
`;
} else {
res.data.forEach(function(emp){
html += `
<div class="col-md-2 col-6">
<label class="employee-check-item d-flex align-items-start">
<input type="checkbox"
class="form-check-input employee-check me-2 mt-1"
value="${emp.id}">
<div>
<div class="employee-check-name">
${emp.full_name}
</div>
<div class="employee-check-id">
${emp.fingerprint_user_id}
</div>
</div>
</label>
</div>
`;
});
}
$('#employeeCheckboxList').html(html);
$('#employeeListWrapper').show();
}
});
});
// =====================================================
// CHECK ALL
// =====================================================
$(document).on('click','#btnCheckAllEmployee',function(){
$('.employee-check').prop('checked',true);
});
// =====================================================
// UNCHECK ALL
// =====================================================
$(document).on('click','#btnUncheckAllEmployee',function(){
$('.employee-check').prop('checked',false);
});
// =====================================================
// ADD SCHEDULE MULTIPLE
// =====================================================
$('#btnAddEmployeeSchedule').click(function(){
let employee_ids = [];
$('.employee-check:checked').each(function(){
employee_ids.push($(this).val());
});
if(employee_ids.length <= 0){
Swal.fire(
'Warning',
'Pilih employee',
'warning'
);
return;
}
if($('#shift_id').val() == ''){
Swal.fire(
'Warning',
'Pilih shift',
'warning'
);
return;
}
$.ajax({
url : base_url + 'generateschedule/save_manual_schedule',
type:'POST',
data:{
employee_ids : employee_ids,
schedule_date: $('#schedule_date').val(),
shift_id : $('#shift_id').val()
},
dataType:'json',
success:function(res){
if(res.status){
loadScheduleDetail(
$('#schedule_date').val()
);
loadCalendar();
$('.employee-check').prop('checked',false);
Swal.fire(
'Success',
res.message,
'success'
);
} else {
Swal.fire(
'Error',
res.message,
'error'
);
}
}
});
});
// =====================================================
// LOAD DETAIL SCHEDULE
// =====================================================
function loadScheduleDetail(date)
{
$.ajax({
url : base_url + 'generateschedule/detail_schedule',
type:'GET',
data:{
schedule_date : date
},
dataType:'json',
success:function(res){
let html = '';
if(!res.data || res.data.length <= 0){
html = `
<div class="card border-0 shadow-sm">
<div class="card-body text-center text-muted py-4">
Belum ada schedule
</div>
</div>
`;
} else {
res.data.forEach(function(group){
html += `
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-warning text-light py-2">
<div style="font-size:12px;font-weight:700;">
${group.position_name}
</div>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0">
<thead>
<tr style="font-size:11px;">
<th>Employee</th>
<th>Finger ID</th>
<th>Masuk</th>
<th>Keluar</th>
<th width="250">Shift</th>
<th width="70">#</th>
</tr>
</thead>
<tbody>
`;
group.employees.forEach(function(emp){
html += `
<tr>
<td style="font-size:11px;">
${emp.full_name}
</td>
<td style="font-size:11px;">
${emp.fingerprint_user_id}
</td>
<td style="font-size:11px;">
${emp.start_datetime}
</td>
<td style="font-size:11px;">
${emp.end_datetime}
</td>
<td>
<select class="form-select form-select-sm changeShift"
data-id="${emp.schedule_id}" data-tanggal="${date}">
`;
<?php foreach($shifts as $s): ?>
html += `
<option value="<?= $s->id ?>"
${emp.shift_id == <?= $s->id ?> ? 'selected' : ''}>
<?= $s->shift_name ?>
</option>
`;
<?php endforeach; ?>
html += `
</select>
</td>
<td>
<button class="btn btn-danger btn-sm btnDeleteSchedule"
data-id="${emp.schedule_id}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
</div>
`;
});
}
$('#scheduleTableWrapper').html(html);
}
});
}
// =====================================================
// UPDATE SHIFT
// =====================================================
$(document).on('change','.changeShift',function(){
let schedule_id = $(this).data('id');
let tanggal = $(this).data('tanggal');
let shift_id = $(this).val();
$.ajax({
url : base_url + 'generateschedule/update_schedule_shift',
type:'POST',
data:{
schedule_id : schedule_id,
shift_id : shift_id
},
dataType:'json',
success:function(res){
if(res.status){
loadCalendar();
loadScheduleDetail(tanggal);
} else {
Swal.fire(
'Error',
res.message,
'error'
);
}
}
});
});
// =====================================================
// DELETE SCHEDULE
// =====================================================
$(document).on('click','.btnDeleteSchedule',function(){
let id = $(this).data('id');
Swal.fire({
title:'Hapus schedule?',
icon:'warning',
showCancelButton:true,
confirmButtonText:'Hapus'
}).then((result)=>{
if(result.isConfirmed){
$.ajax({
url : base_url + 'generateschedule/delete_schedule',
type:'POST',
data:{
schedule_id:id
},
dataType:'json',
success:function(res){
if(res.status){
loadScheduleDetail(
$('#schedule_date').val()
);
loadCalendar();
} else {
Swal.fire(
'Error',
res.message,
'error'
);
}
}
});
}
});
});
});
</script>