518 lines
17 KiB
PHP
518 lines
17 KiB
PHP
@extends('partials.main')
|
|
|
|
@section('content')
|
|
<div class="card card-flush">
|
|
<div class="card-header pt-7">
|
|
<div class="card-title">
|
|
<h2 class="mb-0 fw-bold">Monitoring Pra Akreditasi</h2>
|
|
</div>
|
|
<div class="card-toolbar">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-sm btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="fa-solid fa-file-export me-2"></i>Export
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li>
|
|
<h6 class="dropdown-header">PDF</h6>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf">Internal</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf-external">Eksternal</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<h6 class="dropdown-header">Excel</h6>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel">Internal</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel-external">Eksternal</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body pt-3">
|
|
<ul class="nav nav-tabs nav-line-tabs nav-line-tabs-2x mb-5 fs-6" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<a class="nav-link fw-semibold active" data-bs-toggle="tab" href="#tabAllKaryawan" role="tab" aria-selected="true">Semua Karyawan</a>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<a class="nav-link fw-semibold" data-bs-toggle="tab" href="#tabInternal" role="tab" aria-selected="false">Unit Internal</a>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<a class="nav-link fw-semibold" data-bs-toggle="tab" href="#tabExternal" role="tab" aria-selected="false">Unit Eksternal</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content">
|
|
<div class="tab-pane fade show active" id="tabAllKaryawan" role="tabpanel">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
|
<div class="d-flex flex-column">
|
|
<div class="text-muted">Daftar gabungan internal dan karyawan luar</div>
|
|
</div>
|
|
<div class="d-flex align-items-center position-relative">
|
|
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
|
<i class="fa-solid fa-magnifying-glass"></i>
|
|
</span>
|
|
<input type="text" id="searchAllKaryawan" class="form-control form-control-solid w-300px ps-12" placeholder="Cari nama / NIP / NIK / unit..." autocomplete="off" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="tblAllKaryawan" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
|
<thead>
|
|
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
|
<th>Nama</th>
|
|
<th>Unit / Tipe</th>
|
|
<th style="width: 320px">Progress</th>
|
|
<th>Belum dikerjakan</th>
|
|
<th class="text-end">Jenis</th>
|
|
<th class="text-end">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="fw-semibold text-gray-800"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="tabInternal" role="tabpanel">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
|
<div class="d-flex flex-column">
|
|
<div class="text-muted">Ringkasan monitoring per unit</div>
|
|
</div>
|
|
<div class="d-flex align-items-center position-relative">
|
|
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
|
<i class="fa-solid fa-magnifying-glass"></i>
|
|
</span>
|
|
<input type="text" id="searchUnit" class="form-control form-control-solid w-300px ps-12" placeholder="Cari unit..." autocomplete="off" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="tblUnit" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
|
<thead>
|
|
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
|
<th>Unit</th>
|
|
<th class="text-end">Total Pegawai</th>
|
|
<th style="width: 260px">Total Selesai</th>
|
|
<th class="text-end">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="fw-semibold text-gray-800"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="tabExternal" role="tabpanel">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
|
<div class="d-flex flex-column">
|
|
<div class="text-muted">Ringkasan monitoring per tipe</div>
|
|
</div>
|
|
<div class="d-flex align-items-center position-relative">
|
|
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
|
<i class="fa-solid fa-magnifying-glass"></i>
|
|
</span>
|
|
<input type="text" id="searchExternal" class="form-control form-control-solid w-300px ps-12" placeholder="Cari tipe..." autocomplete="off" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="tblExternal" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
|
<thead>
|
|
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
|
<th>Tipe</th>
|
|
<th class="text-end">Total Karyawan</th>
|
|
<th style="width: 260px">Total Selesai</th>
|
|
<th class="text-end">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="fw-semibold text-gray-800"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="modalAllDetail" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Detail Progress</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<style>
|
|
.modal .cell-ellipsis {
|
|
max-width: 220px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
</style>
|
|
<div class="text-muted mb-4" id="allDetailNama"></div>
|
|
<div class="table-responsive">
|
|
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
|
<thead>
|
|
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
|
<th>Nama Pitstop</th>
|
|
<th>Nilai</th>
|
|
<th style="width: 240px">Pemberi Nilai</th>
|
|
<th class="text-end">Waktu</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="fw-semibold text-gray-800" id="allDetailTable">
|
|
<tr>
|
|
<td colspan="4" class="text-center text-muted py-6">Memuat data...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="modalAllBelum" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">PitStop Belum Dikerjakan</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="text-dark fw-semibold mb-4" id="allBelumNama"></div>
|
|
<ul id="allBelumList" class="mb-0 ps-5"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
|
|
@section('custom_js')
|
|
<script>
|
|
$(document).ready(function () {
|
|
const totalSteps = Number(@json((int) ($totalSteps ?? 0)));
|
|
|
|
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
}[m]));
|
|
|
|
const renderDetailRows = (rows) => {
|
|
if (!rows.length) {
|
|
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Belum ada data</td></tr>');
|
|
return;
|
|
}
|
|
|
|
const html = rows
|
|
.map((r) => {
|
|
const status = String(r.status ?? '-');
|
|
const badge =
|
|
status === 'lulus'
|
|
? '<span class="badge badge-light-success">Lulus</span>'
|
|
: status === 'tidak_lulus'
|
|
? '<span class="badge badge-light-danger">Tidak Lulus</span>'
|
|
: `<span class="badge badge-light">${escapeHtml(status)}</span>`;
|
|
|
|
const waktu = r.created_at ? String(r.created_at) : '-';
|
|
|
|
return `
|
|
<tr>
|
|
<td>${escapeHtml(r.step_nama ?? '-')}</td>
|
|
<td>${r.nilai}</td>
|
|
<td><span class="d-inline-block cell-ellipsis" title="${escapeHtml(r.penilai_nama ?? '-')}">${escapeHtml(r.penilai_nama ?? '-')}</span></td>
|
|
<td class="text-end">${escapeHtml(waktu)}</td>
|
|
</tr>`;
|
|
})
|
|
.join('');
|
|
|
|
$('#allDetailTable').html(html);
|
|
};
|
|
|
|
$('#tblUnit').DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
searchDelay: 350,
|
|
dom: 'rtip',
|
|
pageLength: 10,
|
|
lengthMenu: [10, 20, 50, 100],
|
|
order: [[2, 'desc']],
|
|
ajax: {
|
|
url: '/data/progress-Internal',
|
|
type: 'GET',
|
|
},
|
|
columns: [
|
|
{
|
|
data: 'unit_name',
|
|
render: function (data, type, row) {
|
|
const name = escapeHtml(data ?? '-');
|
|
return `<div class="d-flex flex-column"><span class="fw-bold">${name}</span></div>`;
|
|
},
|
|
},
|
|
{ data: 'total_pegawai', className: 'text-end' },
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
render: function (data, type, row) {
|
|
const pct = Number(row.selesai_pct ?? 0);
|
|
const label = `${pct}%`;
|
|
return `
|
|
<div class="d-flex flex-column gap-1">
|
|
<div class="d-flex justify-content-between">
|
|
<span class="text-muted fs-7">${row.pegawai_selesai ?? 0} / ${row.total_pegawai ?? 0} pegawai</span>
|
|
<span class="text-muted fs-7">${label}</span>
|
|
</div>
|
|
<div class="progress h-6px">
|
|
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>`;
|
|
},
|
|
},
|
|
{
|
|
data: null,
|
|
className: 'text-end',
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function (data, type, row) {
|
|
return `<a class="btn btn-sm btn-light-primary" href="/pitstop/progress-unit/${row.unit_id}">Detail</a>`;
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const table = $('#tblUnit').DataTable();
|
|
let timer = null;
|
|
$('#searchUnit').on('keyup', function () {
|
|
const val = String($(this).val() ?? '');
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
table.search(val).draw();
|
|
}, 250);
|
|
});
|
|
|
|
$('#tblExternal').DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
searchDelay: 350,
|
|
dom: 'rtip',
|
|
pageLength: 10,
|
|
lengthMenu: [10, 20, 50, 100],
|
|
order: [[2, 'desc']],
|
|
ajax: {
|
|
url: '/data/progress-external',
|
|
type: 'GET',
|
|
},
|
|
columns: [
|
|
{
|
|
data: 'tipe',
|
|
render: function (data) {
|
|
const name = escapeHtml(data ?? '-');
|
|
return `<div class="d-flex flex-column"><span class="fw-bold">${name}</span></div>`;
|
|
},
|
|
},
|
|
{ data: 'total_pegawai', className: 'text-end' },
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
render: function (data, type, row) {
|
|
const pct = Number(row.selesai_pct ?? 0);
|
|
const label = `${pct}%`;
|
|
return `
|
|
<div class="d-flex flex-column gap-1">
|
|
<div class="d-flex justify-content-between">
|
|
<span class="text-muted fs-7">${row.pegawai_selesai ?? 0} / ${row.total_pegawai ?? 0} karyawan</span>
|
|
<span class="text-muted fs-7">${label}</span>
|
|
</div>
|
|
<div class="progress h-6px">
|
|
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>`;
|
|
},
|
|
},
|
|
{
|
|
data: null,
|
|
className: 'text-end',
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function (data, type, row) {
|
|
// Hindari path param: nilai `tipe` bisa mengandung "/" sehingga route param bisa gagal.
|
|
const tipeRaw = String(row.tipe ?? '-');
|
|
return `<a class="btn btn-sm btn-light-primary" href="/pitstop/progress-external?tipe=${encodeURIComponent(tipeRaw)}">Detail</a>`;
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const extTable = $('#tblExternal').DataTable();
|
|
let extTimer = null;
|
|
$('#searchExternal').on('keyup', function () {
|
|
const val = String($(this).val() ?? '');
|
|
clearTimeout(extTimer);
|
|
extTimer = setTimeout(function () {
|
|
extTable.search(val).draw();
|
|
}, 250);
|
|
});
|
|
|
|
$('#tblAllKaryawan').DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
searchDelay: 350,
|
|
dom: 'rtip',
|
|
pageLength: 10,
|
|
lengthMenu: [10, 20, 50, 100],
|
|
order: [[2, 'desc']],
|
|
ajax: {
|
|
url: '/data/progress-all-karyawan',
|
|
type: 'GET',
|
|
},
|
|
columns: [
|
|
{
|
|
data: null,
|
|
render: function (data, type, row) {
|
|
const nama = escapeHtml(row.nama ?? '-');
|
|
const idt = escapeHtml(row.identitas ?? '-');
|
|
const label = row.tipe_karyawan === 'luar' ? 'NIK' : 'NIP';
|
|
return `
|
|
<div class="d-flex flex-column">
|
|
<span class="fw-semibold">${nama}</span>
|
|
<span class="text-muted fs-7">${label}: ${idt}</span>
|
|
</div>`;
|
|
},
|
|
},
|
|
{
|
|
data: 'unit_name',
|
|
render: function (data) {
|
|
return `<span class="fw-semibold">${escapeHtml(data ?? '-')}</span>`;
|
|
},
|
|
},
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
render: function (data, type, row) {
|
|
const pct = Number(row.pct ?? 0);
|
|
return `
|
|
<div class="d-flex flex-column gap-1">
|
|
<div class="d-flex justify-content-between">
|
|
<span class="text-muted fs-7">${row.lulus_count ?? 0} / ${totalSteps}</span>
|
|
<span class="text-muted fs-7">${totalSteps > 0 ? pct + '%' : '-'}</span>
|
|
</div>
|
|
<div class="progress h-6px">
|
|
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>`;
|
|
},
|
|
},
|
|
{
|
|
data: null,
|
|
className: 'text-center',
|
|
orderable: false,
|
|
render: function (data, type, row) {
|
|
const c = Number(row.belum_count ?? 0);
|
|
if (c <= 0) return '<span class="text-muted">-</span>';
|
|
const nama = escapeHtml(row.nama ?? '-');
|
|
const idt = escapeHtml(row.identitas ?? '-');
|
|
const label = row.tipe_karyawan === 'luar' ? 'NIK' : 'NIP';
|
|
return `<a href="#" class="showBelumAll" data-nama="${nama}" data-identitas="${idt}" data-label="${label}" data-steps="${escapeHtml(row.belum_steps ?? '')}"><span class="badge badge-warning text-dark">${c} pitstop</span></a>`;
|
|
},
|
|
},
|
|
{
|
|
data: 'tipe_karyawan',
|
|
className: 'text-end',
|
|
render: function (data) {
|
|
const tipe = String(data ?? 'internal');
|
|
return tipe === 'luar'
|
|
? '<span class="badge badge-light-warning text-dark">Eksternal</span>'
|
|
: '<span class="badge badge-light-primary">Internal</span>';
|
|
},
|
|
},
|
|
{
|
|
data: null,
|
|
className: 'text-end',
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function (data, type, row) {
|
|
const tipe = String(row.tipe_karyawan ?? 'internal');
|
|
const nama = escapeHtml(row.nama ?? '-');
|
|
const identitas = escapeHtml(row.identitas ?? '-');
|
|
return `<a href="#" class="btn btn-sm btn-primary viewDetailAll" data-id="${row.id}" data-tipe="${escapeHtml(tipe)}" data-nama="${nama}" data-identitas="${identitas}">Detail</a>`;
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const allTable = $('#tblAllKaryawan').DataTable();
|
|
let allTimer = null;
|
|
$('#searchAllKaryawan').on('keyup', function () {
|
|
const val = String($(this).val() ?? '');
|
|
clearTimeout(allTimer);
|
|
allTimer = setTimeout(function () {
|
|
allTable.search(val).draw();
|
|
}, 250);
|
|
});
|
|
|
|
$(document).on('click', '.viewDetailAll', function (e) {
|
|
e.preventDefault();
|
|
|
|
const id = Number($(this).data('id'));
|
|
const tipe = String($(this).data('tipe') ?? 'internal');
|
|
const nama = String($(this).data('nama') ?? '-');
|
|
const identitas = String($(this).data('identitas') ?? '-');
|
|
const label = tipe === 'luar' ? 'NIK' : 'NIP';
|
|
|
|
$('#allDetailNama').text(`${nama} (${label}: ${identitas})`);
|
|
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
|
|
|
const url = tipe === 'luar' ? '/pitstop/progress-detail-external' : '/pitstop/progress-detail';
|
|
$.get(url, { pegawai_id: id })
|
|
.done(function (res) {
|
|
renderDetailRows(res?.data ?? []);
|
|
})
|
|
.fail(function () {
|
|
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
|
});
|
|
|
|
new bootstrap.Modal(document.getElementById('modalAllDetail')).show();
|
|
});
|
|
|
|
$(document).on('click', '.showBelumAll', function (e) {
|
|
e.preventDefault();
|
|
const nama = String($(this).data('nama') ?? '-');
|
|
const identitas = String($(this).data('identitas') ?? '-');
|
|
const label = String($(this).data('label') ?? '-');
|
|
const stepsRaw = String($(this).data('steps') ?? '').trim();
|
|
|
|
$('#allBelumNama').text(`${nama} (${label}: ${identitas})`);
|
|
|
|
if (!stepsRaw) {
|
|
$('#allBelumList').html('<li class="text-muted">Tidak ada data.</li>');
|
|
} else {
|
|
const steps = stepsRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
const html = steps.map((s) => `<li><span class="fw-semibold">${escapeHtml(s)}</span></li>`).join('');
|
|
$('#allBelumList').html(html || '<li class="text-muted">Tidak ada data.</li>');
|
|
}
|
|
|
|
new bootstrap.Modal(document.getElementById('modalAllBelum')).show();
|
|
});
|
|
|
|
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function () {
|
|
table.columns.adjust();
|
|
extTable.columns.adjust();
|
|
allTable.columns.adjust();
|
|
});
|
|
|
|
});
|
|
</script>
|
|
@endsection
|