Files
shopdb/tv-dashboard/index.html
cproudlock 65b622c361 Add USB checkout system and SSO profile page
New Features:
- USB Device checkout/check-in system with barcode scanning
  - displayusb.asp: List all USB devices with status
  - addusb.asp: Add new USB devices via barcode scan
  - checkout_usb.asp/savecheckout_usb.asp: Check out USB to SSO
  - checkin_usb.asp/savecheckin_usb.asp: Check in with wipe confirmation
  - usb_history.asp: Full checkout history with filters
  - api_usb.asp: JSON API for AJAX lookups
- displayprofile.asp: SSO profile page showing user info and USB history
- Date/time format changed to 12-hour (MM/DD/YYYY h:mm AM/PM)
- SSO links in USB history now link to profile page via search

Database:
- New machinetypeid 44 for USB devices
- New usb_checkouts table for tracking checkouts

Cleanup:
- Removed v2 folder (duplicate/old files)
- Removed old debug/test files
- Removed completed migration documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 11:16:14 -05:00

180 lines
5.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>West Jefferson - Display</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
margin: 0;
padding: 0;
overflow: hidden;
height: 100vh;
width: 100vw;
}
.slideshow-container {
position: relative;
width: 100vw;
height: 100vh;
background: #000;
}
.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 1s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
}
.slide.active {
opacity: 1;
}
.slide img {
width: 100%;
height: 100%;
object-fit: contain;
}
.progress-bar {
position: fixed;
bottom: 0;
left: 0;
height: 4px;
background: #4181ff;
transition: width linear;
z-index: 100;
}
.error-message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 24px;
}
.error-message h2 {
color: #ff4444;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="slideshow-container" id="slideshow"></div>
<div class="progress-bar" id="progressBar"></div>
<script>
const INTERVAL = 10; // seconds between slides
let slides = [];
let currentSlide = 0;
let slideTimer = null;
// Fetch slides from API
async function fetchSlides() {
try {
const response = await fetch('api_slides.asp');
if (!response.ok) throw new Error('API error');
const data = await response.json();
if (data.success && data.slides && data.slides.length > 0) {
updateSlides(data.slides, data.basepath);
} else {
showError(data.message || 'No slides found');
}
} catch (error) {
console.error('Error fetching slides:', error);
showError('Unable to load slides');
}
}
// Update slides in DOM
function updateSlides(newSlides, basepath) {
const slideshow = document.getElementById('slideshow');
const currentSlideNames = slides.map(s => s.filename);
const newSlideNames = newSlides.map(s => s.filename);
// Check if slides changed
if (JSON.stringify(currentSlideNames) !== JSON.stringify(newSlideNames)) {
slideshow.innerHTML = '';
slides = [];
newSlides.forEach((slide, index) => {
const div = document.createElement('div');
div.className = 'slide' + (index === 0 ? ' active' : '');
const img = document.createElement('img');
img.src = basepath + encodeURIComponent(slide.filename);
div.appendChild(img);
slideshow.appendChild(div);
slides.push({ element: div, filename: slide.filename });
});
currentSlide = 0;
if (slides.length > 1) startSlideshow();
}
}
function showError(message) {
document.getElementById('slideshow').innerHTML =
'<div class="error-message"><h2>Display Error</h2><p>' + message + '</p></div>';
}
function startSlideshow() {
if (slideTimer) clearTimeout(slideTimer);
scheduleNextSlide();
}
function scheduleNextSlide() {
const progressBar = document.getElementById('progressBar');
progressBar.style.width = '0%';
progressBar.style.transition = 'none';
setTimeout(() => {
progressBar.style.transition = 'width ' + INTERVAL + 's linear';
progressBar.style.width = '100%';
}, 50);
slideTimer = setTimeout(() => {
nextSlide();
scheduleNextSlide();
}, INTERVAL * 1000);
}
function nextSlide() {
if (slides.length === 0) return;
slides[currentSlide].element.classList.remove('active');
currentSlide = (currentSlide + 1) % slides.length;
slides[currentSlide].element.classList.add('active');
}
// Start
fetchSlides();
// Refresh every 60 seconds to pick up new slides
setInterval(fetchSlides, 60000);
</script>
</body>
</html>