diff --git a/docs/pr-4-dark-light-modes.md b/docs/pr-4-dark-light-modes.md new file mode 100644 index 000000000..84d066d1f --- /dev/null +++ b/docs/pr-4-dark-light-modes.md @@ -0,0 +1,48 @@ +# PR 4 - Dark/Light Modes + +## Objetivo de negocio +Incorporar un sistema de tema visual claro/oscuro con persistencia de preferencia del usuario para mejorar confort de uso, continuidad de experiencia y percepción de calidad del producto. + +## Alcance implementado +- Se agregó selector de tema en la toolbar superior (a la derecha del botón **Buscar**). +- El selector alterna entre: + - icono **sol** para modo claro, + - icono **luna** para modo oscuro. +- La preferencia se persiste en cookie de navegador y se reaplica automáticamente al cargar la app. +- Se mantuvo la interfaz funcional original, encapsulando el cambio en capa visual/estilos. + +## Cambios funcionales y técnicos +1. **Integración en layout principal** +- Se incorporó botón de toggle de tema en `main.html`. +- Se añadió inicialización temprana del tema para evitar parpadeo visual al cargar. + +2. **Lógica de persistencia** +- Se creó script dedicado para: + - leer preferencia desde cookie, + - alternar tema, + - actualizar cookie con duración anual. + +3. **Sistema de estilos por tema** +- Se evolucionó `style.css` a un esquema basado en variables, con reglas para ambos modos. +- Se aseguró consistencia visual y contraste en componentes base sin modificar lógica de negocio. + +4. **Criterio de estabilidad visual** +- Se preservaron colores originales de elementos clave solicitados (toolbar, estados críticos y widgets del dashboard) para mantener familiaridad operativa. + +## Archivos modificados +- `pms/templates/main.html` +- `pms/statics/css/style.css` +- `pms/statics/js/theme_mode.js` (nuevo) + +## Pruebas realizadas +- Validación funcional manual en navegador: + - toggle claro/oscuro, + - cambio de icono sol/luna, + - persistencia de preferencia tras recarga. +- Verificación de configuración Django: + - `python manage.py check` + +## Resultado esperado para el producto +- Mejor ergonomía visual para diferentes contextos de trabajo (día/noche). +- Experiencia más moderna y adaptable, alineada a estándares de productos SaaS. +- Base de theming reutilizable para futuras evoluciones UI sin reescribir templates funcionales. diff --git a/pms/statics/css/style.css b/pms/statics/css/style.css index 91d9999a8..d0f3cec64 100644 --- a/pms/statics/css/style.css +++ b/pms/statics/css/style.css @@ -1,5 +1,52 @@ +:root{ + --app-bg: #cecac4; + --app-bg-secondary: #c3bdb4; + --app-text: #3e3a35; + --app-muted: #6a635a; + --app-text-soft: #948b80; + --app-surface: #fffdf9; + --app-border: #e8e1d6; + --app-divider: #ddd3c4; + --app-hover: #f5f1ea; + --app-navbar-bg: #fffdf9; + --app-navbar-text: #3e3a35; + --app-primary: #b89c7a; + --app-primary-hover: #a98b67; + --app-primary-active: #8f7456; + --app-primary-soft: #f3ece2; + --app-secondary: #6a635a; + --app-accent: #8f7456; + --state-success-soft: #dce8d8; + --state-warning-soft: #f3e6d2; + --state-error-soft: #eed9d2; +} + +[data-theme="dark"]{ + --app-bg: #12161f; + --app-text: #e2e8f0; + --app-muted: #a7b2c1; + --app-surface: #1e2532; + --app-border: #303a4b; + --app-hover: #2b3546; + --app-navbar-bg: #020617; + --app-navbar-text: #e5e7eb; + --app-primary: #3aa7d8; + --app-primary-soft: #173a4d; + --app-secondary: #64748b; + --app-accent: #f59e0b; +} + +[data-theme="dark"] body{ + background: var(--app-bg); +} + body{ - padding-top: 4em + padding-top: 4em; + background: + radial-gradient(1100px 460px at 10% -8%, rgba(184, 156, 122, 0.10), transparent 52%), + radial-gradient(820px 380px at 92% -4%, rgba(143, 116, 86, 0.10), transparent 52%), + linear-gradient(180deg, #d7d2cb 0%, var(--app-bg) 48%, var(--app-bg-secondary) 100%); + color: var(--app-text); } @@ -11,7 +58,7 @@ body{ transition: background-color 250ms linear; } .hover-card:hover{ - background: #f0f0f0; + background: var(--app-hover); } .tag{ @@ -20,13 +67,12 @@ body{ border-radius: 0.3em; } .tag-red{ - - background: #ff7a7a; - color: white; + background: #ff7a7a !important; + color: #ffffff !important; } .tag-green { - background: #01a901; - color: white; + background: #01a901 !important; + color: #ffffff !important; } .card-customization{ width: 250px; @@ -38,4 +84,354 @@ body{ justify-content: center; height: 100%; align-items: center; -} \ No newline at end of file +} + +.rooms-page{ + --rooms-surface: var(--app-surface); + --rooms-muted: var(--app-muted); + --rooms-line: var(--app-border); + --rooms-brand: var(--app-primary); + --rooms-brand-soft: var(--app-primary-soft); +} + +.rooms-hero{ + background: linear-gradient(135deg, #fffdf9 0%, #f5f1ea 100%); + border: 1px solid var(--app-border); + border-radius: 16px; + padding: 1.5rem; + margin-bottom: 1rem; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.75), + 0 12px 30px rgba(15, 23, 42, 0.07); +} + +.rooms-title{ + margin: 0; + font-size: 1.8rem; + font-weight: 700; + letter-spacing: -0.02em; +} + +.rooms-subtitle{ + margin: 0.4rem 0 0; + color: var(--rooms-muted); +} + +.rooms-toolbar{ + background: var(--rooms-surface); + border-radius: 16px; + padding: 1rem; + margin-bottom: 1.2rem; +} + +.rooms-filter-form{ + display: flex; + align-items: center; + gap: 0.6rem; + flex-wrap: wrap; +} + +.rooms-filter-input-wrap{ + position: relative; + flex: 1 1 320px; + min-width: 220px; +} + +.rooms-filter-icon{ + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--rooms-muted); +} + +.rooms-filter-input{ + border-radius: 999px; + border: 1px solid var(--rooms-line); + padding-left: 2.2rem; + height: 44px; +} + +.rooms-filter-input:focus{ + border-color: var(--rooms-brand); + box-shadow: 0 0 0 0.2rem rgba(15, 118, 110, 0.15); +} + +.rooms-filter-btn{ + border-radius: 999px; + background: var(--rooms-brand); + border-color: var(--rooms-brand); + min-width: 100px; + height: 44px; +} + +.rooms-filter-btn:hover{ + background: var(--app-primary-hover); + border-color: var(--app-primary-hover); +} + +.rooms-filter-clear{ + border-radius: 999px; + height: 44px; +} + +.rooms-results-meta{ + margin-top: 0.75rem; + color: var(--rooms-muted); + font-size: 0.95rem; +} + +.rooms-grid{ + display: grid; + gap: 0.9rem; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); +} + +.rooms-card{ + background: var(--rooms-surface); + border: 1px solid var(--rooms-line); + border-radius: 14px; + padding: 1rem; + transition: transform 180ms ease, box-shadow 180ms ease; +} + +.rooms-card:hover{ + transform: translateY(-2px); + box-shadow: 0 10px 24px rgba(16, 24, 40, 0.08); +} + +.rooms-card-head{ + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.8rem; + margin-bottom: 0.9rem; +} + +.rooms-card-title{ + margin: 0; + font-size: 1.05rem; + font-weight: 700; +} + +.rooms-type-chip{ + background: var(--rooms-brand-soft); + color: var(--rooms-brand); + border-radius: 999px; + font-size: 0.82rem; + font-weight: 600; + padding: 0.25rem 0.65rem; + white-space: nowrap; +} + +.rooms-card-actions{ + display: flex; + justify-content: flex-start; +} + +.rooms-empty-state{ + border-radius: 14px; + border: 1px solid #ffe58f; +} + +.dashboard-grid{ + display: grid; + gap: 0.9rem; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); +} + +.dashboard-card{ + width: 100%; + min-height: 180px; + border: 0; + border-radius: 14px; + box-shadow: 0 10px 24px rgba(16, 24, 40, 0.08); +} + +.dashboard-card-blue{ + background-color: #1000ff !important; +} + +.dashboard-card-green{ + background-color: #00ab74 !important; +} + +.dashboard-card-amber{ + background-color: #eeb258 !important; +} + +.dashboard-card-coral{ + background-color: #ff7f7f !important; +} + +.dashboard-card-teal{ + background-color: #005f73 !important; +} + +.navbar{ + background-color: var(--app-navbar-bg) !important; +} + +.navbar .navbar-brand, +.navbar .nav-link{ + color: var(--app-navbar-text) !important; +} + +.navbar .nav-link.active{ + color: var(--app-accent) !important; +} + +.nav-toolbar-btn{ + border: 1px solid var(--app-divider); + color: var(--app-navbar-text); + background: transparent; +} + +.nav-toolbar-btn:hover{ + color: var(--app-navbar-text); + background: var(--app-hover); +} + +/* Keep original toolbar colors in both light and dark modes */ +.navbar{ + background-color: #212529 !important; +} + +.navbar .navbar-brand, +.navbar .nav-link, +.navbar .nav-link.active{ + color: #f8f9fa !important; +} + +.navbar form .form-control{ + background-color: #ffffff; + color: #212529; + border-color: #ced4da; +} + +.navbar form .form-control::placeholder{ + color: #6c757d; +} + +.navbar form .form-control:focus{ + background-color: #ffffff; + color: #212529; + border-color: #86b7fe; + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); +} + +.nav-toolbar-btn{ + border: 1px solid #f8f9fa; + color: #f8f9fa; + background: transparent; +} + +.nav-toolbar-btn:hover{ + color: #212529; + background: #f8f9fa; +} + +.theme-toggle-btn{ + width: 42px; + height: 42px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.theme-toggle-btn i{ + font-size: 1.05rem; +} + +.card{ + background-color: var(--app-surface); + border-color: var(--app-border); + color: var(--app-text); +} + +.form-control{ + background-color: var(--app-surface); + color: var(--app-text); + border-color: var(--app-border); +} + +.form-control::placeholder{ + color: var(--app-text-soft); +} + +.form-control:focus{ + background-color: var(--app-surface); + color: var(--app-text); +} + +[data-theme="dark"] .rooms-hero{ + background: + linear-gradient(130deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%), + linear-gradient(135deg, #1b2330 0%, #17202b 100%); + box-shadow: 0 12px 30px rgba(2, 6, 23, 0.45); +} + +[data-theme="dark"] .rooms-type-chip{ + background: var(--app-primary-soft); + color: #d9f2ff; +} + +.contact-edit-card input, +.contact-edit-card select, +.contact-edit-card textarea{ + border-radius: 10px; + border: 1px solid #d9e1e7; + min-height: 42px; + width: 100%; + padding: 0.5rem 0.75rem; +} + +.contact-edit-card input:focus, +.contact-edit-card select:focus, +.contact-edit-card textarea:focus{ + border-color: var(--app-primary); + box-shadow: 0 0 0 0.2rem rgba(47, 110, 165, 0.22); + outline: none; +} + +.booking-create-card input, +.booking-create-card select, +.booking-create-card textarea{ + border-radius: 10px; + border: 1px solid #d9e1e7; + min-height: 42px; + width: 100%; + padding: 0.5rem 0.75rem; +} + +.booking-create-card input:focus, +.booking-create-card select:focus, +.booking-create-card textarea:focus{ + border-color: var(--app-primary); + box-shadow: 0 0 0 0.2rem rgba(47, 110, 165, 0.22); + outline: none; +} + +.booking-days-panel{ + display: inline-flex; + align-items: center; + gap: 0.6rem; + background: var(--app-bg-secondary); + border: 1px solid var(--app-border); + border-radius: 999px; + padding: 0.35rem 0.9rem; +} + +.booking-days-label{ + color: var(--app-muted); + font-weight: 500; +} + +.booking-days-value{ + color: var(--app-accent); + font-size: 1.1rem; + font-weight: 700; + min-width: 1.4rem; + text-align: center; +} diff --git a/pms/statics/js/theme_mode.js b/pms/statics/js/theme_mode.js new file mode 100644 index 000000000..bb47a2313 --- /dev/null +++ b/pms/statics/js/theme_mode.js @@ -0,0 +1,43 @@ +(function () { + function getCookie(name) { + var value = "; " + document.cookie; + var parts = value.split("; " + name + "="); + if (parts.length === 2) return parts.pop().split(";").shift(); + return null; + } + + function setCookie(name, value, days) { + var maxAge = days * 24 * 60 * 60; + document.cookie = name + "=" + value + "; path=/; max-age=" + maxAge + "; SameSite=Lax"; + } + + function applyTheme(theme) { + document.documentElement.setAttribute("data-theme", theme); + var icon = document.getElementById("theme-toggle-icon"); + if (!icon) return; + + if (theme === "dark") { + icon.classList.remove("bi-sun-fill"); + icon.classList.add("bi-moon-stars-fill"); + } else { + icon.classList.remove("bi-moon-stars-fill"); + icon.classList.add("bi-sun-fill"); + } + } + + function initThemeToggle() { + var button = document.getElementById("theme-toggle-btn"); + var currentTheme = document.documentElement.getAttribute("data-theme") || getCookie("theme_mode") || "light"; + applyTheme(currentTheme); + + if (!button) return; + button.addEventListener("click", function () { + var activeTheme = document.documentElement.getAttribute("data-theme") || "light"; + var nextTheme = activeTheme === "dark" ? "light" : "dark"; + applyTheme(nextTheme); + setCookie("theme_mode", nextTheme, 365); + }); + } + + document.addEventListener("DOMContentLoaded", initThemeToggle); +})(); diff --git a/pms/templates/main.html b/pms/templates/main.html index b2216a759..04189fd2c 100644 --- a/pms/templates/main.html +++ b/pms/templates/main.html @@ -4,6 +4,19 @@ PMS {% load static %} + @@ -35,9 +48,12 @@ Habitaciones -
+ - + +
@@ -48,5 +64,6 @@ {% endblock %} + - \ No newline at end of file +