update dashboard styles

This commit is contained in:
Jessica Orozco 2026-06-29 18:17:03 -04:00
parent 93b1da4c6f
commit a382ec1a57
14 changed files with 346 additions and 229 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
export interface CreateReservaDto {
resourceId: string;
resourceName: string;
date: string; // "YYYY-MM-DD"
startTime: string; // "HH:mm"
endTime: string; // "HH:mm"
author: string;
notes?: string;
}
export interface Reserva extends CreateReservaDto {
id: string;
createdAt: string;
}

View File

@ -0,0 +1,44 @@
import {
Controller,
Get,
Post,
Delete,
Body,
Param,
Query,
} from '@nestjs/common';
import { SpacesService } from './spaces.service';
import { CreateReservaDto } from './reserva.dto';
@Controller('reservas')
export class SpacesController {
constructor(private readonly spacesService: SpacesService) {}
/**
* GET /api/reservas?date=YYYY-MM-DD
* Devuelve todas las reservas (opcionalmente filtradas por fecha)
*/
@Get()
findAll(@Query('date') date?: string) {
return this.spacesService.findAll(date);
}
/**
* POST /api/reservas
* Crea una nueva reserva
*/
@Post()
create(@Body() dto: CreateReservaDto) {
return this.spacesService.create(dto);
}
/**
* DELETE /api/reservas/:id
* Elimina una reserva por ID
*/
@Delete(':id')
delete(@Param('id') id: string) {
this.spacesService.delete(id);
return { message: 'Reserva eliminada correctamente.' };
}
}

View File

@ -0,0 +1,44 @@
import { Injectable, ConflictException } from '@nestjs/common';
import { CreateReservaDto, Reserva } from './reserva.dto';
@Injectable()
export class SpacesService {
private reservas: Reserva[] = [];
private idCounter = 1;
findAll(date?: string): Reserva[] {
if (date) {
return this.reservas.filter((r) => r.date === date);
}
return this.reservas;
}
create(dto: CreateReservaDto): Reserva {
// Verificar conflicto: mismo recurso, mismo día, mismo horario de inicio
const conflict = this.reservas.find(
(r) =>
r.resourceName === dto.resourceName &&
r.date === dto.date &&
r.startTime === dto.startTime,
);
if (conflict) {
throw new ConflictException(
`El recurso "${dto.resourceName}" ya está reservado el ${dto.date} a las ${dto.startTime}.`,
);
}
const newReserva: Reserva = {
...dto,
id: String(this.idCounter++),
createdAt: new Date().toISOString(),
};
this.reservas.unshift(newReserva);
return newReserva;
}
delete(id: string): void {
this.reservas = this.reservas.filter((r) => r.id !== id);
}
}

View File

@ -1744,8 +1744,8 @@
<div class="gantt-timeline-container"
style="width: 78%; background: rgba(255,255,255,0.02); height: 26px; border-radius: 4px; position: relative;">
<div class="gantt-bar"
style="position: absolute; width: 12.5%; left: 12.5%; background: #34D399; height: 100%; border-radius: 4px; font-size: 0.72rem; color: #000; font-weight: 600; display: flex; align-items: center; justify-content: center; padding: 0 4px; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
SSO Azure</div>
style="position: absolute; width: 18.75%; left: 12.5%; background: #34D399; height: 100%; border-radius: 4px; font-size: 0.72rem; color: #000; font-weight: 600; display: flex; align-items: center; justify-content: center; padding: 0 4px; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
SSO Directorio Activo</div>
</div>
</div>
@ -1951,7 +1951,7 @@
</tr>
<tr>
<td><strong>Vendor Lock-in</strong></td>
<td class="highlight-td">Cero (Open Source)</td>
<td class="highlight-td">Herramientas de desarrollo estandarizadas</td>
<td>❌ Alto (Ecosistema)</td>
<td>⚠️ Dependencia de Plugins</td>
<td>⚠️ Dependencia de Plugins</td>

BIN
doc/presentacion_app.pdf Normal file

Binary file not shown.

View File

@ -36,18 +36,25 @@
}
.header-logo {
width: 38px;
height: 38px;
border-radius: 10px;
background: linear-gradient(135deg, #0D1B2A, #1A0E2E);
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 10px rgba(13, 27, 42, 0.25);
flex-shrink: 0;
overflow: hidden;
}
.header-logo svg { width: 22px; height: 22px; }
.header-logo__img {
width: 44px;
height: 44px;
object-fit: contain;
/* Funde el fondo blanco del JPG con el header claro */
mix-blend-mode: multiply;
/* Escala nitida — desactiva suavizado borroso */
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
.header-wordmark {
display: flex;

View File

@ -13,14 +13,10 @@ import { CommonModule } from '@angular/common';
<header *ngIf="!isLoginView()" class="app-header">
<a routerLink="/dashboard" class="header-brand" style="text-decoration: none; cursor: pointer;">
<div class="header-logo">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 4L13 12L6 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 4H18" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
<path d="M13 20H18" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
</svg>
<img src="/assets/wave.jpg" alt="Wave" class="header-logo__img">
</div>
<div class="header-wordmark">
<span class="header-name">App</span>
<span class="header-name">WAVE</span>
<span class="header-sub">Intranet Corporativa</span>
</div>
</a>

View File

@ -42,28 +42,28 @@ interface UserProfile {
<!-- KPI Row (light cards) -->
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;">
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #C9973C;box-shadow:0 1px 6px rgba(15,13,10,0.06);display:flex;flex-direction:column;gap:4px;">
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #0891B2;box-shadow:0 2px 12px rgba(8,145,178,0.12);display:flex;flex-direction:column;gap:4px;">
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.14em;color:#78716C;">Sedes Integradas</span>
<div style="font-family:'Inter',sans-serif;font-size:30px;font-weight:900;color:#0F0D0A;line-height:1.1;">3 Sedes</div>
<span style="font-size:10px;color:#C9973C;font-weight:600;">London · Tokyo · NY</span>
<span style="font-size:10px;color:#0891B2;font-weight:700;">London · Tokyo · NY</span>
</div>
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #3730A3;box-shadow:0 1px 6px rgba(15,13,10,0.06);display:flex;flex-direction:column;gap:4px;">
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #FD8401;box-shadow:0 2px 12px rgba(253,132,1,0.12);display:flex;flex-direction:column;gap:4px;">
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.14em;color:#78716C;">Directorio Activo</span>
<div style="font-family:'Inter',sans-serif;font-size:30px;font-weight:900;color:#0F0D0A;line-height:1.1;">1,450+</div>
<span style="font-size:10px;color:#3730A3;font-weight:600;">Usuarios Sincronizados</span>
<span style="font-size:10px;color:#EA580C;font-weight:700;">Usuarios Sincronizados</span>
</div>
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #065F46;box-shadow:0 1px 6px rgba(15,13,10,0.06);display:flex;flex-direction:column;gap:4px;">
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #059669;box-shadow:0 2px 12px rgba(5,150,105,0.12);display:flex;flex-direction:column;gap:4px;">
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.14em;color:#78716C;">Salas Disponibles</span>
<div style="font-family:'Inter',sans-serif;font-size:30px;font-weight:900;color:#0F0D0A;line-height:1.1;">18 Salas</div>
<span style="font-size:10px;color:#065F46;font-weight:600;">Sistemas IoT Activos</span>
<span style="font-size:10px;color:#059669;font-weight:700;">Sistemas IoT Activos</span>
</div>
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #7C3AED;box-shadow:0 1px 6px rgba(15,13,10,0.06);display:flex;flex-direction:column;gap:4px;">
<div style="background:#fff;border:1px solid rgba(15,13,10,0.08);border-radius:16px;padding:20px 22px;border-top:3px solid #7C3AED;box-shadow:0 2px 12px rgba(124,58,237,0.12);display:flex;flex-direction:column;gap:4px;">
<span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.14em;color:#78716C;">Flujos de Trabajo</span>
<div style="font-family:'Inter',sans-serif;font-size:30px;font-weight:900;color:#0F0D0A;line-height:1.1;">12 Activos</div>
<span style="font-size:10px;color:#7C3AED;font-weight:600;">Reglas AD Operativas</span>
<span style="font-size:10px;color:#7C3AED;font-weight:700;">Reglas AD Operativas</span>
</div>
</div>
@ -77,80 +77,81 @@ interface UserProfile {
<!-- 4 Jewel-Tone Blackjack Cards -->
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:20px;">
<!-- Card 1: Info Hub — Deep Navy #0D1B2A -->
<!-- Card 1: Info Hub — Teal/Cyan Gradient -->
<div (click)="navigateToInfoHub()"
style="position:relative;background:#0D1B2A;border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.22s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 8px 32px rgba(13,27,42,0.28);">
<!-- Inner decorative border -->
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.10);border-radius:12px;pointer-events:none;"></div>
<!-- Ambient top glow -->
<div style="position:absolute;top:-40px;right:-40px;width:160px;height:160px;background:radial-gradient(circle,rgba(201,151,60,0.14) 0%,transparent 70%);pointer-events:none;"></div>
<!-- Gold icon -->
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 20px rgba(201,151,60,0.55));">
<path d="M24 6L42 15L24 24L6 15L24 6Z" stroke="#C9973C" stroke-width="2.5" stroke-linejoin="round"/>
<path d="M6 24L24 33L42 24" stroke="#C9973C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 33L24 42L42 33" stroke="#C9973C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
style="position:relative;background:linear-gradient(135deg,#054D3F 0%,#059669 45%,#0891B2 100%);border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.25s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 12px 40px rgba(8,145,178,0.40),0 2px 8px rgba(5,150,105,0.20);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.22);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;top:-50px;right:-50px;width:200px;height:200px;background:radial-gradient(circle,rgba(6,182,212,0.35) 0%,transparent 65%);pointer-events:none;"></div>
<div style="position:absolute;bottom:-30px;left:-30px;width:140px;height:140px;background:radial-gradient(circle,rgba(52,211,153,0.25) 0%,transparent 65%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 24px rgba(6,182,212,0.70));">
<path d="M24 6L42 15L24 24L6 15L24 6Z" stroke="#FFFFFF" stroke-width="2.5" stroke-linejoin="round"/>
<path d="M6 24L24 33L42 24" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 33L24 42L42 33" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div style="position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;">
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#fff;margin:0;">Info Hub</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.45);line-height:1.65;margin:0;font-weight:300;">Gestión de contenidos corporativos y publicación de artículos.</p>
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#FFFFFF;margin:0;">Info Hub</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.88);line-height:1.65;margin:0;font-weight:400;">Gestión de contenidos corporativos y publicación de artículos.</p>
</div>
</div>
<!-- Card 2: Directorio — Deep Plum #1A0E2E -->
<!-- Card 2: Directorio — Wave Orange Gradient -->
<div (click)="navigateToDirectorio()"
style="position:relative;background:#1A0E2E;border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.22s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 8px 32px rgba(26,14,46,0.30);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.08);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;top:-40px;left:-40px;width:180px;height:180px;background:radial-gradient(circle,rgba(124,58,237,0.16) 0%,transparent 70%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 20px rgba(167,139,250,0.45));">
<path d="M8 16V8H16" stroke="#A78BFA" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40 16V8H32" stroke="#A78BFA" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 32V40H16" stroke="#A78BFA" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40 32V40H32" stroke="#A78BFA" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="18" cy="22" r="4" stroke="#A78BFA" stroke-width="2"/>
<circle cx="30" cy="22" r="4" stroke="#A78BFA" stroke-width="2"/>
<path d="M11 33c0-3.866 3.134-7 7-7s7 3.134 7 7" stroke="#A78BFA" stroke-width="2" stroke-linecap="round"/>
<path d="M23 33c0-3.866 3.134-7 7-7s7 3.134 7 7" stroke="#A78BFA" stroke-width="2" stroke-linecap="round"/>
style="position:relative;background:linear-gradient(135deg,#92400E 0%,#EA580C 45%,#FD8401 100%);border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.25s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 12px 40px rgba(253,132,1,0.45),0 2px 8px rgba(234,88,12,0.20);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.22);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;top:-50px;left:-50px;width:200px;height:200px;background:radial-gradient(circle,rgba(251,191,36,0.30) 0%,transparent 65%);pointer-events:none;"></div>
<div style="position:absolute;bottom:-30px;right:-30px;width:140px;height:140px;background:radial-gradient(circle,rgba(253,186,116,0.25) 0%,transparent 65%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 24px rgba(251,191,36,0.65));">
<path d="M8 16V8H16" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40 16V8H32" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 32V40H16" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40 32V40H32" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="18" cy="22" r="4" stroke="#FFFFFF" stroke-width="2"/>
<circle cx="30" cy="22" r="4" stroke="#FFFFFF" stroke-width="2"/>
<path d="M11 33c0-3.866 3.134-7 7-7s7 3.134 7 7" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"/>
<path d="M23 33c0-3.866 3.134-7 7-7s7 3.134 7 7" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"/>
</svg>
<div style="position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;">
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#fff;margin:0;">Directorio AD</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.40);line-height:1.65;margin:0;font-weight:300;">Sincronización con Active Directory y perfiles dinámicos.</p>
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#FFFFFF;margin:0;">Directorio AD</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.88);line-height:1.65;margin:0;font-weight:400;">Sincronización con Active Directory y perfiles dinámicos.</p>
</div>
</div>
<!-- Card 3: Workflows — Deep Viridian #0A2520 -->
<!-- Card 3: Workflows — Deep Violet Gradient -->
<div (click)="navigateToWorkflows()"
style="position:relative;background:#0A2520;border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.22s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 8px 32px rgba(10,37,32,0.30);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.08);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;bottom:-40px;right:-40px;width:180px;height:180px;background:radial-gradient(circle,rgba(16,185,129,0.14) 0%,transparent 70%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 20px rgba(52,211,153,0.45));">
<circle cx="10" cy="12" r="5" stroke="#34D399" stroke-width="2.5"/>
<circle cx="38" cy="12" r="5" stroke="#34D399" stroke-width="2.5"/>
<circle cx="24" cy="36" r="5" stroke="#34D399" stroke-width="2.5"/>
<path d="M15 12H33" stroke="#34D399" stroke-width="2" stroke-linecap="round"/>
<path d="M10 17V28c0 2.5 2 4.5 4.5 4.5H19" stroke="#34D399" stroke-width="2" stroke-linecap="round"/>
<path d="M38 17V28c0 2.5-2 4.5-4.5 4.5H29" stroke="#34D399" stroke-width="2" stroke-linecap="round"/>
style="position:relative;background:linear-gradient(135deg,#2E1065 0%,#5B21B6 45%,#7C3AED 100%);border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.25s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 12px 40px rgba(124,58,237,0.45),0 2px 8px rgba(91,33,182,0.25);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.22);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;bottom:-50px;right:-50px;width:200px;height:200px;background:radial-gradient(circle,rgba(167,139,250,0.30) 0%,transparent 65%);pointer-events:none;"></div>
<div style="position:absolute;top:-30px;left:-30px;width:140px;height:140px;background:radial-gradient(circle,rgba(196,181,253,0.20) 0%,transparent 65%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 24px rgba(167,139,250,0.70));">
<circle cx="10" cy="12" r="5" stroke="#FFFFFF" stroke-width="2.5"/>
<circle cx="38" cy="12" r="5" stroke="#FFFFFF" stroke-width="2.5"/>
<circle cx="24" cy="36" r="5" stroke="#FFFFFF" stroke-width="2.5"/>
<path d="M15 12H33" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"/>
<path d="M10 17V28c0 2.5 2 4.5 4.5 4.5H19" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"/>
<path d="M38 17V28c0 2.5-2 4.5-4.5 4.5H29" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"/>
</svg>
<div style="position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;">
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#fff;margin:0;">Workflows</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.40);line-height:1.65;margin:0;font-weight:300;">Líneas de aprobación, revisión y publicación de contenido.</p>
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#FFFFFF;margin:0;">Workflows</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.88);line-height:1.65;margin:0;font-weight:400;">Líneas de aprobación, revisión y publicación de contenido.</p>
</div>
</div>
<!-- Card 4: Espacios — Deep Espresso #1C0E08 -->
<!-- Card 4: Espacios — Electric Blue Gradient -->
<div (click)="navigateToCalendario()"
style="position:relative;background:#1C0E08;border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.22s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 8px 32px rgba(28,14,8,0.32);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.08);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;top:-40px;left:-40px;width:180px;height:180px;background:radial-gradient(circle,rgba(251,146,60,0.14) 0%,transparent 70%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 20px rgba(251,146,60,0.50));">
<rect x="6" y="10" width="36" height="32" rx="4" stroke="#FB923C" stroke-width="2.5"/>
<path d="M6 20H42" stroke="#FB923C" stroke-width="2.5" stroke-linecap="round"/>
<path d="M16 6V14" stroke="#FB923C" stroke-width="2.5" stroke-linecap="round"/>
<path d="M32 6V14" stroke="#FB923C" stroke-width="2.5" stroke-linecap="round"/>
<path d="M17 31L22 36L32 26" stroke="#FB923C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
style="position:relative;background:linear-gradient(135deg,#1E3A8A 0%,#1D4ED8 45%,#3B82F6 100%);border:none;border-radius:20px;padding:40px 24px;cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:22px;transition:all 0.25s cubic-bezier(0.34,1.56,0.64,1);overflow:hidden;min-height:270px;justify-content:center;box-shadow:0 12px 40px rgba(29,78,216,0.45),0 2px 8px rgba(30,58,138,0.25);">
<div style="position:absolute;inset:10px;border:1px solid rgba(255,255,255,0.22);border-radius:12px;pointer-events:none;"></div>
<div style="position:absolute;top:-50px;left:-50px;width:200px;height:200px;background:radial-gradient(circle,rgba(96,165,250,0.30) 0%,transparent 65%);pointer-events:none;"></div>
<div style="position:absolute;bottom:-30px;right:-30px;width:140px;height:140px;background:radial-gradient(circle,rgba(147,197,253,0.20) 0%,transparent 65%);pointer-events:none;"></div>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" style="position:relative;z-index:1;filter:drop-shadow(0 0 24px rgba(96,165,250,0.70));">
<rect x="6" y="10" width="36" height="32" rx="4" stroke="#FFFFFF" stroke-width="2.5"/>
<path d="M6 20H42" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round"/>
<path d="M16 6V14" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round"/>
<path d="M32 6V14" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round"/>
<path d="M17 31L22 36L32 26" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div style="position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;">
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#fff;margin:0;">Espacios</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.40);line-height:1.65;margin:0;font-weight:300;">Autogestión de salas, oficinas y activos de la empresa.</p>
<h4 style="font-family:'Inter',sans-serif;font-size:12px;font-weight:900;letter-spacing:0.16em;text-transform:uppercase;color:#FFFFFF;margin:0;">Espacios</h4>
<p style="font-size:12px;color:rgba(255,255,255,0.88);line-height:1.65;margin:0;font-weight:400;">Autogestión de salas, oficinas y activos de la empresa.</p>
</div>
</div>

View File

@ -4,17 +4,20 @@
<header class="gc-header">
<div class="gc-header__left">
<button class="gc-header__menu-btn" title="Menú principal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div class="gc-header__logo">
<div class="gc-header__logo-icon">
<span>16</span>
</div>
<span class="gc-header__logo-text">Calendar</span>
<span class="gc-header__logo-text">Agenda de Salas</span>
</div>
<button class="gc-header__btn gc-header__btn--rounded" (click)="selectedSlot.set(null)" title="Volver al día de hoy">Hoy</button>
<button class="gc-header__btn gc-header__btn--rounded" (click)="selectedSlot.set(null)"
title="Volver al día de hoy">Hoy</button>
<div class="gc-header__nav">
<button class="gc-header__nav-btn" title="Anterior"></button>
@ -27,15 +30,18 @@
<div class="gc-header__right">
<!-- Search Input aligned inside Header -->
<div class="gc-header__search-container">
<svg class="gc-header__search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg class="gc-header__search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" placeholder="Buscar recursos..." (input)="onSearch($event)" class="gc-header__search-input" />
<input type="text" placeholder="Buscar recursos..." (input)="onSearch($event)"
class="gc-header__search-input" />
</div>
<button class="gc-header__icon-btn" title="Asistencia"><span class="material-icons">help_outline</span></button>
<button class="gc-header__icon-btn" title="Menú Configuración"><span class="material-icons">settings</span></button>
<button class="gc-header__icon-btn" title="Menú Configuración"><span
class="material-icons">settings</span></button>
<div class="gc-header__view-select">
<button class="gc-header__view-trigger">
@ -44,7 +50,8 @@
</button>
</div>
<button class="gc-header__icon-btn" title="Aplicaciones de Google"><span class="material-icons">apps</span></button>
<button class="gc-header__icon-btn" title="Aplicaciones de Google"><span
class="material-icons">apps</span></button>
<div class="gc-header__user-avatar">JM</div>
</div>
</header>
@ -58,12 +65,6 @@
<!-- Pill style '+ Crear' Button -->
<div class="gc-sidebar-create">
<button class="gc-btn-create" (click)="navigateToNuevo()">
<svg class="gc-btn-create__icon" viewBox="0 0 36 36">
<path fill="#34A853" d="M16 16v14h4V20z"/>
<path fill="#4285F4" d="M30 16H20l-4 4h14z"/>
<path fill="#FBBC05" d="M6 16v4h10l4-4z"/>
<path fill="#EA4335" d="M20 16V6h-4v10z"/>
</svg>
<span class="gc-btn-create__text">Crear</span>
<span class="gc-btn-create__arrow"></span>
</button>
@ -85,13 +86,12 @@
<div class="gc-mini-calendar__days-grid">
@for (d of miniCalendarDays; track d) {
<span class="gc-mini-calendar__day"
[ngClass]="{
<span class="gc-mini-calendar__day" [ngClass]="{
'gc-mini-calendar__day--inactive': !d.currentMonth,
'gc-mini-calendar__day--active': d.active
}">
{{ d.day }}
</span>
{{ d.day }}
</span>
}
</div>
</div>
@ -113,22 +113,28 @@
<div class="gc-accordion__content">
<label class="gc-checkbox-item">
<input type="checkbox" [checked]="isCategorySelected('Sala Reuniones')" (change)="toggleCategory('Sala Reuniones')" class="gc-checkbox-item__input gc-checkbox-item__input--blue" />
<input type="checkbox" [checked]="isCategorySelected('Sala Reuniones')"
(change)="toggleCategory('Sala Reuniones')"
class="gc-checkbox-item__input gc-checkbox-item__input--blue" />
<span class="gc-checkbox-item__label">Sala Reuniones</span>
</label>
<label class="gc-checkbox-item">
<input type="checkbox" [checked]="isCategorySelected('Oficina Flexible')" (change)="toggleCategory('Oficina Flexible')" class="gc-checkbox-item__input gc-checkbox-item__input--green" />
<input type="checkbox" [checked]="isCategorySelected('Oficina Flexible')"
(change)="toggleCategory('Oficina Flexible')"
class="gc-checkbox-item__input gc-checkbox-item__input--green" />
<span class="gc-checkbox-item__label">Oficina Flexible</span>
</label>
<label class="gc-checkbox-item">
<input type="checkbox" [checked]="isCategorySelected('Zona Relax')" (change)="toggleCategory('Zona Relax')" class="gc-checkbox-item__input gc-checkbox-item__input--yellow" />
<input type="checkbox" [checked]="isCategorySelected('Zona Relax')" (change)="toggleCategory('Zona Relax')"
class="gc-checkbox-item__input gc-checkbox-item__input--yellow" />
<span class="gc-checkbox-item__label">Zona Relax</span>
</label>
<label class="gc-checkbox-item">
<input type="checkbox" [checked]="isCategorySelected('Evento')" (change)="toggleCategory('Evento')" class="gc-checkbox-item__input gc-checkbox-item__input--purple" />
<input type="checkbox" [checked]="isCategorySelected('Evento')" (change)="toggleCategory('Evento')"
class="gc-checkbox-item__input gc-checkbox-item__input--purple" />
<span class="gc-checkbox-item__label">Eventos</span>
</label>
</div>
@ -142,14 +148,14 @@
</div>
<div class="gc-accordion__content gc-accordion__content--scroll">
@for (act of recentActivities(); track act.title) {
<div class="gc-activity-item">
<img [src]="act.avatar" alt="user" class="gc-activity-item__avatar" />
<div class="gc-activity-item__body">
<span class="gc-activity-item__title"><strong>{{ act.title }}</strong></span>
<span class="gc-activity-item__desc">{{ act.detail }}</span>
<span class="gc-activity-item__time">{{ act.time }}</span>
</div>
<div class="gc-activity-item">
<img [src]="act.avatar" alt="user" class="gc-activity-item__avatar" />
<div class="gc-activity-item__body">
<span class="gc-activity-item__title"><strong>{{ act.title }}</strong></span>
<span class="gc-activity-item__desc">{{ act.detail }}</span>
<span class="gc-activity-item__time">{{ act.time }}</span>
</div>
</div>
}
</div>
</div>
@ -169,13 +175,13 @@
<!-- Headers columns representing resources -->
<div class="gc-grid-header__columns">
@for (res of filteredResources(); track res.id; let idx = $index) {
<div class="gc-col-header" [class.gc-col-header--active]="idx === 0">
<span class="gc-col-header__day">{{ res.type }}</span>
<div class="gc-col-header__day-circle" [class.gc-col-header__day-circle--active]="idx === 0">
<span class="gc-col-header__day-num">{{ res.icon }}</span>
</div>
<strong class="gc-col-header__name">{{ res.name }}</strong>
<div class="gc-col-header" [class.gc-col-header--active]="idx === 0">
<span class="gc-col-header__day">{{ res.type }}</span>
<div class="gc-col-header__day-circle" [class.gc-col-header__day-circle--active]="idx === 0">
<span class="gc-col-header__day-num">{{ res.icon }}</span>
</div>
<strong class="gc-col-header__name">{{ res.name }}</strong>
</div>
}
</div>
</div>
@ -187,14 +193,15 @@
<!-- Column of hours scale (left side) -->
<div class="gc-hours-column">
@for (time of timeSlots; track time) {
<div class="gc-hour-cell">
<span class="gc-hour-label">{{ time }}</span>
</div>
<div class="gc-hour-cell">
<span class="gc-hour-label">{{ time }}</span>
</div>
}
</div>
<!-- Columns of Grid scheduler -->
<div class="gc-columns-grid" [style.grid-template-columns]="'repeat(' + filteredResources().length + ', 1fr)'">
<div class="gc-columns-grid"
[style.grid-template-columns]="'repeat(' + filteredResources().length + ', 1fr)'">
<!-- Red Horizontal Line: Current Time Indicator -->
<div class="gc-current-time-line" style="top: 52%;" title="Hora actual aproximada">
@ -203,76 +210,79 @@
<!-- Generate columns per resource -->
@for (res of filteredResources(); track res.id; let colIdx = $index) {
<div class="gc-resource-column">
<div class="gc-resource-column">
@for (time of timeSlots; track time) {
<div class="gc-grid-slot-cell">
@for (time of timeSlots; track time) {
<div class="gc-grid-slot-cell">
@if (isSlotOccupied(res.name, time)) {
<!-- Event Busy Block (pastel styles styled exactly as Google Calendar events) -->
<div class="gc-event gc-event--busy gc-event--color-{{ colIdx % 4 }}">
<div class="gc-event__title">Reservado / Ocupado</div>
<div class="gc-event__time">{{ time }} - {{ getEndTime(time) }}</div>
</div>
} @else {
@if (isSlotOccupied(res.name, time)) {
<!-- Event Busy Block (pastel styles styled exactly as Google Calendar events) -->
<div class="gc-event gc-event--busy gc-event--color-{{ colIdx % 4 }}">
<div class="gc-event__title">Reservado / Ocupado</div>
<div class="gc-event__time">{{ time }} - {{ getEndTime(time) }}</div>
</div>
} @else {
<!-- Flex space placeholder mock showing avatars on Oficina Flex -->
@if (res.name === 'OFICINA FLEX A1' && time === '12:00') {
<div class="gc-event-flex-avatars" (click)="onSlotClick(res, time)">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=40&q=80" alt="emp" class="gc-mini-avatar" />
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=40&q=80" alt="emp" class="gc-mini-avatar" />
</div>
} @else if (res.name === 'OFICINA FLEX A1' && time === '13:30') {
<div class="gc-event-flex-avatars" (click)="onSlotClick(res, time)">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=40&q=80" alt="emp" class="gc-mini-avatar" />
</div>
} @else {
<!-- Empty Available Cell click to add -->
<div class="gc-slot-click-zone" (click)="onSlotClick(res, time)" title="Haga clic para reservar"></div>
}
<!-- Flex space placeholder mock showing avatars on Oficina Flex -->
@if (res.name === 'OFICINA FLEX A1' && time === '12:00') {
<div class="gc-event-flex-avatars" (click)="onSlotClick(res, time)">
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=40&q=80"
alt="emp" class="gc-mini-avatar" />
<img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=40&q=80"
alt="emp" class="gc-mini-avatar" />
</div>
} @else if (res.name === 'OFICINA FLEX A1' && time === '13:30') {
<div class="gc-event-flex-avatars" (click)="onSlotClick(res, time)">
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=40&q=80"
alt="emp" class="gc-mini-avatar" />
</div>
} @else {
<!-- Empty Available Cell click to add -->
<div class="gc-slot-click-zone" (click)="onSlotClick(res, time)" title="Haga clic para reservar"></div>
}
}
<!-- Google Calendar style interactive Event creation popup -->
@if (selectedSlot()?.resource?.id === res.id && selectedSlot()?.time === time) {
<div class="gc-popup-card" (click)="$event.stopPropagation()">
<div class="gc-popup-card__header">
<span class="material-icons gc-popup-card__drag">drag_handle</span>
<button class="gc-popup-card__close-btn" (click)="closePopup()" title="Cerrar"></button>
</div>
<div class="gc-popup-card__body">
<h4 class="gc-popup-card__title">{{ res.name }}</h4>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">schedule</span>
<div class="gc-popup-card__details">
<span>Martes, 16 de Junio</span>
<span class="gc-popup-card__time">{{ time }} {{ getEndTime(time) }}</span>
</div>
</div>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">room</span>
<span>Capacidad máxima: {{ res.capacity }} personas</span>
</div>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">info_outline</span>
<span class="gc-popup-card__tag">{{ res.type }}</span>
</div>
</div>
<div class="gc-popup-card__actions">
<button (click)="navigateToNuevo()" class="gc-popup-btn-secondary">Más opciones</button>
<button (click)="confirmReserva()" class="gc-popup-btn-primary">Guardar</button>
</div>
</div>
}
}
<!-- Google Calendar style interactive Event creation popup -->
@if (selectedSlot()?.resource?.id === res.id && selectedSlot()?.time === time) {
<div class="gc-popup-card" (click)="$event.stopPropagation()">
<div class="gc-popup-card__header">
<span class="material-icons gc-popup-card__drag">drag_handle</span>
<button class="gc-popup-card__close-btn" (click)="closePopup()" title="Cerrar"></button>
</div>
<div class="gc-popup-card__body">
<h4 class="gc-popup-card__title">{{ res.name }}</h4>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">schedule</span>
<div class="gc-popup-card__details">
<span>Martes, 16 de Junio</span>
<span class="gc-popup-card__time">{{ time }} {{ getEndTime(time) }}</span>
</div>
</div>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">room</span>
<span>Capacidad máxima: {{ res.capacity }} personas</span>
</div>
<div class="gc-popup-card__row">
<span class="material-icons gc-popup-card__icon">info_outline</span>
<span class="gc-popup-card__tag">{{ res.type }}</span>
</div>
</div>
<div class="gc-popup-card__actions">
<button (click)="navigateToNuevo()" class="gc-popup-btn-secondary">Más opciones</button>
<button (click)="confirmReserva()" class="gc-popup-btn-primary">Guardar</button>
</div>
</div>
}
</div>
}
</div>
}
</div>
@ -280,10 +290,10 @@
</div>
@if (filteredResources().length === 0) {
<div class="gc-empty-state">
<span class="material-icons gc-empty-state__icon">calendar_today</span>
<p>No hay recursos seleccionados. Marque las categorías en el panel izquierdo.</p>
</div>
<div class="gc-empty-state">
<span class="material-icons gc-empty-state__icon">calendar_today</span>
<p>No hay recursos seleccionados. Marque las categorías en el panel izquierdo.</p>
</div>
}
</main>

View File

@ -26,14 +26,15 @@ export class CalendarBookingComponent implements OnInit {
// Base de datos de recursos por categoría
resources = signal<Resource[]>([
{ 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 }
{ id: '1', name: 'Atom 1', type: 'Sala Reuniones', icon: '🌙', desc: 'Sala Reuniones resutenias or Sala Luna', capacity: 8 },
{ id: '2', name: 'Atom 2', type: 'Sala Reuniones', icon: '☀️', desc: 'Estables:nación de nuevos luxes', capacity: 12 },
{ id: '3', name: 'Atom 3', type: 'Oficina Flexible', icon: '🏢', desc: 'Resultalioes sala tia r Oficina flexible', capacity: 1 },
{ id: '4', name: 'Atom 4', type: 'Oficina Flexible', icon: '🏢', desc: 'Oficina de espacios resutentu-u planetia', capacity: 1 },
{ id: '5', name: 'Atom 5', type: 'Zona Relax', icon: '☕', desc: 'Espacio de café y socialización', capacity: 20 },
{ id: '6', name: 'Atom 6', type: 'Zona Relax', icon: '🎮', desc: 'Mesa de ping pong y consolas', capacity: 10 },
{ id: '7', name: 'Atom 7', type: 'Evento', icon: '🎤', desc: 'Auditorio principal para presentaciones', capacity: 100 },
{ id: '8', name: 'Atom 8', type: 'Evento', icon: '🌿', desc: 'Espacio abierto para eventos corporativos', capacity: 50 },
{ id: '9', name: 'Sala Fusiòn', type: 'Sala Reuniones', icon: '🌿', desc: 'Espacio abierto para eventos corporativos', capacity: 15 }
]);
// Signals para gestionar estado interactivo

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>App Intranet</title>
<title>Wave</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">