2026-04-30 19:27:37 +07:00

300 lines
8.6 KiB
PHP

@extends('partials.main')
@section('custom_css')
<style>
#resultList {
max-height: 420px;
overflow-y: auto;
}
/* Catatan: styling <option> tergantung browser, tapi ini membantu di banyak kasus */
#step option[data-locked='1'] {
color: var(--bs-success) !important;
-webkit-text-fill-color: var(--bs-success);
}
</style>
@endsection
@section('content')
<div class="card card-flush">
<div class="card-header pt-7">
<div class="card-title">
<h2 class="mb-0 fw-bold">PitStop Pra Akreditasi</h2>
</div>
<div class="card-toolbar">
<span class="text-muted">Cari karyawan untuk input status per step</span>
</div>
</div>
<div class="card-body pt-0">
<div class="w-100">
<label for="searchKaryawan" class="form-label fw-semibold">Cari Karyawan</label>
<input
type="text"
id="searchKaryawan"
class="form-control"
placeholder="Ketik nama / NIP..."
autocomplete="off"
aria-label="Cari karyawan"
/>
<ul class="list-group mt-3" id="resultList"></ul>
</div>
</div>
</div>
<div class="modal fade" id="modalPitstop" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form id="formPitstop">
<div class="modal-header">
<h5 class="modal-title">Input PitStop</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" id="karyawan_id" name="karyawan_id" />
<div class="mb-4">
<label for="nama_karyawan" class="form-label">Nama Karyawan</label>
<input type="text" id="nama_karyawan" class="form-control" readonly />
</div>
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed p-4 mb-5">
<div class="d-flex flex-stack flex-grow-1">
<div class="fw-semibold">
<div class="text-gray-900 fw-bold">Info</div>
<div class="text-gray-700">Step akan terkunci jika sudah berstatus lulus.</div>
</div>
</div>
</div>
<div class="mb-4">
<label for="step" class="form-label">Step</label>
<select id="step" name="step" class="form-select" required>
@forelse ($masterPitStop as $ps)
<option value="{{ $ps->id }}">{{ $ps->nama }}</option>
@empty
<option value="" disabled selected>Tidak ada data</option>
@endforelse
</select>
<div class="form-text" id="stepHint"></div>
</div>
<div class="mb-2">
<label class="form-label d-block">Status</label>
<div class="d-flex flex-wrap gap-6">
<label class="form-check form-check-sm form-check-custom form-check-solid">
<input class="form-check-input" type="radio" name="status" value="lulus" required />
<span class="form-check-label">Lulus</span>
</label>
<label class="form-check form-check-sm form-check-custom form-check-solid">
<input class="form-check-input" type="radio" name="status" value="tidak_lulus" required />
<span class="form-check-label">Tidak Lulus</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">
<span class="indicator-label">Simpan</span>
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('custom_js')
<script>
$(document).ready(function () {
let searchTimer = null;
let activeRequest = null;
const $search = $('#searchKaryawan');
const $resultList = $('#resultList');
const $form = $('#formPitstop');
const $step = $('#step');
const $stepHint = $('#stepHint');
const csrf = $('meta[name="csrf-token"]').attr('content');
const renderMessage = (message) => {
$resultList.html(`<li class="list-group-item text-muted">${message}</li>`);
};
const clearResults = () => {
$resultList.empty();
};
const searchKaryawan = (keyword) => {
if (activeRequest) activeRequest.abort();
renderMessage('Mencari...');
activeRequest = $.get('/list-karyawan', { search: keyword, per_page: 20 })
.done(function (res) {
const data = res?.data ?? [];
if (!data.length) {
renderMessage('Data tidak ditemukan.');
return;
}
const html = data
.map((item) => {
const nama = item?.namalengkap ?? '-';
return `
<li class="list-group-item list-group-item-action pilihKaryawan"
role="button"
data-id="${item.id}"
data-nama="${nama}"
>
${nama}
</li>`;
})
.join('');
$resultList.html(html);
})
.fail(function (xhr, status) {
if (status === 'abort') return;
renderMessage('Gagal mengambil data.');
});
};
const lockExistingSteps = (pegawaiId) => {
$stepHint.html('');
$step.find('option').each(function () {
const $opt = $(this);
const original = $opt.attr('data-original');
if (original) $opt.text(original);
$opt.prop('disabled', false).removeAttr('data-locked').removeAttr('data-original');
});
$form.find('button[type="submit"]').prop('disabled', false);
return $.get('/pitstop/pegawai-steps', { pegawai_id: pegawaiId })
.done(function (res) {
const lockedSteps = res?.data?.locked_steps ?? [];
const locked = new Set(lockedSteps.map((v) => String(v)));
$step.find('option').each(function () {
const val = String($(this).val());
if (locked.has(val)) {
const $opt = $(this);
$opt.attr('data-original', $opt.text());
$opt.text(`${$opt.text()} (Selesai)`);
$opt.prop('disabled', true).attr('data-locked', '1');
}
});
if ($step.find('option:selected').prop('disabled')) {
const $firstEnabled = $step.find('option:not([disabled])').first();
if ($firstEnabled.length) $step.val($firstEnabled.val());
}
const enabledCount = $step.find('option:not([disabled])').length;
if (!enabledCount) {
$form.find('button[type="submit"]').prop('disabled', true);
}
const selectedLocked = $step.find('option:selected').attr('data-locked') === '1';
if (selectedLocked) {
$stepHint.html('<span class="text-success fw-semibold">Selesai</span>');
}
})
.fail(function () {
// kalau gagal ambil data lock, tetap biarkan user submit (server akan validasi)
});
};
$step.on('change', function () {
const selectedLocked = $step.find('option:selected').attr('data-locked') === '1';
$stepHint.html(selectedLocked ? '<span class="text-success fw-semibold">Selesai</span>' : '');
});
$search.on('keyup', function () {
const keyword = String($(this).val() ?? '').trim();
if (keyword.length < 2) {
if (activeRequest) activeRequest.abort();
clearResults();
return;
}
clearTimeout(searchTimer);
searchTimer = setTimeout(() => searchKaryawan(keyword), 250);
});
$(document).on('click', '.pilihKaryawan', function () {
$form.trigger('reset');
$('#nama_karyawan').val($(this).data('nama'));
$('#karyawan_id').val($(this).data('id'));
lockExistingSteps($(this).data('id'));
new bootstrap.Modal(document.getElementById('modalPitstop')).show();
});
$form.on('submit', function (e) {
e.preventDefault();
if ($step.find('option:selected').attr('data-locked') === '1') {
Swal.fire({
toast: true,
position: 'top-end',
icon: 'info',
title: 'Step ini sudah lulus dan terkunci.',
showConfirmButton: false,
timer: 2200,
timerProgressBar: true,
});
return;
}
const payload = {
karyawan_id: $('#karyawan_id').val(),
step: $('#step').val(),
status: $('input[name="status"]:checked').val(),
};
$.ajax({
url: '/pitstop/submit',
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrf },
data: payload,
})
.done(function () {
const modalEl = document.getElementById('modalPitstop');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: 'Berhasil submit pitstop.',
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
});
})
.fail(function (xhr) {
const msg =
xhr?.responseJSON?.message ||
(xhr?.status === 422 ? 'Validasi gagal.' : 'Gagal menyimpan data.');
Swal.fire({
toast: true,
position: 'top-end',
icon: 'error',
title: msg,
showConfirmButton: false,
timer: 2600,
timerProgressBar: true,
});
});
});
});
</script>
@endsection