Files
shopfloor-dashboard/server.js
cproudlock ca46c9a404 Feature: Add type-based color coding and optimize for TV display
- Extended time window from 48 hours to 72 hours
- Added isshopfloor filter - only show notifications marked for shopfloor
- Added JOIN with notificationtypes table to get type colors
- Implemented type-based color coding with 40px thick left border:
  * Green (#0ad64f) for Awareness and TBD types
  * Yellow (#ffc107) for Change type
  * Red (#dc3545) for Incident type
- Optimized layout for single-screen TV display (no scrolling):
  * Reduced all font sizes and spacing significantly
  * Set overflow: hidden and height: 100vh on body
  * Reduced header, section titles, event cards, and footer sizes
- Limited display to 3 current + 3 upcoming events max
- Shows "+ X more event(s)" indicator when needed
- Positioned LIVE badge in absolute top-right corner
- Updated all text references from 48 hours to 72 hours

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 09:46:43 -04:00

97 lines
2.9 KiB
JavaScript

const express = require('express');
const mysql = require('mysql2');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Database connection pool
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || '570005354',
password: process.env.DB_PASS || '570005354',
database: process.env.DB_NAME || 'shopdb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// Promisify for async/await
const promisePool = pool.promise();
// Serve static files
app.use(express.static('public'));
// API endpoint to get notifications
app.get('/api/notifications', async (req, res) => {
try {
const now = new Date();
const future = new Date(now.getTime() + (72 * 60 * 60 * 1000)); // 72 hours from now
const [rows] = await promisePool.query(
`SELECT n.notificationid, n.notification, n.starttime, n.endtime, n.ticketnumber, n.link,
n.isactive, n.isshopfloor, nt.typename, nt.typecolor
FROM notifications n
LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid
WHERE n.isactive = 1
AND n.isshopfloor = 1
AND (
(n.starttime <= ? AND (n.endtime IS NULL OR n.endtime >= ?))
OR (n.starttime BETWEEN ? AND ?)
)
ORDER BY n.starttime ASC`,
[future, now, now, future]
);
// Categorize notifications
const currentEvents = [];
const upcomingEvents = [];
rows.forEach(notification => {
const start = new Date(notification.starttime);
const end = notification.endtime ? new Date(notification.endtime) : null;
if (start <= now && (end === null || end >= now)) {
currentEvents.push(notification);
} else {
upcomingEvents.push(notification);
}
});
res.json({
success: true,
timestamp: new Date().toISOString(),
current: currentEvents,
upcoming: upcomingEvents
});
} catch (error) {
console.error('Database error:', error);
res.status(500).json({
success: false,
error: 'Database error: ' + error.message
});
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Start server
app.listen(PORT, () => {
console.log(`Shopfloor Dashboard running on port ${PORT}`);
console.log(`Access at: http://localhost:${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
pool.end(() => {
console.log('Database pool closed');
process.exit(0);
});
});