SammonsG's picture
Please build me a simple and extremely user-friendly website for checking-in using the system time.
f33dec1 verified
Raw History Blame Contribute Delete
15.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tech Support Queue - Admin</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.queue-item { animation: slideIn 0.3s ease-out; }
@keyframes slideIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
.status-badge { transition: all 0.2s; }
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
<div class="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="bg-indigo-600 p-2 rounded-lg">
<i data-lucide="list" class="w-6 h-6 text-white"></i>
</div>
<div>
<h1 class="text-xl font-bold text-gray-800">Support Queue</h1>
<p class="text-sm text-gray-500">Real-time check-in monitoring</p>
</div>
</div>
<div class="text-right">
<p class="text-2xl font-mono font-bold text-gray-800" id="clock">--:--:--</p>
<p class="text-xs text-gray-500" id="date">Loading...</p>
</div>
</div>
</header>
<!-- Main Content -->
<main class="max-w-6xl mx-auto px-4 py-6">
<!-- Stats Bar -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">Waiting</p>
<p class="text-2xl font-bold text-orange-600" id="statWaiting">0</p>
</div>
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
<i data-lucide="clock" class="w-5 h-5 text-orange-600"></i>
</div>
</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">Serving</p>
<p class="text-2xl font-bold text-blue-600" id="statServing">0</p>
</div>
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<i data-lucide="loader" class="w-5 h-5 text-blue-600"></i>
</div>
</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">Completed</p>
<p class="text-2xl font-bold text-green-600" id="statCompleted">0</p>
</div>
<div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i>
</div>
</div>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">Total Today</p>
<p class="text-2xl font-bold text-indigo-600" id="statTotal">0</p>
</div>
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
<i data-lucide="users" class="w-5 h-5 text-indigo-600"></i>
</div>
</div>
</div>
</div>
<!-- Action Bar -->
<div class="flex gap-3 mb-4">
<button onclick="clearAll()" class="px-4 py-2 bg-red-50 text-red-600 rounded-lg text-sm font-medium hover:bg-red-100 transition flex items-center gap-2">
<i data-lucide="trash-2" class="w-4 h-4"></i>
Clear All
</button>
<button onclick="exportData()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition flex items-center gap-2">
<i data-lucide="download" class="w-4 h-4"></i>
Export
</button>
<button onclick="loadQueue()" class="px-4 py-2 bg-indigo-50 text-indigo-600 rounded-lg text-sm font-medium hover:bg-indigo-100 transition flex items-center gap-2 ml-auto">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
Refresh
</button>
</div>
<!-- Queue List -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50 flex items-center justify-between">
<h2 class="font-semibold text-gray-800">Current Queue</h2>
<span class="text-xs text-gray-500 bg-white px-2 py-1 rounded border">Updates automatically</span>
</div>
<div id="queueList" class="divide-y divide-gray-100">
<!-- Queue items will be inserted here -->
</div>
<div id="emptyState" class="hidden py-12 text-center text-gray-500">
<i data-lucide="inbox" class="w-12 h-12 mx-auto mb-3 text-gray-300"></i>
<p>No tickets in queue</p>
</div>
</div>
</main>
<!-- Toast Notification -->
<div id="toast" class="hidden fixed bottom-4 right-4 bg-gray-800 text-white px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 translate-y-20">
<div class="flex items-center gap-2">
<i data-lucide="info" class="w-5 h-5"></i>
<span id="toastMessage">Notification</span>
</div>
</div>
<script>
// Initialize icons
lucide.createIcons();
// Update clock
function updateClock() {
const now = new Date();
document.getElementById('clock').textContent = now.toLocaleTimeString('en-US', { hour12: false });
document.getElementById('date').textContent = now.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
}
setInterval(updateClock, 1000);
updateClock();
// Load and display queue
function loadQueue() {
const queue = JSON.parse(localStorage.getItem('techSupportQueue') || '[]');
const queueList = document.getElementById('queueList');
const emptyState = document.getElementById('emptyState');
// Update stats
const waiting = queue.filter(t => t.status === 'waiting').length;
const serving = queue.filter(t => t.status === 'serving').length;
const completed = queue.filter(t => t.status === 'completed').length;
document.getElementById('statWaiting').textContent = waiting;
document.getElementById('statServing').textContent = serving;
document.getElementById('statCompleted').textContent = completed;
document.getElementById('statTotal').textContent = queue.length;
if (queue.length === 0) {
queueList.innerHTML = '';
emptyState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
// Sort by timestamp (oldest first for FCFS)
const sortedQueue = [...queue].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
queueList.innerHTML = sortedQueue.map((ticket, index) => {
const timeAgo = getTimeAgo(new Date(ticket.timestamp));
const statusColors = {
waiting: 'bg-orange-100 text-orange-800 border-orange-200',
serving: 'bg-blue-100 text-blue-800 border-blue-200',
completed: 'bg-green-100 text-green-800 border-green-200'
};
const techColors = {
Gary: 'bg-blue-50 text-blue-700 border-blue-200',
Will: 'bg-green-50 text-green-700 border-green-200',
Tim: 'bg-purple-50 text-purple-700 border-purple-200',
Anyone: 'bg-orange-50 text-orange-700 border-orange-200'
};
return `
<div class="queue-item p-4 hover:bg-gray-50 transition flex items-center justify-between gap-4">
<div class="flex items-center gap-4 flex-1">
<div class="flex flex-col items-center justify-center w-12">
<span class="text-2xl font-bold text-gray-300">#${index + 1}</span>
</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="font-semibold text-gray-900">${ticket.name}</span>
<span class="text-xs text-gray-400">• ${timeAgo}</span>
</div>
<div class="flex items-center gap-2 flex-wrap">
<span class="px-2 py-1 rounded-full text-xs font-medium border ${techColors[ticket.technician] || 'bg-gray-100 text-gray-700'}">
${ticket.technician}
</span>
<span class="px-2 py-1 rounded-full text-xs font-medium border ${statusColors[ticket.status]} status-badge">
${ticket.status.charAt(0).toUpperCase() + ticket.status.slice(1)}
</span>
<span class="text-xs text-gray-500 font-mono">${ticket.checkInTime}</span>
</div>
</div>
</div>
<div class="flex items-center gap-2">
${ticket.status === 'waiting' ? `
<button onclick="updateStatus(${ticket.id}, 'serving')" class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition" title="Start Serving">
<i data-lucide="play" class="w-5 h-5"></i>
</button>
` : ''}
${ticket.status === 'serving' ? `
<button onclick="updateStatus(${ticket.id}, 'completed')" class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition" title="Complete">
<i data-lucide="check" class="w-5 h-5"></i>
</button>
` : ''}
<button onclick="deleteTicket(${ticket.id})" class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition" title="Remove">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
</div>
`;
}).join('');
lucide.createIcons();
}
function getTimeAgo(date) {
const seconds = Math.floor((new Date() - date) / 1000);
if (seconds < 60) return 'Just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
return `${Math.floor(hours / 24)}d ago`;
}
function updateStatus(id, status) {
let queue = JSON.parse(localStorage.getItem('techSupportQueue') || '[]');
const ticket = queue.find(t => t.id === id);
if (ticket) {
ticket.status = status;
localStorage.setItem('techSupportQueue', JSON.stringify(queue));
loadQueue();
showToast(`Ticket marked as ${status}`);
}
}
function deleteTicket(id) {
if (confirm('Remove this ticket from the queue?')) {
let queue = JSON.parse(localStorage.getItem('techSupportQueue') || '[]');
queue = queue.filter(t => t.id !== id);
localStorage.setItem('techSupportQueue', JSON.stringify(queue));
loadQueue();
showToast('Ticket removed');
}
}
function clearAll() {
if (confirm('Are you sure you want to clear all tickets?')) {
localStorage.setItem('techSupportQueue', '[]');
loadQueue();
showToast('All tickets cleared');
}
}
function exportData() {
const queue = JSON.parse(localStorage.getItem('techSupportQueue') || '[]');
if (queue.length === 0) {
showToast('No data to export');
return;
}
const csv = [
['Name', 'Technician', 'Check-in Time', 'Status', 'Timestamp'].join(','),
...queue.map(t => [
t.name,
t.technician,
t.checkInTime,
t.status,
t.timestamp
].join(','))
].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `tech-support-queue-${new Date().toISOString().split('T')[0]}.csv`;
a.click();
showToast('Data exported to CSV');
}
function showToast(message) {
const toast = document.getElementById('toast');
document.getElementById('toastMessage').textContent = message;
toast.classList.remove('translate-y-20', 'hidden');
setTimeout(() => {
toast.classList.add('translate-y-20');
}, 3000);
}
// Auto-refresh every 5 seconds
setInterval(loadQueue, 5000);
// Initial load
loadQueue();
</script>
</body>
</html>