Files
PilotEdge/loadboard.html
Daniel Kovalevich 260f7c4928 first commit
2026-03-30 13:56:24 -04:00

299 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Oversize Load Board | PilotEdge</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-50 min-h-screen flex flex-col">
<div id="main-nav"></div>
<div id="poc-banner"></div>
<!-- Page Header -->
<section class="bg-slate-900 text-white pt-24 pb-12 px-4">
<div class="max-w-7xl mx-auto flex flex-col md:flex-row md:items-end md:justify-between gap-4">
<div>
<h1 class="text-3xl md:text-4xl font-bold mb-3">Oversize Load Board</h1>
<p class="text-lg text-gray-400">Active loads needing escort/pilot vehicle services. Carriers post free — escort operators browse and bid.</p>
</div>
<div class="flex gap-3">
<button onclick="showPostModal()" class="bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold px-5 py-2.5 rounded-lg transition-colors whitespace-nowrap">
+ Post a Load
</button>
</div>
</div>
</section>
<!-- Filters -->
<section class="max-w-7xl mx-auto px-4 pt-8">
<div class="bg-white rounded-2xl shadow-lg p-6">
<div class="flex flex-col md:flex-row gap-4 items-end">
<div class="flex-1">
<label class="block text-sm font-semibold text-slate-700 mb-1">Search</label>
<input type="text" id="search-input" oninput="filterLoads()" placeholder="Search by location, carrier, or description..." class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Status</label>
<select id="status-filter" onchange="filterLoads()" class="border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
<option value="all">All</option>
<option value="posted" selected>Posted (Available)</option>
<option value="in_transit">In Transit</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Escorts Needed</label>
<select id="escorts-filter" onchange="filterLoads()" class="border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
<option value="all">Any</option>
<option value="1">1 Escort</option>
<option value="2">2 Escorts</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Sort</label>
<select id="sort-select" onchange="filterLoads()" class="border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
<option value="date_asc">Departure (Soonest)</option>
<option value="date_desc">Departure (Latest)</option>
<option value="posted_desc">Recently Posted</option>
</select>
</div>
</div>
</div>
</section>
<!-- Results Count -->
<section class="max-w-7xl mx-auto px-4 pt-4">
<p id="results-count" class="text-sm text-slate-500 font-medium"></p>
</section>
<!-- Load Listings -->
<section class="max-w-7xl mx-auto px-4 py-4 pb-8">
<div id="load-list" class="space-y-4">
<!-- Populated by JS -->
</div>
</section>
<!-- Subscription CTA -->
<section class="max-w-7xl mx-auto px-4 pb-8">
<div class="bg-gradient-to-r from-slate-800 to-slate-900 rounded-2xl p-8 text-center text-white">
<h3 class="text-xl font-bold mb-2">Escort Vehicle Operator?</h3>
<p class="text-gray-400 mb-4 max-w-lg mx-auto">Get unlimited load board access, instant notifications for new loads in your area, and priority bidding with a PilotEdge subscription.</p>
<button class="bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold px-6 py-3 rounded-lg transition-colors">
Subscribe — Starting at $49/mo
</button>
</div>
</section>
<!-- Post Load Modal (simplified for POC) -->
<div id="post-modal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto p-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold text-slate-900">Post a Load</h2>
<button onclick="closePostModal()" class="text-slate-400 hover:text-slate-600 text-2xl">&times;</button>
</div>
<div class="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6">
<p class="text-amber-900 text-sm"><strong>POC Note:</strong> In production, this form would create a real listing. For now, this demonstrates the posting flow.</p>
</div>
<form onsubmit="handlePostLoad(event)" class="space-y-4">
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Carrier / Company Name</label>
<input type="text" required class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Origin</label>
<input type="text" required placeholder="City, State" class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Destination</label>
<input type="text" required placeholder="City, State" class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Departure Date</label>
<input type="date" required class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Load Description</label>
<textarea required rows="2" placeholder="What's being hauled?" class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none"></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Dimensions (W×H×L)</label>
<input type="text" placeholder="16'×14'×135'" class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-1">Escorts Needed</label>
<select class="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-amber-400 focus:border-amber-400 outline-none">
<option>1</option>
<option>2</option>
<option>3+</option>
</select>
</div>
</div>
<button type="submit" class="w-full bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold py-3 rounded-lg transition-colors">
Post Load
</button>
</form>
</div>
</div>
<div id="main-footer"></div>
<script src="mock-data.js"></script>
<script src="nav.js"></script>
<script>
renderNav('loadboard');
renderBanner();
renderFooter();
function getStatusBadge(status) {
if (status === 'posted') return '<span class="bg-green-100 text-green-800 text-xs font-bold px-3 py-1 rounded-full">AVAILABLE</span>';
if (status === 'in_transit') return '<span class="bg-blue-100 text-blue-800 text-xs font-bold px-3 py-1 rounded-full">IN TRANSIT</span>';
return '<span class="bg-slate-100 text-slate-600 text-xs font-bold px-3 py-1 rounded-full">' + status.toUpperCase() + '</span>';
}
function daysUntil(dateStr) {
const today = new Date();
today.setHours(0,0,0,0);
const target = new Date(dateStr);
const diff = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
if (diff < 0) return 'Departed';
if (diff === 0) return 'Today';
if (diff === 1) return 'Tomorrow';
return `In ${diff} days`;
}
function renderLoads(loads) {
const container = document.getElementById('load-list');
document.getElementById('results-count').textContent = `${loads.length} load${loads.length !== 1 ? 's' : ''} found`;
if (loads.length === 0) {
container.innerHTML = `
<div class="bg-white rounded-2xl shadow-lg p-12 text-center">
<p class="text-slate-500 text-lg">No loads match your filters.</p>
</div>
`;
return;
}
container.innerHTML = loads.map(load => `
<div class="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition-shadow">
<div class="flex flex-col lg:flex-row lg:items-start justify-between gap-4">
<!-- Route & Info -->
<div class="flex-1">
<div class="flex items-center gap-3 mb-3">
${getStatusBadge(load.status)}
<span class="text-xs text-slate-400 font-medium">${load.id}</span>
</div>
<!-- Route -->
<div class="flex items-center gap-3 mb-3">
<div class="text-center">
<p class="font-bold text-slate-900 text-lg">${load.origin.city}, ${load.origin.state}</p>
</div>
<div class="flex-shrink-0 text-amber-500 text-2xl px-2">→</div>
<div class="text-center">
<p class="font-bold text-slate-900 text-lg">${load.destination.city}, ${load.destination.state}</p>
</div>
</div>
<!-- Description -->
<p class="text-slate-600 mb-3">${load.description}</p>
<!-- Carrier -->
<p class="text-sm text-slate-500">Posted by <span class="font-semibold text-slate-700">${load.carrier}</span></p>
</div>
<!-- Details Sidebar -->
<div class="lg:w-72 flex-shrink-0 bg-slate-50 rounded-xl p-4 space-y-3">
<div class="flex justify-between">
<span class="text-sm text-slate-500">Departure</span>
<span class="text-sm font-bold text-slate-900">${new Date(load.departureDate).toLocaleDateString('en-US', {month:'short', day:'numeric', year:'numeric'})} <span class="text-amber-600">(${daysUntil(load.departureDate)})</span></span>
</div>
<div class="border-t border-slate-200"></div>
<div class="flex justify-between">
<span class="text-sm text-slate-500">Width</span>
<span class="text-sm font-bold text-slate-900">${load.dimensions.width}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-slate-500">Height</span>
<span class="text-sm font-bold text-slate-900">${load.dimensions.height}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-slate-500">Length</span>
<span class="text-sm font-bold text-slate-900">${load.dimensions.length}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-slate-500">Weight</span>
<span class="text-sm font-bold text-slate-900">${load.dimensions.weight}</span>
</div>
<div class="border-t border-slate-200"></div>
<div class="flex justify-between items-center">
<span class="text-sm text-slate-500">Escorts Needed</span>
<span class="bg-amber-100 text-amber-800 text-xs font-bold px-3 py-1 rounded-full">${load.escortsNeeded} vehicle${load.escortsNeeded > 1 ? 's' : ''}</span>
</div>
${load.status === 'posted' ? `
<button class="w-full mt-2 bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold py-2 rounded-lg transition-colors text-sm">
Contact Carrier
</button>
` : ''}
</div>
</div>
</div>
`).join('');
}
function filterLoads() {
const search = document.getElementById('search-input').value.toLowerCase();
const statusFilter = document.getElementById('status-filter').value;
const escortsFilter = document.getElementById('escorts-filter').value;
const sortBy = document.getElementById('sort-select').value;
let filtered = MOCK_LOAD_BOARD.filter(load => {
if (statusFilter !== 'all' && load.status !== statusFilter) return false;
if (escortsFilter !== 'all' && load.escortsNeeded !== parseInt(escortsFilter)) return false;
if (search) {
const searchText = `${load.origin.city} ${load.origin.state} ${load.destination.city} ${load.destination.state} ${load.carrier} ${load.description}`.toLowerCase();
if (!searchText.includes(search)) return false;
}
return true;
});
// Sort
filtered.sort((a, b) => {
if (sortBy === 'date_asc') return new Date(a.departureDate) - new Date(b.departureDate);
if (sortBy === 'date_desc') return new Date(b.departureDate) - new Date(a.departureDate);
if (sortBy === 'posted_desc') return new Date(b.postedDate) - new Date(a.postedDate);
return 0;
});
renderLoads(filtered);
}
// Initial render
filterLoads();
function showPostModal() {
document.getElementById('post-modal').classList.remove('hidden');
}
function closePostModal() {
document.getElementById('post-modal').classList.add('hidden');
}
function handlePostLoad(e) {
e.preventDefault();
closePostModal();
alert('Load posted! (POC demo — in production, this would create a real listing.)');
}
// Close modal on backdrop click
document.getElementById('post-modal').addEventListener('click', function(e) {
if (e.target === this) closePostModal();
});
</script>
</body>
</html>