diff --git a/doc/infohub.md b/doc/infohub.md new file mode 100644 index 0000000..10694d7 --- /dev/null +++ b/doc/infohub.md @@ -0,0 +1,56 @@ + desarrollado los módulos completos para Info Hub y Workflows de Aprobación, unificando la lógica a través de un servicio reactivo para que la información fluya dinámicamente. + +La aplicación compila al 100% sin advertencias y mantiene el estándar de alta gama (Executive Suite UI) con tarjetas interactivas, animaciones suaves y gráficas vectoriales. + +Aquí tienes el detalle técnico de la implementación de nivel arquitectónico: + +1. Estado Compartido y Lógica Reactiva (Signals) +Para comunicar ambos módulos sin acoplar los componentes creados, implementé un servicio core centralizado en + +content.service.ts +. Este servicio actúa como la base de datos simulada en memoria y utiliza Angular Signals (signal y computed de lectura optimizada) para sincronizar en tiempo real el catálogo de comunicados y la cola de revisión: + +Mantiene los comunicados ya aprobados y listos para publicarse. +Controla la cola de pendientes con niveles incrementales de validación (approvalLevel). +Provee métodos para reaccionar a likes, comentarios y compartidos en redes sociales. +2. Módulo Info Hub (CMS & Integración Social) +Construí el componente en + +info-hub.component.ts + con las siguientes características premium: + +Feed de Noticias: Muestra tarjetas elegantes de comunicados de forma cronológica (título, cuerpo, etiquetas de categoría y targets de redes). +Sección de Comentarios: Desplegable interactivo por artículo para leer aportes y agregar nuevos en tiempo real. +Módulo de Redes Sociales: Widgets estilo "Executive Suite" que muestran el alcance simulado en LinkedIn, X y la Intranet. +Analíticas: Un gráfico de líneas SVG fluido y responsivo que proyecta el alcance de contenido en las últimas semanas (sin requerir librerías pesadas externas). +Modal de Compartido (Glassmorphism): Al hacer clic en "Compartir", se despliega una interfaz translúcida que permite seleccionar las redes objetivo (LinkedIn, X, Intranet) y simular el incremento del alcance orgánico. +3. Módulo Workflows (Formulario & Motor de Aprobación BPM) +Creé el componente en + +workflows.component.ts + enfocado en la administración de publicaciones: + +Formulario de Redacción: Permite escribir nuevos anuncios, asignarlos a categorías (Tecnología, RR.HH., Operaciones, Seguridad) y marcar los canales objetivo donde se desea propagar. +Cola de Aprobaciones Jerárquicas: Lista de forma interactiva las propuestas en curso. +Nivel 1 (Amarillo): Revisión Técnica. +Nivel 2 (Púrpura): Autorización de Recursos Humanos. +Si se rechaza, se elimina de la cola. Si se aprueba en el Nivel 2, el artículo se publica automáticamente en el Info Hub, desapareciendo de la cola del motor BPM y actualizando la lista global al instante. +Historial Rápido: Una sección lateral para auditar los últimos artículos aprobados con un enlace rápido para ir a visualizarlos en el portal general. +4. Rutas y Cableado de Navegación +Registré las dos nuevas rutas lazy-loaded (info-hub y workflows) en + +app.routes.ts +, protegiéndolas bajo el guardia de seguridad Active Directory (authGuard). +Modifiqué el archivo + +dashboard.component.ts + (las tarjetas del menú inicial) para sustituir las pestañas locales anteriores por navegación de rutas reales a través de this.router.navigate(...). +Al hacer clic en la tarjeta de Info Hub, te redirige a /info-hub. +Al hacer clic en la tarjeta de Workflows, te redirige a /workflows. +Cómo probar el flujo completo: +Ve al panel de inicio de la Intranet y haz clic en la tarjeta de Workflows. +Escribe una nueva propuesta (ej: "Plan de Capacitación Angular 18"), selecciona canales de redes y haz clic en "Enviar a Flujo de Aprobación". +En la lista de aprobaciones a la derecha, verás tu post en amarillo (Nivel 1). Haz clic en "Avanzar a Nivel 2". El post cambiará de color a púrpura (Nivel 2). +Vuelve a hacer clic en "Aprobar y Publicar". El post se eliminará de la cola BPM y se posteará de forma automatizada. +Haz clic en el enlace "Ver en Info Hub" de abajo o haz clic en el logo de la Intranet arriba (que te llevará de vuelta al Dashboard) e ingresa al Info Hub. +¡Verás tu nuevo post listo en el feed general! Puedes darle a ❤️, abrir los comentarios para escribir tu opinión, o presionar el botón 🔗 para simular compartirlo en LinkedIn o X. \ No newline at end of file diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index ad3ccc3..a3ac8bc 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -21,6 +21,16 @@ export const routes: Routes = [ canActivate: [authGuard], loadComponent: () => import('./features/directorio/directorio.component').then(m => m.DirectorioComponent) }, + { + path: 'info-hub', + canActivate: [authGuard], + loadComponent: () => import('./features/info-hub/info-hub.component').then(m => m.InfoHubComponent) + }, + { + path: 'workflows', + canActivate: [authGuard], + loadComponent: () => import('./features/workflows/workflows.component').then(m => m.WorkflowsComponent) + }, { path: 'reservas', canActivate: [authGuard], diff --git a/frontend/src/app/core/services/content.service.ts b/frontend/src/app/core/services/content.service.ts new file mode 100644 index 0000000..da9b8f6 --- /dev/null +++ b/frontend/src/app/core/services/content.service.ts @@ -0,0 +1,178 @@ +import { Injectable, signal, computed } from '@angular/core'; + +export interface Article { + id: number; + title: string; + category: string; + body: string; + author: string; + date: string; + status: 'pending' | 'approved' | 'rejected'; + approvalLevel: number; // 1 = Tech Review, 2 = HR Review, 3 = Approved + likes: number; + shares: number; + commentsCount: number; + shareTargets: string[]; + comments: string[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class ContentService { + private currentId = 5; + + // Signal for published articles (visible in Info Hub) + private _articles = signal([ + { + id: 1, + title: 'Lanzamiento de la Nueva Intranet Corporativa', + category: 'Tecnología', + body: 'Hoy lanzamos oficialmente nuestra nueva plataforma de intranet con integración a Active Directory y un sistema unificado de reservas de espacios. ¡Optimiza tus flujos diarios desde hoy!', + author: 'Jess Miller', + date: 'Hace 2 horas', + status: 'approved', + approvalLevel: 3, + likes: 42, + shares: 12, + commentsCount: 3, + shareTargets: ['LinkedIn', 'X', 'Intranet'], + comments: [ + 'Excelente diseño, muy responsivo.', + 'Me encanta el asistente de reservas con IA.', + 'Gran trabajo a todo el equipo!' + ] + }, + { + id: 2, + title: 'Políticas de Trabajo Híbrido 2026', + category: 'Recursos Humanos', + body: 'Actualizamos las directrices de trabajo híbrido. Los equipos podrán acordar hasta 3 días de trabajo remoto a la semana, coordinando la ocupación de puestos a través de la autogestión de espacios.', + author: 'Dirección de RR.HH.', + date: 'Ayer', + status: 'approved', + approvalLevel: 3, + likes: 85, + shares: 24, + commentsCount: 2, + shareTargets: ['LinkedIn', 'Intranet'], + comments: [ + 'Gran paso adelante para la flexibilidad.', + 'Muy claro el proceso de reserva de escritorios.' + ] + }, + { + id: 3, + title: 'Proyecto de Sostenibilidad y Oficina Verde', + category: 'Operaciones', + body: 'Iniciamos el programa Oficina Verde para reducir el consumo de papel y optimizar la climatización de salas mediante sensores IoT integrados. Coordina tus reuniones en salas eco-eficientes.', + author: 'Marta Soler (Operaciones)', + date: 'Hace 3 días', + status: 'approved', + approvalLevel: 3, + likes: 56, + shares: 8, + commentsCount: 1, + shareTargets: ['X', 'Intranet'], + comments: [ + '¡Apoyo total a esta iniciativa!' + ] + } + ]); + + // Signal for pending approvals (visible in Workflow Module) + private _pending = signal([ + { + id: 4, + title: 'Manual de Seguridad de Datos para Desarrolladores', + category: 'Seguridad', + body: 'Documento técnico con las directrices de codificación segura, rotación de secretos en Azure y cumplimiento con OWASP Top 10 para APIs REST.', + author: 'David Chen', + date: 'Hace 1 hora', + status: 'pending', + approvalLevel: 1, // 1st level approval needed + likes: 0, + shares: 0, + commentsCount: 0, + shareTargets: ['Intranet'], + comments: [] + } + ]); + + articles = computed(() => this._articles()); + pending = computed(() => this._pending()); + + addPending(title: string, category: string, body: string, author: string, shareTargets: string[]): void { + const newArticle: Article = { + id: this.currentId++, + title, + category, + body, + author, + date: 'Hace un momento', + status: 'pending', + approvalLevel: 1, + likes: 0, + shares: 0, + commentsCount: 0, + shareTargets, + comments: [] + }; + this._pending.update(prev => [newArticle, ...prev]); + } + + advanceApproval(id: number): void { + let articleToMove: Article | undefined; + + this._pending.update(prev => { + const items = prev.map(art => { + if (art.id === id) { + const nextLevel = art.approvalLevel + 1; + if (nextLevel > 2) { + // Reaches level 3 -> Approved & Published + articleToMove = { ...art, status: 'approved', approvalLevel: 3, date: 'Publicado hace un momento' }; + return null; + } + return { ...art, approvalLevel: nextLevel }; + } + return art; + }); + return items.filter(art => art !== null) as Article[]; + }); + + if (articleToMove) { + this._articles.update(prev => [articleToMove!, ...prev]); + } + } + + rejectArticle(id: number): void { + this._pending.update(prev => prev.filter(art => art.id !== id)); + } + + likeArticle(id: number): void { + this._articles.update(prev => { + return prev.map(art => art.id === id ? { ...art, likes: art.likes + 1 } : art); + }); + } + + shareArticle(id: number): void { + this._articles.update(prev => { + return prev.map(art => art.id === id ? { ...art, shares: art.shares + 1 } : art); + }); + } + + addComment(id: number, comment: string): void { + this._articles.update(prev => { + return prev.map(art => { + if (art.id === id) { + return { + ...art, + comments: [...art.comments, comment], + commentsCount: art.commentsCount + 1 + }; + } + return art; + }); + }); + } +} diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts index 78b865e..63bb9ca 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.ts +++ b/frontend/src/app/features/dashboard/dashboard.component.ts @@ -78,7 +78,7 @@ interface UserProfile {
-
@@ -118,7 +118,7 @@ interface UserProfile {
-
@@ -803,6 +803,14 @@ export class DashboardComponent implements OnInit { this.router.navigate(['/directorio']); } + navigateToInfoHub(): void { + this.router.navigate(['/info-hub']); + } + + navigateToWorkflows(): void { + this.router.navigate(['/workflows']); + } + bookSpace() { this.bookingSuccess.set(true); } diff --git a/frontend/src/app/features/directorio/directorio.component.css b/frontend/src/app/features/directorio/directorio.component.css index 75442f1..2224777 100644 --- a/frontend/src/app/features/directorio/directorio.component.css +++ b/frontend/src/app/features/directorio/directorio.component.css @@ -1,695 +1,643 @@ -/* Layout General y Elementos Comunes */ +/* ========================================================================== + APP PEOPLE — Employee Directory Stylesheet + ========================================================================== */ + .dir-shell { - display: flex; - min-height: 100vh; - background: #F5F4F1; - font-family: 'Inter', 'Outfit', system-ui, sans-serif; -} - -.dir-sidebar, -.dir-main { - min-height: 100vh; -} - -.dir-sidebar { - width: 228px; - background: #0F0D0A; display: flex; flex-direction: column; - padding: 24px 0 20px; - transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1); - position: sticky; - top: 0; - z-index: 40; - flex-shrink: 0; + background: #f8fafc; /* Very light clean grey background */ + border-radius: 24px; + overflow: hidden; + border: 1px solid rgba(15, 13, 10, 0.06); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); + min-height: 85vh; + font-family: 'Outfit', 'Inter', system-ui, sans-serif; } -.dir-sidebar--collapsed { - width: 64px; +/* ── TIMI PEOPLE NAVBAR ── */ +.dir-topbar { + background: #0f172a; /* Slate 900 */ + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 28px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); } -.dir-main { - flex: 1; - padding: 36px 40px; - overflow-y: auto; - min-width: 0; -} - -/* Componentes Flex Comunes */ -.dir-sidebar__brand, -.dir-sidebar__brand-icon, -.dir-sidebar__nav-item, -.dir-sidebar__user, -.dir-header__title-block, -.dir-header__badge, -.dir-filters, -.dir-card__avatar-row, -.dir-card__avatar, -.dir-card__rating, -.dir-card__identity, -.dir-card__metrics, -.dir-card__cta, -.dir-empty, -.dir-toast, -.dir-sidebar__toggle { +.dir-topbar__left, +.dir-topbar__right { display: flex; align-items: center; } -/* Elementos Interactivos y Botones */ -.dir-sidebar__toggle, -.dir-filters__input, -.dir-filters__select, -.dir-filters__reset, -.dir-card__ad-id, -.dir-card__rating, -.dir-card__tag, -.dir-empty button { - border: 1px solid #E8E4DC; +.dir-topbar__left { + gap: 36px; } -.dir-sidebar__toggle, -.dir-sidebar__nav-item, -.dir-filters__input, -.dir-filters__reset, -.dir-card, -.dir-card__cta, -.dir-empty button { - transition: all 0.15s; +.dir-topbar__logo { + display: flex; + align-items: center; + gap: 8px; + color: #ffffff; + font-size: 14px; + letter-spacing: 0.02em; } -.dir-sidebar__toggle { - position: absolute; - top: 20px; - right: -13px; - width: 26px; - height: 26px; - border-radius: 50%; - background: #FFF; - color: #78716C; - justify-content: center; +.dir-topbar__logo-icon { + font-size: 18px; +} + +.dir-topbar__logo-text strong { + color: #facc15; /* Warning yellow */ +} + +.dir-topbar__menu { + display: flex; + list-style: none; + margin: 0; + padding: 0; + gap: 24px; + font-size: 13px; + color: #94a3b8; +} + +.dir-topbar__menu-item { cursor: pointer; - box-shadow: 0 2px 8px rgba(15, 13, 10, 0.12); - z-index: 10; + font-weight: 500; + transition: color 0.15s ease; + padding: 4px 0; } -.dir-sidebar__toggle:hover { - background: #C9973C; - color: #FFF; - border-color: #C9973C; +.dir-topbar__menu-item:hover { + color: #ffffff; } -/* Tipografía y Textos */ -.dir-sidebar__brand-name, -.dir-card__name, -.dir-card__metric-value { +.dir-topbar__menu-item--active { + color: #ffffff; + border-bottom: 2px solid #facc15; + font-weight: 600; +} + +.dir-topbar__right { + gap: 16px; +} + +.dir-topbar__btn-add { + background: #facc15; + border: none; + color: #0f172a; + font-size: 18px; font-weight: 800; - color: #0F0D0A; + width: 30px; + height: 30px; + border-radius: 8px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s ease; +} + +.dir-topbar__btn-add:hover { + background: #eab308; +} + +.dir-topbar__btn-icon { + background: transparent; + border: none; + color: #94a3b8; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.dir-topbar__btn-icon:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.05); +} + +.dir-topbar__user-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: #4f46e5; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + cursor: pointer; +} + +/* ── MAIN CONTENT ── */ +.dir-main { + padding: 32px; + display: flex; + flex-direction: column; + gap: 24px; + flex-grow: 1; +} + +/* Header block */ +.dir-content-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.dir-content-header__left { + display: flex; + align-items: baseline; + gap: 16px; +} + +.dir-content-header__title { + font-size: 24px; + font-weight: 800; + color: #0f172a; + margin: 0; letter-spacing: -0.01em; } -.dir-sidebar__brand { - gap: 10px; - padding: 0 18px 24px; - cursor: pointer; - overflow: hidden; -} - -.dir-sidebar__brand-icon { - width: 36px; - height: 36px; - background: rgba(201, 151, 60, 0.1); - border-radius: 10px; - justify-content: center; - flex-shrink: 0; -} - -.dir-sidebar__brand-name { - font-size: 15px; - color: #FFF; - white-space: nowrap; -} - -.dir-sidebar__section-label { - font-size: 9px; - font-weight: 700; - letter-spacing: 0.18em; - text-transform: uppercase; - color: rgba(255, 255, 255, 0.28); - padding: 0 18px 10px; -} - -.dir-sidebar__nav { +.dir-content-header__stats { display: flex; - flex-direction: column; - gap: 2px; - padding: 0 10px; - flex: 1; - overflow: hidden; -} - -.dir-sidebar__nav-item { - gap: 10px; - padding: 10px; - border-radius: 10px; - text-decoration: none; - color: rgba(255, 255, 255, 0.48); - font-size: 13px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; -} - -.dir-sidebar__nav-item:hover { - background: rgba(255, 255, 255, 0.06); - color: rgba(255, 255, 255, 0.88); -} - -.dir-sidebar__nav-item--active { - background: rgba(201, 151, 60, 0.12); - color: #C9973C; + gap: 12px; + font-size: 11px; font-weight: 600; } -.dir-sidebar__nav-icon { - width: 20px; - height: 20px; - justify-content: center; - flex-shrink: 0; - font-size: 15px; +.dir-content-header__stat-active { + color: #059669; } -/* Sidebar Footer */ -.dir-sidebar__footer { - padding: 16px 14px 0; - border-top: 1px solid rgba(255, 255, 255, 0.06); - margin-top: 16px; +.dir-content-header__stat-inactive { + color: #64748b; +} + +.dir-content-header__right { display: flex; - flex-direction: column; - gap: 10px; - overflow: hidden; + gap: 8px; } -.dir-sidebar__user { - gap: 10px; -} - -.dir-sidebar__user-avatar { - width: 34px; - height: 34px; - border-radius: 10px; - background: linear-gradient(135deg, #C9973C, #A67C28); - color: #FFF; - font-size: 11px; - font-weight: 800; - justify-content: center; - flex-shrink: 0; -} - -.dir-sidebar__user-avatar--sm { - width: 30px; - height: 30px; - margin: 0 auto; -} - -.dir-sidebar__user-info { - display: flex; - flex-direction: column; -} - -.dir-sidebar__user-name { +.dir-btn-secondary { + background: #ffffff; + border: 1px solid #e2e8f0; + color: #334155; font-size: 12px; + font-weight: 600; + padding: 8px 16px; + border-radius: 10px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; + transition: background 0.15s ease, border-color 0.15s ease; +} + +.dir-btn-secondary:hover { + background: #f8fafc; + border-color: #cbd5e1; +} + +.dir-btn-secondary svg { + color: #64748b; +} + +.dir-btn-primary { + background: #0f5132; /* Rich dark forest green */ + border: none; + color: #ffffff; + font-size: 12px; + font-weight: 600; + padding: 8px 16px; + border-radius: 10px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + transition: background 0.15s ease; +} + +.dir-btn-primary:hover { + background: #0b3d26; +} + +.dir-btn-primary__icon { + font-size: 14px; font-weight: 700; - color: #FFF; - white-space: nowrap; } -.dir-sidebar__user-role { - font-size: 10px; - color: rgba(255, 255, 255, 0.36); - white-space: nowrap; -} - -.dir-sidebar__status { - font-size: 9.5px; - color: rgba(255, 255, 255, 0.3); - line-height: 1.5; - white-space: nowrap; -} - -.dir-sidebar__status strong { - color: rgba(255, 255, 255, 0.55); -} - -.dir-sidebar__status-dot { - display: inline-block; - width: 6px; - height: 6px; - border-radius: 50%; - background: #34D399; - margin-right: 5px; - animation: pulse-dot 2s infinite; -} - -.dir-sidebar__status-dot--sm { - width: 8px; - height: 8px; - margin: 4px auto 0; -} - -@keyframes pulse-dot { - - 0%, - 100% { - opacity: 1 - } - - 50% { - opacity: .4 - } -} - -/* Header Principal */ -.dir-header { +/* Filters bar */ +.dir-filters-bar { display: flex; justify-content: space-between; - margin-bottom: 28px; + align-items: center; + flex-wrap: wrap; gap: 16px; - flex-wrap: wrap; + background: #ffffff; + padding: 12px 20px; + border-radius: 16px; + border: 1px solid #f1f5f9; } -.dir-header__title-block { - gap: 14px; - flex-wrap: wrap; -} - -.dir-header__title { - font-size: 26px; - font-weight: 900; - color: #0F0D0A; - letter-spacing: -0.02em; - margin: 0; - line-height: 1.15; -} - -.dir-header__badge { - gap: 7px; - background: #FFF; - border-radius: 9999px; - padding: 5px 12px; - font-size: 11px; - font-weight: 600; - color: #57534E; -} - -.dir-header__badge-dot { - width: 7px; - height: 7px; - border-radius: 50%; - background: #34D399; - animation: pulse-dot 2s infinite; -} - -.dir-header__count { - font-size: 12px; - font-weight: 600; - color: #A8A29E; - align-self: center; -} - -/* Filtros y Buscador */ -.dir-filters { +.dir-filters-bar__left, +.dir-filters-bar__right { + display: flex; + align-items: center; gap: 10px; flex-wrap: wrap; - margin-bottom: 14px; } -.dir-filters__search { +.dir-search-input { position: relative; - flex: 1; - min-width: 240px; - max-width: 440px; + width: 220px; } -.dir-filters__search-icon { +.dir-search-input__icon { position: absolute; - left: 14px; + left: 12px; top: 50%; transform: translateY(-50%); + color: #94a3b8; pointer-events: none; } -.dir-filters__input { +.dir-search-input__field { width: 100%; - padding: 11px 40px; - border-radius: 14px; - background: #FFF; - font-size: 13.5px; - color: #0F0D0A; - outline: none; - box-shadow: 0 1px 3px rgba(15, 13, 10, 0.04); - font-family: inherit; -} - -.dir-filters__input::placeholder { - color: #A8A29E; -} - -.dir-filters__input:focus { - border-color: #C9973C; - box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12); -} - -.dir-filters__clear { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: #A8A29E; - cursor: pointer; - font-size: 13px; - padding: 2px 4px; - border-radius: 4px; -} - -.dir-filters__clear:hover { - color: #0F0D0A; -} - -.dir-filters__select-wrap { - position: relative; - flex-shrink: 0; -} - -.dir-filters__select { - appearance: none; - padding: 11px 36px 11px 16px; - border-radius: 14px; - background: #FFF; - font-size: 13px; - color: #0F0D0A; - cursor: pointer; - outline: none; - font-family: inherit; - box-shadow: 0 1px 3px rgba(15, 13, 10, 0.04); -} - -.dir-filters__select:focus { - border-color: #C9973C; -} - -.dir-filters__select-arrow { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; -} - -.dir-filters__reset { - padding: 10px 16px; - border-radius: 14px; - background: transparent; - font-size: 12.5px; - font-weight: 600; - color: #78716C; - cursor: pointer; - font-family: inherit; -} - -.dir-filters__reset:hover { - border-color: #C9973C; - color: #C9973C; - background: rgba(201, 151, 60, 0.05); -} - -.dir-results-summary { + padding: 8px 12px 8px 36px; + border-radius: 10px; + border: 1px solid #e2e8f0; font-size: 12px; - color: #A8A29E; - margin: 0 0 22px; + outline: none; + background: #f8fafc; + color: #334155; + transition: border-color 0.15s ease, background-color 0.15s ease; } -.dir-results-summary strong { - color: #57534E; +.dir-search-input__field::placeholder { + color: #94a3b8; } -/* Grid y Tarjetas */ +.dir-search-input__field:focus { + border-color: #94a3b8; + background: #ffffff; +} + +.dir-filter-dropdown__trigger { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 10px; + font-size: 12px; + font-weight: 500; + color: #475569; + padding: 8px 14px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: background 0.15s ease, border-color 0.15s ease; +} + +.dir-filter-dropdown__trigger:hover { + background: #f1f5f9; + border-color: #cbd5e1; +} + +.dir-filter-dropdown__arrow { + font-size: 8px; + color: #94a3b8; +} + +.dir-btn-advanced, +.dir-btn-transfer { + background: transparent; + border: none; + color: #475569; + font-size: 12px; + font-weight: 600; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + transition: color 0.15s ease; +} + +.dir-btn-advanced:hover, +.dir-btn-transfer:hover { + color: #0f172a; +} + +.dir-btn-advanced svg, +.dir-btn-transfer svg { + color: #64748b; +} + +.dir-view-toggle { + display: flex; + background: #f1f5f9; + border-radius: 8px; + padding: 2px; +} + +.dir-view-toggle__btn { + background: transparent; + border: none; + color: #64748b; + padding: 6px; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.dir-view-toggle__btn--active { + background: #ffffff; + color: #0f172a; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +/* Grid layout */ .dir-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - gap: 20px; + gap: 24px; } +/* Card details */ .dir-card { - position: relative; - background: #FFF; - border-radius: 20px; - padding: 24px; - border: 1px solid rgba(15, 13, 10, 0.07); - box-shadow: 0 1px 4px rgba(15, 13, 10, 0.05); - gap: 16px; + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 16px; + padding: 20px; + display: flex; flex-direction: column; - cursor: default; - transition: box-shadow 0.22s cubic-bezier(0.4, 0, 0.2, 1), transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.22s; + gap: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.01); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; } .dir-card:hover { - box-shadow: 0 8px 28px rgba(15, 13, 10, 0.1); transform: translateY(-2px); - border-color: rgba(201, 151, 60, 0.25); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.04); + border-color: #cbd5e1; } -.dir-card__ad-id { - position: absolute; - top: 16px; - right: 16px; - font-family: 'SF Mono', 'Fira Code', monospace; +.dir-card__top { + display: flex; + justify-content: space-between; + align-items: center; +} + +.dir-card__status-badge { font-size: 10px; font-weight: 600; - color: #A8A29E; - background: #FAF9F6; - padding: 3px 8px; - border-radius: 6px; - letter-spacing: 0.04em; + padding: 3px 10px; + border-radius: 9999px; } -.dir-card__avatar-row { - gap: 14px; +.dir-card__status-badge--active { + background: #ecfdf5; + color: #047857; + border: 1px solid #a7f3d0; +} + +.dir-card__status-badge--invited { + background: #f3f4f6; + color: #4b5563; + border: 1px solid #e5e7eb; +} + +.dir-card__options { + background: transparent; + border: none; + color: #94a3b8; + font-size: 16px; + cursor: pointer; + transition: color 0.15s ease; +} + +.dir-card__options:hover { + color: #475569; +} + +.dir-card__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.dir-card__avatar-container { + width: 76px; + height: 76px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4px solid #f8fafc; + margin-bottom: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03); } .dir-card__avatar { - width: 56px; - height: 56px; + width: 100%; + height: 100%; border-radius: 50%; + display: flex; + align-items: center; justify-content: center; - font-size: 17px; + font-size: 22px; font-weight: 800; - color: #FFF; - letter-spacing: -0.02em; - flex-shrink: 0; -} - -.dir-card__rating { - gap: 4px; - background: #FAF9F6; - border-radius: 9999px; - padding: 4px 10px; - font-size: 12px; - font-weight: 700; - color: #0F0D0A; -} - -.dir-card__identity { - gap: 3px; } .dir-card__name { - font-size: 17px; - margin: 0; - line-height: 1.2; + font-size: 15px; + font-weight: 800; + color: #0f172a; + margin: 0 0 2px; + letter-spacing: -0.01em; } .dir-card__role { - font-size: 12.5px; - font-weight: 600; - color: #C9973C; -} - -.dir-card__dept { - font-size: 11px; + font-size: 12px; font-weight: 500; - color: #A8A29E; -} - -.dir-card__tags { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.dir-card__tag { - font-size: 11px; - font-weight: 500; - color: #57534E; - background: #F5F4F1; - border-radius: 9999px; - padding: 3px 10px; + color: #64748b; } .dir-card__divider { height: 1px; - background: #F0EEE9; + background: #f1f5f9; + margin: 0 -20px; } -.dir-card__metric { - flex: 1; +.dir-card__details { display: flex; flex-direction: column; - gap: 2px; - text-align: center; + gap: 8px; + font-size: 11.5px; + color: #475569; } -.dir-card__metric-label { +.dir-card__detail-item { + display: flex; + align-items: center; + gap: 8px; +} + +.dir-card__detail-label { + font-family: 'SF Mono', 'Fira Code', monospace; + font-weight: 700; + color: #64748b; + background: #f1f5f9; + padding: 2px 6px; + border-radius: 4px; font-size: 9.5px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.1em; - color: #A8A29E; + letter-spacing: 0.02em; } -.dir-card__metric-value { - font-size: 17px; - font-variant-numeric: tabular-nums; +.dir-card__detail-icon { + color: #94a3b8; + flex-shrink: 0; } -.dir-card__metric-sep { - width: 1px; - height: 36px; - background: #F0EEE9; +.dir-card__detail-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.dir-card__cta { - width: 100%; - padding: 11px 0; - border-radius: 12px; - border: 1.5px solid #0F0D0A; +.dir-card__footer { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 11px; +} + +.dir-card__joined { + color: #94a3b8; +} + +.dir-card__btn-details { background: transparent; - color: #0F0D0A; - font-size: 12px; + border: none; + color: #0284c7; font-weight: 700; - letter-spacing: 0.04em; cursor: pointer; - justify-content: center; - gap: 7px; - margin-top: auto; + transition: color 0.15s ease; } -.dir-card__cta:hover { - background: #0F0D0A; - color: #FFF; -} - -/* Estados Vacíos y Toasts */ -.dir-empty { - grid-column: 1 / -1; - flex-direction: column; - justify-content: center; - gap: 14px; - padding: 60px 20px; - text-align: center; - color: #A8A29E; -} - -.dir-empty p { - font-size: 14px; - margin: 0; -} - -.dir-empty button { - padding: 8px 20px; - border-radius: 10px; - background: transparent; - font-size: 13px; - font-weight: 600; - color: #78716C; - cursor: pointer; - font-family: inherit; -} - -.dir-empty button:hover { - border-color: #C9973C; - color: #C9973C; +.dir-card__btn-details:hover { + color: #0369a1; + text-decoration: underline; } +/* Toast alert */ .dir-toast { position: fixed; bottom: 28px; right: 28px; - background: #FFF; - border: 1px solid #D1FAE5; - box-shadow: 0 8px 24px rgba(15, 13, 10, 0.1); - border-radius: 14px; - padding: 13px 20px; - font-size: 13px; + background: #0f172a; + color: #ffffff; + border-radius: 12px; + padding: 12px 20px; + font-size: 12px; font-weight: 600; - color: #065F46; - gap: 9px; - z-index: 999; - animation: slide-toast 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + display: flex; + align-items: center; + gap: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + z-index: 1000; + animation: slideUp 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } -@keyframes slide-toast { +@keyframes slideUp { from { opacity: 0; - transform: translateY(12px) + transform: translateY(12px); } - to { opacity: 1; - transform: translateY(0) + transform: translateY(0); } } -/* Media Queries Responsivas */ -@media (max-width: 900px) { - .dir-sidebar { - width: 64px; - } +/* Empty search state */ +.dir-empty { + grid-column: 1 / -1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: 60px 20px; + text-align: center; + color: #64748b; + background: #ffffff; + border-radius: 16px; + border: 1px solid #e2e8f0; +} - .dir-sidebar__brand-name, - .dir-sidebar__section-label, - .dir-sidebar__nav-label, - .dir-sidebar__user-info, - .dir-sidebar__status, - .dir-sidebar__toggle { +.dir-empty svg { + color: #94a3b8; +} + +.dir-empty__text { + font-size: 14px; + margin: 0; +} + +.dir-empty__btn { + background: #ffffff; + border: 1px solid #cbd5e1; + color: #475569; + font-size: 12px; + font-weight: 600; + padding: 8px 16px; + border-radius: 10px; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease; +} + +.dir-empty__btn:hover { + background: #f8fafc; + border-color: #94a3b8; +} + +/* Responsiveness adjustments */ +@media (max-width: 900px) { + .dir-topbar__menu { display: none; } - - .dir-sidebar__user { - justify-content: center; - } - + .dir-main { - padding: 24px 18px; + padding: 20px; } } @media (max-width: 600px) { - .dir-sidebar { - display: none; - } - - .dir-header { + .dir-content-header { flex-direction: column; align-items: flex-start; } - - .dir-main { - padding: 20px 14px; + + .dir-filters-bar { + padding: 12px; } - + + .dir-search-input { + width: 100%; + } + .dir-grid { grid-template-columns: 1fr; } diff --git a/frontend/src/app/features/directorio/directorio.component.ts b/frontend/src/app/features/directorio/directorio.component.ts index 67e6e1b..eead5c8 100644 --- a/frontend/src/app/features/directorio/directorio.component.ts +++ b/frontend/src/app/features/directorio/directorio.component.ts @@ -10,12 +10,17 @@ interface Employee { initials: string; role: string; department: string; + contractType: string; rating: number; clients: number; rate: number; tags: string[]; email: string; + phone: string; + joinedDate: string; avatarColor: string; + avatarTextColor: string; + status: string; } @Component({ @@ -24,187 +29,426 @@ interface Employee { imports: [CommonModule, FormsModule, RouterModule], template: `
+ + + - +
- - -
-
-

Directorio de Personal

-
- - Sincronizado con Active Directory + + +
+
+

Employee

+
+ ● Active 28 + ● Inactive 4
- {{ filteredEmployees().length }} empleados -
- - -
- - - - -
- - - - -
- - - @if (selectedDept !== 'ALL' || searchQuery) { - - } + + +
- -

- Mostrando {{ filteredEmployees().length }} de {{ employees().length }} empleados registrados -

+ +
+
+
+ + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ + +
+
+
@for (emp of filteredEmployees(); track emp.id) {
- - -
{{ emp.adId }}
- - -
-
- {{ emp.initials }} -
-
- - - - {{ emp.rating }} -
+ +
+ + {{ emp.status }} + +
- -
+ +
+
+
+ {{ emp.initials }} +
+

{{ emp.name }}

{{ emp.role }} - {{ emp.department }} -
- - -
- @for (tag of emp.tags; track tag) { - {{ tag }} - }
- -
-
- Clientes - {{ emp.clients }} + +
+
+ #{{ emp.adId }}
-
-
- Tarifa/Hora - \${{ emp.rate }} + +
+ + + + + {{ emp.department }} - {{ emp.contractType }} +
+ +
+ + + + + {{ emp.email }} +
+ +
+ + + + {{ emp.phone }}
- - + +
+ +
} - - - @if (filteredEmployees().length === 0) { -
- - - - -

Sin resultados para "{{ searchQuery }}"

- -
- }
- -
+ + @if (filteredEmployees().length === 0) { +
+ + + + +

No se encontraron resultados para "{{ searchQuery }}"

+ +
+ } - - @if (contactToast()) { -
- - - - Mensaje enviado a {{ contactToast() }} -
- } + + + + @if (contactToast()) { +
+ + + + Sincronizando canal AD para {{ contactToast() }}... +
+ } + +
`, styleUrls: ['./directorio.component.css'] }) export class DirectorioComponent implements OnInit { - readonly router = inject(Router); - contactToast = signal(''); - searchQuery = ''; - selectedDept = 'ALL'; private _searchSignal = signal(''); - private _deptSignal = signal('ALL'); - employees = signal([]); + employees = signal([ + { + id: 1, + adId: 'EMP01', + name: 'Bagus Fikri', + initials: 'BF', + role: 'CEO', + department: 'Managerial', + contractType: 'Fulltime', + rating: 4.9, + clients: 85, + rate: 150, + tags: ['Leadership', 'Strategy', 'Active Directory'], + email: 'bagusfikri@gmail.com', + phone: '+62 123 456 789', + joinedDate: '29 Oct, 2020', + avatarColor: '#DEF7EC', + avatarTextColor: '#03543F', + status: 'Active' + }, + { + id: 2, + adId: 'EMP02', + name: 'Indrapm', + initials: 'IP', + role: 'Illustrator', + department: 'Design', + contractType: 'Fulltime', + rating: 4.8, + clients: 64, + rate: 65, + tags: ['Illustration', 'Vector', 'Figma'], + email: 'indrapm@gmail.com', + phone: '+62 765 432 109', + joinedDate: '1 Feb, 2019', + avatarColor: '#E1EFFE', + avatarTextColor: '#1E429F', + status: 'Active' + }, + { + id: 3, + adId: 'EMP03', + name: 'Mufti Hidayat', + initials: 'MH', + role: 'Project Manager', + department: 'Managerial', + contractType: 'Fulltime', + rating: 4.7, + clients: 42, + rate: 90, + tags: ['Agile', 'Scrum', 'Planning'], + email: 'mufti@gmail.com', + phone: '+62 112 233 445', + joinedDate: '15 Feb, 2022', + avatarColor: '#FDF2F2', + avatarTextColor: '#9B1C1C', + status: 'Active' + }, + { + id: 4, + adId: 'EMP04', + name: 'Fauzan Arditianyah', + initials: 'FA', + role: 'UI Designer', + department: 'Design', + contractType: 'Fulltime', + rating: 4.9, + clients: 75, + rate: 80, + tags: ['UI Design', 'Wireframing', 'Prototyping'], + email: 'fauzan@gmail.com', + phone: '+62 998 877 665', + joinedDate: '22 Sep, 2018', + avatarColor: '#F0F5FF', + avatarTextColor: '#5850EC', + status: 'Active' + }, + { + id: 5, + adId: 'EMP05', + name: 'Raihan Fikri', + initials: 'RF', + role: 'QC & Research', + department: 'Human Resources', + contractType: 'Fulltime', + rating: 4.6, + clients: 20, + rate: 70, + tags: ['Quality', 'Testing', 'Research'], + email: 'raihan@gmail.com', + phone: '+62 555 666 777', + joinedDate: '10 Oct, 2020', + avatarColor: '#EDEBFE', + avatarTextColor: '#5521B5', + status: 'Invited' + }, + { + id: 6, + adId: 'EMP06', + name: 'Panji Dwi', + initials: 'PD', + role: 'UI Designer', + department: 'Design', + contractType: 'Fulltime', + rating: 4.8, + clients: 58, + rate: 75, + tags: ['UI/UX', 'Figma', 'Responsive'], + email: 'panji@gmail.com', + phone: '+62 333 444 555', + joinedDate: '5 May, 2021', + avatarColor: '#E3F2FD', + avatarTextColor: '#0D47A1', + status: 'Invited' + }, + { + id: 7, + adId: 'EMP07', + name: 'Bagas', + initials: 'B', + role: 'UI Designer', + department: 'Design', + contractType: 'Fulltime', + rating: 4.8, + clients: 60, + rate: 75, + tags: ['UI Design', 'Design Systems'], + email: 'bagas@gmail.com', + phone: '+62 888 999 000', + joinedDate: '12 Aug, 2022', + avatarColor: '#FCE8E6', + avatarTextColor: '#C53030', + status: 'Active' + }, + { + id: 8, + adId: 'EMP08', + name: 'Luqito Raymorley', + initials: 'LR', + role: 'Project Manager', + department: 'Administration', + contractType: 'Fulltime', + rating: 4.9, + clients: 90, + rate: 110, + tags: ['Management', 'Delivery', 'Budgets'], + email: 'luqito@gmail.com', + phone: '+62 444 333 222', + joinedDate: '30 Nov, 2017', + avatarColor: '#E8F5E9', + avatarTextColor: '#1B5E20', + status: 'Active' + } + ]); filteredEmployees = computed(() => { const q = this._searchSignal().toLowerCase().trim(); - const dept = this._deptSignal(); return this.employees().filter(emp => { - const matchesSearch = !q || + return !q || emp.name.toLowerCase().includes(q) || emp.role.toLowerCase().includes(q) || - emp.tags.some(t => t.toLowerCase().includes(q)); - - const matchesDept = dept === 'ALL' || - emp.department === dept; - - return matchesSearch && matchesDept; + emp.department.toLowerCase().includes(q) || + emp.adId.toLowerCase().includes(q); }); }); @@ -214,10 +458,6 @@ export class DirectorioComponent implements OnInit { this._searchSignal.set(value); } - onDeptChange(value: string): void { - this._deptSignal.set(value); - } - clearSearch(): void { this.searchQuery = ''; this._searchSignal.set(''); @@ -225,9 +465,7 @@ export class DirectorioComponent implements OnInit { resetFilters(): void { this.searchQuery = ''; - this.selectedDept = 'ALL'; this._searchSignal.set(''); - this._deptSignal.set('ALL'); } contact(emp: Employee): void { diff --git a/frontend/src/app/features/info-hub/info-hub.component.ts b/frontend/src/app/features/info-hub/info-hub.component.ts new file mode 100644 index 0000000..5d4967c --- /dev/null +++ b/frontend/src/app/features/info-hub/info-hub.component.ts @@ -0,0 +1,344 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ContentService, Article } from '../../core/services/content.service'; + +@Component({ + selector: 'app-info-hub', + standalone: true, + imports: [CommonModule, FormsModule], + template: ` +
+ + +
+
+

Info Hub

+

Información corporativa centralizada e integración de redes sociales

+
+
+ + + Canal Interno Activo + +
+
+ + +
+ + +
+
+

Comunicados y Publicaciones Recientes

+ {{ contentService.articles().length }} artículos publicados +
+ + +
+ @for (art of contentService.articles(); track art.id) { + +
+ + +
+ + {{ art.category }} + + {{ art.date }} +
+ + +
+

{{ art.title }}

+

{{ art.body }}

+
+ + +
+ Publicado en: + @for (t of art.shareTargets; track t) { + + {{ t }} + + } +
+ +
+ + +
+
+ + + + +
+ + +
+ + + @if (commentsExpanded(art.id)) { +
+

Comentarios ({{ art.comments.length }})

+ +
+ @for (com of art.comments; track com) { +
+ Colaborador + {{ com }} +
+ } +
+ + +
+ + +
+
+ } + +
+ } +
+
+ + +
+

Redes Sociales & Impacto

+ + +
+
+
+ + Métricas Globales +
+
+

12,450

+ Impresiones Totales +
+ +24% este mes +
+ +
+ +
+
+ LinkedIn + 8.4k reach +
+
+ X (Twitter) + 4.0k reach +
+
+
+ + +
+
+

Alcance de Contenidos

+ KPI Activo +
+ +

Visualización del crecimiento e impacto de comunicados internos.

+ + +
+ + + + + + + + + + + + + + + + + + +
+ +
+ Semana 1 + Semana 2 + Semana 3 + Semana 4 +
+
+ + +
+

Canales de Redes Sociales

+ +
+
+
+ Campañas en LinkedIn + Automatización de posts habilitada +
+ +
+ +
+
+ API Integrada de X + Publicación inmediata activa +
+ +
+
+
+ +
+
+ + + @if (activeShareModal()) { +
+
+
+

Compartir Publicación

+ +
+ +

Selecciona los destinos de red social donde deseas propagar este artículo corporativo:

+ +
+ {{ activeShareModal()?.title }} +

{{ activeShareModal()?.body }}

+
+ + +
+ + + +
+ + +
+ + +
+
+
+ } + +
+ `, + styles: [` + .animate-fadeIn { + animation: fadeIn 0.35s ease-out forwards; + } + .animate-scaleUp { + animation: scaleUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; + } + @keyframes fadeIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } + } + @keyframes scaleUp { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } + } + `] +}) +export class InfoHubComponent { + contentService = inject(ContentService); + + // Expanded comments indices map + expandedComments = signal<{ [key: number]: boolean }>({}); + newComments: { [key: number]: string } = {}; + + // Share modal state + activeShareModal = signal
(null); + shareLinkedIn = true; + shareX = false; + shareIntranet = true; + + toggleComments(id: number): void { + this.expandedComments.update(prev => ({ + ...prev, + [id]: !prev[id] + })); + } + + commentsExpanded(id: number): boolean { + return !!this.expandedComments()[id]; + } + + submitComment(id: number): void { + const com = this.newComments[id]?.trim(); + if (com) { + this.contentService.addComment(id, com); + this.newComments[id] = ''; + } + } + + openShareModal(art: Article): void { + this.activeShareModal.set(art); + // Reset check state + this.shareLinkedIn = art.shareTargets.includes('LinkedIn'); + this.shareX = art.shareTargets.includes('X'); + this.shareIntranet = art.shareTargets.includes('Intranet'); + } + + closeShareModal(): void { + this.activeShareModal.set(null); + } + + confirmShare(): void { + const art = this.activeShareModal(); + if (art) { + this.contentService.shareArticle(art.id); + + // Update targets list dynamically + const newTargets: string[] = []; + if (this.shareLinkedIn) newTargets.push('LinkedIn'); + if (this.shareX) newTargets.push('X'); + if (this.shareIntranet) newTargets.push('Intranet'); + + art.shareTargets = newTargets; + + this.closeShareModal(); + } + } +} diff --git a/frontend/src/app/features/workflows/workflows.component.ts b/frontend/src/app/features/workflows/workflows.component.ts new file mode 100644 index 0000000..4df65ce --- /dev/null +++ b/frontend/src/app/features/workflows/workflows.component.ts @@ -0,0 +1,354 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ContentService, Article } from '../../core/services/content.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-workflows', + standalone: true, + imports: [CommonModule, FormsModule], + template: ` +
+ +
+
+
+ + +
+
+

+ + + + + + + + + Workflows de Aprobación +

+

Motor de reglas multinivel para aprobación y publicación de contenidos

+
+
+ + + Motor AD: Conectado + +
+
+ + +
+ + +
+

+ Agregar Nueva Propuesta +

+ +
+ +
+ + +
+ + +
+ +
+ +
+
+
+ + +
+ + +
+ + +
+ +
+ + + + + +
+
+ + + + + @if (formError()) { +
{{ formError() }}
+ } + @if (formSuccess()) { +
¡Propuesta enviada con éxito al nivel de revisión 1!
+ } +
+ + +
+
+

Visualización del Motor AD

+ Servicio Activo +
+ +

Visualización técnica del flujo de validación del Active Directory para jerarquías organizacionales.

+ + +
+ + + + + + + + AD ENGINE + +
+ +
+
+ Servidor AD + LDAP SECURE +
+
+ Latencia + 1.2ms +
+
+
+
+ + +
+
+

+ 📋 Cola de Aprobaciones Pendientes +

+ {{ contentService.pending().length }} solicitudes activas +
+ + +
+ @for (art of contentService.pending(); track art.id) { +
+ + +
+
+ + +
+
+ + 👤 Autor: {{ art.author }} · {{ art.date }} + +

{{ art.title }}

+
+ + + + {{ art.approvalLevel === 1 ? 'Nivel 1: Revisión Técnica' : 'Nivel 2: Autorización RR.HH.' }} + +
+ + +

{{ art.body }}

+ + +
+ Destinos Objetivo: + @for (t of art.shareTargets; track t) { + {{ t }} + } +
+ + +
+ + +
+ +
+ } + + @if (contentService.pending().length === 0) { +
+
+ 🎉 +
+
+

¡Cola de Solicitudes al Día!

+

No hay propuestas pendientes para revisar en el AD Engine en este momento.

+
+
+ } +
+ + +
+

+ 📰 Artículos Publicados Recientemente +

+ +
+ @for (art of contentService.articles().slice(0, 2); track art.id) { +
+
+ {{ art.category }} + Publicado +
+

{{ art.title }}

+

{{ art.body }}

+ +
+ +
+ +
+
+ } +
+
+
+ +
+
+ `, + styles: [` + .animate-fadeIn { + animation: fadeIn 0.35s ease-out forwards; + } + .animate-spin-slow { + animation: spin 16s linear infinite; + } + .animate-spin-medium { + animation: spin 10s linear infinite; + } + .animate-spin-reverse { + animation: spin-back 8s linear infinite; + } + @keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + @keyframes spin-back { + from { transform: rotate(360deg); } + to { transform: rotate(0deg); } + } + `] +}) +export class WorkflowsComponent { + contentService = inject(ContentService); + router = inject(Router); + + // Form states + newTitle = ''; + newCategory = 'Tecnología'; + newBody = ''; + targetIntranet = true; + targetLinkedIn = false; + targetX = false; + + formError = signal(''); + formSuccess = signal(false); + + submitProposal(): void { + const t = this.newTitle.trim(); + const b = this.newBody.trim(); + if (!t || !b) { + this.formError.set('Por favor, completa el título y el cuerpo del comunicado.'); + setTimeout(() => this.formError.set(''), 3000); + return; + } + + const targets: string[] = []; + if (this.targetIntranet) targets.push('Intranet'); + if (this.targetLinkedIn) targets.push('LinkedIn'); + if (this.targetX) targets.push('X'); + + if (targets.length === 0) { + this.formError.set('Debes seleccionar al menos un canal de publicación.'); + setTimeout(() => this.formError.set(''), 3000); + return; + } + + this.contentService.addPending(t, this.newCategory, b, 'Jess Miller', targets); + + // Clear form + this.newTitle = ''; + this.newBody = ''; + this.targetIntranet = true; + this.targetLinkedIn = false; + this.targetX = false; + + this.formSuccess.set(true); + setTimeout(() => this.formSuccess.set(false), 3000); + } + + approveItem(id: number): void { + this.contentService.advanceApproval(id); + } +}