- Renamed Store Articles card to Set Boxes Locations on warehouse main page - Created new mobile-optimized page with two tabs for box location management: - Tab 1: Assign box to location (scan box, change status to closed, assign location) - Tab 2: Move box from location (scan location, list boxes, move to new location) - Added box status management (open/closed) with status change button - Enforced rule: only closed boxes can be assigned to locations - Moved API logic to warehouse.py module: - search_box_by_number() - assign_box_to_location() - search_location_with_boxes() - move_box_to_new_location() - change_box_status() - Added API routes in routes.py as thin wrappers - Aligned page theme colors with application Bootstrap theme - Added dark mode support for the new page - Added Warehouse Main button to page header - Removed 'INNOFA ROMANIA SRL' branding from: - Print module label preview and PDF generation - Print lost labels page - pdf_generator.py PDF creation function
1099 lines
32 KiB
HTML
1099 lines
32 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Set Boxes Locations{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
/* Mobile-first responsive design - Aligned with application theme */
|
||
.location-manager-container {
|
||
max-width: 100%;
|
||
margin: 0 auto;
|
||
padding: 10px;
|
||
}
|
||
|
||
.main-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Tab navigation */
|
||
.tab-navigation {
|
||
display: flex;
|
||
background: #f8f9fa;
|
||
border-bottom: 2px solid #dee2e6;
|
||
}
|
||
|
||
.tab-button {
|
||
flex: 1;
|
||
padding: 15px 10px;
|
||
background: none;
|
||
border: none;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #6c757d;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
border-bottom: 3px solid transparent;
|
||
}
|
||
|
||
.tab-button.active {
|
||
color: #007bff;
|
||
border-bottom-color: #007bff;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.tab-button:hover:not(.active) {
|
||
background: #e9ecef;
|
||
}
|
||
|
||
/* Tab content */
|
||
.tab-content {
|
||
display: none;
|
||
padding: 20px;
|
||
animation: fadeIn 0.3s;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
/* Form sections */
|
||
.form-section {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.form-section h3 {
|
||
color: #333;
|
||
font-size: 18px;
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.section-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
/* Input groups */
|
||
.input-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.input-group label {
|
||
display: block;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
margin-bottom: 8px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.input-group input,
|
||
.input-group select {
|
||
width: 100%;
|
||
padding: 12px 15px;
|
||
font-size: 16px;
|
||
border: 2px solid #ced4da;
|
||
border-radius: 4px;
|
||
transition: all 0.3s;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.input-group input:focus,
|
||
.input-group select:focus {
|
||
outline: none;
|
||
border-color: #007bff;
|
||
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||
}
|
||
|
||
/* Buttons */
|
||
.btn-primary {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: #007bff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #0056b3;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 4px rgba(0,123,255,0.3);
|
||
}
|
||
|
||
.btn-primary:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.btn-secondary {
|
||
width: 100%;
|
||
padding: 10px;
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
.btn-success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
/* Info display */
|
||
.info-box {
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
margin-top: 15px;
|
||
display: none;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
.info-box.show {
|
||
display: block;
|
||
animation: slideDown 0.3s;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.info-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.info-label {
|
||
font-weight: 600;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.info-value {
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.status-badge.open {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
border: 1px solid #ffc107;
|
||
}
|
||
|
||
.status-badge.closed {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #28a745;
|
||
}
|
||
|
||
.btn-status {
|
||
width: 100%;
|
||
padding: 10px;
|
||
background: #ffc107;
|
||
color: #212529;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.btn-status:hover {
|
||
background: #e0a800;
|
||
}
|
||
|
||
.btn-status:disabled {
|
||
background: #e9ecef;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* Box list */
|
||
.box-list {
|
||
margin-top: 15px;
|
||
display: none;
|
||
}
|
||
|
||
.box-list.show {
|
||
display: block;
|
||
}
|
||
|
||
.box-item {
|
||
background: #ffffff;
|
||
border: 2px solid #dee2e6;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.box-item:hover {
|
||
border-color: #007bff;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.box-item.selected {
|
||
border-color: #007bff;
|
||
background: #e7f1ff;
|
||
}
|
||
|
||
.box-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.box-number {
|
||
font-weight: 700;
|
||
color: #333;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.box-status {
|
||
font-size: 12px;
|
||
color: #6c757d;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.box-icon {
|
||
font-size: 24px;
|
||
color: #6c757d;
|
||
}
|
||
|
||
/* Alert messages */
|
||
.alert {
|
||
padding: 12px 15px;
|
||
border-radius: 4px;
|
||
margin-bottom: 15px;
|
||
font-weight: 500;
|
||
display: none;
|
||
animation: slideDown 0.3s;
|
||
}
|
||
|
||
.alert.show {
|
||
display: block;
|
||
}
|
||
|
||
.alert-success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.alert-error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
.alert-info {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
border: 1px solid #bee5eb;
|
||
}
|
||
|
||
/* Loading spinner */
|
||
.spinner {
|
||
display: none;
|
||
border: 3px solid #f3f3f3;
|
||
border-top: 3px solid #007bff;
|
||
border-radius: 50%;
|
||
width: 20px;
|
||
height: 20px;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Laptop view adjustments */
|
||
@media (min-width: 768px) {
|
||
.location-manager-container {
|
||
padding: 30px;
|
||
max-width: 800px;
|
||
}
|
||
|
||
.tab-button {
|
||
font-size: 16px;
|
||
padding: 18px 20px;
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 30px;
|
||
}
|
||
|
||
.form-section h3 {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.btn-primary,
|
||
.btn-secondary {
|
||
max-width: 400px;
|
||
}
|
||
}
|
||
|
||
/* Accessibility */
|
||
.visually-hidden {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
padding: 0;
|
||
margin: -1px;
|
||
overflow: hidden;
|
||
clip: rect(0,0,0,0);
|
||
white-space: nowrap;
|
||
border: 0;
|
||
}
|
||
|
||
/* Dark Mode Support */
|
||
body.dark-mode .main-card {
|
||
background: #2d2d2d;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .tab-navigation {
|
||
background: #1a1a1a;
|
||
border-bottom-color: #444;
|
||
}
|
||
|
||
body.dark-mode .tab-button {
|
||
color: #aaa;
|
||
}
|
||
|
||
body.dark-mode .tab-button.active {
|
||
color: #4a9eff;
|
||
background: #2d2d2d;
|
||
}
|
||
|
||
body.dark-mode .tab-button:hover:not(.active) {
|
||
background: #333;
|
||
}
|
||
|
||
body.dark-mode .form-section h3 {
|
||
color: #fff;
|
||
}
|
||
|
||
body.dark-mode .input-group label {
|
||
color: #ccc;
|
||
}
|
||
|
||
body.dark-mode .input-group input,
|
||
body.dark-mode .input-group select {
|
||
background: #3a3a3a;
|
||
border-color: #555;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .input-group input:focus,
|
||
body.dark-mode .input-group select:focus {
|
||
border-color: #4a9eff;
|
||
background: #404040;
|
||
}
|
||
|
||
body.dark-mode .info-box {
|
||
background: #1a1a1a;
|
||
border-color: #444;
|
||
}
|
||
|
||
body.dark-mode .info-box h4 {
|
||
color: #fff;
|
||
}
|
||
|
||
body.dark-mode .info-row {
|
||
border-bottom-color: #444;
|
||
}
|
||
|
||
body.dark-mode .info-label {
|
||
color: #aaa;
|
||
}
|
||
|
||
body.dark-mode .info-value {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
body.dark-mode .status-badge.open {
|
||
background: #4a3800;
|
||
color: #ffc107;
|
||
border-color: #6a5000;
|
||
}
|
||
|
||
body.dark-mode .status-badge.closed {
|
||
background: #1a3a1a;
|
||
color: #5cb85c;
|
||
border-color: #2a5a2a;
|
||
}
|
||
|
||
body.dark-mode .box-item {
|
||
background: #3a3a3a;
|
||
border-color: #555;
|
||
}
|
||
|
||
body.dark-mode .box-item:hover {
|
||
border-color: #4a9eff;
|
||
background: #404040;
|
||
}
|
||
|
||
body.dark-mode .box-item.selected {
|
||
border-color: #4a9eff;
|
||
background: #2a4a6a;
|
||
}
|
||
|
||
body.dark-mode .box-number {
|
||
color: #fff;
|
||
}
|
||
|
||
body.dark-mode .box-status {
|
||
color: #aaa;
|
||
}
|
||
|
||
body.dark-mode .box-icon {
|
||
color: #aaa;
|
||
}
|
||
|
||
body.dark-mode .alert-success {
|
||
background: #1a3a1a;
|
||
color: #5cb85c;
|
||
border-color: #2a5a2a;
|
||
}
|
||
|
||
body.dark-mode .alert-error {
|
||
background: #3a1a1a;
|
||
color: #e57373;
|
||
border-color: #5a2a2a;
|
||
}
|
||
|
||
body.dark-mode .alert-info {
|
||
background: #1a2a3a;
|
||
color: #64b5f6;
|
||
border-color: #2a4a6a;
|
||
}
|
||
</style>
|
||
|
||
<div class="location-manager-container">
|
||
<div class="main-card">
|
||
<!-- Tab Navigation -->
|
||
<div class="tab-navigation">
|
||
<button class="tab-button active" onclick="switchTab(0)" id="tab-btn-0">
|
||
📦 Assign Box to Location
|
||
</button>
|
||
<button class="tab-button" onclick="switchTab(1)" id="tab-btn-1">
|
||
📍 Move Box from Location
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab 1: Assign Box to Location -->
|
||
<div class="tab-content active" id="tab-0">
|
||
<div class="form-section">
|
||
<h3><span class="section-icon">📦</span> Scan/Enter Box Number</h3>
|
||
|
||
<div id="alert-tab1" class="alert"></div>
|
||
|
||
<div class="input-group">
|
||
<label for="box-number-input">Box Number</label>
|
||
<input
|
||
type="text"
|
||
id="box-number-input"
|
||
placeholder="Scan or enter box number"
|
||
autocomplete="off"
|
||
autofocus
|
||
>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="searchBox()">
|
||
<span class="spinner" id="spinner-search-box"></span>
|
||
<span id="btn-search-text">🔍 Search Box</span>
|
||
</button>
|
||
|
||
<!-- Box Information -->
|
||
<div class="info-box" id="box-info">
|
||
<h4 style="margin-top: 0; color: #1e293b;">Box Details</h4>
|
||
<div class="info-row">
|
||
<span class="info-label">Box Number:</span>
|
||
<span class="info-value" id="display-box-number">-</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Status:</span>
|
||
<span class="info-value">
|
||
<span class="status-badge" id="display-box-status-badge">-</span>
|
||
</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Current Location:</span>
|
||
<span class="info-value" id="display-current-location">-</span>
|
||
</div>
|
||
|
||
<button class="btn-status" id="btn-change-status" onclick="changeBoxStatus()" style="display: none;">
|
||
<span class="spinner" id="spinner-status" style="display: none;"></span>
|
||
<span id="btn-status-text">🔒 Close Box</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Location Assignment -->
|
||
<div class="form-section" id="assign-section" style="display: none;">
|
||
<h3><span class="section-icon">📍</span> Assign to Location</h3>
|
||
|
||
<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 btn-success" onclick="assignBoxToLocation()">
|
||
<span class="spinner" id="spinner-assign"></span>
|
||
<span id="btn-assign-text">✅ Assign to Location</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 2: Move Box from Location -->
|
||
<div class="tab-content" id="tab-1">
|
||
<div class="form-section">
|
||
<h3><span class="section-icon">📍</span> Scan/Enter Location Code</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="searchLocation()">
|
||
<span class="spinner" id="spinner-search-location"></span>
|
||
<span id="btn-search-location-text">🔍 Search Location</span>
|
||
</button>
|
||
|
||
<!-- Location Information -->
|
||
<div class="info-box" id="location-info">
|
||
<h4 style="margin-top: 0; color: #1e293b;">Location Details</h4>
|
||
<div class="info-row">
|
||
<span class="info-label">Location Code:</span>
|
||
<span class="info-value" id="display-location-code">-</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Boxes Count:</span>
|
||
<span class="info-value" id="display-boxes-count">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Boxes List -->
|
||
<div class="box-list" id="boxes-list">
|
||
<h4 style="margin-bottom: 15px; color: #1e293b;">Boxes in This Location</h4>
|
||
<div id="boxes-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Move Box Section -->
|
||
<div class="form-section" id="move-section" 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">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label for="new-location-input">New Location Code</label>
|
||
<input
|
||
type="text"
|
||
id="new-location-input"
|
||
placeholder="Scan or enter new location code"
|
||
autocomplete="off"
|
||
>
|
||
</div>
|
||
|
||
<button class="btn-primary btn-success" onclick="moveBox()">
|
||
<span class="spinner" id="spinner-move"></span>
|
||
<span id="btn-move-text">🚚 Move to New Location</span>
|
||
</button>
|
||
|
||
<button class="btn-secondary" onclick="clearSelection()">
|
||
Clear Selection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Global variables
|
||
let currentBoxId = null;
|
||
let currentLocationId = null;
|
||
let selectedBoxId = null;
|
||
let selectedBoxNumber = null;
|
||
|
||
// Tab switching
|
||
function switchTab(tabIndex) {
|
||
// Hide all tabs
|
||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
});
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
|
||
// Show selected tab
|
||
document.getElementById(`tab-${tabIndex}`).classList.add('active');
|
||
document.getElementById(`tab-btn-${tabIndex}`).classList.add('active');
|
||
|
||
// Clear alerts and reset states
|
||
hideAlert('alert-tab1');
|
||
hideAlert('alert-tab2');
|
||
|
||
if (tabIndex === 0) {
|
||
document.getElementById('box-number-input').focus();
|
||
} else {
|
||
document.getElementById('location-code-input-tab2').focus();
|
||
}
|
||
}
|
||
|
||
// Alert functions
|
||
function showAlert(elementId, message, type) {
|
||
const alert = document.getElementById(elementId);
|
||
alert.className = `alert alert-${type} show`;
|
||
alert.textContent = message;
|
||
}
|
||
|
||
function hideAlert(elementId) {
|
||
const alert = document.getElementById(elementId);
|
||
alert.classList.remove('show');
|
||
}
|
||
|
||
// Loading spinner functions
|
||
function showSpinner(spinnerId, btnTextId, loadingText) {
|
||
document.getElementById(spinnerId).style.display = 'inline-block';
|
||
if (btnTextId) {
|
||
document.getElementById(btnTextId).textContent = loadingText;
|
||
}
|
||
}
|
||
|
||
function hideSpinner(spinnerId, btnTextId, normalText) {
|
||
document.getElementById(spinnerId).style.display = 'none';
|
||
if (btnTextId) {
|
||
document.getElementById(btnTextId).textContent = normalText;
|
||
}
|
||
}
|
||
|
||
// Tab 1: Change Box Status
|
||
async function changeBoxStatus() {
|
||
if (!currentBoxId) {
|
||
showAlert('alert-tab1', 'No box selected', 'error');
|
||
return;
|
||
}
|
||
|
||
hideAlert('alert-tab1');
|
||
showSpinner('spinner-status', 'btn-status-text', 'Changing...');
|
||
|
||
try {
|
||
const response = await fetch('/api/warehouse/box/change-status', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
box_id: currentBoxId,
|
||
new_status: 'closed'
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
// Update status display
|
||
const statusBadge = document.getElementById('display-box-status-badge');
|
||
statusBadge.textContent = 'closed';
|
||
statusBadge.className = 'status-badge closed';
|
||
|
||
// Hide status button and show assign section
|
||
document.getElementById('btn-change-status').style.display = 'none';
|
||
document.getElementById('assign-section').style.display = 'block';
|
||
|
||
showAlert('alert-tab1', 'Box status changed to closed! You can now assign it to a location.', 'success');
|
||
} else {
|
||
showAlert('alert-tab1', data.message || 'Failed to change box status', 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('alert-tab1', 'Error changing box status', 'error');
|
||
console.error('Error:', error);
|
||
} finally {
|
||
hideSpinner('spinner-status', 'btn-status-text', '🔒 Close Box');
|
||
}
|
||
}
|
||
|
||
// Tab 1: Search Box
|
||
async function searchBox() {
|
||
const boxNumber = document.getElementById('box-number-input').value.trim();
|
||
|
||
if (!boxNumber) {
|
||
showAlert('alert-tab1', 'Please enter a box number', 'error');
|
||
return;
|
||
}
|
||
|
||
hideAlert('alert-tab1');
|
||
showSpinner('spinner-search-box', 'btn-search-text', 'Searching...');
|
||
|
||
try {
|
||
const response = await fetch('/api/warehouse/box/search', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ box_number: boxNumber })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentBoxId = data.box.id;
|
||
document.getElementById('display-box-number').textContent = data.box.box_number;
|
||
|
||
// Update status badge
|
||
const statusBadge = document.getElementById('display-box-status-badge');
|
||
statusBadge.textContent = data.box.status;
|
||
statusBadge.className = `status-badge ${data.box.status}`;
|
||
|
||
document.getElementById('display-current-location').textContent =
|
||
data.box.location_code || 'Not assigned';
|
||
|
||
document.getElementById('box-info').classList.add('show');
|
||
|
||
// Show/hide status change button and assign section based on status
|
||
const statusBtn = document.getElementById('btn-change-status');
|
||
const statusBtnText = document.getElementById('btn-status-text');
|
||
const assignSection = document.getElementById('assign-section');
|
||
|
||
if (data.box.status === 'open') {
|
||
statusBtn.style.display = 'block';
|
||
statusBtnText.textContent = '🔒 Close Box';
|
||
assignSection.style.display = 'none';
|
||
showAlert('alert-tab1', 'Box found! Please close the box before assigning to a location.', 'info');
|
||
} else {
|
||
statusBtn.style.display = 'none';
|
||
assignSection.style.display = 'block';
|
||
showAlert('alert-tab1', 'Box found! You can now assign it to a location.', 'success');
|
||
}
|
||
} else {
|
||
currentBoxId = null;
|
||
document.getElementById('box-info').classList.remove('show');
|
||
document.getElementById('assign-section').style.display = 'none';
|
||
showAlert('alert-tab1', data.message || 'Box not found', 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('alert-tab1', 'Error searching for box', 'error');
|
||
console.error('Error:', error);
|
||
} finally {
|
||
hideSpinner('spinner-search-box', 'btn-search-text', '🔍 Search Box');
|
||
}
|
||
}
|
||
|
||
// Tab 1: Assign Box to Location
|
||
async function assignBoxToLocation() {
|
||
const locationCode = document.getElementById('location-code-input-tab1').value.trim();
|
||
|
||
if (!currentBoxId) {
|
||
showAlert('alert-tab1', 'Please search for a box first', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!locationCode) {
|
||
showAlert('alert-tab1', 'Please enter a location code', 'error');
|
||
return;
|
||
}
|
||
|
||
// Check if box is closed before allowing assignment
|
||
const statusBadge = document.getElementById('display-box-status-badge');
|
||
if (statusBadge.textContent === 'open') {
|
||
showAlert('alert-tab1', 'Please close the box before assigning to a location', 'error');
|
||
return;
|
||
}
|
||
|
||
hideAlert('alert-tab1');
|
||
showSpinner('spinner-assign', 'btn-assign-text', 'Assigning...');
|
||
|
||
try {
|
||
const response = await fetch('/api/warehouse/box/assign-location', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
box_id: currentBoxId,
|
||
location_code: locationCode
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
showAlert('alert-tab1', data.message, 'success');
|
||
|
||
// Update display
|
||
document.getElementById('display-current-location').textContent = locationCode;
|
||
|
||
// Clear inputs
|
||
document.getElementById('box-number-input').value = '';
|
||
document.getElementById('location-code-input-tab1').value = '';
|
||
|
||
// Reset after 2 seconds
|
||
setTimeout(() => {
|
||
document.getElementById('box-info').classList.remove('show');
|
||
document.getElementById('assign-section').style.display = 'none';
|
||
hideAlert('alert-tab1');
|
||
currentBoxId = null;
|
||
document.getElementById('box-number-input').focus();
|
||
}, 2000);
|
||
} else {
|
||
showAlert('alert-tab1', data.message || 'Failed to assign box', 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('alert-tab1', 'Error assigning box to location', 'error');
|
||
console.error('Error:', error);
|
||
} finally {
|
||
hideSpinner('spinner-assign', 'btn-assign-text', '✅ Assign to Location');
|
||
}
|
||
}
|
||
|
||
// Tab 2: Search Location
|
||
async function searchLocation() {
|
||
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-location', 'btn-search-location-text', 'Searching...');
|
||
|
||
try {
|
||
const response = await fetch('/api/warehouse/location/search', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ location_code: locationCode })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentLocationId = data.location.id;
|
||
document.getElementById('display-location-code').textContent = data.location.location_code;
|
||
document.getElementById('display-boxes-count').textContent = data.boxes.length;
|
||
|
||
document.getElementById('location-info').classList.add('show');
|
||
|
||
if (data.boxes.length > 0) {
|
||
displayBoxes(data.boxes);
|
||
document.getElementById('boxes-list').classList.add('show');
|
||
showAlert('alert-tab2', `Found ${data.boxes.length} box(es) in this location`, 'success');
|
||
} else {
|
||
document.getElementById('boxes-list').classList.remove('show');
|
||
showAlert('alert-tab2', 'No boxes found in this location', 'info');
|
||
}
|
||
} else {
|
||
currentLocationId = null;
|
||
document.getElementById('location-info').classList.remove('show');
|
||
document.getElementById('boxes-list').classList.remove('show');
|
||
showAlert('alert-tab2', data.message || 'Location not found', 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('alert-tab2', 'Error searching for location', 'error');
|
||
console.error('Error:', error);
|
||
} finally {
|
||
hideSpinner('spinner-search-location', 'btn-search-location-text', '🔍 Search Location');
|
||
}
|
||
}
|
||
|
||
// Display boxes in location
|
||
function displayBoxes(boxes) {
|
||
const container = document.getElementById('boxes-container');
|
||
container.innerHTML = '';
|
||
|
||
boxes.forEach(box => {
|
||
const boxItem = document.createElement('div');
|
||
boxItem.className = 'box-item';
|
||
boxItem.onclick = () => selectBox(box.id, box.box_number, boxItem);
|
||
|
||
boxItem.innerHTML = `
|
||
<div class="box-info">
|
||
<div class="box-number">📦 ${box.box_number}</div>
|
||
<div class="box-status">Status: ${box.status}</div>
|
||
</div>
|
||
<div class="box-icon">›</div>
|
||
`;
|
||
|
||
container.appendChild(boxItem);
|
||
});
|
||
}
|
||
|
||
// Select a box
|
||
function selectBox(boxId, boxNumber, element) {
|
||
// Remove previous selection
|
||
document.querySelectorAll('.box-item').forEach(item => {
|
||
item.classList.remove('selected');
|
||
});
|
||
|
||
// Add selection
|
||
element.classList.add('selected');
|
||
selectedBoxId = boxId;
|
||
selectedBoxNumber = boxNumber;
|
||
|
||
// Show move section
|
||
document.getElementById('selected-box-display').textContent = boxNumber;
|
||
document.getElementById('move-section').style.display = 'block';
|
||
document.getElementById('new-location-input').focus();
|
||
}
|
||
|
||
// Clear selection
|
||
function clearSelection() {
|
||
selectedBoxId = null;
|
||
selectedBoxNumber = null;
|
||
document.querySelectorAll('.box-item').forEach(item => {
|
||
item.classList.remove('selected');
|
||
});
|
||
document.getElementById('move-section').style.display = 'none';
|
||
document.getElementById('new-location-input').value = '';
|
||
}
|
||
|
||
// Move box to new location
|
||
async function moveBox() {
|
||
const newLocationCode = document.getElementById('new-location-input').value.trim();
|
||
|
||
if (!selectedBoxId) {
|
||
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', 'btn-move-text', 'Moving...');
|
||
|
||
try {
|
||
const response = await fetch('/api/warehouse/box/move-location', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
box_id: selectedBoxId,
|
||
new_location_code: newLocationCode
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
showAlert('alert-tab2', data.message, 'success');
|
||
|
||
// Refresh the location search
|
||
setTimeout(() => {
|
||
clearSelection();
|
||
searchLocation();
|
||
}, 1500);
|
||
} else {
|
||
showAlert('alert-tab2', data.message || 'Failed to move box', 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('alert-tab2', 'Error moving box', 'error');
|
||
console.error('Error:', error);
|
||
} finally {
|
||
hideSpinner('spinner-move', 'btn-move-text', '🚚 Move to New Location');
|
||
}
|
||
}
|
||
|
||
// Handle Enter key press for inputs
|
||
document.getElementById('box-number-input').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
searchBox();
|
||
}
|
||
});
|
||
|
||
document.getElementById('location-code-input-tab1').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
assignBoxToLocation();
|
||
}
|
||
});
|
||
|
||
document.getElementById('location-code-input-tab2').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
searchLocation();
|
||
}
|
||
});
|
||
|
||
document.getElementById('new-location-input').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
moveBox();
|
||
}
|
||
});
|
||
</script>
|
||
{% endblock %}
|