Spaces:
Running
Running
Create a responsive internal web app for hotel housekeeping operations, optimized for tablet and mobile use during room rounds. The homepage should display a visual, clickable grid of all 124 rooms, each linking to a dedicated page showing cleaning status, maintenance needs, and occupancy. Use a dark, low-light-friendly interface inspired by Trello, with muted backgrounds, bright highlight colors, and legible typography for night use. Include a search bar and status filter, plus β+β and βββ buttons under each category to add or remove shared notes visible to all employees. Add a management dashboard summarizing room statuses, maintenance alerts, and pending cleanings. Implement user roles with two views: Housekeeping Staff(view and update individual room info) and Managers (access full dashboard and editing privileges across all rooms). Enable notification alerts when room statuses change, when maintenance is requested, or when new notes are added, ensuring timely updates for all users.
9529790 verified | // User roles and authentication simulation | |
| let currentUser = { | |
| role: 'staff', // Default to staff view | |
| name: 'Housekeeper' | |
| }; | |
| // Check for manager view | |
| function checkUserRole() { | |
| const urlParams = new URLSearchParams(window.location.search); | |
| if (urlParams.has('role') && urlParams.get('role') === 'manager') { | |
| currentUser.role = 'manager'; | |
| document.getElementById('manager-view-btn').classList.remove('hidden'); | |
| document.getElementById('stats-dashboard').classList.remove('hidden'); | |
| } | |
| } | |
| // Room status counters | |
| const statusCounts = { | |
| clean: 0, | |
| dirty: 0, | |
| 'in-progress': 0, | |
| maintenance: 0 | |
| }; | |
| // Shared functionality across pages | |
| document.addEventListener('DOMContentLoaded', function() { | |
| checkUserRole(); | |
| // Filter functionality | |
| const filterBtn = document.querySelector('.filter-btn'); | |
| const filterDropdown = document.querySelector('.filter-dropdown'); | |
| const filterOptions = document.querySelectorAll('.filter-option'); | |
| const roomCards = document.querySelectorAll('.room-card'); | |
| filterBtn.addEventListener('click', () => { | |
| filterDropdown.classList.toggle('hidden'); | |
| }); | |
| filterOptions.forEach(option => { | |
| option.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const status = option.dataset.status; | |
| roomCards.forEach(card => { | |
| if (status === 'all') { | |
| card.style.display = 'block'; | |
| } else { | |
| card.style.display = card.classList.contains(`border-${status === 'clean' ? 'green' : status === 'dirty' ? 'red' : status === 'in-progress' ? 'yellow' : 'purple'}-500`) ? 'block' : 'none'; | |
| } | |
| }); | |
| filterDropdown.classList.add('hidden'); | |
| }); | |
| }); | |
| // Count statuses | |
| document.querySelectorAll('.room-card').forEach(card => { | |
| if (card.classList.contains('border-green-500')) statusCounts.clean++; | |
| else if (card.classList.contains('border-red-500')) statusCounts.dirty++; | |
| else if (card.classList.contains('border-yellow-500')) statusCounts['in-progress']++; | |
| else if (card.classList.contains('border-purple-500')) statusCounts.maintenance++; | |
| }); | |
| // Update stats dashboard | |
| document.getElementById('clean-count').textContent = statusCounts.clean; | |
| document.getElementById('dirty-count').textContent = statusCounts.dirty; | |
| document.getElementById('progress-count').textContent = statusCounts['in-progress']; | |
| document.getElementById('maintenance-count').textContent = statusCounts.maintenance; | |
| // Note buttons functionality | |
| document.querySelectorAll('.note-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| const roomNumber = btn.dataset.room; | |
| const action = btn.dataset.action; | |
| if (action === 'add') { | |
| showNotification(`Note added to Room ${roomNumber}`, 'success'); | |
| } else { | |
| showNotification(`Note removed from Room ${roomNumber}`, 'error'); | |
| } | |
| }); | |
| }); | |
| // Manager view button | |
| document.getElementById('manager-view-btn')?.addEventListener('click', () => { | |
| window.location.href = window.location.pathname + '?role=manager'; | |
| }); | |
| // Initialize tooltips | |
| const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); | |
| tooltipTriggerList.map(function (tooltipTriggerEl) { | |
| return new bootstrap.Tooltip(tooltipTriggerEl); | |
| }); | |
| // Handle status filter dropdown | |
| const filterButton = document.querySelector('.filter-button'); | |
| if (filterButton) { | |
| filterButton.addEventListener('click', function() { | |
| document.querySelector('.filter-dropdown').classList.toggle('hidden'); | |
| }); | |
| } | |
| // Close dropdown when clicking outside | |
| document.addEventListener('click', function(event) { | |
| if (!event.target.closest('.filter-button') && !event.target.closest('.filter-dropdown')) { | |
| const dropdowns = document.querySelectorAll('.filter-dropdown'); | |
| dropdowns.forEach(dropdown => { | |
| dropdown.classList.add('hidden'); | |
| }); | |
| } | |
| }); | |
| }); | |
| // Notification system | |
| function showNotification(message, type = 'info') { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed top-4 right-4 z-50 px-6 py-4 rounded-lg shadow-lg flex items-center ${type === 'success' ? 'bg-green-800' : type === 'error' ? 'bg-red-800' : 'bg-primary-700'}`; | |
| notification.innerHTML = ` | |
| <i data-feather="${type === 'success' ? 'check-circle' : type === 'error' ? 'alert-circle' : 'info'}" class="mr-3"></i> | |
| <span>${message}</span> | |
| `; | |
| document.body.appendChild(notification); | |
| feather.replace(); | |
| setTimeout(() => { | |
| notification.classList.add('opacity-0', 'transition-opacity', 'duration-300'); | |
| setTimeout(() => notification.remove(), 300); | |
| }, 3000); | |
| } |