thee-space / index.html
WeakneeB's picture
Add 2 files
e4b8acb verified
<!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">
<!-- Notification Area -->
<div id="notification-area" class="notification"></div>
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header with User Info -->
<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>
<!-- Navigation Tabs -->
<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>
<!-- Dashboard Tab -->
<div id="dashboard" class="tab-content active">
<!-- Summary Cards -->
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-8">
<!-- Empty Pallets Card -->
<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>
<!-- Internal Use Card -->
<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>
<!-- External Use Card -->
<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>
<!-- Total Inventory Card -->
<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>
<!-- Quick Actions -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<!-- Scanner Section -->
<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>
<!-- Recent Activity -->
<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">
<!-- Activity items will be added here dynamically -->
</div>
</div>
<!-- Top Customers -->
<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">
<!-- Top customers will be added here dynamically -->
</div>
</div>
</div>
</div>
<!-- Customers Tab -->
<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>
<!-- Customer Search and Filter -->
<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>
<!-- Customers List -->
<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">
<!-- Customers will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Inventory Tab -->
<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>
<!-- Manual Entry Section -->
<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">
<!-- Customer options will be added dynamically -->
</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>
<!-- Inventory List -->
<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">
<!-- Inventory items will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Reports Tab -->
<div id="reports" class="tab-content">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- External Pallets Report -->
<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">
<!-- External pallets will be added here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Customer Activity Report -->
<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">
<!-- Customer activity will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Pallet Utilization Chart -->
<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>
<!-- Customer Detail Modal -->
<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">
<!-- Customer Info -->
<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">
<!-- Initials will be added here -->
</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">
<!-- Status badge will be added here -->
</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>
<!-- Contact Details -->
<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>
<!-- Current Pallets -->
<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">
<!-- Customer's pallets will be added here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Activity History -->
<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">
<!-- Customer activity will be added here dynamically -->
</div>
</div>
<!-- Modal Footer -->
<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>
<!-- Mock Database for demo purposes -->
<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>