added that sonderraume also update

This commit is contained in:
Kendrick Bollens
2026-01-30 01:00:23 +01:00
parent 3003cad691
commit 132498ea52

View File

@@ -3,6 +3,7 @@ from flask import Flask, render_template_string, jsonify
from datetime import datetime, date from datetime import datetime, date
app = Flask(__name__) app = Flask(__name__)
# Updated path for Docker volume
DB_FILE = "/app/data/cleaning_logs.db" DB_FILE = "/app/data/cleaning_logs.db"
# Room definitions # Room definitions
@@ -25,7 +26,6 @@ BUILDINGS = {
} }
def get_all_defined_rooms(): def get_all_defined_rooms():
"""Get a set of all room numbers defined in buildings"""
rooms = set() rooms = set()
for floors in BUILDINGS.values(): for floors in BUILDINGS.values():
for floor in floors: for floor in floors:
@@ -33,9 +33,8 @@ def get_all_defined_rooms():
return rooms return rooms
def get_room_statuses(): def get_room_statuses():
"""Returns dict: {room_number: (last_cleaned_time, is_today)}"""
today = date.today().strftime("%Y-%m-%d") today = date.today().strftime("%Y-%m-%d")
try:
with sqlite3.connect(DB_FILE) as conn: with sqlite3.connect(DB_FILE) as conn:
cursor = conn.execute(""" cursor = conn.execute("""
SELECT room_number, MAX(cleaned_at) as last_cleaned SELECT room_number, MAX(cleaned_at) as last_cleaned
@@ -43,6 +42,8 @@ def get_room_statuses():
GROUP BY room_number GROUP BY room_number
""") """)
rows = cursor.fetchall() rows = cursor.fetchall()
except sqlite3.OperationalError:
return {}
statuses = {} statuses = {}
for room, last_cleaned in rows: for room, last_cleaned in rows:
@@ -51,18 +52,11 @@ def get_room_statuses():
"is_today": is_today, "is_today": is_today,
"last_cleaned": last_cleaned "last_cleaned": last_cleaned
} }
return statuses return statuses
def get_special_rooms(statuses): def get_special_rooms(statuses):
"""Get rooms that aren't in any defined building"""
defined_rooms = get_all_defined_rooms() defined_rooms = get_all_defined_rooms()
special_rooms = [] special_rooms = [r for r in statuses.keys() if r not in defined_rooms]
for room_number in statuses.keys():
if room_number not in defined_rooms:
special_rooms.append(room_number)
return sorted(special_rooms) return sorted(special_rooms)
@app.route('/') @app.route('/')
@@ -78,7 +72,6 @@ def dashboard():
@app.route('/api/status') @app.route('/api/status')
def api_status(): def api_status():
"""API endpoint for live updates"""
statuses = get_room_statuses() statuses = get_room_statuses()
special_rooms = get_special_rooms(statuses) special_rooms = get_special_rooms(statuses)
return jsonify({ return jsonify({
@@ -95,46 +88,46 @@ TEMPLATE = '''
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; } body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
h1 { color: #333; } h1 { color: #333; }
.controls { margin-bottom: 20px; } .controls { margin-bottom: 20px; }
.toggle { padding: 10px 20px; font-size: 16px; cursor: pointer; } .toggle { padding: 10px 20px; font-size: 16px; cursor: pointer; border-radius: 5px; border: 1px solid #ccc; }
.building { margin-bottom: 30px; background: white; padding: 15px; border-radius: 8px; } .building { margin-bottom: 30px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.building h2 { margin-top: 0; color: #555; } .building h2 { margin-top: 0; color: #555; border-bottom: 2px solid #eee; padding-bottom: 5px; }
.floor { margin-bottom: 15px; } .floor { margin-bottom: 15px; }
.floor-label { font-weight: bold; margin-bottom: 5px; color: #777; } .floor-label { font-weight: bold; margin-bottom: 8px; color: #777; }
.rooms { display: flex; flex-wrap: wrap; gap: 8px; } .rooms { display: flex; flex-wrap: wrap; gap: 8px; }
.room { .room {
padding: 10px 15px; padding: 10px 15px;
border-radius: 5px; border-radius: 5px;
min-width: 80px; min-width: 90px;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
transition: background 0.3s ease; transition: all 0.3s ease;
} }
.room.cleaned { background: #4CAF50; color: white; } .room.cleaned { background: #4CAF50; color: white; }
.room.not-cleaned { background: #f44336; color: white; } .room.not-cleaned { background: #f44336; color: white; }
.room.cleaned .time-display, .room.not-cleaned .time-display { .time-display {
display: block; display: block;
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
margin-top: 6px; margin-top: 6px;
line-height: 1.4; line-height: 1.2;
} }
.hidden { display: none !important; } .hidden { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
<h1>🧹 Room Cleaning Status</h1> <h1>🧹 Room Cleaning Status</h1>
<div class="controls"> <div class="controls">
<button class="toggle" onclick="toggleFilter()"> <button class="toggle" onclick="toggleFilter()">
<span id="toggle-text">Show Only Cleaned Today</span> <span id="toggle-text">Show Only Cleaned Today</span>
</button> </button>
</div> </div>
<div id="buildings-container">
{% for building_name, floors in buildings.items() %} {% for building_name, floors in buildings.items() %}
<div class="building" data-building="{{ building_name }}"> <div class="building" data-building="{{ building_name }}">
<h2>{{ building_name }}</h2> <h2>{{ building_name }}</h2>
{% for floor in floors %} {% for floor in floors %}
<div class="floor" data-floor="{{ building_name }}-{{ loop.index }}"> <div class="floor">
<div class="floor-label">Etage {{ loop.index }}</div> <div class="floor-label">Etage {{ loop.index }}</div>
<div class="rooms"> <div class="rooms">
{% for room in floor %} {% for room in floor %}
@@ -145,9 +138,7 @@ TEMPLATE = '''
data-room="{{ room }}"> data-room="{{ room }}">
{{ room }} {{ room }}
<span class="time-display"> <span class="time-display">
{% if status.last_cleaned %} {% if status.last_cleaned %}{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}{% else %}Never{% endif %}
{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}
{% else %}Never{% endif %}
</span> </span>
</div> </div>
{% endfor %} {% endfor %}
@@ -157,88 +148,70 @@ TEMPLATE = '''
</div> </div>
{% endfor %} {% endfor %}
{% if special_rooms %} <div id="sonderraeume-section" class="building {{ 'hidden' if not special_rooms }}" data-building="Sonderräume">
<div class="building" data-building="Sonderräume">
<h2>Sonderräume</h2> <h2>Sonderräume</h2>
<div class="floor" data-floor="Sonderräume-1"> <div class="floor">
<div class="rooms" id="special-rooms-container"> <div class="rooms" id="special-rooms-container">
{% for room in special_rooms %} {% for room in special_rooms %}
{% set status = statuses.get(room, {"is_today": False, "last_cleaned": None}) %} {% set status = statuses.get(room, {"is_today": False, "last_cleaned": None}) %}
<div class="room {{ 'cleaned' if status.is_today else 'not-cleaned' }}" <div class="room {{ 'cleaned' if status.is_today else 'not-cleaned' }}"
data-cleaned="{{ 'yes' if status.is_today else 'no' }}" data-cleaned="{{ 'yes' if status.is_today else 'no' }}"
data-room="{{ room }}" data-room="{{ room }}">
data-special="true">
{{ room }} {{ room }}
<span class="time-display"> <span class="time-display">{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}</span>
{% if status.last_cleaned %}
{{ status.last_cleaned[5:10] }}<br>{{ status.last_cleaned[11:16] }}
{% else %}Never{% endif %}
</span>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} </div>
<script> <script>
let filterActive = false; let filterActive = false;
function formatDateTime(datetimeStr) { function formatDateTime(ts) {
if (!datetimeStr) return 'Never'; if (!ts) return 'Never';
const date = datetimeStr.slice(5, 10); return ts.slice(5, 10) + '<br>' + ts.slice(11, 16);
const time = datetimeStr.slice(11, 16);
return date + '<br>' + time;
} }
function applyFilter() { function applyFilter() {
const rooms = document.querySelectorAll('.room');
const floors = document.querySelectorAll('.floor');
const buildings = document.querySelectorAll('.building'); const buildings = document.querySelectorAll('.building');
buildings.forEach(building => {
let buildingHasVisibleRoom = false;
const floors = building.querySelectorAll('.floor');
floors.forEach(floor => {
let floorHasVisibleRoom = false;
const rooms = floor.querySelectorAll('.room');
if (filterActive) {
rooms.forEach(room => { rooms.forEach(room => {
if (room.dataset.cleaned === 'no') { const isCleaned = room.dataset.cleaned === 'yes';
if (filterActive && !isCleaned) {
room.classList.add('hidden'); room.classList.add('hidden');
} else { } else {
room.classList.remove('hidden'); room.classList.remove('hidden');
floorHasVisibleRoom = true;
buildingHasVisibleRoom = true;
} }
}); });
floors.forEach(floor => { if (filterActive && !floorHasVisibleRoom) floor.classList.add('hidden');
const visibleRooms = floor.querySelectorAll('.room:not(.hidden)'); else floor.classList.remove('hidden');
if (visibleRooms.length === 0) {
floor.classList.add('hidden');
} else {
floor.classList.remove('hidden');
}
}); });
buildings.forEach(building => { if (filterActive && !buildingHasVisibleRoom) building.classList.add('hidden');
const visibleFloors = building.querySelectorAll('.floor:not(.hidden)'); else building.classList.remove('hidden');
if (visibleFloors.length === 0) {
// Special override: if building is Sonderräume and empty, always hide
if (building.dataset.building === 'Sonderräume' && building.querySelectorAll('.room').length === 0) {
building.classList.add('hidden'); building.classList.add('hidden');
} else {
building.classList.remove('hidden');
} }
}); });
} else {
rooms.forEach(room => room.classList.remove('hidden'));
floors.forEach(floor => floor.classList.remove('hidden'));
buildings.forEach(building => building.classList.remove('hidden'));
}
} }
function toggleFilter() { function toggleFilter() {
filterActive = !filterActive; filterActive = !filterActive;
const toggleText = document.getElementById('toggle-text'); document.getElementById('toggle-text').textContent = filterActive ? 'Show All Rooms' : 'Show Only Cleaned Today';
if (filterActive) {
toggleText.textContent = 'Show All Rooms';
} else {
toggleText.textContent = 'Show Only Cleaned Today';
}
applyFilter(); applyFilter();
} }
@@ -249,68 +222,36 @@ TEMPLATE = '''
const statuses = data.statuses; const statuses = data.statuses;
const specialRooms = data.special_rooms; const specialRooms = data.special_rooms;
// Update existing rooms // Update All existing rooms in standard buildings
document.querySelectorAll('.room').forEach(roomDiv => { document.querySelectorAll('.room').forEach(roomDiv => {
const roomNumber = roomDiv.dataset.room; const roomNo = roomDiv.dataset.room;
const status = statuses[roomNumber] || { is_today: false, last_cleaned: null }; const s = statuses[roomNo];
if (s) {
if (status.is_today) { roomDiv.classList.toggle('cleaned', s.is_today);
roomDiv.classList.remove('not-cleaned'); roomDiv.classList.toggle('not-cleaned', !s.is_today);
roomDiv.classList.add('cleaned'); roomDiv.dataset.cleaned = s.is_today ? 'yes' : 'no';
roomDiv.dataset.cleaned = 'yes'; roomDiv.querySelector('.time-display').innerHTML = formatDateTime(s.last_cleaned);
} else {
roomDiv.classList.remove('cleaned');
roomDiv.classList.add('not-cleaned');
roomDiv.dataset.cleaned = 'no';
} }
const timeDisplay = roomDiv.querySelector('.time-display');
timeDisplay.innerHTML = formatDateTime(status.last_cleaned);
}); });
// Update special rooms section // Rebuild Sonderräume container
const specialContainer = document.getElementById('special-rooms-container'); const specialContainer = document.getElementById('special-rooms-container');
if (specialContainer && specialRooms.length > 0) { const sonderSection = document.getElementById('sonderraeume-section');
const existingSpecialRooms = new Set(
Array.from(specialContainer.querySelectorAll('.room'))
.map(r => r.dataset.room)
);
// Add new special rooms if (specialRooms.length > 0) {
specialRooms.forEach(roomNumber => { sonderSection.classList.remove('hidden');
if (!existingSpecialRooms.has(roomNumber)) { let html = '';
const status = statuses[roomNumber]; specialRooms.forEach(room => {
const roomDiv = document.createElement('div'); const s = statuses[room];
roomDiv.className = 'room ' + (status.is_today ? 'cleaned' : 'not-cleaned'); html += `<div class="room ${s.is_today ? 'cleaned' : 'not-cleaned'}" data-cleaned="${s.is_today ? 'yes' : 'no'}" data-room="${room}">${room}<span class="time-display">${formatDateTime(s.last_cleaned)}</span></div>`;
roomDiv.dataset.cleaned = status.is_today ? 'yes' : 'no';
roomDiv.dataset.room = roomNumber;
roomDiv.dataset.special = 'true';
roomDiv.innerHTML = `
${roomNumber}
<span class="time-display">${formatDateTime(status.last_cleaned)}</span>
`;
specialContainer.appendChild(roomDiv);
}
}); });
specialContainer.innerHTML = html;
// Show Sonderräume section if it was hidden } else {
const sonderraumeBuilding = document.querySelector('[data-building="Sonderräume"]'); sonderSection.classList.add('hidden');
if (sonderraumeBuilding) {
sonderraumeBuilding.classList.remove('hidden');
}
} else if (specialRooms.length === 0) {
// Hide Sonderräume if no special rooms exist
const sonderraumeBuilding = document.querySelector('[data-building="Sonderräume"]');
if (sonderraumeBuilding) {
sonderraumeBuilding.classList.add('hidden');
}
} }
applyFilter(); applyFilter();
} catch (e) { console.error("Update failed", e); }
} catch (error) {
console.error('Failed to update statuses:', error);
}
} }
setInterval(updateStatuses, 1000); setInterval(updateStatuses, 1000);
@@ -320,4 +261,4 @@ TEMPLATE = '''
''' '''
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True) app.run(host='0.0.0.0', port=5001)