Files
quality_app-v2/app/templates/modules/warehouse/set_boxes_locations.html
Quality App Developer f54e1bebc3 feat: Add Set Orders on Boxes feature with debouncing and page refresh
- Created warehouse_orders.py module with 8 order management functions
- Added /warehouse/set-orders-on-boxes route and 7 API endpoints
- Implemented 4-tab interface: assign, find, move, and view orders
- Changed assign input from dropdown to text field with BOX validation
- Fixed location join issue in warehouse.py (use boxes_crates.location_id)
- Added debouncing flag to prevent multiple rapid form submissions
- Added page refresh after successful order assignment
- Disabled assign button during processing
- Added page refresh with 2 second delay for UX feedback
- Added CP code validation in inventory page
- Improved modal styling with theme support
- Fixed set_boxes_locations page to refresh box info after assignments
2026-02-02 01:06:03 +02:00

1081 lines
37 KiB
HTML

{% extends "base.html" %}
{% block title %}Set Boxes Locations - Quality App v2{% endblock %}
{% block content %}
<style>
.location-manager-container {
max-width: 1200px;
margin: 0 auto;
padding: 15px;
}
.main-card {
background: var(--bg-primary);
border-radius: 8px;
box-shadow: 0 2px 4px var(--card-shadow);
overflow: hidden;
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
}
/* Tab Navigation */
.tab-navigation {
display: flex;
background: var(--bg-tertiary);
border-bottom: 2px solid var(--border-color);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.tab-button {
flex: 1;
padding: 15px 10px;
background: none;
border: none;
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.3s;
border-bottom: 3px solid transparent;
}
.tab-button.active {
color: #0d6efd;
border-bottom-color: #0d6efd;
background: rgba(13, 110, 253, 0.08);
}
.tab-button:hover {
background: var(--bg-tertiary);
color: #0d6efd;
}
.tab-content {
display: none;
padding: 25px;
background: var(--bg-primary);
color: var(--text-primary);
}
.tab-content.active {
display: block;
}
/* Form Sections */
.form-section {
margin-bottom: 25px;
}
.form-section h3 {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
color: var(--text-primary);
padding-bottom: 10px;
border-bottom: 2px solid var(--border-color);
transition: color 0.3s ease, border-color 0.3s ease;
}
.section-icon {
margin-right: 8px;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
font-weight: 500;
margin-bottom: 8px;
color: var(--text-primary);
font-size: 14px;
transition: color 0.3s ease;
}
.input-group input,
.input-group select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
background: var(--input-bg);
color: var(--text-primary);
transition: border-color 0.3s, background-color 0.3s ease, color 0.3s ease;
}
.input-group input:focus,
.input-group select:focus {
outline: none;
border-color: var(--input-focus-border);
box-shadow: 0 0 0 3px var(--input-focus-shadow);
}
/* Buttons */
.btn-primary {
background: #0d6efd;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
}
.btn-primary:hover {
background: #0b5ed7;
}
.btn-primary:disabled {
background: var(--border-color);
cursor: not-allowed;
}
.btn-secondary {
background: #6c757d;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
margin-top: 5px;
}
.btn-secondary:hover {
background: #5a6268;
}
.btn-success {
background: #198754;
}
.btn-success:hover {
background: #157347;
}
.btn-danger {
background: #dc3545;
}
.btn-danger:hover {
background: #c82333;
}
.btn-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
/* Alerts */
.alert {
padding: 12px 15px;
border-radius: 4px;
margin-bottom: 15px;
display: none;
font-size: 14px;
}
.alert.show {
display: block;
}
.alert-success {
background: rgba(209, 231, 221, 0.3);
color: #0f5132;
border: 1px solid rgba(186, 219, 204, 0.5);
}
[data-theme="dark"] .alert-success {
background: rgba(15, 81, 50, 0.2);
color: #a6d5b9;
border: 1px solid rgba(166, 213, 185, 0.3);
}
.alert-error {
background: rgba(248, 215, 218, 0.3);
color: #842029;
border: 1px solid rgba(245, 194, 199, 0.5);
}
[data-theme="dark"] .alert-error {
background: rgba(132, 32, 41, 0.2);
color: #f8b9c2;
border: 1px solid rgba(248, 185, 194, 0.3);
}
.alert-info {
background: rgba(207, 226, 255, 0.3);
color: #084298;
border: 1px solid rgba(182, 212, 254, 0.5);
}
[data-theme="dark"] .alert-info {
background: rgba(8, 66, 152, 0.2);
color: #8ab4f8;
border: 1px solid rgba(138, 180, 248, 0.3);
}
.alert-warning {
background: rgba(255, 243, 205, 0.3);
color: #664d03;
border: 1px solid rgba(255, 236, 181, 0.5);
}
[data-theme="dark"] .alert-warning {
background: rgba(102, 77, 3, 0.2);
color: #ffd966;
border: 1px solid rgba(255, 217, 102, 0.3);
}
/* Info Boxes */
.info-box {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
display: none;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.info-box.show {
display: block;
}
.info-box h4 {
margin-top: 0;
margin-bottom: 10px;
color: var(--text-primary);
font-size: 16px;
transition: color 0.3s ease;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
transition: border-color 0.3s ease;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-weight: 600;
color: var(--text-secondary);
transition: color 0.3s ease;
}
.info-value {
color: #0d6efd;
font-weight: 500;
}
/* Box List */
.box-list {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
display: none;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.box-list.show {
display: block;
}
.box-list h4 {
margin-top: 0;
margin-bottom: 12px;
color: var(--text-primary);
transition: color 0.3s ease;
}
.box-item {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.3s;
color: var(--text-primary);
}
.box-item:hover {
background: rgba(13, 110, 253, 0.06);
border-color: #0d6efd;
}
.box-item.selected {
background: rgba(13, 110, 253, 0.12);
border-color: #0d6efd;
}
.box-item-info {
flex-grow: 1;
}
.box-number {
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
transition: color 0.3s ease;
}
.box-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 3px;
font-weight: 500;
}
.box-status.open {
background: rgba(212, 237, 218, 0.3);
color: #155724;
}
[data-theme="dark"] .box-status.open {
background: rgba(21, 87, 36, 0.2);
color: #9fee99;
}
.box-status.closed {
background: rgba(248, 215, 218, 0.3);
color: #721c24;
}
[data-theme="dark"] .box-status.closed {
background: rgba(114, 28, 36, 0.2);
color: #f8a6b0;
}
/* Spinner */
.spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid var(--border-color);
border-top: 2px solid #0d6efd;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 5px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Back Button */
.back-button {
margin-bottom: 20px;
}
/* Responsive */
@media (max-width: 768px) {
.tab-button {
font-size: 12px;
padding: 12px 8px;
}
.tab-content {
padding: 15px;
}
.btn-group {
flex-direction: column;
}
.btn-primary, .btn-secondary, .btn-success, .btn-danger {
width: 100%;
}
}
</style>
<div class="location-manager-container">
<!-- Back Button -->
<div class="back-button">
<a href="{{ url_for('warehouse.warehouse_index') }}" class="btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Warehouse
</a>
</div>
<!-- Main Card -->
<div class="main-card">
<!-- Tab Navigation -->
<div class="tab-navigation">
<button class="tab-button active" data-tab="0">
<i class="fas fa-cube"></i> Assign Box to Location
</button>
<button class="tab-button" data-tab="1">
<i class="fas fa-map-pin"></i> Find Boxes by Location
</button>
<button class="tab-button" data-tab="2">
<i class="fas fa-exchange-alt"></i> Move Box to New Location
</button>
</div>
<!-- Tab 0: Assign Box to Location -->
<div class="tab-content active" id="tab-0">
<div class="form-section">
<h3><span class="section-icon">🔍</span> Scan or Enter Box Number</h3>
<div id="alert-tab0" class="alert"></div>
<div class="input-group">
<label for="box-number-input-tab0">Box Number</label>
<input
type="text"
id="box-number-input-tab0"
placeholder="Scan or enter box number"
autocomplete="off"
>
</div>
<button class="btn-primary" onclick="searchBoxTab0()">
<span class="spinner" id="spinner-search-tab0" style="display: none;"></span>
<span id="btn-search-tab0-text">🔍 Search Box</span>
</button>
<!-- Box Information -->
<div class="info-box" id="box-info-tab0">
<h4>Box Details</h4>
<div class="info-row">
<span class="info-label">Box Number:</span>
<span class="info-value" id="display-box-number-tab0">-</span>
</div>
<div class="info-row">
<span class="info-label">Current Status:</span>
<span class="info-value" id="display-box-status-tab0">-</span>
</div>
<div class="info-row">
<span class="info-label">Current Location:</span>
<span class="info-value" id="display-box-location-tab0">-</span>
</div>
</div>
</div>
<!-- Assign Location Section -->
<div class="form-section" id="assign-section-tab0" style="display: none;">
<h3><span class="section-icon">📍</span> Assign to Location</h3>
<div class="input-group">
<label for="location-code-input-tab0">Location Code</label>
<input
type="text"
id="location-code-input-tab0"
placeholder="Scan or enter location code"
autocomplete="off"
>
</div>
<div class="btn-group">
<button class="btn-primary btn-success" onclick="assignBoxToLocationTab0()">
<span class="spinner" id="spinner-assign-tab0" style="display: none;"></span>
<span id="btn-assign-tab0-text">✅ Assign to Location</span>
</button>
<button class="btn-secondary" onclick="clearTab0()">
Clear
</button>
</div>
</div>
</div>
<!-- Tab 1: Find Boxes by Location -->
<div class="tab-content" id="tab-1">
<div class="form-section">
<h3><span class="section-icon">📍</span> Scan or Enter Location Code</h3>
<div id="alert-tab1" class="alert"></div>
<div class="input-group">
<label for="location-code-input-tab1">Location Code</label>
<input
type="text"
id="location-code-input-tab1"
placeholder="Scan or enter location code"
autocomplete="off"
>
</div>
<button class="btn-primary" onclick="searchLocationTab1()">
<span class="spinner" id="spinner-search-tab1" style="display: none;"></span>
<span id="btn-search-tab1-text">🔍 Search Location</span>
</button>
<!-- Location Information -->
<div class="info-box" id="location-info-tab1">
<h4>Location Details</h4>
<div class="info-row">
<span class="info-label">Location Code:</span>
<span class="info-value" id="display-location-code-tab1">-</span>
</div>
<div class="info-row">
<span class="info-label">Boxes Count:</span>
<span class="info-value" id="display-boxes-count-tab1">-</span>
</div>
</div>
<!-- Boxes List -->
<div class="box-list" id="boxes-list-tab1">
<h4>Boxes in This Location</h4>
<div id="boxes-container-tab1"></div>
</div>
<button class="btn-secondary" onclick="clearTab1()" style="display: none;" id="clear-tab1-btn">
Clear Search
</button>
</div>
</div>
<!-- Tab 2: Move Box to New Location -->
<div class="tab-content" id="tab-2">
<div class="form-section">
<h3><span class="section-icon">🔍</span> Find Box to Move</h3>
<div id="alert-tab2" class="alert"></div>
<div class="input-group">
<label for="location-code-input-tab2">Location Code</label>
<input
type="text"
id="location-code-input-tab2"
placeholder="Scan or enter location code"
autocomplete="off"
>
</div>
<button class="btn-primary" onclick="searchLocationTab2()">
<span class="spinner" id="spinner-search-tab2" style="display: none;"></span>
<span id="btn-search-tab2-text">🔍 Search Location</span>
</button>
<!-- Location Information -->
<div class="info-box" id="location-info-tab2">
<h4>Location Details</h4>
<div class="info-row">
<span class="info-label">Location Code:</span>
<span class="info-value" id="display-location-code-tab2">-</span>
</div>
<div class="info-row">
<span class="info-label">Boxes Count:</span>
<span class="info-value" id="display-boxes-count-tab2">-</span>
</div>
</div>
<!-- Boxes List -->
<div class="box-list" id="boxes-list-tab2">
<h4>Boxes in This Location</h4>
<div id="boxes-container-tab2"></div>
</div>
</div>
<!-- Move Box Section -->
<div class="form-section" id="move-section-tab2" style="display: none;">
<h3><span class="section-icon">🚚</span> Move Selected Box</h3>
<div class="info-box show">
<div class="info-row">
<span class="info-label">Selected Box:</span>
<span class="info-value" id="selected-box-display-tab2">-</span>
</div>
</div>
<div class="input-group">
<label for="new-location-input-tab2">New Location Code</label>
<input
type="text"
id="new-location-input-tab2"
placeholder="Scan or enter new location code"
autocomplete="off"
>
</div>
<div class="btn-group">
<button class="btn-primary btn-success" onclick="moveBoxTab2()">
<span class="spinner" id="spinner-move-tab2" style="display: none;"></span>
<span id="btn-move-tab2-text">🚚 Move to New Location</span>
</button>
<button class="btn-secondary" onclick="clearTab2()">
Clear Selection
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentBoxId_Tab0 = null;
let currentBoxId_Tab2 = null;
// Show/hide spinner
function showSpinner(spinnerId, textId, text) {
const spinner = document.getElementById(spinnerId);
const textSpan = document.getElementById(textId);
if (spinner) spinner.style.display = 'inline-block';
if (textSpan) textSpan.textContent = text;
}
function hideSpinner(spinnerId, textId, text) {
const spinner = document.getElementById(spinnerId);
const textSpan = document.getElementById(textId);
if (spinner) spinner.style.display = 'none';
if (textSpan) textSpan.textContent = text;
}
// Show alert
function showAlert(alertId, message, type = 'info') {
const alertEl = document.getElementById(alertId);
if (alertEl) {
alertEl.textContent = message;
alertEl.className = `alert alert-${type} show`;
alertEl.style.display = 'block';
}
}
function hideAlert(alertId) {
const alertEl = document.getElementById(alertId);
if (alertEl) {
alertEl.className = 'alert';
alertEl.style.display = 'none';
}
}
// Tab switching
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', function() {
const tabIndex = this.dataset.tab;
// Remove active from all tabs
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// Add active to clicked tab
this.classList.add('active');
document.getElementById(`tab-${tabIndex}`).classList.add('active');
});
});
// ========== TAB 0: ASSIGN BOX TO LOCATION ==========
async function searchBoxTab0() {
const boxNumber = document.getElementById('box-number-input-tab0').value.trim();
if (!boxNumber) {
showAlert('alert-tab0', 'Please enter a box number', 'error');
return;
}
hideAlert('alert-tab0');
showSpinner('spinner-search-tab0', 'btn-search-tab0-text', 'Searching...');
try {
const response = await fetch('{{ url_for("warehouse.api_search_box") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ box_number: boxNumber })
});
const data = await response.json();
if (data.success) {
currentBoxId_Tab0 = data.box.id;
document.getElementById('display-box-number-tab0').textContent = data.box.box_number;
document.getElementById('display-box-status-tab0').textContent = data.box.status.toUpperCase();
document.getElementById('display-box-location-tab0').textContent = data.box.location_code;
document.getElementById('box-info-tab0').classList.add('show');
document.getElementById('assign-section-tab0').style.display = 'block';
showAlert('alert-tab0', `Box "${boxNumber}" found!`, 'success');
} else {
document.getElementById('assign-section-tab0').style.display = 'none';
showAlert('alert-tab0', data.error || 'Box not found', 'error');
}
} catch (error) {
console.error('Error:', error);
showAlert('alert-tab0', 'An error occurred while searching', 'error');
} finally {
hideSpinner('spinner-search-tab0', 'btn-search-tab0-text', '🔍 Search Box');
}
}
async function assignBoxToLocationTab0() {
const locationCode = document.getElementById('location-code-input-tab0').value.trim();
const boxNumber = document.getElementById('box-number-input-tab0').value.trim();
if (!currentBoxId_Tab0) {
showAlert('alert-tab0', 'Please search for a box first', 'error');
return;
}
if (!locationCode) {
showAlert('alert-tab0', 'Please enter a location code', 'error');
return;
}
hideAlert('alert-tab0');
showSpinner('spinner-assign-tab0', 'btn-assign-tab0-text', 'Assigning...');
try {
const response = await fetch('{{ url_for("warehouse.api_assign_box_to_location") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
box_id: currentBoxId_Tab0,
location_code: locationCode
})
});
const data = await response.json();
if (data.success) {
showAlert('alert-tab0', data.message + ' - Refreshing box info...', 'success');
// Clear location input but keep box number to refresh details
document.getElementById('location-code-input-tab0').value = '';
document.getElementById('assign-section-tab0').style.display = 'none';
// Automatically refresh box details after a brief delay
setTimeout(async () => {
try {
const searchResponse = await fetch('{{ url_for("warehouse.api_search_box") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ box_number: boxNumber })
});
const searchData = await searchResponse.json();
if (searchData.success) {
document.getElementById('display-box-number-tab0').textContent = searchData.box.box_number;
document.getElementById('display-box-status-tab0').textContent = searchData.box.status;
document.getElementById('display-box-location-tab0').textContent = searchData.box.location_code || 'Unassigned';
document.getElementById('box-info-tab0').classList.add('show');
showAlert('alert-tab0', 'Box updated successfully', 'success');
}
} catch (error) {
console.error('Error refreshing box:', error);
}
}, 800);
} else {
showAlert('alert-tab0', data.message || 'Error assigning box', 'error');
}
} catch (error) {
console.error('Error:', error);
showAlert('alert-tab0', 'An error occurred', 'error');
} finally {
hideSpinner('spinner-assign-tab0', 'btn-assign-tab0-text', '✅ Assign to Location');
}
}
function clearTab0() {
document.getElementById('box-number-input-tab0').value = '';
document.getElementById('location-code-input-tab0').value = '';
document.getElementById('box-info-tab0').classList.remove('show');
document.getElementById('assign-section-tab0').style.display = 'none';
hideAlert('alert-tab0');
currentBoxId_Tab0 = null;
}
// ========== TAB 1: FIND BOXES BY LOCATION ==========
async function searchLocationTab1() {
const locationCode = document.getElementById('location-code-input-tab1').value.trim();
if (!locationCode) {
showAlert('alert-tab1', 'Please enter a location code', 'error');
return;
}
hideAlert('alert-tab1');
showSpinner('spinner-search-tab1', 'btn-search-tab1-text', 'Searching...');
try {
const response = await fetch('{{ url_for("warehouse.api_search_location") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ location_code: locationCode })
});
const data = await response.json();
if (data.success) {
document.getElementById('display-location-code-tab1').textContent = data.location.location_code;
document.getElementById('display-boxes-count-tab1').textContent = data.boxes.length;
document.getElementById('location-info-tab1').classList.add('show');
document.getElementById('clear-tab1-btn').style.display = 'block';
if (data.boxes.length > 0) {
displayBoxesTab1(data.boxes);
document.getElementById('boxes-list-tab1').classList.add('show');
} else {
document.getElementById('boxes-list-tab1').classList.remove('show');
showAlert('alert-tab1', `Location "${locationCode}" found but no boxes inside`, 'info');
}
} else {
document.getElementById('location-info-tab1').classList.remove('show');
document.getElementById('boxes-list-tab1').classList.remove('show');
document.getElementById('clear-tab1-btn').style.display = 'none';
showAlert('alert-tab1', data.error || 'Location not found', 'error');
}
} catch (error) {
console.error('Error:', error);
showAlert('alert-tab1', 'An error occurred', 'error');
} finally {
hideSpinner('spinner-search-tab1', 'btn-search-tab1-text', '🔍 Search Location');
}
}
function displayBoxesTab1(boxes) {
const container = document.getElementById('boxes-container-tab1');
container.innerHTML = '';
boxes.forEach(box => {
const boxEl = document.createElement('div');
boxEl.className = 'box-item';
boxEl.innerHTML = `
<div class="box-item-info">
<div class="box-number">${box.box_number}</div>
</div>
<span class="box-status ${box.status}">${box.status.toUpperCase()}</span>
`;
container.appendChild(boxEl);
});
}
function clearTab1() {
document.getElementById('location-code-input-tab1').value = '';
document.getElementById('location-info-tab1').classList.remove('show');
document.getElementById('boxes-list-tab1').classList.remove('show');
document.getElementById('clear-tab1-btn').style.display = 'none';
hideAlert('alert-tab1');
}
// ========== TAB 2: MOVE BOX TO NEW LOCATION ==========
async function searchLocationTab2() {
const locationCode = document.getElementById('location-code-input-tab2').value.trim();
if (!locationCode) {
showAlert('alert-tab2', 'Please enter a location code', 'error');
return;
}
hideAlert('alert-tab2');
showSpinner('spinner-search-tab2', 'btn-search-tab2-text', 'Searching...');
try {
const response = await fetch('{{ url_for("warehouse.api_search_location") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ location_code: locationCode })
});
const data = await response.json();
if (data.success) {
document.getElementById('display-location-code-tab2').textContent = data.location.location_code;
document.getElementById('display-boxes-count-tab2').textContent = data.boxes.length;
document.getElementById('location-info-tab2').classList.add('show');
if (data.boxes.length > 0) {
displayBoxesTab2(data.boxes);
document.getElementById('boxes-list-tab2').classList.add('show');
showAlert('alert-tab2', `Found ${data.boxes.length} box(es). Click to select.`, 'info');
} else {
document.getElementById('boxes-list-tab2').classList.remove('show');
showAlert('alert-tab2', `Location "${locationCode}" is empty`, 'warning');
}
} else {
document.getElementById('location-info-tab2').classList.remove('show');
document.getElementById('boxes-list-tab2').classList.remove('show');
showAlert('alert-tab2', data.error || 'Location not found', 'error');
}
} catch (error) {
console.error('Error:', error);
showAlert('alert-tab2', 'An error occurred', 'error');
} finally {
hideSpinner('spinner-search-tab2', 'btn-search-tab2-text', '🔍 Search Location');
}
}
function displayBoxesTab2(boxes) {
const container = document.getElementById('boxes-container-tab2');
container.innerHTML = '';
boxes.forEach(box => {
const boxEl = document.createElement('div');
boxEl.className = 'box-item';
boxEl.onclick = () => selectBoxTab2(box.id, box.box_number);
boxEl.innerHTML = `
<div class="box-item-info">
<div class="box-number">${box.box_number}</div>
</div>
<span class="box-status ${box.status}">${box.status.toUpperCase()}</span>
`;
container.appendChild(boxEl);
});
}
function selectBoxTab2(boxId, boxNumber) {
currentBoxId_Tab2 = boxId;
document.getElementById('selected-box-display-tab2').textContent = boxNumber;
document.getElementById('move-section-tab2').style.display = 'block';
// Highlight selected box
document.querySelectorAll('#boxes-container-tab2 .box-item').forEach(el => {
el.classList.remove('selected');
});
event.currentTarget.classList.add('selected');
}
async function moveBoxTab2() {
const locationCode = document.getElementById('location-code-input-tab2').value.trim();
const newLocationCode = document.getElementById('new-location-input-tab2').value.trim();
if (!currentBoxId_Tab2) {
showAlert('alert-tab2', 'Please select a box first', 'error');
return;
}
if (!newLocationCode) {
showAlert('alert-tab2', 'Please enter a new location code', 'error');
return;
}
hideAlert('alert-tab2');
showSpinner('spinner-move-tab2', 'btn-move-tab2-text', 'Moving...');
try {
const response = await fetch('{{ url_for("warehouse.api_move_box_to_location") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
box_id: currentBoxId_Tab2,
new_location_code: newLocationCode
})
});
const data = await response.json();
if (data.success) {
showAlert('alert-tab2', data.message + ' - Refreshing location...', 'success');
// Clear the selection but keep the location code to show updated list
document.getElementById('new-location-input-tab2').value = '';
document.getElementById('move-section-tab2').style.display = 'none';
document.getElementById('selected-box-display-tab2').textContent = '-';
currentBoxId_Tab2 = null;
// Automatically refresh and show the updated location list after a brief delay
setTimeout(async () => {
try {
const searchResponse = await fetch('{{ url_for("warehouse.api_search_location") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ location_code: locationCode })
});
const searchData = await searchResponse.json();
if (searchData.success) {
document.getElementById('display-location-code-tab2').textContent = searchData.location.location_code;
document.getElementById('display-boxes-count-tab2').textContent = searchData.boxes.length;
document.getElementById('location-info-tab2').classList.add('show');
if (searchData.boxes.length > 0) {
displayBoxesTab2(searchData.boxes);
document.getElementById('boxes-list-tab2').classList.add('show');
showAlert('alert-tab2', `Location updated - ${searchData.boxes.length} box(es) found`, 'success');
} else {
document.getElementById('boxes-list-tab2').classList.remove('show');
showAlert('alert-tab2', `Location "${locationCode}" is now empty`, 'warning');
}
}
} catch (error) {
console.error('Error refreshing location:', error);
}
}, 800);
} else {
showAlert('alert-tab2', data.message || 'Error moving box', 'error');
}
} catch (error) {
console.error('Error:', error);
showAlert('alert-tab2', 'An error occurred', 'error');
} finally {
hideSpinner('spinner-move-tab2', 'btn-move-tab2-text', '🚚 Move to New Location');
}
}
function clearTab2() {
document.getElementById('location-code-input-tab2').value = '';
document.getElementById('new-location-input-tab2').value = '';
document.getElementById('location-info-tab2').classList.remove('show');
document.getElementById('boxes-list-tab2').classList.remove('show');
document.getElementById('move-section-tab2').style.display = 'none';
document.getElementById('selected-box-display-tab2').textContent = '-';
hideAlert('alert-tab2');
currentBoxId_Tab2 = null;
}
// Auto-submit on Enter key
document.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const activeTab = document.querySelector('.tab-content.active');
if (activeTab.id === 'tab-0') {
if (document.activeElement.id === 'box-number-input-tab0') {
searchBoxTab0();
} else if (document.activeElement.id === 'location-code-input-tab0' && currentBoxId_Tab0) {
assignBoxToLocationTab0();
}
} else if (activeTab.id === 'tab-1') {
if (document.activeElement.id === 'location-code-input-tab1') {
searchLocationTab1();
}
} else if (activeTab.id === 'tab-2') {
if (document.activeElement.id === 'location-code-input-tab2') {
searchLocationTab2();
} else if (document.activeElement.id === 'new-location-input-tab2' && currentBoxId_Tab2) {
moveBoxTab2();
}
}
}
});
</script>
{% endblock %}