332 lines
14 KiB
PHP
Executable File
332 lines
14 KiB
PHP
Executable File
@extends('m3::liberty-theme.layout.main')
|
|
|
|
@section('title')
|
|
Solon Sentinel Portal - Load Boxes on Pallets
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="container-fluid px-4">
|
|
<h4 class="mt-4">Load Boxes on Pallets</h4>
|
|
|
|
<div class="row mb-2">
|
|
{{-- ── Pallet Header Card ─────────────────────────────── --}}
|
|
<div class="col-6">
|
|
<div class="card mb-3 border-primary h-100">
|
|
<div class="card-header bg-primary text-white fw-bold d-flex justify-content-between align-items-center">
|
|
<span>Pallet Information</span>
|
|
<span><a class="btn btn-info" href="{{ route('m3.BoxPackToPallet') }}">Next Pallet</a></span>
|
|
</div>
|
|
<div class="card-body py-2">
|
|
<div class="row g-2 align-items-center">
|
|
<div class="col-auto">
|
|
<span class="text-muted small">Pallet #</span>
|
|
<span id="hdr-pallet" class="fs-5 fw-bold ms-1">{{ $R659PALT }}</span>
|
|
</div>
|
|
<div class="col-auto border-start ps-3">
|
|
<span class="text-muted small">User</span>
|
|
<span id="hdr-user" class="ms-1">
|
|
{{ $palletHeader->R659USER ?? (m3User() !== '' ? Str::upper(m3User()) : '—') }}
|
|
</span>
|
|
</div>
|
|
<div class="col-auto border-start ps-3">
|
|
<span class="text-muted small">Date</span>
|
|
<span id="hdr-date" class="ms-1">
|
|
{{ $palletHeader ? $palletHeader->R659DATE : '—' }}
|
|
</span>
|
|
</div>
|
|
<div class="col-auto border-start ps-3">
|
|
<span class="text-muted small">Time</span>
|
|
<span id="hdr-time" class="ms-1">
|
|
{{ $palletHeader ? $palletHeader->R659TIME : '—' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- ── Scan Input Card ────────────────────────────────── --}}
|
|
<div class="col-6">
|
|
<div class="card mb-4 border-secondary h-100">
|
|
<div class="card-header fw-semibold d-flex justify-content-between align-items-center">
|
|
<span>Scan Box Barcode</span>
|
|
{{-- <span id="scan-alert-badge" class="badge bg-danger d-none">Danger</span> --}}
|
|
</div>
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
<div>
|
|
<div class="input-group">
|
|
<div class="input-group-text">Box #</div>
|
|
<label for="scan-input" class="visually-hidden">Scan or type box #</label>
|
|
<input type="text" id="scan-input" class="form-control" maxlength="12"
|
|
placeholder="Scan or type box #" autocomplete="off" autofocus style="width: 180px;">
|
|
</div>
|
|
</div>
|
|
<div class="mt-auto">
|
|
<button id="btn-scan" class="btn btn-success">
|
|
<i class="fas fa-barcode me-1"></i> Add Box
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{{-- <div id="scan-alert-1" class="d-none align-self-center"></div> --}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- ── Boxes DataTable Card ────────────────────────────── --}}
|
|
<div class="card mb-4">
|
|
<div class="card-header fw-semibold d-flex justify-content-between align-items-center">
|
|
<span>Boxes on Pallet</span>
|
|
<span id="box-count" class="badge bg-secondary">0 boxes</span>
|
|
</div>
|
|
<div class="card-body p-1">
|
|
<div class="table-responsive">
|
|
<table id="boxes-table" class="table table-striped table-hover mb-0 w-100">
|
|
<thead>
|
|
<tr>
|
|
<th>Box #</th>
|
|
<th>Carrier</th>
|
|
<th>Conveyor</th>
|
|
<th>Country</th>
|
|
<th>Status</th>
|
|
<th>User</th>
|
|
<th>Date</th>
|
|
<th>Time</th>
|
|
<th class="text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('scripts')
|
|
<script>
|
|
$(document).ready(function() {
|
|
const PALT = '{{ $R659PALT ?? '' }}';
|
|
const GET_URL = '{{ route('m3.BoxPackToPallet.getboxes') }}';
|
|
const SCAN_URL = '{{ route('m3.BoxPackToPallet.scanboxes') }}';
|
|
const DEL_URL = '{{ route('m3.BoxPackToPallet.deletebox') }}';
|
|
|
|
// Seed from PHP on first load — avoids a redundant AJAX call on page load
|
|
const initialData = @json($boxes ?? []);
|
|
|
|
// ── DataTable init ────────────────────────────────────────
|
|
const dt = $('#boxes-table').DataTable({
|
|
data: initialData,
|
|
columns: [{
|
|
data: 'R659BOX'
|
|
},
|
|
{
|
|
data: 'AYSPR15A01',
|
|
defaultContent: '—'
|
|
},
|
|
{
|
|
data: 'CONVEYOR_NAME',
|
|
defaultContent: '—'
|
|
},
|
|
{
|
|
data: 'A4SHSHCT',
|
|
defaultContent: '—'
|
|
},
|
|
{
|
|
data: 'R659LOADED',
|
|
render: val => (val ?? '').toString().trim() ?
|
|
'<span class="badge bg-success">Loaded</span>' :
|
|
'<span class="badge bg-warning text-dark">Pending</span>'
|
|
},
|
|
{
|
|
data: 'R659USER'
|
|
},
|
|
{
|
|
data: 'R659DATE',
|
|
render: val => val
|
|
},
|
|
{
|
|
data: 'R659TIME',
|
|
render: val => val
|
|
},
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
searchable: false,
|
|
className: 'text-center',
|
|
render: row => {
|
|
const loaded = (row.R659LOADED ?? '').toString().trim();
|
|
if (loaded) return '—';
|
|
return `<button class="btn btn-danger btn-sm btn-delete"
|
|
data-box="${row.R659BOX}">
|
|
<i class="fas fa-trash-alt"></i>
|
|
</button>`;
|
|
}
|
|
},
|
|
],
|
|
order: [],
|
|
pageLength: 25,
|
|
responsive: true,
|
|
select: false,
|
|
buttons: false,
|
|
dom: "<'row mt-2'<'col-sm-12 col-md-2'l><'col-sm-12 col-md-3'f>>" +
|
|
"<'row'<'col-sm-12'tr>>" +
|
|
"<'row mt-2'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
|
// Override columnDefs to remove the checkbox class
|
|
columnDefs: [{
|
|
orderable: false,
|
|
searchable: false,
|
|
targets: -1
|
|
}]
|
|
});
|
|
|
|
// Update count badge on init
|
|
updateCount();
|
|
|
|
// ── Refresh table via AJAX (after scan or delete) ─────────
|
|
function reloadBoxes() {
|
|
$.get(GET_URL, {
|
|
R659PALT: PALT
|
|
}, function(res) {
|
|
if (res.success) {
|
|
dt.clear().rows.add(res.data).draw();
|
|
updateCount();
|
|
}
|
|
}).fail(function() {
|
|
showAlert('danger', 'Failed to refresh boxes.');
|
|
});
|
|
}
|
|
|
|
// ── Scan / Add box ────────────────────────────────────────
|
|
function doScan() {
|
|
const box = $('#scan-input').val().trim().toUpperCase();
|
|
if (!box) return;
|
|
|
|
$('#btn-scan').prop('disabled', true).html(
|
|
'<span class="spinner-border spinner-border-sm"></span>');
|
|
|
|
$.post(SCAN_URL, {
|
|
R659PALT: PALT,
|
|
R659BOX: box,
|
|
_token: '{{ csrf_token() }}'
|
|
})
|
|
.done(function(res) {
|
|
showAlert('success', res.message);
|
|
dt.clear().rows.add(res.data).draw();
|
|
updateCount();
|
|
$('#scan-input').val('').focus();
|
|
})
|
|
.fail(function(xhr) {
|
|
showAlert('danger', xhr.responseJSON?.message ?? 'Scan failed.');
|
|
$('#scan-input').select().focus();
|
|
})
|
|
.always(function() {
|
|
$('#btn-scan').prop('disabled', false).html(
|
|
'<i class="fas fa-barcode me-1"></i> Add Box');
|
|
});
|
|
}
|
|
|
|
$('#btn-scan').on('click', doScan);
|
|
$('#scan-input').on('change', function(e) {
|
|
// if (e.key === 'Enter') doScan();
|
|
doScan();
|
|
});
|
|
|
|
// ── Delete box ────────────────────────────────────────────
|
|
$('#boxes-table').on('click', '.btn-delete', function() {
|
|
const box = $(this).data('box');
|
|
|
|
Swal.fire({
|
|
title: 'Remove Box?',
|
|
text: `Remove box ${box} from pallet ${PALT}?`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
confirmButtonText: 'Yes, remove it',
|
|
}).then(result => {
|
|
if (!result.isConfirmed) return;
|
|
|
|
$.post(DEL_URL, {
|
|
R659PALT: PALT,
|
|
R659BOX: box,
|
|
_token: '{{ csrf_token() }}'
|
|
})
|
|
.done(function(res) {
|
|
showAlert('success', res.message);
|
|
dt.clear().rows.add(res.data).draw();
|
|
updateCount();
|
|
})
|
|
.fail(function(xhr) {
|
|
showAlert('danger', xhr.responseJSON?.message ?? 'Delete failed.');
|
|
});
|
|
});
|
|
});
|
|
|
|
// ── Helpers ───────────────────────────────────────────────
|
|
function updateCount() {
|
|
const n = dt.data().length;
|
|
$('#box-count').text(n + ' box' + (n !== 1 ? 'es' : ''));
|
|
}
|
|
|
|
function playDing() {
|
|
const ctx = new AudioContext();
|
|
const oscillator = ctx.createOscillator();
|
|
const gainNode = ctx.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(ctx.destination);
|
|
|
|
oscillator.type = 'sine';
|
|
oscillator.frequency.setValueAtTime(880, ctx.currentTime); // A5 note
|
|
|
|
// Fade out for a natural "ding" decay
|
|
gainNode.gain.setValueAtTime(1, ctx.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.5);
|
|
|
|
oscillator.start(ctx.currentTime);
|
|
oscillator.stop(ctx.currentTime + 1.5);
|
|
}
|
|
|
|
function showAlert(type, msg) {
|
|
let iconType;
|
|
|
|
// Map your badge types to SweetAlert icons
|
|
if (type === 'success') iconType = 'success';
|
|
else if (type === 'danger') iconType = 'error';
|
|
else if (type === 'warning') iconType = 'warning';
|
|
else iconType = 'info';
|
|
|
|
if (type === 'danger' || type === 'warning') {
|
|
playDing();
|
|
|
|
Swal.fire({
|
|
icon: iconType,
|
|
title: msg,
|
|
showConfirmButton: true,
|
|
}).then((result) => {
|
|
if (result.isConfirmed) $('#scan-input').select().focus();
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
toast: true,
|
|
position: 'bottom-end',
|
|
icon: iconType,
|
|
title: msg,
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
timerProgressBar: true
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
document.body.classList.toggle('sb-sidenav-toggled');
|
|
|
|
});
|
|
</script>
|
|
@endsection
|