Database changes (run sql/migration_drop_pc_tables.sql on prod): - Drop pc, pc_backup_phase2, pc_to_machine_id_mapping tables - Rename pcid columns to machineid in machineoverrides, dualpathassignments, networkinterfaces - Recreate 9 views to use machines.machineid instead of pcid - Clean orphaned records and add FK constraints to machines table ASP fixes: - editprinter.asp: Fix CLng type mismatch when no printerid provided - includes/sql.asp: Remove AutoDeactivateExpiredNotifications (endtime handles expiry) - includes/leftsidebar.asp: Update fiscal week banner styling, remove dead Information link - charts/warrantychart.asp: Use vw_warranty_status instead of pc table Dashboard API renames (naming convention): - shopfloor-dashboard: Update to use apishopfloor.asp, apibusinessunits.asp - tv-dashboard: Rename api_slides.asp to apislides.asp 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
180 lines
5.4 KiB
HTML
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('apislides.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>
|