first commit
This commit is contained in:
259
documents.html
Normal file
259
documents.html
Normal file
@@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document Vault | 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">
|
||||
<h1 class="text-3xl md:text-4xl font-bold mb-3">Document Vault</h1>
|
||||
<p class="text-lg text-gray-400 max-w-3xl">Securely store and manage your permits, insurance certificates, and professional certifications — all in one place.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 max-w-7xl mx-auto w-full px-4 sm:px-6 lg:px-8 py-10">
|
||||
|
||||
<!-- Stats Bar -->
|
||||
<div id="stats-bar" class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"></div>
|
||||
|
||||
<!-- Expiry Warnings -->
|
||||
<div id="expiry-warnings" class="mb-8"></div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="bg-white rounded-2xl shadow-lg p-4 sm:p-6 mb-8">
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<input id="search-input" type="text" placeholder="Search documents…"
|
||||
class="w-full border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition-colors">
|
||||
</div>
|
||||
<select id="type-filter"
|
||||
class="border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition-colors">
|
||||
<option value="all">All Types</option>
|
||||
<option value="permit">Permit</option>
|
||||
<option value="insurance">Insurance</option>
|
||||
<option value="certification">Certification</option>
|
||||
<option value="registration">Registration</option>
|
||||
</select>
|
||||
<select id="status-filter"
|
||||
class="border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition-colors">
|
||||
<option value="all">All Statuses</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="expired">Expired</option>
|
||||
</select>
|
||||
<button onclick="openUploadModal()"
|
||||
class="bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold px-5 py-2.5 rounded-lg transition-colors shadow-md hover:shadow-lg text-sm whitespace-nowrap">
|
||||
+ Upload Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document List -->
|
||||
<div id="document-list" class="grid gap-4"></div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div id="empty-state" class="hidden text-center py-16">
|
||||
<p class="text-5xl mb-4">📄</p>
|
||||
<p class="text-slate-500 text-lg">No documents match your filters.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Upload Modal -->
|
||||
<div id="upload-modal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50 p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl max-w-md w-full p-6 relative">
|
||||
<button onclick="closeUploadModal()" class="absolute top-4 right-4 text-slate-400 hover:text-slate-600 text-xl leading-none">×</button>
|
||||
<h2 class="text-xl font-bold text-slate-900 mb-1">Upload Document</h2>
|
||||
<p class="text-sm text-amber-600 mb-5">⚠️ POC demo — file upload not functional.</p>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Document Type</label>
|
||||
<select id="upload-type"
|
||||
class="w-full border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none">
|
||||
<option value="permit">Permit</option>
|
||||
<option value="insurance">Insurance</option>
|
||||
<option value="certification">Certification</option>
|
||||
<option value="registration">Registration</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Document Name</label>
|
||||
<input id="upload-name" type="text" placeholder="e.g. TX Single Trip Permit"
|
||||
class="w-full border border-slate-300 rounded-lg px-4 py-2.5 text-sm focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Choose File</label>
|
||||
<div class="border-2 border-dashed border-slate-300 rounded-lg p-6 text-center text-slate-400 text-sm">
|
||||
Drag & drop or click to browse<br>
|
||||
<span class="text-xs">(disabled in POC)</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="alert('POC demo — upload not functional.'); closeUploadModal();"
|
||||
class="w-full bg-amber-500 hover:bg-amber-600 text-slate-900 font-bold py-2.5 rounded-lg transition-colors shadow-md text-sm">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-footer"></div>
|
||||
|
||||
<script src="mock-data.js"></script>
|
||||
<script src="mock-data-extended.js"></script>
|
||||
<script src="nav.js"></script>
|
||||
<script>
|
||||
renderNav('documents');
|
||||
renderBanner();
|
||||
renderFooter();
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────
|
||||
const today = new Date();
|
||||
const MS_PER_DAY = 86400000;
|
||||
|
||||
function daysUntil(dateStr) {
|
||||
return Math.ceil((new Date(dateStr) - today) / MS_PER_DAY);
|
||||
}
|
||||
|
||||
function fmtDate(dateStr) {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', { year:'numeric', month:'short', day:'numeric' });
|
||||
}
|
||||
|
||||
const typeBadge = {
|
||||
permit: 'bg-blue-100 text-blue-700',
|
||||
insurance: 'bg-green-100 text-green-700',
|
||||
certification: 'bg-purple-100 text-purple-700',
|
||||
registration: 'bg-slate-200 text-slate-700'
|
||||
};
|
||||
|
||||
function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
|
||||
|
||||
// ── Stats ───────────────────────────────────────────
|
||||
function renderStats(docs) {
|
||||
const total = docs.length;
|
||||
const active = docs.filter(d => d.status === 'active').length;
|
||||
const expired = docs.filter(d => d.status === 'expired').length;
|
||||
const expiring = docs.filter(d => d.status === 'active' && daysUntil(d.expiryDate) <= 30 && daysUntil(d.expiryDate) > 0).length;
|
||||
|
||||
const items = [
|
||||
{ label:'Total Documents', value:total, color:'bg-slate-900 text-white' },
|
||||
{ label:'Active Permits', value:active, color:'bg-green-600 text-white' },
|
||||
{ label:'Expiring Soon', value:expiring, color:'bg-amber-500 text-white' },
|
||||
{ label:'Expired', value:expired, color:'bg-red-600 text-white' }
|
||||
];
|
||||
|
||||
document.getElementById('stats-bar').innerHTML = items.map(s => `
|
||||
<div class="${s.color} rounded-2xl shadow-lg p-5 text-center">
|
||||
<div class="text-3xl font-bold">${s.value}</div>
|
||||
<div class="text-sm mt-1 opacity-90">${s.label}</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// ── Expiry Warnings ─────────────────────────────────
|
||||
function renderExpiryWarnings(docs) {
|
||||
const soon = docs.filter(d => d.status === 'active' && daysUntil(d.expiryDate) <= 30 && daysUntil(d.expiryDate) > 0);
|
||||
const el = document.getElementById('expiry-warnings');
|
||||
if (!soon.length) { el.innerHTML = ''; return; }
|
||||
el.innerHTML = `
|
||||
<div class="bg-amber-50 border border-amber-300 rounded-2xl p-5">
|
||||
<h3 class="font-bold text-amber-800 text-lg mb-3">⚠️ Expiring Within 30 Days</h3>
|
||||
<div class="space-y-2">
|
||||
${soon.map(d => `
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between bg-white rounded-xl px-4 py-3 shadow-sm border border-amber-200">
|
||||
<div>
|
||||
<span class="font-semibold text-slate-900">${d.name}</span>
|
||||
<span class="inline-block ml-2 text-xs font-medium px-2 py-0.5 rounded-full ${typeBadge[d.type]}">${capitalize(d.type)}</span>
|
||||
</div>
|
||||
<span class="text-amber-700 font-medium text-sm mt-1 sm:mt-0">Expires ${fmtDate(d.expiryDate)} (${daysUntil(d.expiryDate)} day${daysUntil(d.expiryDate) === 1 ? '' : 's'})</span>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Document Cards ──────────────────────────────────
|
||||
function renderDocuments(docs) {
|
||||
const list = document.getElementById('document-list');
|
||||
const empty = document.getElementById('empty-state');
|
||||
|
||||
if (!docs.length) {
|
||||
list.innerHTML = '';
|
||||
empty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
empty.classList.add('hidden');
|
||||
|
||||
list.innerHTML = docs.map(d => {
|
||||
const statusBadge = d.status === 'active'
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-red-100 text-red-700';
|
||||
return `
|
||||
<div class="bg-white rounded-2xl shadow-lg p-5 sm:p-6 hover:shadow-xl transition-shadow border border-slate-100">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||||
<h3 class="font-bold text-slate-900 text-base truncate">${d.name}</h3>
|
||||
<span class="inline-block text-xs font-medium px-2.5 py-0.5 rounded-full ${typeBadge[d.type]}">${capitalize(d.type)}</span>
|
||||
<span class="inline-block text-xs font-medium px-2.5 py-0.5 rounded-full ${statusBadge}">${capitalize(d.status)}</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-5 gap-y-1 text-sm text-slate-500">
|
||||
${d.state ? `<span>📍 ${d.state}</span>` : ''}
|
||||
<span>📤 Uploaded ${fmtDate(d.uploadDate)}</span>
|
||||
<span>📅 Expires ${fmtDate(d.expiryDate)}</span>
|
||||
<span>💾 ${d.fileSize}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<button onclick="alert('Viewing: ${d.name}')" class="px-3 py-1.5 text-sm font-medium rounded-lg border border-slate-300 text-slate-700 hover:bg-slate-50 transition-colors">View</button>
|
||||
<button onclick="alert('Downloading: ${d.name}')" class="px-3 py-1.5 text-sm font-medium rounded-lg border border-amber-400 text-amber-700 hover:bg-amber-50 transition-colors">Download</button>
|
||||
<button onclick="alert('Delete requested for: ${d.name}')" class="px-3 py-1.5 text-sm font-medium rounded-lg border border-red-300 text-red-600 hover:bg-red-50 transition-colors">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Filtering ────────────────────────────────────────
|
||||
function applyFilters() {
|
||||
const query = document.getElementById('search-input').value.toLowerCase();
|
||||
const type = document.getElementById('type-filter').value;
|
||||
const status = document.getElementById('status-filter').value;
|
||||
|
||||
const filtered = MOCK_DOCUMENTS.filter(d => {
|
||||
if (query && !d.name.toLowerCase().includes(query) && !d.id.toLowerCase().includes(query)) return false;
|
||||
if (type !== 'all' && d.type !== type) return false;
|
||||
if (status !== 'all' && d.status !== status) return false;
|
||||
return true;
|
||||
});
|
||||
renderDocuments(filtered);
|
||||
}
|
||||
|
||||
document.getElementById('search-input').addEventListener('input', applyFilters);
|
||||
document.getElementById('type-filter').addEventListener('change', applyFilters);
|
||||
document.getElementById('status-filter').addEventListener('change', applyFilters);
|
||||
|
||||
// ── Upload Modal ─────────────────────────────────────
|
||||
function openUploadModal() {
|
||||
const modal = document.getElementById('upload-modal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
|
||||
function closeUploadModal() {
|
||||
const modal = document.getElementById('upload-modal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
|
||||
// ── Initial Render ───────────────────────────────────
|
||||
renderStats(MOCK_DOCUMENTS);
|
||||
renderExpiryWarnings(MOCK_DOCUMENTS);
|
||||
renderDocuments(MOCK_DOCUMENTS);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user