Website Service Typical Scenarios
Delivery Vehicle Information Registration List

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Delivery Vehicle Information Registration List</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: "#1E40AF", // Primary: dark blue (professional, reliable)
secondary: "#3B82F6", // Secondary: light blue
success: "#10B981", // Success: green
warning: "#F59E0B", // Warning: yellow
danger: "#EF4444", // Danger: red
neutral: "#6B7280", // Neutral: gray
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
},
},
};
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.table-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.filter-card {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.input-error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1) !important;
}
.error-hint {
color: #ef4444;
font-size: 0.75rem;
margin-top: 0.25rem;
display: none;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen font-sans">
<!-- Page Header -->
<header class="bg-white shadow-sm sticky top-0 z-10">
<div class="container mx-auto px-4 py-4 flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<i class="fa fa-truck text-primary text-2xl mr-3"></i>
<h1 class="text-[clamp(1.25rem,3vw,1.75rem)] font-bold text-gray-800">Delivery Vehicle Information Registration List</h1>
</div>
<div class="flex space-x-3">
<button id="refreshBtn" class="flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
<i class="fa fa-refresh mr-2"></i>
<span>Refresh Data</span>
</button>
<button id="exportBtn" class="flex items-center px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
<i class="fa fa-download mr-2"></i>
<span>Export Excel</span>
</button>
</div>
</div>
</header>
<!-- Filter Area -->
<section class="container mx-auto px-4 py-6">
<div class="bg-white rounded-lg p-4 filter-card">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- License Plate Filter -->
<div>
<label for="plateNumber" class="block text-sm font-medium text-gray-700 mb-1">License Plate</label>
<div class="relative">
<i class="fa fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input type="text" id="plateNumber" placeholder="Enter license plate (optional)" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" />
<p id="plateNumberError" class="error-hint">Please enter a valid license plate</p>
</div>
</div>
<!-- Shipping Date Filter -->
<div>
<label for="shipDateRange" class="block text-sm font-medium text-gray-700 mb-1">Shipping Date Range</label>
<div class="flex space-x-2">
<input type="date" id="startDate" placeholder="Start Date" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" />
<span class="flex items-center text-gray-500">to</span>
<input type="date" id="endDate" placeholder="End Date" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all" />
<p id="dateRangeError" class="error-hint absolute mt-10 text-xs">End date cannot be earlier than start date</p>
</div>
</div>
<!-- Filter Button -->
<div class="flex items-end">
<button id="searchBtn" class="w-full px-4 py-2 bg-secondary text-white rounded-md hover:bg-secondary/90 transition-colors">
<i class="fa fa-filter mr-2"></i>
<span>Filter</span>
</button>
</div>
</div>
</div>
</section>
<!-- Data Statistics Overview -->
<section class="container mx-auto px-4 py-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Total Vehicles -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-500">Total Vehicles</h3>
<i class="fa fa-car text-primary text-xl"></i>
</div>
<p id="totalVehicles" class="text-2xl font-bold text-gray-800">0</p>
<p class="text-xs text-gray-400">As of current statistics</p>
</div>
<!-- Today's Shipments -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-500">Today's Shipments</h3>
<i class="fa fa-calendar-check-o text-success text-xl"></i>
</div>
<p id="todayShipments" class="text-2xl font-bold text-gray-800">0</p>
<p class="text-xs text-gray-400">Added today</p>
</div>
<!-- Pending Shipments -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-500">Pending Shipments</h3>
<i class="fa fa-clock-o text-warning text-xl"></i>
</div>
<p id="pendingShipments" class="text-2xl font-bold text-gray-800">0</p>
<p class="text-xs text-gray-400">Pending orders</p>
</div>
<!-- Abnormal Vehicles -->
<div class="bg-white rounded-lg p-4 shadow-sm">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-500">Abnormal Vehicles</h3>
<i class="fa fa-exclamation-triangle text-danger text-xl"></i>
</div>
<p id="abnormalVehicles" class="text-2xl font-bold text-gray-800">0</p>
<p class="text-xs text-gray-400">Requires manual handling</p>
</div>
</div>
</section>
<!-- List Display Area -->
<section class="container mx-auto px-4 py-4">
<div class="bg-white rounded-lg table-shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">No.</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">License Plate</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Driver Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Shipping Date</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Vehicle Status</th>
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="vehicleTableBody" class="bg-white divide-y divide-gray-200">
<!-- Initial state: please filter data -->
<tr id="initialRow" class="">
<td colspan="6" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fa fa-filter text-gray-300 text-2xl mb-3"></i>
<p class="text-gray-500">Please fill in filter conditions and click the Filter button to query data</p>
</div>
</td>
</tr>
<!-- Loading data -->
<tr id="loadingRow" class="hidden">
<td colspan="6" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fa fa-spinner fa-spin text-primary text-2xl mb-3"></i>
<p class="text-gray-500">Loading data...</p>
</div>
</td>
</tr>
<!-- No data hint -->
<tr id="emptyRow" class="hidden">
<td colspan="6" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fa fa-folder-open-o text-gray-300 text-2xl mb-3"></i>
<p class="text-gray-500">No delivery vehicle information matching the criteria</p>
</div>
</td>
</tr>
<!-- API error hint -->
<tr id="errorRow" class="hidden">
<td colspan="6" class="px-6 py-12 text-center">
<div class="flex flex-col items-center">
<i class="fa fa-exclamation-circle text-danger text-2xl mb-3"></i>
<p class="text-gray-500 mb-4" id="errorMessage">Data loading failed</p>
<button id="retryBtn" class="px-4 py-2 bg-secondary text-white rounded-md hover:bg-secondary/90 transition-colors">Retry</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<div class="px-6 py-4 flex items-center justify-between border-t border-gray-200 hidden" id="paginationContainer">
<div class="flex-1 flex justify-between sm:hidden">
<button id="prevPageMobile" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>Previous</button>
<button id="nextPageMobile" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>Next</button>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">Showing page <span id="currentPageText">1</span> of <span id="totalPagesText">0</span>, total <span id="totalItemsText">0</span> records</p>
</div>
<div>
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<button id="prevPage" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<span class="sr-only">Previous</span>
<i class="fa fa-chevron-left h-5 w-5"></i>
</button>
<button id="nextPage" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<span class="sr-only">Next</span>
<i class="fa fa-chevron-right h-5 w-5"></i>
</button>
</nav>
</div>
</div>
</div>
</div>
</section>
<!-- View Details Modal -->
<div id="detailModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg max-w-md w-full mx-4 overflow-hidden transform transition-all">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Vehicle Shipping Details</h3>
<button id="closeModal" class="text-gray-400 hover:text-gray-500">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<div class="px-6 py-4">
<div class="space-y-4">
<div class="grid grid-cols-3 gap-2">
<label class="text-sm font-medium text-gray-500">No.</label>
<p id="detailSeq" class="col-span-2 text-sm text-gray-900">-</p>
</div>
<div class="grid grid-cols-3 gap-2">
<label class="text-sm font-medium text-gray-500">License Plate</label>
<p id="detailPlateNumber" class="col-span-2 text-sm text-gray-900">-</p>
</div>
<div class="grid grid-cols-3 gap-2">
<label class="text-sm font-medium text-gray-500">Driver Name</label>
<p id="detailName" class="col-span-2 text-sm text-gray-900">-</p>
</div>
<div class="grid grid-cols-3 gap-2">
<label class="text-sm font-medium text-gray-500">Shipping Date</label>
<p id="detailShipDate" class="col-span-2 text-sm text-gray-900">-</p>
</div>
<div class="grid grid-cols-3 gap-2">
<label class="text-sm font-medium text-gray-500">Vehicle Status</label>
<p id="detailStatus" class="col-span-2 text-sm font-medium">-</p>
</div>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-200 flex justify-end">
<button id="closeDetailBtn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors">Close</button>
</div>
</div>
</div>
<!-- Footer -->
<footer class="bg-white border-t border-gray-200 mt-8">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<p class="text-sm text-gray-500 mb-4 md:mb-0">© 2025 Delivery Vehicle Management System. All rights reserved.</p>
<div class="flex space-x-4">
<a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">Help Center</a>
<a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">Contact Us</a>
<a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">System Info</a>
</div>
</div>
</div>
</footer>
<script>
// Global variables
let vehicleData = []; // Vehicle data list
let currentPage = 1; // Current page number
let pageSize = 10; // Items per page
let totalItems = 0; // Total records
let totalPages = 0; // Total pages
// API configuration parameters (modify tableId and apiKey according to your actual setup)
const API_CONFIG = {
url: "https://ai.ainformat.com//web0/webapi/table_record_list",
apiKey: "ojw76e1htyb2p3e37gcay", // Example apiKey, use your actual key in production
tableId: "shipPlan", // Example tableId, use the actual delivery vehicle data table identifier
// Field mapping: ensure consistency with field IDs in the data table
fieldMap: {
seq: "seq", // Sequence number field ID
plateNumber: "plateNumber", // License plate field ID
name: "name", // Driver name field ID
shipDate: "shipDate", // Shipping date field ID
state: "state",
state_name: "state_name", // Vehicle status field ID
},
};
// DOM elements
const vehicleTableBody = document.getElementById("vehicleTableBody");
const initialRow = document.getElementById("initialRow");
const loadingRow = document.getElementById("loadingRow");
const emptyRow = document.getElementById("emptyRow");
const errorRow = document.getElementById("errorRow");
const errorMessage = document.getElementById("errorMessage");
const retryBtn = document.getElementById("retryBtn");
const refreshBtn = document.getElementById("refreshBtn");
const exportBtn = document.getElementById("exportBtn");
const searchBtn = document.getElementById("searchBtn");
const plateNumberInput = document.getElementById("plateNumber");
const startDateInput = document.getElementById("startDate");
const endDateInput = document.getElementById("endDate");
const plateNumberError = document.getElementById("plateNumberError");
const dateRangeError = document.getElementById("dateRangeError");
const prevPageBtn = document.getElementById("prevPage");
const nextPageBtn = document.getElementById("nextPage");
const prevPageMobileBtn = document.getElementById("prevPageMobile");
const nextPageMobileBtn = document.getElementById("nextPageMobile");
const currentPageText = document.getElementById("currentPageText");
const totalPagesText = document.getElementById("totalPagesText");
const totalItemsText = document.getElementById("totalItemsText");
const totalVehiclesEl = document.getElementById("totalVehicles");
const todayShipmentsEl = document.getElementById("todayShipments");
const pendingShipmentsEl = document.getElementById("pendingShipments");
const abnormalVehiclesEl = document.getElementById("abnormalVehicles");
const detailModal = document.getElementById("detailModal");
const closeModalBtn = document.getElementById("closeModal");
const closeDetailBtn = document.getElementById("closeDetailBtn");
const detailSeqEl = document.getElementById("detailSeq");
const detailPlateNumberEl = document.getElementById("detailPlateNumber");
const detailNameEl = document.getElementById("detailName");
const detailShipDateEl = document.getElementById("detailShipDate");
const detailStatusEl = document.getElementById("detailStatus");
const paginationContainer = document.getElementById("paginationContainer");
// Initialize page
document.addEventListener("DOMContentLoaded", () => {
// Bind events
bindEvents();
fetchVehicleData();
});
// Bind events
function bindEvents() {
// Refresh button
refreshBtn.addEventListener("click", () => {
// Reset filter conditions
plateNumberInput.value = "";
startDateInput.value = "";
endDateInput.value = "";
// Hide other states, show initial state
hideAllStates();
initialRow.classList.remove("hidden");
paginationContainer.classList.add("hidden");
// Reset statistics
resetStatistics();
});
// Export button
exportBtn.addEventListener("click", exportToExcel);
// Search button
searchBtn.addEventListener("click", () => {
currentPage = 1; // Reset to first page
// Validate filter conditions first
if (validateFilterConditions()) {
fetchVehicleData();
}
});
// License plate input event: clear error hint on input
plateNumberInput.addEventListener("input", () => {
plateNumberInput.classList.remove("input-error");
plateNumberError.style.display = "none";
});
// Date input event: clear error hint on input
startDateInput.addEventListener("input", () => {
dateRangeError.style.display = "none";
});
endDateInput.addEventListener("input", () => {
dateRangeError.style.display = "none";
});
// Pagination buttons
prevPageBtn.addEventListener("click", goToPrevPage);
nextPageBtn.addEventListener("click", goToNextPage);
prevPageMobileBtn.addEventListener("click", goToPrevPage);
nextPageMobileBtn.addEventListener("click", goToNextPage);
// Detail modal close buttons
closeModalBtn.addEventListener("click", closeDetailModal);
closeDetailBtn.addEventListener("click", closeDetailModal);
// Click outside modal to close
detailModal.addEventListener("click", (e) => {
if (e.target === detailModal) {
closeDetailModal();
}
});
// Retry button
retryBtn.addEventListener("click", () => {
if (validateFilterConditions()) {
fetchVehicleData();
}
});
}
// Validate filter conditions
function validateFilterConditions() {
let isValid = true;
// License plate validation (optional, validate format if filled)
const plateNumber = plateNumberInput.value.trim();
if (plateNumber && !/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/.test(plateNumber)) {
plateNumberInput.classList.add("input-error");
plateNumberError.style.display = "block";
plateNumberInput.scrollIntoView({ behavior: "smooth", block: "center" });
isValid = false;
}
// Date range validation (if both filled, end date cannot be earlier than start date)
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate && endDate && new Date(endDate) < new Date(startDate)) {
dateRangeError.style.display = "block";
endDateInput.scrollIntoView({ behavior: "smooth", block: "center" });
isValid = false;
}
return isValid;
}
// Build API request parameters
function buildRequestParams() {
const params = {
apiKey: API_CONFIG.apiKey,
tableId: API_CONFIG.tableId,
pageIndex: currentPage,
pageSize: pageSize,
filter: {
conditionList: [],
},
orderByList: [
{
fieldId: API_CONFIG.fieldMap.shipDate,
order: "desc", // Sort by shipping date descending
},
],
};
// License plate filter (exact match)
const plateNumber = plateNumberInput.value.trim();
if (plateNumber) {
params.filter.conditionList.push({
fieldId: API_CONFIG.fieldMap.plateNumber,
opt: "eq",
value: `${plateNumber}`,
});
}
// Shipping date range filter
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate) {
// Convert to timestamp (milliseconds)
const startTimestamp = new Date(startDate).getTime();
// Greater than or equal to start time
params.filter.conditionList.push({
fieldId: API_CONFIG.fieldMap.shipDate,
opt: "ge",
value: startTimestamp,
});
}
if (endDate) {
const endTimestamp = new Date(endDate).setHours(23, 59, 59, 999);
// Less than or equal to end time
params.filter.conditionList.push({
fieldId: API_CONFIG.fieldMap.shipDate,
opt: "le",
value: endTimestamp,
});
}
return params;
}
// Fetch vehicle data from API
async function fetchVehicleData() {
// const plateNumber = plateNumberInput.value.trim();
// // Validate license plate is not empty
// if (!plateNumber) {
// plateNumberInput.classList.add('input-error');
// plateNumberError.style.display = 'block';
//
// // Scroll to error position (mobile friendly)
// plateNumberInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
// return;
// }
// Hide all state rows
hideAllStates();
// Show loading state
showLoading();
try {
// Build request parameters
const requestParams = buildRequestParams();
// Send POST request
const response = await fetch(API_CONFIG.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(requestParams),
});
// Check response status
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
// Parse response data
const result = await response.json();
// Check API return status
if (result.code !== 0) {
throw new Error(`API returned error: ${result.message || "Unknown error"}`);
}
// Process data (adapt to API response format)
const { recordList = [], count = 0 } = result.data || {};
vehicleData = formatVehicleData(recordList);
totalItems = count;
totalPages = Math.ceil(totalItems / pageSize);
// Update table
updateVehicleTable(vehicleData);
// Update pagination info
updatePaginationInfo();
// Update statistics
updateStatistics(vehicleData);
// Hide loading state
hideLoading();
// Show empty data hint (if needed)
if (vehicleData.length === 0) {
showEmptyState();
} else {
// Show pagination controls
paginationContainer.classList.remove("hidden");
}
} catch (error) {
console.error("Failed to fetch vehicle data:", error);
hideLoading();
showErrorState(error.message);
}
}
// Format vehicle data (adapt for page display)
function formatVehicleData(rawData) {
return rawData
.map((item) => {
// Map fields to ensure consistent field names
return {
seq: item[API_CONFIG.fieldMap.seq] || "",
plateNumber: item[API_CONFIG.fieldMap.plateNumber] || "",
name: item[API_CONFIG.fieldMap.name] || "",
shipDate: item[API_CONFIG.fieldMap.shipDate] || 0,
state_name: item[API_CONFIG.fieldMap.state_name] || "Unknown",
state: item[API_CONFIG.fieldMap.state] || "Unknown",
};
})
.filter((item) => {
// Filter out invalid data
return item.plateNumber || item.name;
});
}
// Update vehicle table
function updateVehicleTable(data) {
// Clear table (keep all state rows)
const rows = vehicleTableBody.querySelectorAll("tr:not(#initialRow):not(#loadingRow):not(#emptyRow):not(#errorRow)");
rows.forEach((row) => row.remove());
// Pagination handling
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, data.length);
const pageData = data.slice(startIndex, endIndex);
// Add data rows
pageData.forEach((item) => {
const row = document.createElement("tr");
row.className = "hover:bg-gray-50 transition-colors";
// Format date
const shipDate = item.shipDate ? new Date(item.shipDate) : new Date();
const formattedDate = shipDate.toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
// Handle status styling
let statusClass = "";
switch (item.state) {
case "hasIn":
statusClass = "text-success";
break;
case "notIn":
statusClass = "text-warning";
break;
case "abnormal":
statusClass = "text-danger";
break;
default:
statusClass = "text-neutral";
}
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">${item.seq || "-"}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900 font-medium">${item.plateNumber || "-"}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">${item.name || "-"}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">${formattedDate}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}">
${item.state_name}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button class="view-detail text-primary hover:text-primary/80 mr-3" data-id="${item.seq}">
View
</button>
</td>
`;
vehicleTableBody.appendChild(row);
// Bind view detail event
const viewDetailBtn = row.querySelector(".view-detail");
viewDetailBtn.addEventListener("click", () => {
showDetailModal(item);
});
});
}
// Update pagination info
function updatePaginationInfo() {
currentPageText.textContent = currentPage;
totalPagesText.textContent = totalPages;
totalItemsText.textContent = totalItems;
// Update pagination button states
prevPageBtn.disabled = currentPage === 1;
nextPageBtn.disabled = currentPage === totalPages || totalPages === 0;
prevPageMobileBtn.disabled = currentPage === 1;
nextPageMobileBtn.disabled = currentPage === totalPages || totalPages === 0;
}
// Update statistics
function updateStatistics(data) {
// Total vehicles
totalVehiclesEl.textContent = data.length;
// Today's shipments (status is "shipped" and date is today)
const today = new Date();
const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
const todayShipments = data.filter((item) => {
if (item.state !== "shipped" || !item.shipDate) return false;
const shipDateOnly = new Date(new Date(item.shipDate).getFullYear(), new Date(item.shipDate).getMonth(), new Date(item.shipDate).getDate()).getTime();
return shipDateOnly === todayOnly;
}).length;
todayShipmentsEl.textContent = todayShipments;
// Pending shipments
const pendingShipments = data.filter((item) => item.status === "pending").length;
pendingShipmentsEl.textContent = pendingShipments;
// Abnormal vehicles
const abnormalVehicles = data.filter((item) => item.status === "abnormal").length;
abnormalVehiclesEl.textContent = abnormalVehicles;
}
// Reset statistics
function resetStatistics() {
totalVehiclesEl.textContent = "0";
todayShipmentsEl.textContent = "0";
pendingShipmentsEl.textContent = "0";
abnormalVehiclesEl.textContent = "0";
}
// Show detail modal
function showDetailModal(item) {
// Format date
const shipDate = item.shipDate ? new Date(item.shipDate) : new Date();
const formattedDate = shipDate.toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
// Handle status styling
let statusClass = "";
switch (item.status) {
case "shipped":
statusClass = "text-success";
break;
case "pending":
statusClass = "text-warning";
break;
case "abnormal":
statusClass = "text-danger";
break;
default:
statusClass = "text-neutral";
}
// Populate data
detailSeqEl.textContent = item.seq || "-";
detailPlateNumberEl.textContent = item.plateNumber || "-";
detailNameEl.textContent = item.name || "-";
detailShipDateEl.textContent = formattedDate;
detailStatusEl.textContent = item.state_name;
detailStatusEl.className = `col-span-2 text-sm font-medium ${statusClass}`;
// Show modal
detailModal.classList.remove("hidden");
// Disable background scrolling
document.body.style.overflow = "hidden";
}
// Close detail modal
function closeDetailModal() {
detailModal.classList.add("hidden");
// Restore background scrolling
document.body.style.overflow = "";
}
// Previous page
function goToPrevPage() {
if (currentPage > 1) {
currentPage--;
fetchVehicleData();
// Scroll to table top
vehicleTableBody.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
// Next page
function goToNextPage() {
if (currentPage < totalPages) {
currentPage++;
fetchVehicleData();
// Scroll to table top
vehicleTableBody.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
// Export to Excel
function exportToExcel() {
if (vehicleData.length === 0) {
alert("No data available to export");
return;
}
// Prepare export data
const exportData = vehicleData.map((item) => {
const shipDate = item.shipDate ? new Date(item.shipDate) : new Date();
const formattedDate = shipDate.toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
return {
"No.": item.seq || "-",
"License Plate": item.plateNumber || "-",
"Driver Name": item.name || "-",
"Shipping Date": formattedDate,
"Vehicle Status": item.state_name,
};
});
// Create workbook and worksheet
const worksheet = XLSX.utils.json_to_sheet(exportData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Delivery Vehicles");
// Set column widths
const wscols = [
{ wch: 8 }, // No.
{ wch: 12 }, // License Plate
{ wch: 12 }, // Driver Name
{ wch: 16 }, // Shipping Date
{ wch: 12 }, // Vehicle Status
];
worksheet["!cols"] = wscols;
// Export file
const today = new Date().toLocaleDateString("en-US").replace(/\//g, "-");
XLSX.writeFile(workbook, `Delivery_Vehicles_${today}.xlsx`);
}
// Hide all state rows
function hideAllStates() {
initialRow.classList.add("hidden");
loadingRow.classList.add("hidden");
emptyRow.classList.add("hidden");
errorRow.classList.add("hidden");
}
// Show loading state
function showLoading() {
loadingRow.classList.remove("hidden");
}
// Hide loading state
function hideLoading() {
loadingRow.classList.add("hidden");
}
// Show empty data state
function showEmptyState(message = "No delivery vehicle information matching the criteria") {
emptyRow.querySelector("p").textContent = message;
emptyRow.classList.remove("hidden");
paginationContainer.classList.add("hidden");
}
// Show error state
function showErrorState(message = "Data loading failed, please try again later") {
errorMessage.textContent = message;
errorRow.classList.remove("hidden");
paginationContainer.classList.add("hidden");
}
</script>
</body>
</html>
