604 lines
17 KiB
PHP
604 lines
17 KiB
PHP
<?php $inventory_title='Kelola Barcode & Serial';$inventory_subtitle='Lengkapi serial unit pending sebelum diaktifkan.';$this->load->view('inventory_professional/_module_nav',get_defined_vars());?>
|
||
<style>
|
||
body {
|
||
background: #f5f7fb;
|
||
}
|
||
|
||
/* ===========================
|
||
CARD
|
||
=========================== */
|
||
.main-card,
|
||
.scanner-card,
|
||
.table-card {
|
||
border: none;
|
||
border-radius: 18px;
|
||
overflow: hidden;
|
||
/*box-shadow: 0 8px 25px rgba(0, 0, 0, .08);*/
|
||
}
|
||
|
||
/* ===========================
|
||
HEADER
|
||
=========================== */
|
||
.header-gradient {
|
||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||
color: #fff;
|
||
padding: 14px 28px;
|
||
}
|
||
|
||
.info-box {
|
||
background: rgba(255,255,255,.18);
|
||
border-radius: 12px;
|
||
padding: 10px 16px;
|
||
}
|
||
|
||
.progress {
|
||
height: 8px;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
/* ===========================
|
||
SCANNER
|
||
=========================== */
|
||
.barcode-avatar {
|
||
width: 110px;
|
||
height: 110px;
|
||
margin: auto;
|
||
border-radius: 50%;
|
||
background: #fff7e6;
|
||
color: #f59e0b;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-size: 45px;
|
||
}
|
||
|
||
.barcode-code {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.form-control-lg {
|
||
height: 60px;
|
||
font-size: 22px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.form-control:focus {
|
||
border-color: #f59e0b;
|
||
box-shadow: 0 0 0 .2rem rgba(245, 158, 11, .2);
|
||
}
|
||
|
||
.scan-note {
|
||
color: #6c757d;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.camera-btn {
|
||
height: 55px;
|
||
border-radius: 12px;
|
||
font-size: 17px;
|
||
}
|
||
|
||
/* ===========================
|
||
TABLE
|
||
=========================== */
|
||
.table thead th {
|
||
background: #d97706;
|
||
color: #fff;
|
||
border: none;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.table tbody td {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.table tbody tr:hover {
|
||
background: #fffaf0;
|
||
}
|
||
|
||
/* ===========================
|
||
BADGE
|
||
=========================== */
|
||
.badge-success-soft {
|
||
display: inline-block;
|
||
padding: 6px 14px;
|
||
border-radius: 30px;
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.badge-danger-soft {
|
||
display: inline-block;
|
||
padding: 6px 14px;
|
||
border-radius: 30px;
|
||
background: #fee2e2;
|
||
color: #991b1b;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.badge-warning-soft {
|
||
display: inline-block;
|
||
padding: 6px 14px;
|
||
border-radius: 30px;
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
font-weight: 600;
|
||
}
|
||
#reader{
|
||
width:100%;
|
||
min-height:280px;
|
||
border-radius:12px;
|
||
overflow:hidden;
|
||
}
|
||
</style>
|
||
|
||
<div class="container mt-4">
|
||
<div class="card main-card">
|
||
<!-- HEADER -->
|
||
<div class="header-gradient">
|
||
<div class="row align-items-center">
|
||
<div class="col-md-8">
|
||
<h5 class="mb-1">
|
||
<i class="fa fa-barcode"></i>
|
||
Input Serial Number
|
||
</h5>
|
||
<div class="small">
|
||
Silahkan scan Serial Number perangkat.
|
||
Data akan otomatis tersimpan.
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4 text-right">
|
||
<div class="info-box">
|
||
<div class="small">
|
||
Progress
|
||
</div>
|
||
<h6 class="font-weight-bold mb-2" id="progress_text">
|
||
0 / 0 Barcode
|
||
</h6>
|
||
<div class="progress">
|
||
<div class="progress-bar bg-success" style="width:35%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- BODY -->
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<!-- LEFT -->
|
||
<div class="col-lg-4 mb-4">
|
||
<div class="card scanner-card">
|
||
<div class="card-body">
|
||
<div class="barcode-avatar">
|
||
<i class="bi bi-qr-code-scan"></i>
|
||
</div>
|
||
<div class="text-center mt-3">
|
||
<div class="barcode-code">
|
||
ONU-250626-0007
|
||
</div>
|
||
<div class="scan-note mt-2">
|
||
Barcode yang sedang menunggu Serial Number
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<center>
|
||
<label class="font-weight-bold">
|
||
Serial Number
|
||
</label>
|
||
<!-- <input
|
||
type="text"
|
||
id="serial_number"
|
||
class="form-control form-control-lg text-center"
|
||
placeholder="Scan Serial Number"
|
||
autocomplete="off"
|
||
autofocus
|
||
>
|
||
<small class="scan-note">
|
||
Scanner akan otomatis menyimpan.
|
||
</small>
|
||
<button class="btn btn-warning btn-block camera-btn mt-4">
|
||
<i class="fa fa-camera mr-2"></i>
|
||
Scan dengan Kamera
|
||
</button> -->
|
||
<div id="scannerArea">
|
||
|
||
<input
|
||
type="text"
|
||
id="serial_number"
|
||
class="form-control form-control-lg text-center"
|
||
placeholder="Scan Serial Number"
|
||
autocomplete="off"
|
||
autofocus>
|
||
|
||
<small class="scan-note">
|
||
Scanner akan otomatis menyimpan.
|
||
</small>
|
||
|
||
<button
|
||
type="button"
|
||
class="btn btn-warning btn-block camera-btn mt-4"
|
||
id="btnCamera">
|
||
|
||
<i class="fa fa-camera mr-2"></i>
|
||
Scan dengan Kamera
|
||
|
||
</button>
|
||
|
||
</div>
|
||
|
||
<div id="cameraArea" style="display:none;">
|
||
|
||
<div id="reader"></div>
|
||
|
||
<small class="scan-note d-block mt-2">
|
||
Arahkan QR Code ke kamera.
|
||
</small>
|
||
|
||
<button
|
||
type="button"
|
||
class="btn btn-secondary btn-block mt-3"
|
||
id="btnCloseCamera">
|
||
|
||
Tutup Kamera
|
||
|
||
</button>
|
||
|
||
</div>
|
||
</center>
|
||
<div class="alert alert-warning mt-3 mb-0 text-center">
|
||
<i class="fa fa-check-circle"></i>
|
||
Auto Save Aktif
|
||
</div>
|
||
<input type="hidden" id="barcode_id">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- RIGHT -->
|
||
<div class="col-lg-8">
|
||
<div class="card table-card">
|
||
<div class="card-header bg-white">
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||
<h5 class="mb-0">
|
||
Daftar Barcode
|
||
</h5>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<input type="search" id="barcodeSearch" class="form-control form-control-sm" placeholder="Cari barcode / SN" autocomplete="off">
|
||
<span class="badge badge-warning px-3 py-2" id="barcodeCount">0 Data</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-footer bg-white d-flex justify-content-between align-items-center">
|
||
<small id="barcodePageInfo" class="text-muted">Memuat data...</small>
|
||
<div class="btn-group btn-group-sm">
|
||
<button type="button" class="btn btn-outline-secondary" id="barcodePrev">Sebelumnya</button>
|
||
<button type="button" class="btn btn-outline-secondary" id="barcodeNext">Berikutnya</button>
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
|
||
<table class="table table-hover mb-0" id="tableBarcode">
|
||
<thead>
|
||
<tr>
|
||
<th width="60">No</th>
|
||
<th>Barcode</th>
|
||
<th>Serial Number</th>
|
||
<th width="120">Status</th>
|
||
<th width="150" class="text-center">Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td></td>
|
||
<td>
|
||
<strong></strong>
|
||
</td>
|
||
<td></td>
|
||
<td>
|
||
<span class="badge-success-soft">
|
||
<i class="fa fa-check mr-1"></i>
|
||
|
||
</span>
|
||
</td>
|
||
<td class="text-center">
|
||
<button class="btn btn-sm btn-outline-danger">
|
||
<i class="fa fa-trash"></i>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
|
||
let ITEM_ID = `<?= $item_id; ?>` ;
|
||
let isSaving = false;
|
||
let barcodeStart = 0;
|
||
const barcodeLength = 50;
|
||
let barcodeSearchTimer = null;
|
||
|
||
$(document).ready(function () {
|
||
loadData();
|
||
$("#barcodeSearch").on("input", function () {
|
||
clearTimeout(barcodeSearchTimer);
|
||
barcodeSearchTimer = setTimeout(function () { barcodeStart = 0; loadData(); }, 300);
|
||
});
|
||
$("#barcodePrev").on("click", function () { barcodeStart = Math.max(0, barcodeStart - barcodeLength); loadData(); });
|
||
$("#barcodeNext").on("click", function () { barcodeStart += barcodeLength; loadData(); });
|
||
$("#serial_number").focus();
|
||
$("#serial_number").on("keypress", function (e) {
|
||
if (e.which == 13) {
|
||
e.preventDefault();
|
||
if (!isSaving) {
|
||
saveSerial();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
function loadData() {
|
||
$.getJSON( "<?= base_url(); ?>" + "items/load_data_barcode/" + ITEM_ID, {
|
||
start: barcodeStart,
|
||
length: barcodeLength,
|
||
search: $("#barcodeSearch").val() || ""
|
||
})
|
||
.done(function (r) {
|
||
render(r);
|
||
})
|
||
.fail(function () {
|
||
Swal.fire({
|
||
icon: 'error',
|
||
title: 'Oops...',
|
||
text: 'Gagal mengambil data barcode.'
|
||
});
|
||
});
|
||
}
|
||
|
||
function saveSerial() {
|
||
let serial = $("#serial_number").val().trim();
|
||
if (serial == "") {
|
||
$("#serial_number").focus();
|
||
return;
|
||
}
|
||
isSaving = true;
|
||
$("#serial_number")
|
||
.prop("disabled", true);
|
||
$.ajax({
|
||
url: "<?= base_url(); ?>" + "items/save_serial_number",
|
||
type: "POST",
|
||
dataType: "json",
|
||
data: {
|
||
barcode_id: $("#barcode_id").val(),
|
||
serial_number: serial
|
||
},
|
||
success: function (r) {
|
||
isSaving = false;
|
||
if (r.status) loadData(); else render(r);
|
||
},
|
||
error: function () {
|
||
isSaving = false;
|
||
$("#serial_number")
|
||
.prop("disabled", false)
|
||
.focus();
|
||
Swal.fire({
|
||
icon: "error",
|
||
title: "Error",
|
||
text: "Terjadi kesalahan pada server."
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
function render(r) {
|
||
|
||
if (!r.status) {
|
||
$("#serial_number")
|
||
.val("")
|
||
.prop("disabled", false)
|
||
.focus();
|
||
|
||
Swal.fire({
|
||
icon: "error",
|
||
title: "Oops...",
|
||
text: r.message
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
// =======================
|
||
// Semua SN sudah selesai
|
||
// =======================
|
||
|
||
if (r.next == null) {
|
||
|
||
$("#barcode_id").val("");
|
||
$(".barcode-code").html("SELESAI");
|
||
|
||
$(".progress-bar").css("width", "100%");
|
||
$("#progress_text").html(r.done + " / " + r.total);
|
||
|
||
if (r.item_status == "draft") {
|
||
|
||
$("#serial_number")
|
||
.val("")
|
||
.prop("disabled", false)
|
||
.attr("placeholder", "Semua SN selesai. Edit melalui daftar di bawah jika ada yang salah.")
|
||
.blur();
|
||
|
||
Swal.fire({
|
||
icon: "success",
|
||
title: "Selesai",
|
||
text: "Semua Serial Number telah diinput. Karena status barang masih Draft, Anda masih dapat mengubah Serial Number melalui daftar di bawah."
|
||
});
|
||
|
||
} else {
|
||
|
||
$("#serial_number")
|
||
.val("")
|
||
.prop("disabled", true);
|
||
|
||
Swal.fire({
|
||
icon: "success",
|
||
title: "Selesai",
|
||
text: "Semua Serial Number berhasil diinput."
|
||
});
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
$("#barcode_id").val(r.next.id);
|
||
$(".barcode-code").text(r.next.barcode);
|
||
|
||
$("#progress_text").text(r.done + " / " + r.total);
|
||
|
||
$(".progress-bar").css("width", r.percent + "%");
|
||
|
||
$("#serial_number")
|
||
.val("")
|
||
.prop("disabled", false)
|
||
.attr("placeholder", "Scan Serial Number")
|
||
.focus();
|
||
|
||
}
|
||
|
||
// =======================
|
||
// Table
|
||
// =======================
|
||
|
||
let html = "";
|
||
const escapeHtml = function (value) { return $("<div>").text(value == null ? "" : String(value)).html(); };
|
||
|
||
$.each(r.list, function (i, v) {
|
||
|
||
html += "<tr>";
|
||
html += "<td>" + (r.start + i + 1) + "</td>";
|
||
html += "<td><strong>" + escapeHtml(v.barcode) + "</strong></td>";
|
||
html += "<td>" + escapeHtml(v.serial_number ? v.serial_number : "-") + "</td>";
|
||
html += "<td>";
|
||
|
||
if (v.status === 'available') {
|
||
html += '<span class="badge-success-soft"><i class="fa fa-check mr-1"></i>Aktif</span>';
|
||
} else if (v.serial_number) {
|
||
html += '<span class="badge-warning-soft">Siap Diposting</span>';
|
||
} else {
|
||
html += '<span class="badge-warning-soft">Menunggu</span>';
|
||
}
|
||
|
||
html += "</td>";
|
||
|
||
html += '<td class="text-center">';
|
||
|
||
if (v.status === 'available') {
|
||
html += '<button class="btn btn-sm btn-outline-secondary" disabled>Sudah Aktif</button>';
|
||
} else if (v.serial_number) {
|
||
html += '<button class="btn btn-sm btn-outline-danger btnDelete" data-id="' + v.id + '">Hapus SN</button>';
|
||
} else {
|
||
html += '<button class="btn btn-sm btn-outline-secondary" disabled>Belum Siap</button>';
|
||
}
|
||
|
||
html += "</td>";
|
||
html += "</tr>";
|
||
|
||
});
|
||
|
||
$("#tableBarcode tbody").html(html);
|
||
$("#barcodeCount").text(r.total + " Data");
|
||
const shownTo = Math.min(r.start + r.list.length, r.filtered);
|
||
$("#barcodePageInfo").text(r.filtered ? ((r.start + 1) + "–" + shownTo + " dari " + r.filtered) : "Tidak ada data");
|
||
$("#barcodePrev").prop("disabled", r.start <= 0);
|
||
$("#barcodeNext").prop("disabled", r.start + r.length >= r.filtered);
|
||
}
|
||
|
||
|
||
|
||
$(document).on("click", ".btnDelete", function () {
|
||
let id = $(this).data("id");
|
||
Swal.fire({
|
||
title: "Hapus Serial Number?",
|
||
icon: "warning",
|
||
showCancelButton: true,
|
||
confirmButtonText: "Ya",
|
||
cancelButtonText: "Batal"
|
||
}).then(function (result) {
|
||
if (!result.isConfirmed) return;
|
||
$.post(
|
||
"<?= base_url(); ?>" + "items/delete_serial_number",
|
||
{
|
||
barcode_id: id
|
||
},
|
||
function (r) {
|
||
if (r.status) loadData(); else render(r);
|
||
},
|
||
"json"
|
||
);
|
||
});
|
||
});
|
||
|
||
|
||
let html5QrCode=null;
|
||
|
||
$("#btnCamera").click(function(){
|
||
|
||
$("#scannerArea").hide();
|
||
$("#cameraArea").show();
|
||
|
||
html5QrCode=new Html5Qrcode("reader");
|
||
|
||
html5QrCode.start(
|
||
{
|
||
facingMode:"environment"
|
||
},
|
||
{
|
||
fps:10,
|
||
qrbox:220
|
||
},
|
||
function(decodedText){
|
||
|
||
html5QrCode.stop().then(function(){
|
||
|
||
$("#cameraArea").hide();
|
||
$("#scannerArea").show();
|
||
|
||
$("#serial_number")
|
||
.val(decodedText);
|
||
|
||
saveSerial();
|
||
|
||
});
|
||
|
||
}
|
||
);
|
||
|
||
});
|
||
|
||
$("#btnCloseCamera").click(function(){
|
||
|
||
if(html5QrCode){
|
||
|
||
html5QrCode.stop().then(function(){
|
||
|
||
$("#cameraArea").hide();
|
||
$("#scannerArea").show();
|
||
|
||
$("#serial_number").focus();
|
||
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
|
||
</script>
|