update dashboard styles
This commit is contained in:
parent
93b1da4c6f
commit
a382ec1a57
2
backend/dist/tsconfig.tsbuildinfo
vendored
2
backend/dist/tsconfig.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
14
backend/src/spaces/reserva.dto.ts
Normal file
14
backend/src/spaces/reserva.dto.ts
Normal 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;
|
||||
}
|
||||
44
backend/src/spaces/spaces.controller.ts
Normal file
44
backend/src/spaces/spaces.controller.ts
Normal 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.' };
|
||||
}
|
||||
}
|
||||
44
backend/src/spaces/spaces.service.ts
Normal file
44
backend/src/spaces/spaces.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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
BIN
doc/presentacion_app.pdf
Normal file
Binary file not shown.
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -1,42 +1,48 @@
|
||||
<div class="gc-container">
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════ HEADER (GOOGLE CALENDAR TOPBAR) ═══════════════════════════════ -->
|
||||
<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>
|
||||
<button class="gc-header__nav-btn" title="Siguiente">›</button>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 class="gc-header__title">Junio de 2026</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<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">
|
||||
<span>Semana</span>
|
||||
@ -44,31 +50,26 @@
|
||||
</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>
|
||||
|
||||
<!-- ═══════════════════════════════ MAIN LAYOUT BODY ═══════════════════════════════ -->
|
||||
<div class="gc-body">
|
||||
|
||||
|
||||
<!-- ── SIDEBAR IZQUIERDO (256px) ── -->
|
||||
<aside class="gc-left-sidebar">
|
||||
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Mini monthly calendar widget -->
|
||||
<div class="gc-mini-calendar">
|
||||
<div class="gc-mini-calendar__header">
|
||||
@ -78,24 +79,23 @@
|
||||
<button class="gc-mini-calendar__nav-btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="gc-mini-calendar__days-header">
|
||||
<span>D</span><span>L</span><span>M</span><span>M</span><span>J</span><span>V</span><span>S</span>
|
||||
</div>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
<!-- Search Box inside Sidebar -->
|
||||
<div class="gc-sidebar-search">
|
||||
<div class="gc-search-box">
|
||||
@ -103,32 +103,38 @@
|
||||
<input type="text" placeholder="Buscar a gente" class="gc-search-box__input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Accordion: Mis Calendarios / Categorías de Reservas -->
|
||||
<div class="gc-accordion">
|
||||
<div class="gc-accordion__header">
|
||||
<span class="gc-accordion__title">Mis Espacios</span>
|
||||
<span class="material-icons gc-accordion__chevron">keyboard_arrow_up</span>
|
||||
</div>
|
||||
|
||||
|
||||
<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,60 +148,61 @@
|
||||
</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>
|
||||
|
||||
|
||||
</aside>
|
||||
|
||||
|
||||
<!-- ── CENTRAL CALENDAR VIEW (GRID WEEK VIEW STYLE) ── -->
|
||||
<main class="gc-main-calendar">
|
||||
|
||||
|
||||
<!-- Columns header (equivalent to days header) -->
|
||||
<div class="gc-grid-header">
|
||||
<!-- Corner empty label for alignment with time column -->
|
||||
<div class="gc-grid-header__corner">
|
||||
<span class="gc-corner-gmt">GMT-04</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 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>
|
||||
|
||||
|
||||
<!-- Scrollable time cells grid -->
|
||||
<div class="gc-grid-scroll-area">
|
||||
<div class="gc-grid-body">
|
||||
|
||||
|
||||
<!-- 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">
|
||||
<div class="gc-current-time-line__circle"></div>
|
||||
@ -203,91 +210,94 @@
|
||||
|
||||
<!-- Generate columns per resource -->
|
||||
@for (res of filteredResources(); track res.id; let colIdx = $index) {
|
||||
<div class="gc-resource-column">
|
||||
|
||||
@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 {
|
||||
|
||||
<!-- 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>
|
||||
}
|
||||
<div class="gc-resource-column">
|
||||
|
||||
}
|
||||
|
||||
<!-- 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>
|
||||
}
|
||||
@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 {
|
||||
|
||||
<!-- 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>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
|
||||
<!-- ── SIDEBAR DERECHO (48px) ── -->
|
||||
<aside class="gc-right-sidebar">
|
||||
<div class="gc-right-sidebar__icons">
|
||||
@ -303,14 +313,14 @@
|
||||
<button class="gc-right-sidebar__btn gc-right-sidebar__btn--maps" title="Maps">
|
||||
<img src="https://www.gstatic.com/companion/icon_assets/maps_v2.png" alt="Maps" />
|
||||
</button>
|
||||
|
||||
|
||||
<div class="gc-right-sidebar__divider"></div>
|
||||
|
||||
|
||||
<button class="gc-right-sidebar__btn gc-right-sidebar__btn--plus" title="Obtener complementos">
|
||||
<span class="material-icons">add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="gc-right-sidebar__btn gc-right-sidebar__btn--chevron" title="Ocultar panel lateral">
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</button>
|
||||
|
||||
@ -26,20 +26,21 @@ 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
|
||||
selectedCategories = signal<string[]>(['Sala Reuniones', 'Oficina Flexible', 'Zona Relax', 'Evento']);
|
||||
searchQuery = signal<string>('');
|
||||
|
||||
|
||||
// Slot seleccionado para el popup dinámico
|
||||
selectedSlot = signal<{ resource: Resource, time: string } | null>(null);
|
||||
|
||||
@ -109,8 +110,8 @@ export class CalendarBookingComponent implements OnInit {
|
||||
filteredResources = computed(() => {
|
||||
const selectedCats = this.selectedCategories();
|
||||
const query = this.searchQuery().toLowerCase().trim();
|
||||
return this.resources().filter(r =>
|
||||
selectedCats.includes(r.type) &&
|
||||
return this.resources().filter(r =>
|
||||
selectedCats.includes(r.type) &&
|
||||
(!query || r.name.toLowerCase().includes(query) || r.desc.toLowerCase().includes(query))
|
||||
);
|
||||
});
|
||||
|
||||
BIN
frontend/src/assets/wave.jpg
Normal file
BIN
frontend/src/assets/wave.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@ -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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user