403 lines
12 KiB
PHP
403 lines
12 KiB
PHP
@extends('partials.main')
|
|
|
|
@section('custom_css')
|
|
<style>
|
|
#resultList {
|
|
max-height: 420px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.pitstop-type-sticky {
|
|
position: sticky;
|
|
top: 16px;
|
|
z-index: 2;
|
|
}
|
|
|
|
#resultList .list-group-item-action:hover {
|
|
background-color: var(--bs-gray-100);
|
|
}
|
|
|
|
/* 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="row g-4 align-items-end">
|
|
<div class="col-12 col-md-4 align-self-start">
|
|
<div class="pitstop-type-sticky">
|
|
<label class="form-label fw-semibold mb-2">Tipe Karyawan</label>
|
|
<div class="d-flex flex-wrap gap-6">
|
|
<label class="form-check form-check-sm form-check-custom form-check-solid mb-0">
|
|
<input class="form-check-input" type="radio" name="karyawan_type" id="karyawanTypeInternal" value="internal" checked />
|
|
<span class="form-check-label fw-semibold">Karyawan Internal</span>
|
|
</label>
|
|
<label class="form-check form-check-sm form-check-custom form-check-solid mb-0">
|
|
<input class="form-check-input" type="radio" name="karyawan_type" id="karyawanTypeLuar" value="luar" />
|
|
<span class="form-check-label fw-semibold">Karyawan Eksternal</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-text">Mode pencarian akan menyesuaikan tipe karyawan.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-8">
|
|
<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>
|
|
|
|
<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="mb-4">
|
|
<label for="nip" class="form-label" id="label_nip">NIP</label>
|
|
<input type="text" id="nip" 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">Data pitstop yang telah diinput tidak akan ditampilkan kembali.</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->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-4">
|
|
<label for="nilai" class="form-label" id="label_nilai">Nilai (0-100)</label>
|
|
<input
|
|
type="number"
|
|
id="nilai"
|
|
name="nilai"
|
|
class="form-control"
|
|
min="0"
|
|
max="100"
|
|
step="1"
|
|
inputmode="numeric"
|
|
autocomplete="off"
|
|
required
|
|
/>
|
|
</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 escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
}[m]));
|
|
|
|
const renderMessage = (message) => {
|
|
$resultList.html(`<li class="list-group-item text-muted">${message}</li>`);
|
|
};
|
|
|
|
const clearResults = () => {
|
|
$resultList.empty();
|
|
};
|
|
|
|
const getKaryawanType = () => $('input[name="karyawan_type"]:checked').val() || 'internal';
|
|
|
|
const getListEndpoint = () => (getKaryawanType() === 'luar' ? '/list-karyawan-luar' : '/list-karyawan');
|
|
|
|
const syncPlaceholder = () => {
|
|
const type = getKaryawanType();
|
|
$search.attr('placeholder', type === 'luar' ? 'Ketik nama / NIK...' : 'Ketik nama / NIP...');
|
|
};
|
|
|
|
const searchKaryawan = (keyword) => {
|
|
if (activeRequest) activeRequest.abort();
|
|
|
|
renderMessage('Mencari...');
|
|
|
|
const endpoint = getListEndpoint();
|
|
const params = getKaryawanType() === 'luar'
|
|
? { search: keyword, limit: 50 }
|
|
: { search: keyword, limit: 50 };
|
|
|
|
activeRequest = $.get(endpoint, params)
|
|
.done(function (res) {
|
|
const data = res?.data ?? [];
|
|
if (!data.length) {
|
|
renderMessage('Data tidak ditemukan.');
|
|
return;
|
|
}
|
|
|
|
const html = data
|
|
.map((item) => {
|
|
const nama = item?.namalengkap ?? '-';
|
|
const nip = item?.nip_pns ?? '-';
|
|
const unit = item?.unit_name ?? '-';
|
|
return `
|
|
<li class="list-group-item list-group-item-action pilihKaryawan"
|
|
role="button"
|
|
data-id="${item.id}"
|
|
data-nama="${escapeHtml(nama)}"
|
|
data-nip="${escapeHtml(nip)}"
|
|
data-unit="${escapeHtml(unit)}"
|
|
>
|
|
<div class="d-flex align-items-center justify-content-between gap-3">
|
|
<div class="d-flex flex-column">
|
|
<span class="fw-semibold">${escapeHtml(nama)}</span>
|
|
<span class="text-muted fs-7">${escapeHtml(nip)}</span>
|
|
</div>
|
|
<span class="badge badge-light-primary">${escapeHtml(unit)}</span>
|
|
</div>
|
|
</li>`;
|
|
})
|
|
.join('');
|
|
|
|
$resultList.html(html);
|
|
})
|
|
.fail(function (xhr, status) {
|
|
if (status === 'abort') return;
|
|
renderMessage('Gagal mengambil data.');
|
|
});
|
|
};
|
|
|
|
const lockExistingSteps = (pegawaiId) => {
|
|
$stepHint.html('');
|
|
|
|
// simpan semua opsi awal, supaya bisa di-restore setiap ganti pegawai
|
|
if (!$step.data('allOptionsHtml')) {
|
|
$step.data('allOptionsHtml', $step.html());
|
|
} else {
|
|
$step.html($step.data('allOptionsHtml'));
|
|
}
|
|
|
|
$form.find('button[type="submit"]').prop('disabled', false);
|
|
|
|
const stepsEndpoint = getKaryawanType() === 'luar' ? '/pitstop/pegawai-steps-external' : '/pitstop/pegawai-steps';
|
|
|
|
return $.get(stepsEndpoint, { pegawai_id: pegawaiId })
|
|
.done(function (res) {
|
|
const lockedSteps = res?.data?.locked_steps ?? [];
|
|
const locked = new Set(lockedSteps.map((v) => String(v)));
|
|
|
|
// hanya tampilkan step yang belum lulus (yang locked dihapus dari list)
|
|
$step.find('option').each(function () {
|
|
const val = String($(this).val());
|
|
if (locked.has(val)) {
|
|
$(this).remove();
|
|
}
|
|
});
|
|
|
|
const $first = $step.find('option').first();
|
|
if ($first.length) $step.val($first.val());
|
|
|
|
const availableCount = $step.find('option').length;
|
|
if (!availableCount) {
|
|
$form.find('button[type="submit"]').prop('disabled', true);
|
|
$stepHint.html('<span class="text-success fw-semibold">Semua step sudah selesai dikerjakan</span>');
|
|
}
|
|
})
|
|
.fail(function () {
|
|
// kalau gagal ambil data lock, tetap biarkan user submit (server akan validasi)
|
|
});
|
|
};
|
|
|
|
$step.on('change', function () {
|
|
// opsi yang tampil sudah pasti belum lulus
|
|
$stepHint.html('');
|
|
});
|
|
|
|
$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('change', 'input[name="karyawan_type"]', function () {
|
|
if (activeRequest) activeRequest.abort();
|
|
$search.val('');
|
|
syncPlaceholder();
|
|
clearResults();
|
|
});
|
|
|
|
syncPlaceholder();
|
|
|
|
$(document).on('click', '.pilihKaryawan', function () {
|
|
$form.trigger('reset');
|
|
$('#nama_karyawan').val($(this).data('nama'));
|
|
$('#nip').val($(this).data('nip'));
|
|
$('#karyawan_id').val($(this).data('id'));
|
|
lockExistingSteps($(this).data('id'));
|
|
getKaryawanType() === 'luar' ? $('#label_nip').text('NIK') : $('#label_nip').text('NIP');
|
|
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 rawNilai = String($('#nilai').val() ?? '').trim();
|
|
const nilai = rawNilai === '' ? NaN : Number(rawNilai);
|
|
if (!Number.isFinite(nilai) || nilai < 0 || nilai > 100) {
|
|
Swal.fire({
|
|
toast: true,
|
|
position: 'top-end',
|
|
icon: 'warning',
|
|
title: 'Nilai harus di antara 0 sampai 100.',
|
|
showConfirmButton: false,
|
|
timer: 2400,
|
|
timerProgressBar: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
karyawan_id: $('#karyawan_id').val(),
|
|
step: $('#step').val(),
|
|
// status: $('input[name="status"]:checked').val(),
|
|
nilai: nilai,
|
|
tipe_karyawan: getKaryawanType(),
|
|
};
|
|
$.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
|