diff --git a/docs/pr-1-rooms-filter.md b/docs/pr-1-rooms-filter.md new file mode 100644 index 000000000..836f4d6b4 --- /dev/null +++ b/docs/pr-1-rooms-filter.md @@ -0,0 +1,42 @@ +# PR 1 - Filtro de Habitaciones + +## Objetivo de negocio +Incorporar un mecanismo de búsqueda en la sección **Habitaciones** para mejorar la localización de cuartos por nombre y reducir tiempo operativo en front desk/soporte. + +## Alcance implementado +- Se agregó un formulario de filtro por nombre en la vista de Habitaciones. +- El filtro opera por coincidencia parcial (`icontains`) sobre `Room.name`. +- Se mantiene el comportamiento original cuando no hay criterio de búsqueda (listado completo). +- Se agregó estado vacío cuando no existen coincidencias. +- Se mejoró la presentación de los controles de búsqueda para una experiencia más clara y consistente. + +## Cambios funcionales y técnicos +1. **Backend** +- Se actualizó la lógica de `RoomsView` para aceptar parámetro `name` y filtrar resultados. +- Se agregó un formulario dedicado de filtro para desacoplar validación y render. + +2. **Frontend** +- Se incorporó barra de filtro con input, botón de búsqueda y acción de limpieza. +- Se añadió feedback contextual del total de resultados. +- Se mejoró el layout visual del bloque de controles para reforzar jerarquía y legibilidad. + +3. **Calidad** +- Se incorporaron pruebas automatizadas que validan: + - listado completo sin filtro, + - filtro por coincidencia parcial, + - estado sin resultados. + +## Archivos modificados +- `pms/forms.py` +- `pms/views.py` +- `pms/templates/rooms.html` +- `pms/statics/css/style.css` +- `pms/tests.py` + +## Pruebas ejecutadas +- `python manage.py test pms.tests.RoomsFilterTests` + +## Resultado esperado para el producto +- Menor fricción en operación diaria al buscar habitaciones. +- Mejor percepción de calidad UI en una pantalla de uso frecuente. +- Base funcional y visual estable para iteraciones futuras en catálogo/gestión de habitaciones. diff --git a/pms/forms.py b/pms/forms.py index f1bc68d08..9472f7de5 100644 --- a/pms/forms.py +++ b/pms/forms.py @@ -56,3 +56,17 @@ class Meta: 'total': forms.HiddenInput(), 'state': forms.HiddenInput(), } + + +class RoomFilterForm(forms.Form): + name = forms.CharField( + required=False, + label="Buscar habitación", + widget=forms.TextInput( + attrs={ + "placeholder": "Ej: Room 1", + "class": "form-control rooms-filter-input", + "autocomplete": "off", + } + ), + ) diff --git a/pms/statics/css/style.css b/pms/statics/css/style.css index 91d9999a8..07ad88f3a 100644 --- a/pms/statics/css/style.css +++ b/pms/statics/css/style.css @@ -38,4 +38,172 @@ body{ justify-content: center; height: 100%; align-items: center; -} \ No newline at end of file +} + +.rooms-page{ + --rooms-surface: #ffffff; + --rooms-muted: #6c757d; + --rooms-line: #d9e1e7; + --rooms-brand: #0f766e; + --rooms-brand-soft: #e6f4f3; +} + +.rooms-hero{ + background: linear-gradient(135deg, #eff6ff 0%, #f0fdf4 100%); + border: 1px solid #d9e1e7; + border-radius: 16px; + padding: 1.5rem; + margin-bottom: 1rem; +} + +.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-controls-panel{ + border: 1px solid var(--rooms-line); +} + +.rooms-filter-form{ + display: flex; + align-items: flex-end; + gap: 0.6rem; + flex-wrap: wrap; +} + +.rooms-filter-field{ + flex: 1 1 360px; + min-width: 240px; +} + +.rooms-filter-label{ + display: inline-block; + margin-bottom: 0.35rem; + color: var(--rooms-muted); + font-size: 0.9rem; + font-weight: 600; +} + +.rooms-filter-input-wrap{ + position: relative; +} + +.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: #0a5e57; + border-color: #0a5e57; +} + +.rooms-filter-clear{ + border-radius: 999px; + height: 44px; +} + +.rooms-results-meta{ + margin-top: 0.75rem; + color: var(--rooms-muted); + font-size: 0.95rem; +} + +.rooms-results-chip{ + display: inline-flex; + border: 1px solid var(--rooms-line); + border-radius: 999px; + padding: 0.35rem 0.8rem; + background: rgba(15, 118, 110, 0.05); +} + +.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; +} diff --git a/pms/templates/rooms.html b/pms/templates/rooms.html index c30929f1f..a17f83393 100644 --- a/pms/templates/rooms.html +++ b/pms/templates/rooms.html @@ -1,19 +1,53 @@ {% extends "main.html"%} {% block content %} -
Filtra por nombre y encuentra la habitación en segundos.
+