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.
Browse files- README.md +7 -4
- components/footer.js +62 -0
- components/navbar.js +106 -0
- index.html +138 -19
- room.html +203 -0
- script.js +130 -0
- style.css +81 -18
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
colorFrom: purple
|
| 5 |
colorTo: gray
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: HotelKeeper Pro 🏨
|
| 3 |
+
colorFrom: blue
|
|
|
|
| 4 |
colorTo: gray
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
components/footer.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
background-color: rgb(17, 24, 39);
|
| 10 |
+
color: rgba(255, 255, 255, 0.6);
|
| 11 |
+
padding: 1.5rem;
|
| 12 |
+
margin-top: 2rem;
|
| 13 |
+
}
|
| 14 |
+
.footer-content {
|
| 15 |
+
max-width: 1200px;
|
| 16 |
+
margin: 0 auto;
|
| 17 |
+
display: flex;
|
| 18 |
+
justify-content: space-between;
|
| 19 |
+
align-items: center;
|
| 20 |
+
flex-wrap: wrap;
|
| 21 |
+
}
|
| 22 |
+
.footer-links {
|
| 23 |
+
display: flex;
|
| 24 |
+
gap: 1.5rem;
|
| 25 |
+
}
|
| 26 |
+
.footer-link {
|
| 27 |
+
color: rgba(255, 255, 255, 0.6);
|
| 28 |
+
text-decoration: none;
|
| 29 |
+
transition: color 0.2s;
|
| 30 |
+
}
|
| 31 |
+
.footer-link:hover {
|
| 32 |
+
color: white;
|
| 33 |
+
}
|
| 34 |
+
.copyright {
|
| 35 |
+
font-size: 0.875rem;
|
| 36 |
+
}
|
| 37 |
+
@media (max-width: 768px) {
|
| 38 |
+
.footer-content {
|
| 39 |
+
flex-direction: column;
|
| 40 |
+
gap: 1rem;
|
| 41 |
+
text-align: center;
|
| 42 |
+
}
|
| 43 |
+
.footer-links {
|
| 44 |
+
order: -1;
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
</style>
|
| 48 |
+
<div class="footer-content">
|
| 49 |
+
<div class="copyright">
|
| 50 |
+
© ${new Date().getFullYear()} RoomRanger. All rights reserved.
|
| 51 |
+
</div>
|
| 52 |
+
<div class="footer-links">
|
| 53 |
+
<a href="/privacy" class="footer-link">Privacy</a>
|
| 54 |
+
<a href="/terms" class="footer-link">Terms</a>
|
| 55 |
+
<a href="/contact" class="footer-link">Contact</a>
|
| 56 |
+
<a href="/help" class="footer-link">Help</a>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
`;
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
customElements.define('custom-footer', CustomFooter);
|
components/navbar.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
background-color: rgb(17, 24, 39);
|
| 10 |
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 11 |
+
}
|
| 12 |
+
.navbar {
|
| 13 |
+
display: flex;
|
| 14 |
+
justify-content: space-between;
|
| 15 |
+
align-items: center;
|
| 16 |
+
padding: 1rem 1.5rem;
|
| 17 |
+
max-width: 100%;
|
| 18 |
+
}
|
| 19 |
+
.logo {
|
| 20 |
+
display: flex;
|
| 21 |
+
align-items: center;
|
| 22 |
+
font-weight: 700;
|
| 23 |
+
font-size: 1.25rem;
|
| 24 |
+
color: white;
|
| 25 |
+
}
|
| 26 |
+
.logo-icon {
|
| 27 |
+
margin-right: 0.75rem;
|
| 28 |
+
color: #3B82F6;
|
| 29 |
+
}
|
| 30 |
+
.nav-links {
|
| 31 |
+
display: flex;
|
| 32 |
+
gap: 1.5rem;
|
| 33 |
+
}
|
| 34 |
+
.nav-link {
|
| 35 |
+
color: rgba(255, 255, 255, 0.7);
|
| 36 |
+
text-decoration: none;
|
| 37 |
+
transition: color 0.2s;
|
| 38 |
+
display: flex;
|
| 39 |
+
align-items: center;
|
| 40 |
+
}
|
| 41 |
+
.nav-link:hover {
|
| 42 |
+
color: white;
|
| 43 |
+
}
|
| 44 |
+
.nav-icon {
|
| 45 |
+
margin-right: 0.5rem;
|
| 46 |
+
width: 1rem;
|
| 47 |
+
height: 1rem;
|
| 48 |
+
}
|
| 49 |
+
.user-menu {
|
| 50 |
+
display: flex;
|
| 51 |
+
align-items: center;
|
| 52 |
+
gap: 1rem;
|
| 53 |
+
}
|
| 54 |
+
.avatar {
|
| 55 |
+
width: 2.5rem;
|
| 56 |
+
height: 2.5rem;
|
| 57 |
+
border-radius: 9999px;
|
| 58 |
+
background-color: rgb(55, 65, 81);
|
| 59 |
+
display: flex;
|
| 60 |
+
align-items: center;
|
| 61 |
+
justify-content: center;
|
| 62 |
+
color: white;
|
| 63 |
+
font-weight: 600;
|
| 64 |
+
}
|
| 65 |
+
@media (max-width: 768px) {
|
| 66 |
+
.nav-links {
|
| 67 |
+
display: none;
|
| 68 |
+
}
|
| 69 |
+
.mobile-menu-button {
|
| 70 |
+
display: block;
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
</style>
|
| 74 |
+
<nav class="navbar">
|
| 75 |
+
<a href="/" class="logo">
|
| 76 |
+
<i data-feather="home" class="logo-icon"></i>
|
| 77 |
+
RoomRanger
|
| 78 |
+
</a>
|
| 79 |
+
|
| 80 |
+
<div class="nav-links">
|
| 81 |
+
<a href="/" class="nav-link">
|
| 82 |
+
<i data-feather="grid" class="nav-icon"></i>
|
| 83 |
+
Dashboard
|
| 84 |
+
</a>
|
| 85 |
+
<a href="/rooms.html" class="nav-link">
|
| 86 |
+
<i data-feather="list" class="nav-icon"></i>
|
| 87 |
+
All Rooms
|
| 88 |
+
</a>
|
| 89 |
+
<a href="/reports.html" class="nav-link">
|
| 90 |
+
<i data-feather="file-text" class="nav-icon"></i>
|
| 91 |
+
Reports
|
| 92 |
+
</a>
|
| 93 |
+
<a href="/settings.html" class="nav-link">
|
| 94 |
+
<i data-feather="settings" class="nav-icon"></i>
|
| 95 |
+
Settings
|
| 96 |
+
</a>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<div class="user-menu">
|
| 100 |
+
<div class="avatar">JD</div>
|
| 101 |
+
</div>
|
| 102 |
+
</nav>
|
| 103 |
+
`;
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
customElements.define('custom-navbar', CustomNavbar);
|
index.html
CHANGED
|
@@ -1,19 +1,138 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>RoomRanger | Dashboard</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<script>
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: 'class',
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
primary: {
|
| 18 |
+
500: '#3B82F6',
|
| 19 |
+
600: '#2563EB'
|
| 20 |
+
},
|
| 21 |
+
secondary: {
|
| 22 |
+
500: '#10B981',
|
| 23 |
+
600: '#059669'
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
</script>
|
| 30 |
+
</head>
|
| 31 |
+
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
| 32 |
+
<custom-navbar></custom-navbar>
|
| 33 |
+
<main class="container mx-auto px-4 py-6">
|
| 34 |
+
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
|
| 35 |
+
<h1 class="text-2xl font-bold">Room Status Dashboard</h1>
|
| 36 |
+
|
| 37 |
+
<div class="flex flex-col sm:flex-row gap-2 w-full md:w-auto">
|
| 38 |
+
<div class="relative w-full">
|
| 39 |
+
<input type="text" placeholder="Search rooms..." class="bg-gray-800 border border-gray-700 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500 w-full">
|
| 40 |
+
<i data-feather="search" class="absolute left-3 top-2.5 text-gray-400"></i>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<div class="flex gap-2">
|
| 44 |
+
<div class="relative">
|
| 45 |
+
<button class="filter-btn bg-primary-500 hover:bg-primary-600 px-4 py-2 rounded-lg flex items-center whitespace-nowrap">
|
| 46 |
+
<i data-feather="filter" class="mr-2"></i> Status Filter
|
| 47 |
+
</button>
|
| 48 |
+
<div class="filter-dropdown hidden absolute right-0 mt-2 bg-gray-800 border border-gray-700 rounded-lg shadow-lg z-10 w-48">
|
| 49 |
+
<ul class="py-1">
|
| 50 |
+
<li><a href="#" class="filter-option block px-4 py-2 hover:bg-gray-700" data-status="all">All Rooms</a></li>
|
| 51 |
+
<li><a href="#" class="filter-option block px-4 py-2 hover:bg-gray-700" data-status="clean">Clean</a></li>
|
| 52 |
+
<li><a href="#" class="filter-option block px-4 py-2 hover:bg-gray-700" data-status="dirty">Dirty</a></li>
|
| 53 |
+
<li><a href="#" class="filter-option block px-4 py-2 hover:bg-gray-700" data-status="in-progress">In Progress</a></li>
|
| 54 |
+
<li><a href="#" class="filter-option block px-4 py-2 hover:bg-gray-700" data-status="maintenance">Maintenance</a></li>
|
| 55 |
+
</ul>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<button id="manager-view-btn" class="hidden bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-lg flex items-center">
|
| 60 |
+
<i data-feather="bar-chart-2" class="mr-2"></i> Manager View
|
| 61 |
+
</button>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<div class="hidden md:block mb-6 p-4 bg-gray-800 rounded-lg" id="stats-dashboard">
|
| 67 |
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 68 |
+
<div class="stat-card bg-green-900/50 border-l-4 border-green-500 p-3">
|
| 69 |
+
<h3 class="text-gray-400 text-sm">Clean Rooms</h3>
|
| 70 |
+
<p class="text-2xl font-bold" id="clean-count">0</p>
|
| 71 |
+
</div>
|
| 72 |
+
<div class="stat-card bg-red-900/50 border-l-4 border-red-500 p-3">
|
| 73 |
+
<h3 class="text-gray-400 text-sm">Dirty Rooms</h3>
|
| 74 |
+
<p class="text-2xl font-bold" id="dirty-count">0</p>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="stat-card bg-yellow-900/50 border-l-4 border-yellow-500 p-3">
|
| 77 |
+
<h3 class="text-gray-400 text-sm">In Progress</h3>
|
| 78 |
+
<p class="text-2xl font-bold" id="progress-count">0</p>
|
| 79 |
+
</div>
|
| 80 |
+
<div class="stat-card bg-purple-900/50 border-l-4 border-purple-500 p-3">
|
| 81 |
+
<h3 class="text-gray-400 text-sm">Maintenance</h3>
|
| 82 |
+
<p class="text-2xl font-bold" id="maintenance-count">0</p>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<div class="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3">
|
| 88 |
+
<!-- Room cards will be dynamically generated here -->
|
| 89 |
+
</div>
|
| 90 |
+
</main>
|
| 91 |
+
|
| 92 |
+
<custom-footer></custom-footer>
|
| 93 |
+
|
| 94 |
+
<script src="components/navbar.js"></script>
|
| 95 |
+
<script src="components/footer.js"></script>
|
| 96 |
+
<script src="script.js"></script>
|
| 97 |
+
<script>
|
| 98 |
+
feather.replace();
|
| 99 |
+
// Generate room cards dynamically
|
| 100 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 101 |
+
const roomGrid = document.querySelector('.grid');
|
| 102 |
+
for (let i = 1; i <= 124; i++) {
|
| 103 |
+
const statuses = ['clean', 'dirty', 'in-progress', 'maintenance'];
|
| 104 |
+
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
|
| 105 |
+
|
| 106 |
+
roomGrid.innerHTML += `
|
| 107 |
+
<a href="room.html?number=${i}" class="room-card bg-gray-800 rounded-lg p-3 border-l-4 ${randomStatus === 'clean' ? 'border-green-500' : randomStatus === 'dirty' ? 'border-red-500' : randomStatus === 'in-progress' ? 'border-yellow-500' : 'border-purple-500'} hover:bg-gray-700 transition-colors relative">
|
| 108 |
+
<div class="absolute top-2 right-2 flex gap-1">
|
| 109 |
+
<button class="note-btn bg-gray-700 hover:bg-gray-600 rounded-full w-6 h-6 flex items-center justify-center text-xs" data-room="${i}" data-action="add">
|
| 110 |
+
<i data-feather="plus" class="w-3 h-3"></i>
|
| 111 |
+
</button>
|
| 112 |
+
<button class="note-btn bg-gray-700 hover:bg-gray-600 rounded-full w-6 h-6 flex items-center justify-center text-xs" data-room="${i}" data-action="remove">
|
| 113 |
+
<i data-feather="minus" class="w-3 h-3"></i>
|
| 114 |
+
</button>
|
| 115 |
+
</div>
|
| 116 |
+
<div class="flex justify-between items-start">
|
| 117 |
+
<h3 class="font-bold text-lg">Room ${i}</h3>
|
| 118 |
+
<span class="text-xs px-2 py-1 rounded-full ${randomStatus === 'clean' ? 'bg-green-900 text-green-300' : randomStatus === 'dirty' ? 'bg-red-900 text-red-300' : randomStatus === 'in-progress' ? 'bg-yellow-900 text-yellow-300' : 'bg-purple-900 text-purple-300'}">${randomStatus.replace('-', ' ')}</span>
|
| 119 |
+
</div>
|
| 120 |
+
<div class="mt-2 text-sm text-gray-400">
|
| 121 |
+
<div class="flex items-center">
|
| 122 |
+
<i data-feather="${Math.random() > 0.5 ? 'user' : 'user-check'}" class="w-4 h-4 mr-2"></i>
|
| 123 |
+
${Math.random() > 0.5 ? 'Occupied' : 'Vacant'}
|
| 124 |
+
</div>
|
| 125 |
+
<div class="flex items-center mt-1">
|
| 126 |
+
<i data-feather="alert-circle" class="w-4 h-4 mr-2"></i>
|
| 127 |
+
${Math.random() > 0.7 ? 'Maintenance' : 'No issues'}
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
</a>
|
| 131 |
+
`;
|
| 132 |
+
}
|
| 133 |
+
feather.replace();
|
| 134 |
+
});
|
| 135 |
+
</script>
|
| 136 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 137 |
+
</body>
|
| 138 |
+
</html>
|
room.html
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Room Details | HotelKeeper Pro</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
</head>
|
| 11 |
+
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
| 12 |
+
<custom-navbar></custom-navbar>
|
| 13 |
+
|
| 14 |
+
<main class="container mx-auto px-4 py-6">
|
| 15 |
+
<div class="flex flex-col md:flex-row gap-6">
|
| 16 |
+
<div class="md:w-1/3">
|
| 17 |
+
<div class="bg-gray-800 rounded-lg p-4">
|
| 18 |
+
<div class="flex justify-between items-center mb-4">
|
| 19 |
+
<h1 class="text-2xl font-bold" id="room-number">Room 101</h1>
|
| 20 |
+
<span class="status-badge px-3 py-1 rounded-full text-sm font-medium" id="room-status">Clean</span>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<div class="space-y-4">
|
| 24 |
+
<div>
|
| 25 |
+
<h3 class="text-gray-400 text-sm mb-1">Current Status</h3>
|
| 26 |
+
<div class="flex gap-2 flex-wrap">
|
| 27 |
+
<button class="status-btn bg-green-900/50 hover:bg-green-800 px-3 py-1 rounded-full" data-status="clean">Clean</button>
|
| 28 |
+
<button class="status-btn bg-red-900/50 hover:bg-red-800 px-3 py-1 rounded-full" data-status="dirty">Dirty</button>
|
| 29 |
+
<button class="status-btn bg-yellow-900/50 hover:bg-yellow-800 px-3 py-1 rounded-full" data-status="in-progress">In Progress</button>
|
| 30 |
+
<button class="status-btn bg-purple-900/50 hover:bg-purple-800 px-3 py-1 rounded-full" data-status="maintenance">Maintenance</button>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<div>
|
| 35 |
+
<h3 class="text-gray-400 text-sm mb-1">Occupancy</h3>
|
| 36 |
+
<div class="flex gap-2">
|
| 37 |
+
<button class="occupancy-btn bg-blue-900/50 hover:bg-blue-800 px-3 py-1 rounded-full" data-occupancy="occupied">Occupied</button>
|
| 38 |
+
<button class="occupancy-btn bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded-full" data-occupancy="vacant">Vacant</button>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="bg-gray-800 rounded-lg p-4 mt-4">
|
| 45 |
+
<div class="flex justify-between items-center mb-3">
|
| 46 |
+
<h3 class="font-medium">Maintenance Issues</h3>
|
| 47 |
+
<button class="add-issue-btn bg-primary-500 hover:bg-primary-600 px-2 py-1 rounded text-sm flex items-center">
|
| 48 |
+
<i data-feather="plus" class="w-4 h-4 mr-1"></i> Add
|
| 49 |
+
</button>
|
| 50 |
+
</div>
|
| 51 |
+
<ul class="space-y-2" id="issues-list">
|
| 52 |
+
<li class="flex items-center justify-between bg-gray-700/50 px-3 py-2 rounded">
|
| 53 |
+
<span>Broken shower head</span>
|
| 54 |
+
<button class="text-red-400 hover:text-red-300">
|
| 55 |
+
<i data-feather="trash-2" class="w-4 h-4"></i>
|
| 56 |
+
</button>
|
| 57 |
+
</li>
|
| 58 |
+
</ul>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div class="md:w-2/3">
|
| 63 |
+
<div class="bg-gray-800 rounded-lg p-4">
|
| 64 |
+
<div class="flex justify-between items-center mb-4">
|
| 65 |
+
<h2 class="text-xl font-bold">Room Notes</h2>
|
| 66 |
+
<div class="flex gap-2">
|
| 67 |
+
<button class="add-note-btn bg-primary-500 hover:bg-primary-600 px-3 py-1 rounded-lg flex items-center">
|
| 68 |
+
<i data-feather="plus" class="mr-1"></i> Add Note
|
| 69 |
+
</button>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div class="space-y-3" id="notes-container">
|
| 74 |
+
<div class="note-card bg-gray-700/50 p-3 rounded-lg">
|
| 75 |
+
<div class="flex justify-between items-start mb-2">
|
| 76 |
+
<span class="text-sm text-gray-400">Housekeeping - Today</span>
|
| 77 |
+
<button class="text-gray-400 hover:text-gray-200">
|
| 78 |
+
<i data-feather="x" class="w-4 h-4"></i>
|
| 79 |
+
</button>
|
| 80 |
+
</div>
|
| 81 |
+
<p>Extra towels requested by guest</p>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div class="bg-gray-800 rounded-lg p-4 mt-4">
|
| 87 |
+
<h2 class="text-xl font-bold mb-4">Cleaning Checklist</h2>
|
| 88 |
+
<div class="space-y-2">
|
| 89 |
+
<label class="flex items-center space-x-3">
|
| 90 |
+
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-500 rounded">
|
| 91 |
+
<span>Dust all surfaces</span>
|
| 92 |
+
</label>
|
| 93 |
+
<label class="flex items-center space-x-3">
|
| 94 |
+
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-500 rounded">
|
| 95 |
+
<span>Vacuum carpets</span>
|
| 96 |
+
</label>
|
| 97 |
+
<label class="flex items-center space-x-3">
|
| 98 |
+
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-500 rounded">
|
| 99 |
+
<span>Clean bathroom</span>
|
| 100 |
+
</label>
|
| 101 |
+
<label class="flex items-center space-x-3">
|
| 102 |
+
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-500 rounded">
|
| 103 |
+
<span>Change linens</span>
|
| 104 |
+
</label>
|
| 105 |
+
<label class="flex items-center space-x-3">
|
| 106 |
+
<input type="checkbox" class="form-checkbox h-5 w-5 text-primary-500 rounded">
|
| 107 |
+
<span>Restock amenities</span>
|
| 108 |
+
</label>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<button class="mt-4 w-full bg-primary-500 hover:bg-primary-600 px-4 py-2 rounded-lg">
|
| 112 |
+
Mark as Complete
|
| 113 |
+
</button>
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
</div>
|
| 117 |
+
</main>
|
| 118 |
+
|
| 119 |
+
<custom-footer></custom-footer>
|
| 120 |
+
|
| 121 |
+
<script src="components/navbar.js"></script>
|
| 122 |
+
<script src="components/footer.js"></script>
|
| 123 |
+
<script src="script.js"></script>
|
| 124 |
+
<script>
|
| 125 |
+
feather.replace();
|
| 126 |
+
|
| 127 |
+
// Get room number from URL
|
| 128 |
+
const urlParams = new URLSearchParams(window.location.search);
|
| 129 |
+
const roomNumber = urlParams.get('number');
|
| 130 |
+
document.getElementById('room-number').textContent = `Room ${roomNumber}`;
|
| 131 |
+
|
| 132 |
+
// Status buttons functionality
|
| 133 |
+
document.querySelectorAll('.status-btn').forEach(btn => {
|
| 134 |
+
btn.addEventListener('click', function() {
|
| 135 |
+
const status = this.dataset.status;
|
| 136 |
+
const statusBadge = document.getElementById('room-status');
|
| 137 |
+
|
| 138 |
+
// Update badge
|
| 139 |
+
statusBadge.textContent = status.charAt(0).toUpperCase() + status.slice(1).replace('-', ' ');
|
| 140 |
+
statusBadge.className = 'status-badge px-3 py-1 rounded-full text-sm font-medium';
|
| 141 |
+
|
| 142 |
+
if (status === 'clean') statusBadge.classList.add('bg-green-900/50', 'text-green-300');
|
| 143 |
+
else if (status === 'dirty') statusBadge.classList.add('bg-red-900/50', 'text-red-300');
|
| 144 |
+
else if (status === 'in-progress') statusBadge.classList.add('bg-yellow-900/50', 'text-yellow-300');
|
| 145 |
+
else if (status === 'maintenance') statusBadge.classList.add('bg-purple-900/50', 'text-purple-300');
|
| 146 |
+
|
| 147 |
+
// Show notification
|
| 148 |
+
showNotification(`Room status updated to ${status}`, 'success');
|
| 149 |
+
});
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
// Occupancy buttons
|
| 153 |
+
document.querySelectorAll('.occupancy-btn').forEach(btn => {
|
| 154 |
+
btn.addEventListener('click', function() {
|
| 155 |
+
const occupancy = this.dataset.occupancy;
|
| 156 |
+
|
| 157 |
+
// Toggle active state
|
| 158 |
+
document.querySelectorAll('.occupancy-btn').forEach(b => {
|
| 159 |
+
b.classList.remove('bg-blue-900/50', 'bg-gray-700');
|
| 160 |
+
b.classList.add('bg-gray-700');
|
| 161 |
+
});
|
| 162 |
+
|
| 163 |
+
if (occupancy === 'occupied') {
|
| 164 |
+
this.classList.remove('bg-gray-700');
|
| 165 |
+
this.classList.add('bg-blue-900/50');
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
showNotification(`Occupancy set to ${occupancy}`, 'info');
|
| 169 |
+
});
|
| 170 |
+
});
|
| 171 |
+
|
| 172 |
+
// Add note functionality
|
| 173 |
+
document.querySelector('.add-note-btn')?.addEventListener('click', function() {
|
| 174 |
+
const noteText = prompt('Enter your note:');
|
| 175 |
+
if (noteText) {
|
| 176 |
+
const notesContainer = document.getElementById('notes-container');
|
| 177 |
+
const now = new Date().toLocaleString();
|
| 178 |
+
|
| 179 |
+
notesContainer.insertAdjacentHTML('afterbegin', `
|
| 180 |
+
<div class="note-card bg-gray-700/50 p-3 rounded-lg">
|
| 181 |
+
<div class="flex justify-between items-start mb-2">
|
| 182 |
+
<span class="text-sm text-gray-400">${currentUser.name} - ${now}</span>
|
| 183 |
+
<button class="text-gray-400 hover:text-gray-200 delete-note-btn">
|
| 184 |
+
<i data-feather="x" class="w-4 h-4"></i>
|
| 185 |
+
</button>
|
| 186 |
+
</div>
|
| 187 |
+
<p>${noteText}</p>
|
| 188 |
+
</div>
|
| 189 |
+
`);
|
| 190 |
+
|
| 191 |
+
feather.replace();
|
| 192 |
+
showNotification('Note added successfully', 'success');
|
| 193 |
+
|
| 194 |
+
// Add delete handler to new note
|
| 195 |
+
document.querySelector('.delete-note-btn')?.addEventListener('click', function() {
|
| 196 |
+
this.closest('.note-card').remove();
|
| 197 |
+
showNotification('Note deleted', 'error');
|
| 198 |
+
});
|
| 199 |
+
}
|
| 200 |
+
});
|
| 201 |
+
</script>
|
| 202 |
+
</body>
|
| 203 |
+
</html>
|
script.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
// User roles and authentication simulation
|
| 3 |
+
let currentUser = {
|
| 4 |
+
role: 'staff', // Default to staff view
|
| 5 |
+
name: 'Housekeeper'
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
// Check for manager view
|
| 9 |
+
function checkUserRole() {
|
| 10 |
+
const urlParams = new URLSearchParams(window.location.search);
|
| 11 |
+
if (urlParams.has('role') && urlParams.get('role') === 'manager') {
|
| 12 |
+
currentUser.role = 'manager';
|
| 13 |
+
document.getElementById('manager-view-btn').classList.remove('hidden');
|
| 14 |
+
document.getElementById('stats-dashboard').classList.remove('hidden');
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
// Room status counters
|
| 19 |
+
const statusCounts = {
|
| 20 |
+
clean: 0,
|
| 21 |
+
dirty: 0,
|
| 22 |
+
'in-progress': 0,
|
| 23 |
+
maintenance: 0
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
// Shared functionality across pages
|
| 27 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 28 |
+
checkUserRole();
|
| 29 |
+
|
| 30 |
+
// Filter functionality
|
| 31 |
+
const filterBtn = document.querySelector('.filter-btn');
|
| 32 |
+
const filterDropdown = document.querySelector('.filter-dropdown');
|
| 33 |
+
const filterOptions = document.querySelectorAll('.filter-option');
|
| 34 |
+
const roomCards = document.querySelectorAll('.room-card');
|
| 35 |
+
|
| 36 |
+
filterBtn.addEventListener('click', () => {
|
| 37 |
+
filterDropdown.classList.toggle('hidden');
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
filterOptions.forEach(option => {
|
| 41 |
+
option.addEventListener('click', (e) => {
|
| 42 |
+
e.preventDefault();
|
| 43 |
+
const status = option.dataset.status;
|
| 44 |
+
|
| 45 |
+
roomCards.forEach(card => {
|
| 46 |
+
if (status === 'all') {
|
| 47 |
+
card.style.display = 'block';
|
| 48 |
+
} else {
|
| 49 |
+
card.style.display = card.classList.contains(`border-${status === 'clean' ? 'green' : status === 'dirty' ? 'red' : status === 'in-progress' ? 'yellow' : 'purple'}-500`) ? 'block' : 'none';
|
| 50 |
+
}
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
filterDropdown.classList.add('hidden');
|
| 54 |
+
});
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// Count statuses
|
| 58 |
+
document.querySelectorAll('.room-card').forEach(card => {
|
| 59 |
+
if (card.classList.contains('border-green-500')) statusCounts.clean++;
|
| 60 |
+
else if (card.classList.contains('border-red-500')) statusCounts.dirty++;
|
| 61 |
+
else if (card.classList.contains('border-yellow-500')) statusCounts['in-progress']++;
|
| 62 |
+
else if (card.classList.contains('border-purple-500')) statusCounts.maintenance++;
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
// Update stats dashboard
|
| 66 |
+
document.getElementById('clean-count').textContent = statusCounts.clean;
|
| 67 |
+
document.getElementById('dirty-count').textContent = statusCounts.dirty;
|
| 68 |
+
document.getElementById('progress-count').textContent = statusCounts['in-progress'];
|
| 69 |
+
document.getElementById('maintenance-count').textContent = statusCounts.maintenance;
|
| 70 |
+
|
| 71 |
+
// Note buttons functionality
|
| 72 |
+
document.querySelectorAll('.note-btn').forEach(btn => {
|
| 73 |
+
btn.addEventListener('click', (e) => {
|
| 74 |
+
e.stopPropagation();
|
| 75 |
+
const roomNumber = btn.dataset.room;
|
| 76 |
+
const action = btn.dataset.action;
|
| 77 |
+
|
| 78 |
+
if (action === 'add') {
|
| 79 |
+
showNotification(`Note added to Room ${roomNumber}`, 'success');
|
| 80 |
+
} else {
|
| 81 |
+
showNotification(`Note removed from Room ${roomNumber}`, 'error');
|
| 82 |
+
}
|
| 83 |
+
});
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
// Manager view button
|
| 87 |
+
document.getElementById('manager-view-btn')?.addEventListener('click', () => {
|
| 88 |
+
window.location.href = window.location.pathname + '?role=manager';
|
| 89 |
+
});
|
| 90 |
+
// Initialize tooltips
|
| 91 |
+
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
| 92 |
+
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
| 93 |
+
return new bootstrap.Tooltip(tooltipTriggerEl);
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
// Handle status filter dropdown
|
| 97 |
+
const filterButton = document.querySelector('.filter-button');
|
| 98 |
+
if (filterButton) {
|
| 99 |
+
filterButton.addEventListener('click', function() {
|
| 100 |
+
document.querySelector('.filter-dropdown').classList.toggle('hidden');
|
| 101 |
+
});
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// Close dropdown when clicking outside
|
| 105 |
+
document.addEventListener('click', function(event) {
|
| 106 |
+
if (!event.target.closest('.filter-button') && !event.target.closest('.filter-dropdown')) {
|
| 107 |
+
const dropdowns = document.querySelectorAll('.filter-dropdown');
|
| 108 |
+
dropdowns.forEach(dropdown => {
|
| 109 |
+
dropdown.classList.add('hidden');
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
});
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
// Notification system
|
| 116 |
+
function showNotification(message, type = 'info') {
|
| 117 |
+
const notification = document.createElement('div');
|
| 118 |
+
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'}`;
|
| 119 |
+
notification.innerHTML = `
|
| 120 |
+
<i data-feather="${type === 'success' ? 'check-circle' : type === 'error' ? 'alert-circle' : 'info'}" class="mr-3"></i>
|
| 121 |
+
<span>${message}</span>
|
| 122 |
+
`;
|
| 123 |
+
document.body.appendChild(notification);
|
| 124 |
+
feather.replace();
|
| 125 |
+
|
| 126 |
+
setTimeout(() => {
|
| 127 |
+
notification.classList.add('opacity-0', 'transition-opacity', 'duration-300');
|
| 128 |
+
setTimeout(() => notification.remove(), 300);
|
| 129 |
+
}, 3000);
|
| 130 |
+
}
|
style.css
CHANGED
|
@@ -1,28 +1,91 @@
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
.card
|
| 27 |
-
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
/* Base styles */
|
| 3 |
body {
|
| 4 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 5 |
+
-webkit-tap-highlight-color: transparent;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
/* Dark mode optimized colors */
|
| 9 |
+
.dark {
|
| 10 |
+
--bg-primary: #111827;
|
| 11 |
+
--bg-secondary: #1F2937;
|
| 12 |
+
--text-primary: #F3F4F6;
|
| 13 |
+
--text-secondary: #9CA3AF;
|
| 14 |
+
--highlight-green: #10B981;
|
| 15 |
+
--highlight-red: #EF4444;
|
| 16 |
+
--highlight-yellow: #F59E0B;
|
| 17 |
+
--highlight-purple: #8B5CF6;
|
| 18 |
}
|
| 19 |
|
| 20 |
+
/* Touch-friendly elements */
|
| 21 |
+
button, a {
|
| 22 |
+
-webkit-user-select: none;
|
| 23 |
+
user-select: none;
|
| 24 |
}
|
| 25 |
|
| 26 |
+
.note-btn {
|
| 27 |
+
transition: transform 0.1s ease;
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
+
.note-btn:active {
|
| 31 |
+
transform: scale(0.9);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
+
.stat-card {
|
| 35 |
+
transition: all 0.3s ease;
|
| 36 |
}
|
| 37 |
+
|
| 38 |
+
.stat-card:hover {
|
| 39 |
+
transform: translateY(-2px);
|
| 40 |
+
}
|
| 41 |
+
/* Room card animations */
|
| 42 |
+
.room-card {
|
| 43 |
+
transition: all 0.2s ease;
|
| 44 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.room-card:hover {
|
| 48 |
+
transform: translateY(-2px);
|
| 49 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/* Status indicators */
|
| 53 |
+
.status-clean {
|
| 54 |
+
border-left-color: #10B981;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.status-dirty {
|
| 58 |
+
border-left-color: #EF4444;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.status-in-progress {
|
| 62 |
+
border-left-color: #F59E0B;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.status-maintenance {
|
| 66 |
+
border-left-color: #8B5CF6;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
/* Dark mode overrides */
|
| 70 |
+
.dark .status-clean {
|
| 71 |
+
border-left-color: #10B981;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.dark .status-dirty {
|
| 75 |
+
border-left-color: #EF4444;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.dark .status-in-progress {
|
| 79 |
+
border-left-color: #F59E0B;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.dark .status-maintenance {
|
| 83 |
+
border-left-color: #8B5CF6;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/* Responsive adjustments */
|
| 87 |
+
@media (max-width: 640px) {
|
| 88 |
+
.room-card {
|
| 89 |
+
padding: 0.75rem;
|
| 90 |
+
}
|
| 91 |
+
}
|