|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Pallet Tracker Pro | Customer Management</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script> |
|
|
<style> |
|
|
.scan-animation { |
|
|
animation: scanPulse 2s infinite; |
|
|
} |
|
|
@keyframes scanPulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
|
|
} |
|
|
.pallet-card:hover { |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
.barcode-container { |
|
|
height: 80px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
margin: 10px 0; |
|
|
} |
|
|
.notification { |
|
|
position: fixed; |
|
|
top: 20px; |
|
|
right: 20px; |
|
|
z-index: 1000; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.customer-avatar { |
|
|
background: linear-gradient(135deg, #3b82f6, #8b5cf6); |
|
|
} |
|
|
.tab-content { |
|
|
display: none; |
|
|
} |
|
|
.tab-content.active { |
|
|
display: block; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
|
|
|
<div id="notification-area" class="notification"></div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-8 max-w-6xl"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
|
<div> |
|
|
<h1 class="text-3xl font-bold text-gray-800"> |
|
|
<i class="fas fa-pallet text-blue-500 mr-2"></i> Pallet Tracker Pro |
|
|
</h1> |
|
|
<p class="text-gray-600">Advanced pallet inventory & customer management</p> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<div class="text-right"> |
|
|
<p class="font-medium" id="current-user">Loading user...</p> |
|
|
<p class="text-sm text-gray-500" id="current-location">Location: Warehouse A</p> |
|
|
</div> |
|
|
<div class="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center"> |
|
|
<i class="fas fa-user text-blue-500"></i> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="flex border-b border-gray-200 mb-6"> |
|
|
<button onclick="showTab('dashboard')" class="tab-button py-2 px-4 font-medium text-gray-500 hover:text-blue-500 border-b-2 border-transparent hover:border-blue-500"> |
|
|
<i class="fas fa-tachometer-alt mr-2"></i> Dashboard |
|
|
</button> |
|
|
<button onclick="showTab('customers')" class="tab-button py-2 px-4 font-medium text-gray-500 hover:text-blue-500 border-b-2 border-transparent hover:border-blue-500"> |
|
|
<i class="fas fa-users mr-2"></i> Customers |
|
|
</button> |
|
|
<button onclick="showTab('inventory')" class="tab-button py-2 px-4 font-medium text-gray-500 hover:text-blue-500 border-b-2 border-transparent hover:border-blue-500"> |
|
|
<i class="fas fa-boxes mr-2"></i> Inventory |
|
|
</button> |
|
|
<button onclick="showTab('reports')" class="tab-button py-2 px-4 font-medium text-gray-500 hover:text-blue-500 border-b-2 border-transparent hover:border-blue-500"> |
|
|
<i class="fas fa-chart-bar mr-2"></i> Reports |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="dashboard" class="tab-content active"> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-8"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">Empty Pallets</h2> |
|
|
<div class="bg-blue-100 p-2 rounded-lg"> |
|
|
<i class="fas fa-pallet text-blue-500 text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-end justify-between"> |
|
|
<div> |
|
|
<p class="text-4xl font-bold text-gray-800" id="empty-pallets-count">0</p> |
|
|
<p class="text-gray-500">Available for use</p> |
|
|
</div> |
|
|
<button onclick="incrementEmptyPallets()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-plus mr-1"></i> Add |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">Internal Use</h2> |
|
|
<div class="bg-green-100 p-2 rounded-lg"> |
|
|
<i class="fas fa-warehouse text-green-500 text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-end justify-between"> |
|
|
<div> |
|
|
<p class="text-4xl font-bold text-gray-800" id="internal-pallets-count">0</p> |
|
|
<p class="text-gray-500">Used internally</p> |
|
|
</div> |
|
|
<button onclick="moveToInternalUse()" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-exchange-alt mr-1"></i> Move |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">External Use</h2> |
|
|
<div class="bg-yellow-100 p-2 rounded-lg"> |
|
|
<i class="fas fa-truck text-yellow-500 text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-end justify-between"> |
|
|
<div> |
|
|
<p class="text-4xl font-bold text-gray-800" id="external-pallets-count">0</p> |
|
|
<p class="text-gray-500">With customers</p> |
|
|
</div> |
|
|
<button onclick="moveToExternalUse()" class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-exchange-alt mr-1"></i> Move |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">Total Inventory</h2> |
|
|
<div class="bg-purple-100 p-2 rounded-lg"> |
|
|
<i class="fas fa-clipboard-list text-purple-500 text-xl"></i> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-end justify-between"> |
|
|
<div> |
|
|
<p class="text-4xl font-bold text-gray-800" id="total-pallets-count">0</p> |
|
|
<p class="text-gray-500">All pallets tracked</p> |
|
|
</div> |
|
|
<button onclick="syncInventory()" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-sync-alt mr-1"></i> Sync |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> |
|
|
<i class="fas fa-barcode mr-2 text-blue-500"></i> Quick Scan |
|
|
</h2> |
|
|
|
|
|
<div class="border-2 border-dashed border-gray-300 rounded-xl p-6 flex flex-col items-center justify-center"> |
|
|
<div class="mb-4 text-center"> |
|
|
<div class="scan-animation h-32 w-32 bg-blue-50 rounded-lg flex items-center justify-center mb-4 mx-auto"> |
|
|
<i class="fas fa-barcode text-blue-500 text-4xl"></i> |
|
|
</div> |
|
|
<p class="text-gray-600">Scan pallet barcode to update inventory</p> |
|
|
</div> |
|
|
<button onclick="startScanning()" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg transition-colors"> |
|
|
<i class="fas fa-camera mr-2"></i> Start Scanning |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-history mr-2 text-blue-500"></i> Recent Activity |
|
|
</h2> |
|
|
<button onclick="refreshActivity()" class="text-blue-500 hover:text-blue-700"> |
|
|
<i class="fas fa-sync-alt"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="space-y-4 max-h-96 overflow-y-auto" id="dashboard-activity"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-star mr-2 text-blue-500"></i> Top Customers |
|
|
</h2> |
|
|
</div> |
|
|
|
|
|
<div class="space-y-4" id="top-customers"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="customers" class="tab-content"> |
|
|
<div class="bg-white rounded-xl shadow p-6 mb-8"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-2xl font-semibold text-gray-800"> |
|
|
<i class="fas fa-users mr-2 text-blue-500"></i> Customer Management |
|
|
</h2> |
|
|
<button onclick="openNewCustomerModal()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-plus mr-2"></i> New Customer |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> |
|
|
<div class="md:col-span-2"> |
|
|
<input type="text" id="customer-search" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Search customers..."> |
|
|
</div> |
|
|
<div> |
|
|
<select id="customer-filter" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="all">All Customers</option> |
|
|
<option value="active">Active Only</option> |
|
|
<option value="overdue">With Overdue Pallets</option> |
|
|
<option value="inactive">Inactive</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pallets Out</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Overdue</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="customers-table-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="inventory" class="tab-content"> |
|
|
<div class="bg-white rounded-xl shadow p-6 mb-8"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-2xl font-semibold text-gray-800"> |
|
|
<i class="fas fa-boxes mr-2 text-blue-500"></i> Pallet Inventory |
|
|
</h2> |
|
|
<div class="flex space-x-2"> |
|
|
<select id="inventory-filter" class="px-3 py-1 border rounded-lg focus:ring-blue-500 focus:border-blue-500" onchange="updateInventory()"> |
|
|
<option value="all">All Pallets</option> |
|
|
<option value="empty">Empty</option> |
|
|
<option value="internal">Internal Use</option> |
|
|
<option value="external">External Use</option> |
|
|
<option value="damaged">Damaged</option> |
|
|
</select> |
|
|
<button onclick="refreshInventory()" class="text-blue-500 hover:text-blue-700"> |
|
|
<i class="fas fa-sync-alt"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-50 rounded-lg p-4 mb-6"> |
|
|
<h3 class="text-lg font-medium text-gray-800 mb-3 flex items-center"> |
|
|
<i class="fas fa-keyboard mr-2 text-blue-500"></i> Manual Entry |
|
|
</h3> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Pallet ID</label> |
|
|
<div class="flex space-x-2"> |
|
|
<input type="text" id="pallet-id" class="flex-1 px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Enter pallet ID"> |
|
|
<button onclick="generateBarcode()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-barcode"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div id="barcode-preview" class="barcode-container mt-2"></div> |
|
|
</div> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Status</label> |
|
|
<select id="pallet-status" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="empty">Empty</option> |
|
|
<option value="internal">Internal Use</option> |
|
|
<option value="external">External Use</option> |
|
|
<option value="damaged">Damaged</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Location</label> |
|
|
<select id="pallet-location" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="warehouse_a">Warehouse A</option> |
|
|
<option value="warehouse_b">Warehouse B</option> |
|
|
<option value="loading_dock">Loading Dock</option> |
|
|
<option value="shipping">Shipping Area</option> |
|
|
<option value="customer">Customer Site</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="customer-info-container" class="hidden mt-4"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Customer</label> |
|
|
<select id="customer-name" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
|
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Expected Return Date</label> |
|
|
<input type="date" id="expected-return" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Notes</label> |
|
|
<input type="text" id="pallet-notes" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Optional notes"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6"> |
|
|
<button onclick="addPalletManually()" class="w-full bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-save mr-2"></i> Save Pallet |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Barcode</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pallet ID</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Location</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer/Details</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Updated</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="inventory-table-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="reports" class="tab-content"> |
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-file-invoice mr-2 text-blue-500"></i> External Pallets Report |
|
|
</h2> |
|
|
<button onclick="generateExternalReport()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-download mr-2"></i> Export |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pallet ID</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Out</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expected Return</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Days Overdue</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="external-report-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-chart-line mr-2 text-blue-500"></i> Customer Activity |
|
|
</h2> |
|
|
<button onclick="generateCustomerReport()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors"> |
|
|
<i class="fas fa-download mr-2"></i> Export |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Pallets</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Current Pallets</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Overdue</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Activity</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="customer-report-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow p-6 mb-8"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> |
|
|
<i class="fas fa-chart-pie mr-2 text-blue-500"></i> Pallet Utilization |
|
|
</h2> |
|
|
<select id="chart-timeframe" class="px-3 py-1 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="week">Last 7 Days</option> |
|
|
<option value="month">Last 30 Days</option> |
|
|
<option value="quarter">Last 90 Days</option> |
|
|
<option value="year">Last 12 Months</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="h-80 flex items-center justify-center bg-gray-50 rounded-lg"> |
|
|
<p class="text-gray-500">Chart visualization would appear here in a real application</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="customer-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-xl shadow-xl w-full max-w-4xl max-h-[90vh] overflow-y-auto"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-start mb-6"> |
|
|
<div> |
|
|
<h2 class="text-2xl font-semibold text-gray-800" id="customer-modal-title">Customer Details</h2> |
|
|
<p class="text-gray-600" id="customer-modal-subtitle">View and edit customer information</p> |
|
|
</div> |
|
|
<button onclick="closeCustomerModal()" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times text-xl"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> |
|
|
|
|
|
<div class="md:col-span-1"> |
|
|
<div class="flex flex-col items-center mb-6"> |
|
|
<div class="customer-avatar h-24 w-24 rounded-full flex items-center justify-center text-white text-4xl mb-3" id="customer-avatar"> |
|
|
|
|
|
</div> |
|
|
<h3 class="text-xl font-semibold text-gray-800" id="customer-name-display">Customer Name</h3> |
|
|
<p class="text-gray-500" id="customer-id">ID: CUST-0000</p> |
|
|
|
|
|
<div class="mt-4"> |
|
|
<span id="customer-status-badge" class="px-3 py-1 rounded-full text-sm font-medium"> |
|
|
|
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="space-y-3"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Account Type</label> |
|
|
<select id="customer-type" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="standard">Standard</option> |
|
|
<option value="preferred">Preferred</option> |
|
|
<option value="vip">VIP</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Account Status</label> |
|
|
<select id="customer-status" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
|
|
<option value="active">Active</option> |
|
|
<option value="pending">Pending</option> |
|
|
<option value="suspended">Suspended</option> |
|
|
<option value="inactive">Inactive</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Credit Limit</label> |
|
|
<input type="number" id="customer-credit" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Credit limit"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="md:col-span-2"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Company Name</label> |
|
|
<input type="text" id="customer-company" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Company name"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Contact Person</label> |
|
|
<input type="text" id="customer-contact" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Contact person"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Email</label> |
|
|
<input type="email" id="customer-email" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Email address"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Phone</label> |
|
|
<input type="tel" id="customer-phone" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Phone number"> |
|
|
</div> |
|
|
<div class="md:col-span-2"> |
|
|
<label class="block text-gray-700 mb-1">Address</label> |
|
|
<input type="text" id="customer-address" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Street address"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">City</label> |
|
|
<input type="text" id="customer-city" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="City"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">ZIP/Postal Code</label> |
|
|
<input type="text" id="customer-zip" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="ZIP code"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-gray-700 mb-1">Notes</label> |
|
|
<textarea id="customer-notes" class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500" rows="3" placeholder="Additional notes about this customer"></textarea> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center"> |
|
|
<i class="fas fa-pallet mr-2 text-blue-500"></i> Current Pallets |
|
|
</h3> |
|
|
|
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pallet ID</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Out</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expected Return</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Days Overdue</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="customer-pallets-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center"> |
|
|
<i class="fas fa-history mr-2 text-blue-500"></i> Activity History |
|
|
</h3> |
|
|
|
|
|
<div class="space-y-3 max-h-60 overflow-y-auto p-2" id="customer-activity"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex justify-end space-x-3 pt-4 border-t"> |
|
|
<button onclick="closeCustomerModal()" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50"> |
|
|
Cancel |
|
|
</button> |
|
|
<button onclick="deleteCustomer()" class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg"> |
|
|
<i class="fas fa-trash mr-2"></i> Delete |
|
|
</button> |
|
|
<button onclick="saveCustomer()" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg"> |
|
|
<i class="fas fa-save mr-2"></i> Save Changes |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<script> |
|
|
// Mock database - in a real app this would be replaced with Firebase/Firestore or similar |
|
|
let mockDatabase = { |
|
|
emptyPallets: 42, |
|
|
internalPallets: 85, |
|
|
externalPallets: 73, |
|
|
totalPallets: 200, |
|
|
inventory: [ |
|
|
{ |
|
|
palletId: "PLT-4892", |
|
|
status: "empty", |
|
|
location: "Warehouse A", |
|
|
lastUpdated: "2023-11-15T10:30:00" |
|
|
}, |
|
|
{ |
|
|
palletId: "PLT-3821", |
|
|
status: "internal", |
|
|
location: "Loading Dock", |
|
|
lastUpdated: "2023-11-15T09:15:00" |
|
|
}, |
|
|
{ |
|
|
palletId: "PLT-5567", |
|
|
status: "internal", |
|
|
location: "Warehouse B", |
|
|
lastUpdated: "2023-11-14T14:45:00" |
|
|
}, |
|
|
{ |
|
|
palletId: "PLT-1298", |
|
|
status: "external", |
|
|
location: "Customer Site", |
|
|
customer: "CUST-1001", |
|
|
expectedReturn: "2023-12-15", |
|
|
notes: "For Acme Corp project", |
|
|
lastUpdated: "2023-11-10T11:20:00" |
|
|
}, |
|
|
{ |
|
|
palletId: "PLT-6734", |
|
|
status: "external", |
|
|
location: "Customer Site", |
|
|
customer: "CUST-1002", |
|
|
expectedReturn: "2023-11-20", |
|
|
notes: "Urgent shipment", |
|
|
lastUpdated: "2023-11-05T08:45:00" |
|
|
}, |
|
|
{ |
|
|
palletId: "PLT-9012", |
|
|
status: "damaged", |
|
|
location: "Shipping Area", |
|
|
lastUpdated: "2023-11-14T11:20:00" |
|
|
} |
|
|
], |
|
|
customers: [ |
|
|
{ |
|
|
id: "CUST-1001", |
|
|
company: "Acme Corporation", |
|
|
contact: "John Smith", |
|
|
email: "john.smith@acme.com", |
|
|
phone: "+1 (555) 123-4567", |
|
|
address: "123 Business Ave", |
|
|
city: "New York", |
|
|
zip: "10001", |
|
|
type: "preferred", |
|
|
status: "active", |
|
|
creditLimit: 5000, |
|
|
notes: "Regular customer, pays on time", |
|
|
createdAt: "2022-05-15T09:30:00", |
|
|
palletsHistory: 24, |
|
|
currentPallets: 3 |
|
|
}, |
|
|
{ |
|
|
id: "CUST-1002", |
|
|
company: "Globex Industries", |
|
|
contact: "Sarah Johnson", |
|
|
email: "s.johnson@globex.com", |
|
|
phone: "+1 (555) 987-6543", |
|
|
address: "456 Industrial Way", |
|
|
city: "Chicago", |
|
|
zip: "60601", |
|
|
type: "standard", |
|
|
status: "active", |
|
|
creditLimit: 2500, |
|
|
notes: "Occasional late payments", |
|
|
createdAt: "2023-01-10T14:15:00", |
|
|
palletsHistory: 12, |
|
|
currentPallets: 2 |
|
|
}, |
|
|
{ |
|
|
id: "CUST-1003", |
|
|
company: "Oceanic Airlines", |
|
|
contact: "Michael Chang", |
|
|
email: "michael.chang@oceanic.com", |
|
|
phone: "+1 (555) 456-7890", |
|
|
address: "789 Airport Blvd", |
|
|
city: "Los Angeles", |
|
|
zip: "90045", |
|
|
type: "vip", |
|
|
status: "active", |
|
|
creditLimit: 10000, |
|
|
notes: "High volume customer", |
|
|
createdAt: "2021-11-22T11:00:00", |
|
|
palletsHistory: 87, |
|
|
currentPallets: 5 |
|
|
}, |
|
|
{ |
|
|
id: "CUST-1004", |
|
|
company: "Stark Industries", |
|
|
contact: "Tony Stark", |
|
|
email: "tony@stark.com", |
|
|
phone: "+1 (555) 789-0123", |
|
|
address: "1 Stark Tower", |
|
|
city: "New York", |
|
|
zip: "10001", |
|
|
type: "vip", |
|
|
status: "active", |
|
|
creditLimit: 20000, |
|
|
notes: "Special pricing agreement", |
|
|
createdAt: "2020-08-05T16:45:00", |
|
|
palletsHistory: 156, |
|
|
currentPallets: 8 |
|
|
}, |
|
|
{ |
|
|
id: "CUST-1005", |
|
|
company: "Wayne Enterprises", |
|
|
contact: "Bruce Wayne", |
|
|
email: "b.wayne@wayne.com", |
|
|
phone: "+1 (555) 321-6547", |
|
|
address: "1007 Mountain Drive", |
|
|
city: "Gotham", |
|
|
zip: "10002", |
|
|
type: "preferred", |
|
|
status: "pending", |
|
|
creditLimit: 7500, |
|
|
notes: "New customer, pending credit approval", |
|
|
createdAt: "2023-10-01T10:00:00", |
|
|
palletsHistory: 0, |
|
|
currentPallets: 0 |
|
|
} |
|
|
], |
|
|
activityLog: [ |
|
|
{ time: "2 min ago", palletId: "PLT-4892", action: "Scanned (Empty)", user: "John D.", location: "Warehouse A" }, |
|
|
{ time: "15 min ago", palletId: "PLT-3821", action: "Status Changed (Internal Use)", user: "Sarah M.", location: "Loading Dock" }, |
|
|
{ time: "32 min ago", palletId: "PLT-5567", action: "Added to Inventory", user: "Mike T.", location: "Warehouse B" }, |
|
|
{ time: "1 hour ago", palletId: "PLT-1298", action: "Marked for External Use", user: "Lisa K.", location: "Acme Corp" }, |
|
|
{ time: "2 hours ago", palletId: "N/A", action: "Customer Updated: Globex Industries", user: "John D.", location: "System" }, |
|
|
{ time: "3 hours ago", palletId: "PLT-6734", action: "Reported as Overdue", user: "System", location: "Globex Industries" }, |
|
|
{ time: "5 hours ago", palletId: "N/A", action: "New Customer Added: Wayne Enterprises", user: "Sarah M.", location: "System" } |
|
|
], |
|
|
teamMembers: [ |
|
|
{ name: "John D.", role: "Warehouse Supervisor", active: true }, |
|
|
{ name: "Sarah M.", role: "Inventory Manager", active: true }, |
|
|
{ name: "Mike T.", role: "Forklift Operator", active: false }, |
|
|
{ name: "Lisa K.", role: "Shipping Coordinator", active: true } |
|
|
], |
|
|
chatMessages: [] |
|
|
}; |
|
|
|
|
|
// Current user simulation |
|
|
const currentUser = { |
|
|
name: "John Doe", |
|
|
role: "Warehouse Supervisor", |
|
|
location: "Warehouse A" |
|
|
}; |
|
|
|
|
|
// Current customer being viewed/edited |
|
|
let currentCustomer = null; |
|
|
|
|
|
// Initialize the app |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
// Set current user info |
|
|
document.getElementById('current-user').textContent = currentUser.name + " (" + currentUser.role + ")"; |
|
|
document.getElementById('current-location').textContent = "Location: " + currentUser.location; |
|
|
|
|
|
// Load initial counts |
|
|
updateCounts(); |
|
|
|
|
|
// Load inventory |
|
|
updateInventory(); |
|
|
|
|
|
// Load activity log |
|
|
updateActivityLog(); |
|
|
|
|
|
// Load team members |
|
|
updateTeamMembers(); |
|
|
|
|
|
// Load external pallets report |
|
|
updateExternalReport(); |
|
|
|
|
|
// Load customers |
|
|
updateCustomersList(); |
|
|
updateCustomerDropdown(); |
|
|
updateCustomerReport(); |
|
|
|
|
|
// Load dashboard activity |
|
|
updateDashboardActivity(); |
|
|
updateTopCustomers(); |
|
|
|
|
|
// Show customer info when external is selected |
|
|
document.getElementById('pallet-status').addEventListener('change', function() { |
|
|
const customerInfo = document.getElementById('customer-info-container'); |
|
|
if (this.value === 'external') { |
|
|
customerInfo.classList.remove('hidden'); |
|
|
} else { |
|
|
customerInfo.classList.add('hidden'); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
// Tab navigation |
|
|
function showTab(tabId) { |
|
|
// Hide all tabs |
|
|
document.querySelectorAll('.tab-content').forEach(tab => { |
|
|
tab.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
// Deactivate all tab buttons |
|
|
document.querySelectorAll('.tab-button').forEach(button => { |
|
|
button.classList.remove('border-blue-500', 'text-blue-600'); |
|
|
button.classList.add('border-transparent', 'text-gray-500'); |
|
|
}); |
|
|
|
|
|
// Show selected tab |
|
|
document.getElementById(tabId).classList.add('active'); |
|
|
|
|
|
// Activate selected tab button |
|
|
const buttons = document.querySelectorAll('.tab-button'); |
|
|
for (let i = 0; i < buttons.length; i++) { |
|
|
if (buttons[i].getAttribute('onclick').includes(tabId)) { |
|
|
buttons[i].classList.remove('border-transparent', 'text-gray-500'); |
|
|
buttons[i].classList.add('border-blue-500', 'text-blue-600'); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Update the pallet counts display |
|
|
function updateCounts() { |
|
|
document.getElementById('empty-pallets-count').textContent = mockDatabase.emptyPallets; |
|
|
document.getElementById('internal-pallets-count').textContent = mockDatabase.internalPallets; |
|
|
document.getElementById('external-pallets-count').textContent = mockDatabase.externalPallets; |
|
|
document.getElementById('total-pallets-count').textContent = mockDatabase.totalPallets; |
|
|
} |
|
|
|
|
|
// Update inventory display |
|
|
function updateInventory() { |
|
|
const tableBody = document.getElementById('inventory-table-body'); |
|
|
tableBody.innerHTML = ""; |
|
|
|
|
|
const filter = document.getElementById('inventory-filter').value; |
|
|
|
|
|
mockDatabase.inventory.forEach(item => { |
|
|
// Apply filter if not showing all |
|
|
if (filter !== 'all' && item.status !== filter) { |
|
|
return; |
|
|
} |
|
|
|
|
|
const row = document.createElement('tr'); |
|
|
|
|
|
// Barcode cell |
|
|
const barcodeCell = document.createElement('td'); |
|
|
barcodeCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const barcodeDiv = document.createElement('div'); |
|
|
barcodeDiv.className = "barcode-container"; |
|
|
barcodeDiv.style.width = "150px"; |
|
|
|
|
|
const barcodeSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
|
|
barcodeSvg.setAttribute("id", "barcode-" + item.palletId.replace(/[^a-zA-Z0-9]/g, '')); |
|
|
barcodeSvg.setAttribute("width", "150"); |
|
|
barcodeSvg.setAttribute("height", "50"); |
|
|
|
|
|
barcodeDiv.appendChild(barcodeSvg); |
|
|
barcodeCell.appendChild(barcodeDiv); |
|
|
|
|
|
// Pallet ID cell |
|
|
const palletCell = document.createElement('td'); |
|
|
palletCell.className = "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"; |
|
|
palletCell.textContent = item.palletId; |
|
|
|
|
|
// Status cell |
|
|
const statusCell = document.createElement('td'); |
|
|
statusCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const statusSpan = document.createElement('span'); |
|
|
statusSpan.className = "px-2 inline-flex text-xs leading-5 font-semibold rounded-full "; |
|
|
|
|
|
if (item.status === "empty") { |
|
|
statusSpan.className += "bg-blue-100 text-blue-800"; |
|
|
statusSpan.textContent = "Empty"; |
|
|
} else if (item.status === "internal") { |
|
|
statusSpan.className += "bg-green-100 text-green-800"; |
|
|
statusSpan.textContent = "Internal Use"; |
|
|
} else if (item.status === "external") { |
|
|
statusSpan.className += "bg-yellow-100 text-yellow-800"; |
|
|
statusSpan.textContent = "External Use"; |
|
|
} else { |
|
|
statusSpan.className += "bg-red-100 text-red-800"; |
|
|
statusSpan.textContent = "Damaged"; |
|
|
} |
|
|
|
|
|
statusCell.appendChild(statusSpan); |
|
|
|
|
|
// Location cell |
|
|
const locationCell = document.createElement('td'); |
|
|
locationCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
locationCell.textContent = item.location; |
|
|
|
|
|
// Customer/Details cell |
|
|
const detailsCell = document.createElement('td'); |
|
|
detailsCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
|
|
|
if (item.status === "external") { |
|
|
const customer = mockDatabase.customers.find(c => c.id === item.customer); |
|
|
const customerName = customer ? customer.company : "Unknown Customer"; |
|
|
|
|
|
detailsCell.innerHTML = ` |
|
|
<div class="font-medium">${customerName}</div> |
|
|
<div class="text-xs">${item.notes || 'No notes'}</div> |
|
|
`; |
|
|
} else if (item.status === "internal") { |
|
|
detailsCell.textContent = "Internal circulation"; |
|
|
} else { |
|
|
detailsCell.textContent = "N/A"; |
|
|
} |
|
|
|
|
|
// Last updated cell |
|
|
const updatedCell = document.createElement('td'); |
|
|
updatedCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
updatedCell.textContent = new Date(item.lastUpdated).toLocaleString(); |
|
|
|
|
|
// Actions cell |
|
|
const actionsCell = document.createElement('td'); |
|
|
actionsCell.className = "px-6 py-4 whitespace-nowrap text-sm font-medium"; |
|
|
|
|
|
const returnButton = document.createElement('button'); |
|
|
returnButton.className = "text-blue-600 hover:text-blue-900 mr-3"; |
|
|
returnButton.innerHTML = '<i class="fas fa-undo"></i>'; |
|
|
returnButton.title = "Mark as returned"; |
|
|
returnButton.onclick = function() { |
|
|
markAsReturned(item.palletId); |
|
|
}; |
|
|
|
|
|
const editButton = document.createElement('button'); |
|
|
editButton.className = "text-green-600 hover:text-green-900 mr-3"; |
|
|
editButton.innerHTML = '<i class="fas fa-edit"></i>'; |
|
|
editButton.title = "Edit pallet"; |
|
|
editButton.onclick = function() { |
|
|
editPallet(item.palletId); |
|
|
}; |
|
|
|
|
|
const deleteButton = document.createElement('button'); |
|
|
deleteButton.className = "text-red-600 hover:text-red-900"; |
|
|
deleteButton.innerHTML = '<i class="fas fa-trash"></i>'; |
|
|
deleteButton.title = "Delete pallet"; |
|
|
deleteButton.onclick = function() { |
|
|
deletePallet(item.palletId); |
|
|
}; |
|
|
|
|
|
// Only show return button for external pallets |
|
|
if (item.status === "external") { |
|
|
actionsCell.appendChild(returnButton); |
|
|
} |
|
|
|
|
|
actionsCell.appendChild(editButton); |
|
|
actionsCell.appendChild(deleteButton); |
|
|
|
|
|
row.appendChild(barcodeCell); |
|
|
row.appendChild(palletCell); |
|
|
row.appendChild(statusCell); |
|
|
row.appendChild(locationCell); |
|
|
row.appendChild(detailsCell); |
|
|
row.appendChild(updatedCell); |
|
|
row.appendChild(actionsCell); |
|
|
|
|
|
tableBody.appendChild(row); |
|
|
|
|
|
// Generate barcode for this item |
|
|
setTimeout(() => { |
|
|
JsBarcode("#barcode-" + item.palletId.replace(/[^a-zA-Z0-9]/g, ''), item.palletId, { |
|
|
format: "CODE128", |
|
|
lineColor: "#000", |
|
|
width: 1.5, |
|
|
height: 50, |
|
|
displayValue: false |
|
|
}); |
|
|
}, 100); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update customers list |
|
|
function updateCustomersList() { |
|
|
const tableBody = document.getElementById('customers-table-body'); |
|
|
tableBody.innerHTML = ""; |
|
|
|
|
|
const searchTerm = document.getElementById('customer-search').value.toLowerCase(); |
|
|
const filter = document.getElementById('customer-filter').value; |
|
|
|
|
|
mockDatabase.customers.forEach(customer => { |
|
|
// Apply search filter |
|
|
if (searchTerm && !customer.company.toLowerCase().includes(searchTerm) && |
|
|
!customer.contact.toLowerCase().includes(searchTerm) && |
|
|
!customer.id.toLowerCase().includes(searchTerm)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
// Apply status filter |
|
|
if (filter === "active" && customer.status !== "active") return; |
|
|
if (filter === "overdue" && customer.currentPallets === 0) return; |
|
|
if (filter === "inactive" && customer.status === "active") return; |
|
|
|
|
|
const row = document.createElement('tr'); |
|
|
|
|
|
// Customer cell |
|
|
const customerCell = document.createElement('td'); |
|
|
customerCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const customerDiv = document.createElement('div'); |
|
|
customerDiv.className = "flex items-center"; |
|
|
|
|
|
const avatarDiv = document.createElement('div'); |
|
|
avatarDiv.className = "flex-shrink-0 h-10 w-10"; |
|
|
|
|
|
const avatarInner = document.createElement('div'); |
|
|
avatarInner.className = "customer-avatar h-10 w-10 rounded-full flex items-center justify-center text-white"; |
|
|
|
|
|
// Get initials for avatar |
|
|
const initials = customer.company.split(' ').map(word => word[0]).join('').substring(0, 2); |
|
|
avatarInner.textContent = initials; |
|
|
|
|
|
avatarDiv.appendChild(avatarInner); |
|
|
|
|
|
const infoDiv = document.createElement('div'); |
|
|
infoDiv.className = "ml-4"; |
|
|
|
|
|
const nameDiv = document.createElement('div'); |
|
|
nameDiv.className = "text-sm font-medium text-gray-900"; |
|
|
nameDiv.textContent = customer.company; |
|
|
|
|
|
const idDiv = document.createElement('div'); |
|
|
idDiv.className = "text-sm text-gray-500"; |
|
|
idDiv.textContent = customer.id; |
|
|
|
|
|
infoDiv.appendChild(nameDiv); |
|
|
infoDiv.appendChild(idDiv); |
|
|
|
|
|
customerDiv.appendChild(avatarDiv); |
|
|
customerDiv.appendChild(infoDiv); |
|
|
|
|
|
customerCell.appendChild(customerDiv); |
|
|
|
|
|
// Contact cell |
|
|
const contactCell = document.createElement('td'); |
|
|
contactCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const contactDiv = document.createElement('div'); |
|
|
contactDiv.className = "text-sm text-gray-900"; |
|
|
contactDiv.textContent = customer.contact; |
|
|
|
|
|
const emailDiv = document.createElement('div'); |
|
|
emailDiv.className = "text-sm text-gray-500"; |
|
|
emailDiv.textContent = customer.email; |
|
|
|
|
|
contactCell.appendChild(contactDiv); |
|
|
contactCell.appendChild(emailDiv); |
|
|
|
|
|
// Pallets Out cell |
|
|
const palletsCell = document.createElement('td'); |
|
|
palletsCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-900"; |
|
|
palletsCell.textContent = customer.currentPallets; |
|
|
|
|
|
// Overdue cell |
|
|
const overdueCell = document.createElement('td'); |
|
|
overdueCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
// Calculate overdue pallets for this customer |
|
|
const overduePallets = mockDatabase.inventory.filter(item => |
|
|
item.status === "external" && |
|
|
item.customer === customer.id && |
|
|
new Date(item.expectedReturn) < new Date() |
|
|
).length; |
|
|
|
|
|
if (overduePallets > 0) { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">${overduePallets} overdue</span>`; |
|
|
} else { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">On time</span>`; |
|
|
} |
|
|
|
|
|
// Status cell |
|
|
const statusCell = document.createElement('td'); |
|
|
statusCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const statusSpan = document.createElement('span'); |
|
|
statusSpan.className = "px-2 inline-flex text-xs leading-5 font-semibold rounded-full "; |
|
|
|
|
|
if (customer.status === "active") { |
|
|
statusSpan.className += "bg-green-100 text-green-800"; |
|
|
statusSpan.textContent = "Active"; |
|
|
} else if (customer.status === "pending") { |
|
|
statusSpan.className += "bg-yellow-100 text-yellow-800"; |
|
|
statusSpan.textContent = "Pending"; |
|
|
} else { |
|
|
statusSpan.className += "bg-gray-100 text-gray-800"; |
|
|
statusSpan.textContent = "Inactive"; |
|
|
} |
|
|
|
|
|
statusCell.appendChild(statusSpan); |
|
|
|
|
|
// Actions cell |
|
|
const actionsCell = document.createElement('td'); |
|
|
actionsCell.className = "px-6 py-4 whitespace-nowrap text-sm font-medium"; |
|
|
|
|
|
const viewButton = document.createElement('button'); |
|
|
viewButton.className = "text-blue-600 hover:text-blue-900 mr-3"; |
|
|
viewButton.innerHTML = '<i class="fas fa-eye"></i>'; |
|
|
viewButton.title = "View customer"; |
|
|
viewButton.onclick = function() { |
|
|
viewCustomer(customer.id); |
|
|
}; |
|
|
|
|
|
const editButton = document.createElement('button'); |
|
|
editButton.className = "text-green-600 hover:text-green-900"; |
|
|
editButton.innerHTML = '<i class="fas fa-edit"></i>'; |
|
|
editButton.title = "Edit customer"; |
|
|
editButton.onclick = function() { |
|
|
editCustomer(customer.id); |
|
|
}; |
|
|
|
|
|
actionsCell.appendChild(viewButton); |
|
|
actionsCell.appendChild(editButton); |
|
|
|
|
|
row.appendChild(customerCell); |
|
|
row.appendChild(contactCell); |
|
|
row.appendChild(palletsCell); |
|
|
row.appendChild(overdueCell); |
|
|
row.appendChild(statusCell); |
|
|
row.appendChild(actionsCell); |
|
|
|
|
|
tableBody.appendChild(row); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update customer dropdown in pallet form |
|
|
function updateCustomerDropdown() { |
|
|
const select = document.getElementById('customer-name'); |
|
|
select.innerHTML = ""; |
|
|
|
|
|
mockDatabase.customers.forEach(customer => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = customer.id; |
|
|
option.textContent = `${customer.company} (${customer.id})`; |
|
|
select.appendChild(option); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update external pallets report |
|
|
function updateExternalReport() { |
|
|
const tableBody = document.getElementById('external-report-body'); |
|
|
tableBody.innerHTML = ""; |
|
|
|
|
|
const externalPallets = mockDatabase.inventory.filter(item => item.status === "external"); |
|
|
|
|
|
externalPallets.forEach(item => { |
|
|
const row = document.createElement('tr'); |
|
|
|
|
|
// Pallet ID cell |
|
|
const palletCell = document.createElement('td'); |
|
|
palletCell.className = "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"; |
|
|
palletCell.textContent = item.palletId; |
|
|
|
|
|
// Customer cell |
|
|
const customerCell = document.createElement('td'); |
|
|
customerCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
|
|
|
const customer = mockDatabase.customers.find(c => c.id === item.customer); |
|
|
if (customer) { |
|
|
customerCell.textContent = customer.company; |
|
|
} else { |
|
|
customerCell.textContent = "Unknown Customer"; |
|
|
} |
|
|
|
|
|
// Date Out cell |
|
|
const dateOutCell = document.createElement('td'); |
|
|
dateOutCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
dateOutCell.textContent = new Date(item.lastUpdated).toLocaleDateString(); |
|
|
|
|
|
// Expected Return cell |
|
|
const expectedReturnCell = document.createElement('td'); |
|
|
expectedReturnCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
expectedReturnCell.textContent = new Date(item.expectedReturn).toLocaleDateString(); |
|
|
|
|
|
// Days Overdue cell |
|
|
const overdueCell = document.createElement('td'); |
|
|
overdueCell.className = "px-6 py-4 whitespace-nowrap text-sm font-medium"; |
|
|
|
|
|
const today = new Date(); |
|
|
const returnDate = new Date(item.expectedReturn); |
|
|
const daysOverdue = Math.floor((today - returnDate) / (1000 * 60 * 60 * 24)); |
|
|
|
|
|
if (daysOverdue > 0) { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">${daysOverdue} days</span>`; |
|
|
} else { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">On time</span>`; |
|
|
} |
|
|
|
|
|
row.appendChild(palletCell); |
|
|
row.appendChild(customerCell); |
|
|
row.appendChild(dateOutCell); |
|
|
row.appendChild(expectedReturnCell); |
|
|
row.appendChild(overdueCell); |
|
|
|
|
|
tableBody.appendChild(row); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update customer activity report |
|
|
function updateCustomerReport() { |
|
|
const tableBody = document.getElementById('customer-report-body'); |
|
|
tableBody.innerHTML = ""; |
|
|
|
|
|
mockDatabase.customers.forEach(customer => { |
|
|
const row = document.createElement('tr'); |
|
|
|
|
|
// Customer cell |
|
|
const customerCell = document.createElement('td'); |
|
|
customerCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
const customerDiv = document.createElement('div'); |
|
|
customerDiv.className = "flex items-center"; |
|
|
|
|
|
const avatarDiv = document.createElement('div'); |
|
|
avatarDiv.className = "flex-shrink-0 h-10 w-10"; |
|
|
|
|
|
const avatarInner = document.createElement('div'); |
|
|
avatarInner.className = "customer-avatar h-10 w-10 rounded-full flex items-center justify-center text-white"; |
|
|
|
|
|
// Get initials for avatar |
|
|
const initials = customer.company.split(' ').map(word => word[0]).join('').substring(0, 2); |
|
|
avatarInner.textContent = initials; |
|
|
|
|
|
avatarDiv.appendChild(avatarInner); |
|
|
|
|
|
const infoDiv = document.createElement('div'); |
|
|
infoDiv.className = "ml-4"; |
|
|
|
|
|
const nameDiv = document.createElement('div'); |
|
|
nameDiv.className = "text-sm font-medium text-gray-900"; |
|
|
nameDiv.textContent = customer.company; |
|
|
|
|
|
infoDiv.appendChild(nameDiv); |
|
|
|
|
|
customerDiv.appendChild(avatarDiv); |
|
|
customerDiv.appendChild(infoDiv); |
|
|
|
|
|
customerCell.appendChild(customerDiv); |
|
|
|
|
|
// Total Pallets cell |
|
|
const totalPalletsCell = document.createElement('td'); |
|
|
totalPalletsCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-900"; |
|
|
totalPalletsCell.textContent = customer.palletsHistory; |
|
|
|
|
|
// Current Pallets cell |
|
|
const currentPalletsCell = document.createElement('td'); |
|
|
currentPalletsCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-900"; |
|
|
currentPalletsCell.textContent = customer.currentPallets; |
|
|
|
|
|
// Overdue cell |
|
|
const overdueCell = document.createElement('td'); |
|
|
overdueCell.className = "px-6 py-4 whitespace-nowrap"; |
|
|
|
|
|
// Calculate overdue pallets for this customer |
|
|
const overduePallets = mockDatabase.inventory.filter(item => |
|
|
item.status === "external" && |
|
|
item.customer === customer.id && |
|
|
new Date(item.expectedReturn) < new Date() |
|
|
).length; |
|
|
|
|
|
if (overduePallets > 0) { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">${overduePallets}</span>`; |
|
|
} else { |
|
|
overdueCell.innerHTML = `<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">0</span>`; |
|
|
} |
|
|
|
|
|
// Last Activity cell |
|
|
const lastActivityCell = document.createElement('td'); |
|
|
lastActivityCell.className = "px-6 py-4 whitespace-nowrap text-sm text-gray-500"; |
|
|
|
|
|
// Find last activity for this customer |
|
|
const customerActivities = mockDatabase.activityLog.filter(activity => |
|
|
activity.location.includes(customer.company) || |
|
|
(activity.palletId !== "N/A" && |
|
|
mockDatabase.inventory.some(item => |
|
|
item.palletId === activity.palletId && |
|
|
item.customer === customer.id |
|
|
)) |
|
|
); |
|
|
|
|
|
if (customerActivities.length > 0) { |
|
|
lastActivityCell.textContent = customerActivities[0].time; |
|
|
} else { |
|
|
lastActivityCell.textContent = "No recent activity"; |
|
|
} |
|
|
|
|
|
row.appendChild(customerCell); |
|
|
row.appendChild(totalPalletsCell); |
|
|
row.appendChild(currentPalletsCell); |
|
|
row.appendChild(overdueCell); |
|
|
row.appendChild(lastActivityCell); |
|
|
|
|
|
tableBody.appendChild(row); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update dashboard activity |
|
|
function updateDashboardActivity() { |
|
|
const activityDiv = document.getElementById('dashboard-activity'); |
|
|
activityDiv.innerHTML = ""; |
|
|
|
|
|
// Show only the 5 most recent activities |
|
|
const recentActivities = mockDatabase.activityLog.slice(0, 5); |
|
|
|
|
|
recentActivities.forEach(activity => { |
|
|
const activityItem = document.createElement('div'); |
|
|
activityItem.className = "flex items-start pb-4"; |
|
|
|
|
|
const iconDiv = document.createElement('div'); |
|
|
iconDiv.className = "bg-blue-100 p-2 rounded-full mr-3"; |
|
|
|
|
|
let iconClass = "fas fa-info-circle text-blue-500"; |
|
|
if (activity.action.includes("Scanned")) iconClass = "fas fa-barcode text-blue-500"; |
|
|
if (activity.action.includes("Changed")) iconClass = "fas fa-exchange-alt text-green-500"; |
|
|
if (activity.action.includes("Added")) iconClass = "fas fa-plus-circle text-purple-500"; |
|
|
if (activity.action.includes("Deleted")) iconClass = "fas fa-trash-alt text-red-500"; |
|
|
|
|
|
const icon = document.createElement('i'); |
|
|
icon.className = iconClass; |
|
|
|
|
|
iconDiv.appendChild(icon); |
|
|
|
|
|
const contentDiv = document.createElement('div'); |
|
|
contentDiv.className = "flex-1"; |
|
|
|
|
|
const actionDiv = document.createElement('div'); |
|
|
actionDiv.className = "text-sm font-medium text-gray-800"; |
|
|
actionDiv.textContent = activity.action; |
|
|
|
|
|
const metaDiv = document.createElement('div'); |
|
|
metaDiv.className = "text-xs text-gray-500"; |
|
|
|
|
|
const userSpan = document.createElement('span'); |
|
|
userSpan.className = "font-medium"; |
|
|
userSpan.textContent = activity.user; |
|
|
|
|
|
const palletSpan = document.createElement('span'); |
|
|
if (activity.palletId !== "N/A") { |
|
|
palletSpan.textContent = ` • Pallet: ${activity.palletId}`; |
|
|
} |
|
|
|
|
|
const locationSpan = document.createElement('span'); |
|
|
locationSpan.textContent = ` • ${activity.location}`; |
|
|
|
|
|
const timeSpan = document.createElement('span'); |
|
|
timeSpan.textContent = ` • ${activity.time}`; |
|
|
|
|
|
metaDiv.appendChild(userSpan); |
|
|
metaDiv.appendChild(palletSpan); |
|
|
metaDiv.appendChild(locationSpan); |
|
|
metaDiv.appendChild(timeSpan); |
|
|
|
|
|
contentDiv.appendChild(actionDiv); |
|
|
contentDiv.appendChild(metaDiv); |
|
|
|
|
|
activityItem.appendChild(iconDiv); |
|
|
activityItem.appendChild(contentDiv); |
|
|
|
|
|
activityDiv.appendChild(activityItem); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Update top customers for dashboard |
|
|
function updateTopCustomers() { |
|
|
const topCustomersDiv = document.getElementById('top-customers'); |
|
|
topCustomersDiv.innerHTML = ""; |
|
|
|
|
|
// Sort customers by pallet history (descending) and take top 3 |
|
|
const sortedCustomers = [...mockDatabase.customers].sort((a, b) => b.palletsHistory - a.palletsHistory).slice(0, 3); |
|
|
|
|
|
sortedCustomers.forEach(customer => { |
|
|
const customerItem = document.createElement('div'); |
|
|
customerItem.className = "flex items-center p-3 border rounded-lg"; |
|
|
|
|
|
const avatarDiv = document.createElement('div'); |
|
|
avatarDiv.className = "customer-avatar h-12 w-12 rounded-full flex items-center justify-center text-white text-xl mr-3"; |
|
|
|
|
|
// Get initials for avatar |
|
|
const initials = customer.company.split(' ').map(word => word[0]).join('').substring(0, 2); |
|
|
avatarDiv.textContent = initials; |
|
|
|
|
|
const infoDiv = document.createElement('div'); |
|
|
infoDiv.className = "flex-1"; |
|
|
|
|
|
const nameDiv = document.createElement('div'); |
|
|
nameDiv.className = "font-medium"; |
|
|
nameDiv.textContent = customer.company; |
|
|
|
|
|
const statsDiv = document.createElement('div'); |
|
|
statsDiv.className = "flex justify-between text-sm"; |
|
|
|
|
|
const palletsDiv = document.createElement('div'); |
|
|
palletsDiv.className = "text-gray-500"; |
|
|
palletsDiv.textContent = `${customer.currentPallets} pallets out`; |
|
|
|
|
|
const totalDiv = document.createElement('div'); |
|
|
totalDiv.className = "text-gray-500"; |
|
|
totalDiv.textContent = `${customer.palletsHistory} total`; |
|
|
|
|
|
statsDiv.appendChild(palletsDiv); |
|
|
statsDiv.appendChild(totalDiv); |
|
|
|
|
|
infoDiv.appendChild(nameDiv); |
|
|
infoDiv.appendChild(statsDiv); |
|
|
|
|
|
customerItem.appendChild(avatarDiv); |
|
|
customerItem.appendChild(infoDiv); |
|
|
|
|
|
topCustomersDiv.appendChild(customerItem); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Generate a random barcode |
|
|
function generateBarcode() { |
|
|
const palletId = "PLT-" + Math.floor(1000 + Math.random() * 9000); |
|
|
document.getElementById('pallet-id').value = palletId; |
|
|
|
|
|
// Display the barcode preview |
|
|
const barcodePreview = document.getElementById('barcode-preview'); |
|
|
barcodePreview.innerHTML = ""; |
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
|
|
svg.setAttribute("id", "barcode-preview-svg"); |
|
|
svg.setAttribute("width", "200"); |
|
|
svg.setAttribute("height", "80"); |
|
|
|
|
|
barcodePreview.appendChild(svg); |
|
|
|
|
|
JsBarcode("#barcode-preview-svg", palletId, { |
|
|
format: "CODE128", |
|
|
lineColor: "#000", |
|
|
width: 2, |
|
|
height: 50, |
|
|
displayValue: true, |
|
|
fontSize: 16, |
|
|
margin: 10 |
|
|
}); |
|
|
|
|
|
showNotification("Generated new pallet ID: " + palletId, 'success'); |
|
|
} |
|
|
|
|
|
// Move pallet to internal use |
|
|
function moveToInternalUse() { |
|
|
if (mockDatabase.emptyPallets > 0) { |
|
|
mockDatabase.emptyPallets--; |
|
|
mockDatabase.internalPallets++; |
|
|
updateCounts(); |
|
|
|
|
|
// Add to activity log |
|
|
const newActivity = { |
|
|
time: "Just now", |
|
|
palletId: "N/A", |
|
|
action: "Pallet moved to internal use", |
|
|
user: currentUser.name, |
|
|
location: currentUser.location |
|
|
}; |
|
|
mockDatabase.activityLog.unshift(newActivity); |
|
|
updateActivityLog(); |
|
|
updateDashboardActivity(); |
|
|
|
|
|
showNotification("Pallet moved to internal use", 'success'); |
|
|
} else { |
|
|
showNotification("No empty pallets available!", 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
// Move pallet to external use |
|
|
function moveToExternalUse() { |
|
|
if (mockDatabase.emptyPallets > 0) { |
|
|
mockDatabase.emptyPallets--; |
|
|
mockDatabase.externalPallets++; |
|
|
updateCounts(); |
|
|
|
|
|
// Add to activity log |
|
|
const newActivity = { |
|
|
time: "Just now", |
|
|
palletId: "N/A", |
|
|
action: "Pallet moved to external use", |
|
|
user: currentUser.name, |
|
|
location: currentUser.location |
|
|
}; |
|
|
mockDatabase.activityLog.unshift(newActivity); |
|
|
updateActivityLog(); |
|
|
updateDashboardActivity(); |
|
|
|
|
|
showNotification("Pallet moved to external use", 'success'); |
|
|
} else { |
|
|
showNotification("No empty pallets available!", 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
// Increment empty pallets (when pallets are returned) |
|
|
function incrementEmptyPallets() { |
|
|
mockDatabase.emptyPallets++; |
|
|
|
|
|
// Determine where pallet is coming from |
|
|
if (mockDatabase.internalPallets > 0) { |
|
|
mockDatabase.internalPallets--; |
|
|
} else if (mockDatabase.externalPallets > 0) { |
|
|
mockDatabase.externalPallets--; |
|
|
} |
|
|
|
|
|
updateCounts(); |
|
|
|
|
|
// Add to activity log |
|
|
const newActivity = { |
|
|
time: "Just now", |
|
|
palletId: "N/A", |
|
|
action: "Empty pallet added", |
|
|
user: currentUser.name, |
|
|
location: currentUser.location |
|
|
}; |
|
|
mockDatabase.activityLog.unshift(newActivity); |
|
|
updateActivityLog(); |
|
|
updateDashboardActivity(); |
|
|
|
|
|
showNotification("Empty pallet added to inventory", 'success'); |
|
|
} |
|
|
|
|
|
// Sync with "cloud" (mock for demo) |
|
|
function syncInventory() { |
|
|
showNotification("Inventory synced with cloud database", 'success'); |
|
|
} |
|
|
|
|
|
// Start scanning simulation |
|
|
function startScanning() { |
|
|
// In a real app, this would interface with device camera/barcode scanner |
|
|
// For demo, we'll simulate a scan after 1.5 seconds |
|
|
showNotification("Scanning started - point camera at barcode", 'info'); |
|
|
|
|
|
setTimeout(function() { |
|
|
// Pick a random pallet from inventory or generate a new one |
|
|
let palletId; |
|
|
if (mockDatabase.inventory.length > 0 && Math.random() > 0.3) { |
|
|
// 70% chance of scanning an existing pallet |
|
|
const randomIndex = Math.floor(Math.random() * mockDatabase.inventory.length); |
|
|
palletId = mockDatabase.inventory[randomIndex].palletId; |
|
|
} else { |
|
|
// 30% chance of scanning a new pallet |
|
|
palletId = "PLT |
|
|
</html> |