Files
WMS/Modules/M3/Resources/views/liberty-theme/pages/BoxPackToPalletScan.blade.php
2026-09-21 13:25:44 +01:00

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