diff --git a/frontend/src/app/features/info-hub/info-hub.component.ts b/frontend/src/app/features/info-hub/info-hub.component.ts index 0d667f8..fc6891d 100644 --- a/frontend/src/app/features/info-hub/info-hub.component.ts +++ b/frontend/src/app/features/info-hub/info-hub.component.ts @@ -11,13 +11,6 @@ import { Router } from '@angular/router'; template: `
- -
diff --git a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css index f2a00dc..c03dd29 100644 --- a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css +++ b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css @@ -1,511 +1,588 @@ -/* Layout Principal y Contenedores */ -.calendar-layout { +/* ========================================================================== + APP SPACES — Resource Scheduler Layout + ========================================================================== */ + +.wf-shell { display: flex; - width: 100%; - height: calc(100vh - 160px); - background-color: var(--bg-surface, #FFF); - border: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - border-radius: 20px; + background: #f4f3ef; /* Light beige/grey background */ + min-height: 90vh; + border-radius: 24px; overflow: hidden; - box-shadow: 0 4px 20px rgba(15, 13, 10, 0.04); + border: 1px solid rgba(15, 13, 10, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); + font-family: 'Outfit', 'Inter', system-ui, sans-serif; + width: 100%; } -.calendar-sidebar, -.calendar-view-container { - overflow-y: auto; -} - -.calendar-sidebar { - width: 280px; - background: #FFF; - border-right: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - padding: 24px 16px; +/* Sidebar */ +.wf-sidebar { + width: 90px; + background: #000000; display: flex; flex-direction: column; - gap: 24px; - box-sizing: border-box; + align-items: center; + padding: 24px 0; flex-shrink: 0; } -.calendar-main { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - background-color: #FFF; +.wf-sidebar__brand { + font-size: 11px; + font-weight: 900; + letter-spacing: 0.12em; + color: rgba(255, 255, 255, 0.4); + margin-bottom: 24px; } -.calendar-view-container { - flex: 1; - display: flex; - flex-direction: column; - background: #FFF; -} - -/* Botones y Elementos Interactivos */ -.btn-create, -.btn-today, -.view-select { - border: 1px solid var(--border-medium, rgba(15, 13, 10, 0.14)); - background: #FFF; - font-weight: 600; - cursor: pointer; -} - -.btn-create { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 12px; - color: var(--text-primary, #0F0D0A); - padding: 12px 24px; - border-radius: 24px; - font-family: 'Inter', sans-serif; - font-size: 14px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.05); - transition: box-shadow 0.2s, background-color 0.2s; - width: 100%; - box-sizing: border-box; -} - -.btn-create:hover { - background-color: #F8F9FA; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); -} - -.btn-create .plus-icon { - font-size: 24px; - font-weight: 300; - color: var(--gold-luxe, #C9973C); - line-height: 1; -} - -.btn-today, -.view-select { - padding: 6px 16px; - border-radius: 18px; - font-size: 12px; -} - -.btn-today:hover, -.hour-slot-cell:hover, -.btn-create:hover { - background-color: #F8F9FA; -} - -.nav-arrow { - width: 28px; - height: 28px; - border-radius: 50%; - border: 1px solid var(--border-medium, rgba(15, 13, 10, 0.08)); - background: none; - cursor: pointer; +.wf-sidebar__logo-container { + width: 44px; + height: 44px; + border-radius: 12px; + background: #ffffff; display: flex; align-items: center; justify-content: center; - font-size: 13px; - font-weight: bold; - transition: background-color 0.2s; + cursor: pointer; } -.nav-arrow:hover, -.mini-day-cell:hover, -.month-day-cell:hover { - background-color: var(--bg-page, #F5F4F1); +.wf-sidebar__logo-btn { + font-size: 18px; + font-weight: 900; + color: #000000; } -.view-select { - outline: none; -} - -/* Mini Calendario de la Barra Lateral */ -.mini-calendar, -.filter-section, -.filter-list { +/* Content Area */ +.wf-content { + flex-grow: 1; + padding: 24px; display: flex; flex-direction: column; - gap: 8px; + gap: 20px; + min-width: 0; + position: relative; } -.mini-calendar, -.filter-section { +/* Topbar */ +.wf-topbar { + background: #111625; + border-radius: 16px; + padding: 14px 24px; + display: flex; + flex-direction: column; gap: 12px; } -.mini-calendar { - gap: 8px; -} - -.mini-calendar-header { +.wf-topbar__header-row { display: flex; justify-content: space-between; align-items: center; - padding: 0 8px; + flex-wrap: wrap; + gap: 12px; + width: 100%; } -.mini-calendar-title { - font-size: 13px; - font-weight: 600; - color: var(--text-secondary, #44403C); - text-transform: capitalize; -} - -.mini-calendar-nav { - display: flex; - gap: 4px; -} - -.mini-calendar-grid, -.grid-month { - display: grid; - grid-template-cols: repeat(7, 1fr); -} - -.mini-calendar-grid { - text-align: center; - row-gap: 4px; -} - -.mini-day-name, -.day-name-cell { +.wf-topbar__title { + font-size: 18px; font-weight: 700; - text-transform: uppercase; - color: var(--text-muted, #78716C); -} - -.mini-day-name { - font-size: 10px; - padding-bottom: 4px; -} - -.mini-day-cell { - font-size: 11px; - height: 28px; - width: 28px; - margin: 0 auto; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - border-radius: 50%; - color: var(--text-primary); - box-sizing: border-box; -} - -.mini-day-cell.other-month { - color: var(--text-placeholder, #A8A29E); -} - -.mini-day-cell.today, -.day-number.today, -.month-day-number.today { - background-color: var(--gold-luxe, #C9973C); - color: #FFF !important; - font-weight: bold; -} - -/* Filtros */ -.filter-title { - font-size: 10px; - font-weight: bold; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-muted, #78716C); + color: #ffffff; margin: 0; } -.filter-item { +.wf-topbar__controls { display: flex; align-items: center; - gap: 10px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - user-select: none; -} - -.filter-checkbox { - width: 16px; - height: 16px; - border-radius: 4px; - cursor: pointer; - accent-color: var(--gold-luxe, #C9973C); -} - -/* Header Principal */ -.main-header { - height: 64px; - background: var(--bg-surface, #FFF); - border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 24px; - flex-shrink: 0; -} - -.main-header-left, -.header-title-group, -.header-app-logo, -.main-header-right { - display: flex; - align-items: center; - gap: 16px; -} - -.header-title-group { gap: 12px; } -.header-app-logo { - gap: 8px; - font-family: 'Inter', sans-serif; - font-weight: 900; - font-size: 16px; - letter-spacing: 1px; - color: var(--text-primary); - text-transform: uppercase; +.wf-search { + position: relative; + width: 180px; } -.header-app-logo span { - color: var(--gold-luxe); - font-weight: 400; - text-transform: none; +.wf-search__field { + width: 100%; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + padding: 6px 12px 6px 32px; + font-size: 11px; + color: #ffffff; + outline: none; } -.current-date-label { - font-size: 18px; - font-weight: 700; - text-transform: capitalize; - color: var(--text-primary, #0F0D0A); +.wf-search__field::placeholder { + color: rgba(255, 255, 255, 0.4); } -/* Vista Semanal */ -.week-view { +.wf-search__btn { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: rgba(255, 255, 255, 0.4); + cursor: pointer; display: flex; - flex-direction: column; - min-width: 800px; - flex: 1; -} - -.week-header, -.week-grid-body { - display: grid; - grid-template-cols: 64px repeat(7, 1fr); -} - -.week-header { - border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); - position: sticky; - top: 0; - background: #FFF; - z-index: 10; -} - -.timezone-label { - display: flex; - align-items: flex-end; - justify-content: center; - font-size: 9px; - color: var(--text-muted, #78716C); - padding-bottom: 8px; - border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); -} - -.week-day-header, -.day-column { - border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); -} - -.week-day-header { - display: flex; - flex-direction: column; align-items: center; - padding: 10px 0; } -.week-day-header:last-child, -.day-column:last-child, -.day-name-cell:last-child { - border-right: none; -} - -.day-name { +.wf-filter-dropdown__trigger { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; font-size: 11px; font-weight: 600; - text-transform: uppercase; - color: var(--text-muted, #78716C); - margin-bottom: 4px; + color: #ffffff; + padding: 6px 12px; + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; } -.day-number { - font-size: 20px; - font-weight: 500; - width: 34px; - height: 34px; +.wf-filter-dropdown__arrow { + font-size: 8px; + color: rgba(255, 255, 255, 0.4); +} + +.wf-btn-filter { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + color: #ffffff; + font-size: 11px; + font-weight: 600; + padding: 6px 14px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; +} + +/* Category Filters */ +.ih-category-filters { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.ih-category-btn { + background: rgba(255, 255, 255, 0.08); + border: none; + color: rgba(255, 255, 255, 0.6); + font-size: 11px; + font-weight: 700; + padding: 6px 14px; + border-radius: 10px; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease; +} + +.ih-category-btn:hover { + background: rgba(255, 255, 255, 0.15); + color: #ffffff; +} + +.ih-category-btn--active { + background: #ffffff !important; + color: #000000 !important; +} + +/* Main Grid */ +.ih-grid { + display: grid; + grid-template-columns: 1fr; + gap: 24px; + flex-grow: 1; +} + +@media (min-width: 1024px) { + .ih-grid { + grid-template-columns: 7fr 3fr; + } +} + +/* Calendar Card */ +.wf-card { + background: #ffffff; + border-radius: 20px; + padding: 24px; + border: 1px solid #e2e8f0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.01); + display: flex; + flex-direction: column; + gap: 16px; +} + +.wf-card__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.wf-card__title { + font-size: 16px; + font-weight: 800; + color: #000000; + margin: 0; + letter-spacing: -0.01em; +} + +/* View toggle */ +.wf-view-toggle { + display: flex; + background: #e2e8f0; + border-radius: 8px; + padding: 2px; +} + +.wf-view-toggle__btn { + background: transparent; + border: none; + color: #475569; + font-size: 11px; + font-weight: 700; + padding: 6px 12px; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.15s, color 0.15s; +} + +.wf-view-toggle__btn--active { + background: #084d47; /* Teal-dark green */ + color: #ffffff; +} + +/* Scheduler Table Layout */ +.res-calendar-container { + display: flex; + flex-direction: column; + overflow-x: auto; + width: 100%; +} + +.res-grid-header { + display: grid; + grid-template-columns: 60px repeat(4, 1fr); + border-bottom: 2px solid #f1f5f9; + padding-bottom: 12px; + gap: 16px; + min-width: 600px; +} + +.res-time-column-label { + width: 60px; +} + +.res-resource-column { + min-width: 120px; +} + +.res-resource-header { + display: flex; + align-items: center; + gap: 8px; +} + +.res-resource-icon { + width: 32px; + height: 32px; + border-radius: 8px; + background: #e0f2fe; /* Light blue */ display: flex; align-items: center; justify-content: center; - border-radius: 50%; - color: var(--text-primary); + font-size: 16px; + flex-shrink: 0; } -.week-grid-body { - position: relative; - flex: 1; +.res-resource-icon--yellow { + background: #fef3c7; /* Light yellow */ } -.hour-labels { +.res-resource-icon--office { + background: #f1f5f9; /* Grey */ +} + +.res-resource-info { display: flex; flex-direction: column; - border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); + min-width: 0; } -.hour-label-cell { - height: 60px; - display: flex; - align-items: flex-start; - justify-content: flex-end; - padding-right: 8px; - font-size: 10px; - color: var(--text-muted, #78716C); - transform: translateY(-6px); -} - -.day-column { - position: relative; - background-color: #FFF; -} - -.hour-slot-cell { - height: 60px; - border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.05)); - box-sizing: border-box; -} - -/* Eventos e Indicadores */ -.calendar-event { - position: absolute; - left: 4px; - right: 4px; - border-radius: 4px; - padding: 6px 8px; +.res-resource-name { font-size: 11px; - font-weight: 500; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - border-left: 4px solid; + font-weight: 800; + color: #0f172a; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} + +.res-resource-desc { + font-size: 9px; + color: #94a3b8; white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Body of Scheduler */ +.res-grid-body { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 8px; + min-width: 600px; +} + +.res-grid-row { + display: grid; + grid-template-columns: 60px repeat(4, 1fr); + gap: 16px; + align-items: center; +} + +.res-time-cell { + font-size: 11px; + font-weight: 700; + color: #94a3b8; + height: 32px; + display: flex; + align-items: center; +} + +.res-slot-cell { + position: relative; + height: 32px; +} + +.res-booking-block { + width: 100%; + height: 100%; + background: #e2e8f0; /* Default available grey cell */ + border-radius: 8px; + transition: background-color 0.15s; +} + +.res-booking-block--filled { + background: #084d47; /* Green busy block */ +} + +.res-booking-block--luna { + background: #084d47; + color: #ffffff; + font-size: 10px; + font-weight: 700; + display: flex; + align-items: center; + padding-left: 12px; + border-radius: 8px; +} + +.res-mini-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + border: 1.5px solid #ffffff; + margin-left: -5px; + object-fit: cover; +} + +.res-mini-avatar:first-child { + margin-left: 0; +} + +/* Flotante Popup Card (Luna) */ +.res-popup-card { + position: absolute; + top: -30px; + left: 20px; + width: 240px; + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 16px; + padding: 16px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12); + z-index: 50; + display: flex; + flex-direction: column; + gap: 8px; +} + +.res-popup-title { + font-size: 12px; + font-weight: 900; + color: #000000; + margin: 0; + letter-spacing: -0.01em; +} + +.res-popup-detail { + font-size: 11px; + color: #475569; + margin: 0; +} + +.res-popup-capacity { + font-size: 9px; + font-weight: 700; + color: #64748b; + text-transform: uppercase; + background: #f1f5f9; + padding: 2px 6px; + border-radius: 4px; + align-self: flex-start; +} + +.res-popup-actions { + display: flex; + gap: 6px; + margin-top: 4px; +} + +.res-popup-btn-confirm { + flex: 1; + background: #084d47; + border: none; + color: #ffffff; + font-size: 10px; + font-weight: 700; + padding: 6px 0; + border-radius: 8px; + cursor: pointer; + transition: background 0.15s; +} + +.res-popup-btn-confirm:hover { + background: #053b36; +} + +.res-popup-btn-details { + flex: 1; + background: #f1f5f9; + border: 1px solid #e2e8f0; + color: #334155; + font-size: 10px; + font-weight: 700; + padding: 6px 0; + border-radius: 8px; + cursor: pointer; + transition: background 0.15s; +} + +.res-popup-btn-details:hover { + background: #e2e8f0; +} + +.res-popup-status { + font-size: 9.5px; + color: #059669; + display: flex; + align-items: center; + gap: 4px; + margin-top: 2px; + font-weight: 600; +} + +.res-popup-status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #10b981; +} + +/* ── COLUMN DERECHA: NOTIFICACIONES ── */ +.ih-activity-section { + display: flex; + flex-direction: column; +} + +.ih-card-activity { + background: #ffffff; + border-radius: 20px; + border: 1px solid #e2e8f0; + padding: 24px; + display: flex; + flex-direction: column; + gap: 20px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.01); +} + +.ih-card-activity__title { + font-size: 15px; + font-weight: 800; + color: #000000; + margin: 0; + letter-spacing: -0.01em; +} + +.ih-activity-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.ih-activity-item { + display: flex; + gap: 12px; + align-items: flex-start; + border-bottom: 1px solid #f1f5f9; + padding-bottom: 12px; +} + +.ih-activity-item:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.ih-activity-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.ih-activity-avatar-icon { + width: 32px; + height: 32px; + border-radius: 50%; + background: #f1f5f9; + font-size: 15px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.ih-activity-body { display: flex; flex-direction: column; gap: 2px; } -.calendar-event .event-time { - font-size: 9px; - font-weight: 600; - opacity: 0.8; +.ih-activity-text { + font-size: 11.5px; + color: #475569; + margin: 0; + line-height: 1.4; } -.current-time-indicator { - position: absolute; - left: 0; - right: 0; - height: 2px; - background-color: #EA4335; - z-index: 5; +.ih-activity-text strong { + color: #0f172a; } -.current-time-indicator::before { - content: ''; - position: absolute; - left: -6px; - top: -5px; - width: 12px; - height: 12px; - border-radius: 50%; - background-color: #EA4335; +.ih-activity-time { + font-size: 10px; + color: #94a3b8; } -/* Vista Mensal */ -.day-names-row { - display: grid; - grid-template-cols: repeat(7, 1fr); - background: var(--bg-page, #F0EEE9); - border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - width: 100%; +.animate-fadeIn { + animation: fadeIn 0.2s ease-out forwards; } -.day-name-cell { - padding: 12px 0; - text-align: center; - font-size: 11px; - letter-spacing: 0.5px; - border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.05)); -} - -.grid-month { - grid-template-rows: repeat(6, 1fr); - flex: 1; - width: 100%; - height: 100%; - min-height: 500px; - border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - box-sizing: border-box; -} - -.month-day-cell { - padding: 8px; - display: flex; - flex-direction: column; - gap: 4px; - border-right: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); - cursor: pointer; - background-color: #FFF; - transition: background-color 0.2s; - box-sizing: border-box; - min-height: 80px; -} - -.month-day-cell.other-month { - background-color: #FAF9F6; -} - -.month-day-number { - font-size: 11px; - font-weight: 600; - color: var(--text-secondary, #44403C); - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -/* Media Queries */ -@media (max-width: 992px) { - .calendar-layout { - flex-direction: column; - height: auto; - overflow: visible; - } - - .calendar-sidebar { - width: 100% !important; - border-right: none !important; - border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); - padding: 24px; - } - - .calendar-main { - height: auto; - overflow: visible; - } - - .calendar-view-container { - overflow-x: auto; - } +@keyframes fadeIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } } \ No newline at end of file diff --git a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html index a8fbfa5..6ac32a1 100644 --- a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html +++ b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html @@ -1,234 +1,201 @@ -
- - - - -
-
-
-
- -
- - - - {{ monthYearLabel() }} -
-
- + + +
+ + + + + + +
- -
- - - @if (currentView() === 'week') { -
- -
-
GMT-04
- @for (day of currentWeekDays(); track day.getTime()) { -
- {{ weekDays[day.getDay()] }} - - {{ day.getDate() }} - -
- } -
- - -
-
- @for (hour of hours; track hour) { -
- {{ formatHour(hour) }} + +
+ + +
+
+ +
+

Calendario de Espacios

+ + +
+ + +
- }
- - @for (day of currentWeekDays(); track day.getTime()) { -
- @if (isToday(day)) { -
- } - - @for (hour of hours; track hour) { -
- } - - - @for (event of getEventsForDay(day); track event.id) { -
- {{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }} - {{ event.title }} - {{ event.resourceName }} -
- } -
- } -
-
- } - - - @if (currentView() === 'month') { -
- -
- @for (dayName of weekDays; track $index) { -
- {{ dayName }} -
- } -
- - -
- @for (day of calendarDays(); track day.getTime()) { -
- -
- - {{ day.getDate() }} - + +
+
+
+ + + @for (res of filteredResources(); track res.id) { +
+
+ {{ res.icon }} +
+ {{ res.name }} + {{ res.desc }} +
+
+
+ }
-
- @for (event of getEventsForDay(day); track event.id) { -
-
{{ - event.title }}
-
- {{ event.resourceName }}
-
+ +
+ @for (time of timeSlots; track time) { +
+ +
{{ time }}
+ + + @for (res of filteredResources(); track res.id) { +
+ + @if (isSlotOccupied(res.name, time)) { + +
+ Ocupado +
+ } @else { + + @if (res.name === 'OFICINA FLEX A1' && time === '12:00') { +
+ emp + emp +
+ } @else if (res.name === 'OFICINA FLEX A1' && time === '13:30') { +
+ emp +
+ } @else { + +
+ } + } + + + @if (selectedSlot()?.resource?.id === res.id && selectedSlot()?.time === time) { +
+
+

{{ res.name }}

+ +
+

Available {{ time }} - {{ getEndTime(time) }}

+ Capacidad {{ res.capacity }} + +
+ + +
+ + + + Disponibilidad, disponible + +
+ } + +
+ } +
}
+ + @if (filteredResources().length === 0) { +
+ No se encontraron recursos disponibles en esta sección. +
} +
- } - - @if (currentView() === 'day') { -
- -
-
GMT-04
-
- {{ weekDays[currentDate().getDay()] }} - - {{ currentDate().getDate() }} - -
-
- - -
-
- @for (hour of hours; track hour) { -
- {{ formatHour(hour) }} -
- } -
- - -
- @if (isToday(currentDate())) { -
- } - - @for (hour of hours; track hour) { -
- } - - - @for (event of getEventsForDay(currentDate()); track event.id) { -
- {{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }} - {{ event.title }} - {{ event.resourceName }} -
+ +
+
+

Mis Reservas y Notificaciones

+ +
+ @for (act of recentActivities(); track act.title) { +
+ @if (act.type === 'reserva') { + res +
+

{{ act.title }}
{{ act.detail }}

+ {{ act.time }} +
+ } @else { + notif +
+

David Chen compartió Resultados del Trimestre

+ {{ act.time }} +
+ } +
}
- } +
+
\ No newline at end of file diff --git a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.ts b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.ts index 1e52c71..d0f386a 100644 --- a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.ts +++ b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.ts @@ -2,14 +2,13 @@ import { Component, OnInit, signal, computed, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterModule } from '@angular/router'; -export interface CalendarEvent { +export interface Resource { id: string; - title: string; - start: Date; - end: Date; - type: 'room' | 'desk' | 'asset'; - resourceName: string; - colorClass: string; + name: string; + type: 'Sala Reuniones' | 'Oficina Flexible' | 'Zona Relax' | 'Evento'; + icon: string; + desc: string; + capacity: number; } @Component({ @@ -22,201 +21,137 @@ export interface CalendarEvent { export class CalendarBookingComponent implements OnInit { private router = inject(Router); - // Array de horas (0 a 23) - hours: number[] = Array.from({ length: 24 }, (_, i) => i); + // Horarios de la captura + timeSlots = ['08:00', '09:30', '10:00', '11:30', '12:00', '13:30', '14:00', '15:00']; - // Signals para gestionar estado reactivamente - currentView = signal<'month' | 'week' | 'day'>('month'); - currentDate = signal(new Date()); - selectedFilter = signal<'all' | 'room' | 'desk' | 'asset'>('all'); - - // Eventos de prueba - events = signal([ - { - id: '1', - title: 'Reunión de Sincronización Mensual', - start: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 10, 0), - end: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 11, 30), - type: 'room', - resourceName: 'Sala Alpha', - colorClass: 'border-l-4 border-indigo-600 bg-indigo-50/50 text-indigo-900 dark:border-indigo-500 dark:bg-indigo-950/20 dark:text-indigo-200' - }, - { - id: '2', - title: 'Reserva de Escritorio Diario', - start: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 1, 9, 0), - end: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 1, 18, 0), - type: 'desk', - resourceName: 'Escritorio Standby 04', - colorClass: 'border-l-4 border-amber-600 bg-amber-50/50 text-amber-900 dark:border-amber-500 dark:bg-amber-950/20 dark:text-amber-200' - }, - { - id: '3', - title: 'Préstamo MacBook Pro para Demo', - start: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() - 1, 14, 0), - end: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() - 1, 16, 0), - type: 'asset', - resourceName: 'MacBook Pro M3', - colorClass: 'border-l-4 border-emerald-600 bg-emerald-50/50 text-emerald-900 dark:border-emerald-500 dark:bg-emerald-950/20 dark:text-emerald-200' - } + // Base de datos de recursos por categoría + resources = signal([ + { id: '1', name: 'SALA LUNA', type: 'Sala Reuniones', icon: '🌙', desc: 'Sala Reuniones resutenias or Sala Luna', capacity: 8 }, + { id: '2', name: 'SALA SOL', type: 'Sala Reuniones', icon: '☀️', desc: 'Estables:nación de nuevos luxes', capacity: 12 }, + { id: '3', name: 'OFICINA FLEX', type: 'Oficina Flexible', icon: '🏢', desc: 'Resultalioes sala tia r Oficina flexible', capacity: 1 }, + { id: '4', name: 'OFICINA FLEX A1', type: 'Oficina Flexible', icon: '🏢', desc: 'Oficina de espacios resutentu-u planetia', capacity: 1 }, + { id: '5', name: 'ZONA COFFEE', type: 'Zona Relax', icon: '☕', desc: 'Espacio de café y socialización', capacity: 20 }, + { id: '6', name: 'SALA DE JUEGOS', type: 'Zona Relax', icon: '🎮', desc: 'Mesa de ping pong y consolas', capacity: 10 }, + { id: '7', name: 'AUDITORIO A', type: 'Evento', icon: '🎤', desc: 'Auditorio principal para presentaciones', capacity: 100 }, + { id: '8', name: 'TERRAZA', type: 'Evento', icon: '🌿', desc: 'Espacio abierto para eventos corporativos', capacity: 50 } ]); - // Lista de días de la semana - weekDays = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']; + // Signals para gestionar estado interactivo + activeCategory = signal('Sala Reuniones'); + searchQuery = signal(''); + + // Slot seleccionado para el popup dinámico + selectedSlot = signal<{ resource: Resource, time: string } | null>(null); - // Nombre legible del mes y año actual - monthYearLabel = computed(() => { - const date = this.currentDate(); - return date.toLocaleDateString('es-ES', { month: 'long', year: 'numeric' }); + // Ocupación en tiempo real (inicializada con la captura) + reservations = signal([ + { resourceName: 'SALA LUNA', time: '08:00', author: 'Sistema', date: 'Hace 10h' }, + { resourceName: 'SALA LUNA', time: '09:30', author: 'Sistema', date: 'Hace 10h' }, + { resourceName: 'SALA LUNA', time: '13:30', author: 'Sistema', date: 'Hace 10h' }, + { resourceName: 'SALA LUNA', time: '14:00', author: 'Sistema', date: 'Hace 10h' }, + { resourceName: 'SALA SOL', time: '08:00', author: 'Sistema', date: 'Hace 10h' }, + { resourceName: 'SALA SOL', time: '09:30', author: 'Sistema', date: 'Hace 10h' } + ]); + + // Notificaciones estáticas de David Chen + staticNotifications = [ + { title: 'David Chen', detail: 'compartió Resultados del Trimestre', time: 'Hace 15m', type: 'notif', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=120&q=80' }, + { title: 'David Chen', detail: 'compartió Resultados del Trimestre', time: 'Hace 15m', type: 'notif', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=120&q=80' } + ]; + + // Computed signals + filteredResources = computed(() => { + const cat = this.activeCategory(); + const query = this.searchQuery().toLowerCase().trim(); + return this.resources().filter(r => + r.type === cat && + (!query || r.name.toLowerCase().includes(query) || r.desc.toLowerCase().includes(query)) + ); }); - // Días a renderizar en la cuadrícula mensual del calendario - calendarDays = computed(() => { - const date = this.currentDate(); - const year = date.getFullYear(); - const month = date.getMonth(); - - const firstDayIndex = new Date(year, month, 1).getDay(); - const totalDays = new Date(year, month + 1, 0).getDate(); - - const days: Date[] = []; - - // Agregar días de la última semana del mes anterior - const prevMonthTotalDays = new Date(year, month, 0).getDate(); - for (let i = firstDayIndex - 1; i >= 0; i--) { - days.push(new Date(year, month - 1, prevMonthTotalDays - i)); - } - - // Agregar días del mes actual - for (let i = 1; i <= totalDays; i++) { - days.push(new Date(year, month, i)); - } - - // Completar el grid hasta 42 posiciones (6 filas) - const nextDaysCount = 42 - days.length; - for (let i = 1; i <= nextDaysCount; i++) { - days.push(new Date(year, month + 1, i)); - } - - return days; - }); - - // Filtros aplicados - filteredEvents = computed(() => { - const filter = this.selectedFilter(); - if (filter === 'all') return this.events(); - return this.events().filter(e => e.type === filter); - }); - - ngOnInit(): void {} - - prev(): void { - const d = this.currentDate(); - this.currentDate.set(new Date(d.getFullYear(), d.getMonth() - 1, 1)); - } - - next(): void { - const d = this.currentDate(); - this.currentDate.set(new Date(d.getFullYear(), d.getMonth() + 1, 1)); - } - - today(): void { - this.currentDate.set(new Date()); - } - - setFilter(filter: 'all' | 'room' | 'desk' | 'asset'): void { - this.selectedFilter.set(filter); - } - - setView(view: 'month' | 'week' | 'day'): void { - this.currentView.set(view); - } - - selectDay(day: Date): void { - const dateStr = day.toISOString().split('T')[0]; - this.router.navigate(['/reservas/nuevo'], { - queryParams: { - fecha: dateStr, - tipo: this.selectedFilter() !== 'all' ? this.selectedFilter() : 'room' + recentActivities = computed(() => { + // Convertir reservas a actividades + const resList = this.reservations().map(res => { + let avatar = 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=120&q=80'; + if (res.resourceName.includes('LUNA')) { + avatar = 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=120&q=80'; } + return { + title: res.resourceName, + detail: `Reservado ${res.time}`, + time: res.date, + type: 'reserva', + avatar: avatar + }; }); - } - - getEventsForDay(day: Date): CalendarEvent[] { - return this.filteredEvents().filter(e => { - return e.start.getFullYear() === day.getFullYear() && - e.start.getMonth() === day.getMonth() && - e.start.getDate() === day.getDate(); - }); - } - - isToday(day: Date): boolean { - const today = new Date(); - return day.getDate() === today.getDate() && - day.getMonth() === today.getMonth() && - day.getFullYear() === today.getFullYear(); - } - - isCurrentMonth(day: Date): boolean { - return day.getMonth() === this.currentDate().getMonth(); - } - - // Días de la semana actual para la vista semanal - currentWeekDays = computed(() => { - const today = this.currentDate(); - const startOfWeek = new Date(today); - // Ajustar para que comience en el domingo de la semana - startOfWeek.setDate(today.getDate() - today.getDay()); - - const days: Date[] = []; - for (let i = 0; i < 7; i++) { - const d = new Date(startOfWeek); - d.setDate(startOfWeek.getDate() + i); - days.push(d); - } - return days; + return [...resList, ...this.staticNotifications]; }); - formatHour(hour: number): string { - if (hour === 0) return '12 AM'; - if (hour === 12) return '12 PM'; - return hour > 12 ? `${hour - 12} PM` : `${hour} AM`; + ngOnInit(): void { + // Inicializar el popup en Sala Luna de las 10:00 para emular la captura al cargar + const luna = this.resources().find(r => r.name === 'SALA LUNA'); + if (luna) { + this.selectedSlot.set({ resource: luna, time: '11:30' }); + } } - getEventStyle(event: CalendarEvent): any { - const startHour = event.start.getHours() + event.start.getMinutes() / 60; - const endHour = event.end.getHours() + event.end.getMinutes() / 60; - const duration = Math.max(endHour - startHour, 0.5); // mínimo 30 min - - const top = startHour * 60; // 60px por hora - const height = duration * 60; - - return { - top: `${top}px`, - height: `${height}px` - }; + setCategory(cat: string): void { + this.activeCategory.set(cat); + this.selectedSlot.set(null); // Reset popup al cambiar pestaña } - getCurrentTimeTop(): number { - const now = new Date(); - const currentHour = now.getHours() + now.getMinutes() / 60; - return currentHour * 60; // 60px por hora + onSearch(event: Event): void { + const value = (event.target as HTMLInputElement).value; + this.searchQuery.set(value); } - isSameDay(date1: Date, date2: Date): boolean { - return date1.getDate() === date2.getDate() && - date1.getMonth() === date2.getMonth() && - date1.getFullYear() === date2.getFullYear(); + isSlotOccupied(resourceName: string, time: string): boolean { + return this.reservations().some(res => res.resourceName === resourceName && res.time === time); } - selectTimeSlot(day: Date, hour: number): void { - const dateStr = day.toISOString().split('T')[0]; - const hourStr = String(hour).padStart(2, '0') + ':00'; + onSlotClick(resource: Resource, time: string): void { + if (this.isSlotOccupied(resource.name, time)) { + return; + } + this.selectedSlot.set({ resource, time }); + } + + getEndTime(startTime: string): string { + const [h, m] = startTime.split(':').map(Number); + let fh = h + 1; + let fm = m + 30; + if (fm >= 60) { + fh += 1; + fm -= 60; + } + return `${String(fh).padStart(2, '0')}:${String(fm).padStart(2, '0')}`; + } + + confirmReserva(): void { + const slot = this.selectedSlot(); + if (slot) { + const newRes = { + resourceName: slot.resource.name, + time: slot.time, + author: 'Jess Miller', + date: 'Hace un momento' + }; + this.reservations.update(prev => [newRes, ...prev]); + this.selectedSlot.set(null); + } + } + + closePopup(): void { + this.selectedSlot.set(null); + } + + navigateToNuevo(): void { + const slot = this.selectedSlot(); this.router.navigate(['/reservas/nuevo'], { queryParams: { - fecha: dateStr, - hora: hourStr, - tipo: this.selectedFilter() !== 'all' ? this.selectedFilter() : 'room' + fecha: new Date().toISOString().split('T')[0], + tipo: slot ? (slot.resource.type === 'Sala Reuniones' ? 'room' : 'desk') : 'room', + hora: slot ? slot.time : '10:00' } }); } diff --git a/frontend/src/app/features/workflows/workflows.component.ts b/frontend/src/app/features/workflows/workflows.component.ts index cec60b5..74c0125 100644 --- a/frontend/src/app/features/workflows/workflows.component.ts +++ b/frontend/src/app/features/workflows/workflows.component.ts @@ -11,13 +11,6 @@ import { Router } from '@angular/router'; template: `
- -