Ylopez/anycoder-5bca639a
0
1<!DOCTYPE html>2<html lang="es">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Gestión de Talento Corporativo</title>7 <!-- Importar iconos modernos (RemixIcon) -->8 <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">9 10 <style>11 /* =========================================12 VARIABLES Y RESET CSS13 ========================================= */14 :root {15 /* Paleta Corporativa Profesional */16 --primary-color: #0f172a; /* Dark Slate */17 --secondary-color: #334155; /* Slate Gray */18 --accent-color: #2563eb; /* Royal Blue */19 --accent-hover: #1d4ed8;20 --success-color: #10b981;21 --danger-color: #ef4444;22 --warning-color: #f59e0b;23 24 /* Fondos y Textos */25 --bg-body: #f1f5f9;26 --bg-surface: #ffffff;27 --text-main: #1e293b;28 --text-muted: #64748b;29 --border-color: #e2e8f0;30 31 /* Espaciado y Radios */32 --radius-sm: 4px;33 --radius-md: 8px;34 --radius-lg: 12px;35 --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);36 --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);37 --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);38 39 --font-family: 'Inter', system-ui, -apple-system, sans-serif;40 --transition: all 0.2s ease-in-out;41 }42 43 * {44 box-sizing: border-box;45 margin: 0;46 padding: 0;47 }48 49 body {50 font-family: var(--font-family);51 background-color: var(--bg-body);52 color: var(--text-main);53 height: 100vh;54 display: flex;55 overflow: hidden; /* Evita scroll en el body, usamos scroll interno */56 }57 58 /* =========================================59 LAYOUT PRINCIPAL60 ========================================= */61 62 /* Sidebar de Navegación */63 .sidebar {64 width: 260px;65 background-color: var(--primary-color);66 color: white;67 display: flex;68 flex-direction: column;69 padding: 1.5rem;70 transition: transform 0.3s ease;71 z-index: 100;72 }73 74 .brand {75 font-size: 1.25rem;76 font-weight: 700;77 letter-spacing: 0.5px;78 margin-bottom: 2.5rem;79 display: flex;80 align-items: center;81 gap: 10px;82 }83 84 .brand i {85 color: var(--accent-color);86 font-size: 1.5rem;87 }88 89 .nav-menu {90 list-style: none;91 flex: 1;92 }93 94 .nav-item {95 margin-bottom: 0.5rem;96 }97 98 .nav-link {99 display: flex;100 align-items: center;101 gap: 12px;102 padding: 0.75rem 1rem;103 color: #94a3b8;104 text-decoration: none;105 border-radius: var(--radius-md);106 transition: var(--transition);107 font-weight: 500;108 }109 110 .nav-link:hover, .nav-link.active {111 background-color: rgba(255,255,255, 0.1);112 color: white;113 }114 115 .nav-link i {116 font-size: 1.2rem;117 }118 119 /* Área Principal de Contenido */120 .main-content {121 flex: 1;122 display: flex;123 flex-direction: column;124 overflow: hidden;125 position: relative;126 }127 128 /* Header Superior */129 header {130 background-color: var(--bg-surface);131 border-bottom: 1px solid var(--border-color);132 padding: 1rem 2rem;133 display: flex;134 justify-content: space-between;135 align-items: center;136 height: 70px;137 }138 139 .header-title h2 {140 font-size: 1.25rem;141 font-weight: 600;142 }143 144 .header-actions {145 display: flex;146 align-items: center;147 gap: 1rem;148 }149 150 .anycoder-link {151 font-size: 0.85rem;152 color: var(--text-muted);153 text-decoration: none;154 border: 1px solid var(--border-color);155 padding: 0.4rem 0.8rem;156 border-radius: 20px;157 transition: var(--transition);158 }159 160 .anycoder-link:hover {161 border-color: var(--accent-color);162 color: var(--accent-color);163 }164 165 /* Contenedor Scrollable */166 .content-scroll {167 padding: 2rem;168 overflow-y: auto;169 flex: 1;170 }171 172 /* =========================================173 COMPONENTES UI174 ========================================= */175 176 /* Tarjetas de Estadísticas (Dashboard) */177 .stats-grid {178 display: grid;179 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));180 gap: 1.5rem;181 margin-bottom: 2rem;182 }183 184 .stat-card {185 background: var(--bg-surface);186 padding: 1.5rem;187 border-radius: var(--radius-lg);188 box-shadow: var(--shadow-sm);189 border: 1px solid var(--border-color);190 display: flex;191 align-items: center;192 justify-content: space-between;193 }194 195 .stat-info h3 {196 font-size: 2rem;197 font-weight: 700;198 color: var(--secondary-color);199 }200 201 .stat-info p {202 color: var(--text-muted);203 font-size: 0.9rem;204 }205 206 .stat-icon {207 width: 48px;208 height: 48px;209 border-radius: 50%;210 background-color: #eff6ff;211 color: var(--accent-color);212 display: flex;213 align-items: center;214 justify-content: center;215 font-size: 1.5rem;216 }217 218 /* Barra de Herramientas */219 .toolbar {220 display: flex;221 justify-content: space-between;222 align-items: center;223 margin-bottom: 1.5rem;224 flex-wrap: wrap;225 gap: 1rem;226 }227 228 .search-wrapper {229 position: relative;230 width: 300px;231 }232 233 .search-wrapper i {234 position: absolute;235 left: 12px;236 top: 50%;237 transform: translateY(-50%);238 color: var(--text-muted);239 }240 241 .search-input {242 width: 100%;243 padding: 0.6rem 1rem 0.6rem 2.5rem;244 border: 1px solid var(--border-color);245 border-radius: var(--radius-md);246 font-family: inherit;247 outline: none;248 transition: var(--transition);249 }250 251 .search-input:focus {252 border-color: var(--accent-color);253 box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);254 }255 256 .btn {257 display: inline-flex;258 align-items: center;259 gap: 8px;260 padding: 0.6rem 1.2rem;261 border-radius: var(--radius-md);262 border: none;263 font-weight: 500;264 cursor: pointer;265 transition: var(--transition);266 font-family: inherit;267 }268 269 .btn-primary {270 background-color: var(--accent-color);271 color: white;272 }273 274 .btn-primary:hover {275 background-color: var(--accent-hover);276 transform: translateY(-1px);277 }278 279 .btn-icon {280 padding: 0.4rem;281 border-radius: 4px;282 background: transparent;283 color: var(--text-muted);284 }285 286 .btn-icon:hover {287 background: var(--bg-body);288 color: var(--text-main);289 }290 291 .btn-icon.delete:hover {292 background: #fee2e2;293 color: var(--danger-color);294 }295 296 /* Tabla de Datos */297 .table-container {298 background: var(--bg-surface);299 border-radius: var(--radius-lg);300 box-shadow: var(--shadow-sm);301 border: 1px solid var(--border-color);302 overflow: hidden; /* Para bordes redondeados */303 overflow-x: auto; /* Scroll horizontal en móviles */304 }305 306 table {307 width: 100%;308 border-collapse: collapse;309 text-align: left;310 }311 312 thead {313 background-color: #f8fafc;314 border-bottom: 1px solid var(--border-color);315 }316 317 th {318 padding: 1rem 1.5rem;319 font-weight: 600;320 color: var(--text-muted);321 font-size: 0.85rem;322 text-transform: uppercase;323 letter-spacing: 0.05em;324 }325 326 td {327 padding: 1rem 1.5rem;328 border-bottom: 1px solid var(--border-color);329 color: var(--text-main);330 vertical-align: middle;331 }332 333 tr:last-child td {334 border-bottom: none;335 }336 337 tr:hover td {338 background-color: #f8fafc;339 }340 341 /* Avatar y Información de Usuario */342 .user-cell {343 display: flex;344 align-items: center;345 gap: 12px;346 }347 348 .avatar {349 width: 40px;350 height: 40px;351 border-radius: 50%;352 object-fit: cover;353 background-color: var(--border-color);354 display: flex;355 align-items: center;356 justify-content: center;357 font-weight: 600;358 color: var(--text-muted);359 font-size: 0.9rem;360 }361 362 .user-info div:first-child {363 font-weight: 500;364 color: var(--text-main);365 }366 367 .user-info div:last-child {368 font-size: 0.85rem;369 color: var(--text-muted);370 }371 372 /* Badges de Estado */373 .status-badge {374 display: inline-flex;375 align-items: center;376 padding: 0.25rem 0.75rem;377 border-radius: 9999px;378 font-size: 0.75rem;379 font-weight: 600;380 }381 382 .status-active {383 background-color: #dcfce7;384 color: #166534;385 }386 387 .status-inactive {388 background-color: #f1f5f9;389 color: #475569;390 }391 392 .status-vacation {393 background-color: #ffedd5;394 color: #9a3412;395 }396 397 /* =========================================398 MODAL Y FORMULARIO399 ========================================= */400 .modal-overlay {401 position: fixed;402 top: 0;403 left: 0;404 width: 100%;405 height: 100%;406 background: rgba(15, 23, 42, 0.6);407 backdrop-filter: blur(4px);408 z-index: 1000;409 display: flex;410 justify-content: center;411 align-items: center;412 opacity: 0;413 visibility: hidden;414 transition: var(--transition);415 }416 417 .modal-overlay.open {418 opacity: 1;419 visibility: visible;420 }421 422 .modal {423 background: var(--bg-surface);424 width: 100%;425 max-width: 500px;426 border-radius: var(--radius-lg);427 box-shadow: var(--shadow-lg);428 transform: translateY(20px);429 transition: var(--transition);430 display: flex;431 flex-direction: column;432 }433 434 .modal-overlay.open .modal {435 transform: translateY(0);436 }437 438 .modal-header {439 padding: 1.5rem;440 border-bottom: 1px solid var(--border-color);441 display: flex;442 justify-content: space-between;443 align-items: center;444 }445 446 .modal-header h3 {447 font-size: 1.25rem;448 color: var(--text-main);449 }450 451 .close-modal {452 background: none;453 border: none;454 font-size: 1.5rem;455 color: var(--text-muted);456 cursor: pointer;457 }458 459 .modal-body {460 padding: 1.5rem;461 }462 463 .form-group {464 margin-bottom: 1.25rem;465 }466 467 .form-label {468 display: block;469 margin-bottom: 0.5rem;470 font-size: 0.9rem;471 font-weight: 500;472 color: var(--text-main);473 }474 475 .form-control {476 width: 100%;477 padding: 0.75rem;478 border: 1px solid var(--border-color);479 border-radius: var(--radius-md);480 font-family: inherit;481 font-size: 0.95rem;482 transition: var(--transition);483 }484 485 .form-control:focus {486 outline: none;487 border-color: var(--accent-color);488 box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);489 }490 491 .modal-footer {492 padding: 1rem 1.5rem;493 border-top: 1px solid var(--border-color);494 display: flex;495 justify-content: flex-end;496 gap: 1rem;497 background-color: #f8fafc;498 border-radius: 0 0 var(--radius-lg) var(--radius-lg);499 }500 501 .btn-secondary {502 background: white;503 border: 1px solid var(--border-color);504 color: var(--text-main);505 }506 507 .btn-secondary:hover {508 background-color: #f1f5f9;509 }510 511 /* =========================================512 NOTIFICACIONES (TOAST)513 ========================================= */514 .toast-container {515 position: fixed;516 bottom: 2rem;517 right: 2rem;518 z-index: 2000;519 display: flex;520 flex-direction: column;521 gap: 0.75rem;522 }523 524 .toast {525 background: var(--bg-surface);526 padding: 1rem 1.5rem;527 border-radius: var(--radius-md);528 box-shadow: var(--shadow-lg);529 border-left: 4px solid var(--accent-color);530 display: flex;531 align-items: center;532 gap: 12px;533 min-width: 300px;534 animation: slideIn 0.3s ease forwards;535 }536 537 .toast.success { border-left-color: var(--success-color); }538 .toast.error { border-left-color: var(--danger-color); }539 540 .toast i { font-size: 1.25rem; }541 .toast.success i { color: var(--success-color); }542 .toast.error i { color: var(--danger-color); }543 544 @keyframes slideIn {545 from { transform: translateX(100%); opacity: 0; }546 to { transform: translateX(0); opacity: 1; }547 }548 549 @keyframes fadeOut {550 to { opacity: 0; transform: translateY(10px); }551 }552 553 /* =========================================554 RESPONSIVE555 ========================================= */556 @media (max-width: 768px) {557 .sidebar {558 position: fixed;559 left: -260px;560 height: 100%;561 box-shadow: var(--shadow-lg);562 }563 564 .sidebar.active {565 left: 0;566 }567 568 .menu-toggle {569 display: block !important;570 margin-right: 1rem;571 font-size: 1.5rem;572 cursor: pointer;573 color: var(--text-main);574 }575 576 .header-title h2 {577 font-size: 1rem;578 }579 580 .stats-grid {581 grid-template-columns: 1fr;582 }583 }584 585 @media (min-width: 769px) {586 .menu-toggle {587 display: none;588 }589 }590 </style>591</head>592<body>593 594 <!-- SIDEBAR -->595 <aside class="sidebar" id="sidebar">596 <div class="brand">597 <i class="ri-building-4-line"></i>598 <span>NEXUS CORP</span>599 </div>600 <ul class="nav-menu">601 <li class="nav-item">602 <a href="#" class="nav-link">603 <i class="ri-dashboard-line"></i> Dashboard604 </a>605 </li>606 <li class="nav-item">607 <a href="#" class="nav-link active">608 <i class="ri-team-line"></i> Empleados609 </a>610 </li>611 <li class="nav-item">612 <a href="#" class="nav-link">613 <i class="ri-briefcase-line"></i> Departamentos614 </a>615 </li>616 <li class="nav-item">617 <a href="#" class="nav-link">618 <i class="ri-settings-3-line"></i> Configuración619 </a>620 </li>621 </ul>622 <div style="margin-top: auto; font-size: 0.8rem; color: #64748b;">623 v2.4.0 Enterprise624 </div>625 </aside>626 627 <!-- MAIN CONTENT -->628 <main class="main-content">629 <header>630 <div style="display: flex; align-items: center;">631 <i class="ri-menu-line menu-toggle" id="menuToggle"></i>632 <div class="header-title">633 <h2>Gestión de Personal</h2>634 </div>635 </div>636 <div class="header-actions">637 <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">638 Built with anycoder639 </a>640 <div class="avatar" style="background: var(--accent-color); color: white; width: 35px; height: 35px; font-size: 0.8rem;">641 HR642 </div>643 </div>644 </header>645 646 <div class="content-scroll">647 <!-- Estadísticas Rápidas -->648 <div class="stats-grid">649 <div class="stat-card">650 <div class="stat-info">651 <p>Total Empleados</p>652 <h3 id="statTotal">0</h3>653 </div>654 <div class="stat-icon">655 <i class="ri-group-line"></i>656 </div>657 </div>658 <div class="stat-card">659 <div class="stat-info">660 <p>Activos</p>661 <h3 id="statActive">0</h3>662 </div>663 <div class="stat-icon" style="color: var(--success-color); background: #dcfce7;">664 <i class="ri-user-follow-line"></i>665 </div>666 </div>667 <div class="stat-card">668 <div class="stat-info">669 <p>Departamentos</p>670 <h3>4</h3>671 </div>672 <div class="stat-icon" style="color: var(--warning-color); background: #ffedd5;">673 <i class="ri-building-line"></i>674 </div>675 </div>676 </div>677 678 <!-- Barra de herramientas -->679 <div class="toolbar">680 <div class="search-wrapper">681 <i class="ri-search-line"></i>682 <input type="text" id="searchInput" class="search-input" placeholder="Buscar por nombre, cargo o email...">683 </div>684 <button class="btn btn-primary" id="addBtn">685 <i class="ri-add-line"></i> Nuevo Empleado686 </button>687 </div>688 689 <!-- Tabla de Datos -->690 <div class="table-container">691 <table>692 <thead>693 <tr>694 <th>Empleado</th>695 <th>Cargo</th>696 <th>Departamento</th>697 <th>Estado</th>698 <th style="text-align: right;">Acciones</th>699 </tr>700 </thead>701 <tbody id="employeeTableBody">702 <!-- Las filas se generarán con JS -->703 </tbody>704 </table>705 <!-- Estado vacío -->706 <div id="emptyState" style="display: none; padding: 3rem; text-align: center; color: var(--text-muted);">707 <i class="ri-inbox-line" style="font-size: 3rem; margin-bottom: 1rem; display: block;"></i>708 <p>No se encontraron empleados.</p>709 </div>710 </div>711 </div>712 </main>713 714 <!-- MODAL FORMULARIO -->715 <div class="modal-overlay" id="modalOverlay">716 <div class="modal">717 <div class="modal-header">718 <h3 id="modalTitle">Añadir Empleado</h3>719 <button class="close-modal" id="closeModal"><i class="ri-close-line"></i></button>720 </div>721 <div class="modal-body">722 <form id="employeeForm">723 <input type="hidden" id="employeeId">724 <div class="form-group">725 <label class="form-label">Nombre Completo</label>726 <input type="text" class="form-control" id="fullName" required placeholder="Ej. Ana García">727 </div>728 <div class="form-group">729 <label class="form-label">Correo Electrónico</label>730 <input type="email" class="form-control" id="email" required placeholder="Ej. ana.garcia@nexus.com">731 </div>732 <div class="form-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">733 <div>734 <label class="form-label">Cargo</label>735 <input type="text" class="form-control" id="role" required placeholder="Ej. Desarrollador">736 </div>737 <div>738 <label class="form-label">Departamento</label>739 <select class="form-control" id="department" required>740 <option value="Tecnología">Tecnología</option>741 <option value="Recursos Humanos">Recursos Humanos</option>742 <option value="Ventas">Ventas</option>743 <option value="Marketing">Marketing</option>744 </select>745 </div>746 </div>747 <div class="form-group">748 <label class="form-label">Estado</label>749 <select class="form-control" id="status">750 <option value="Active">Activo</option>751 <option value="Inactive">Inactivo</option>752 <option value="Vacation">Vacaciones</option>753 </select>754 </div>755 </form>756 </div>757 <div class="modal-footer">758 <button class="btn btn-secondary" id="cancelBtn">Cancelar</button>759 <button class="btn btn-primary" id="saveBtn">Guardar Datos</button>760 </div>761 </div>762 </div>763 764 <!-- TOAST CONTAINER -->765 <div class="toast-container" id="toastContainer"></div>766 767 <script>768 /* =========================================769 LÓGICA JAVASCRIPT770 ========================================= */771 772 // Estado inicial de la aplicación773 let employees = JSON.parse(localStorage.getItem('nexus_employees')) || [774 { id: 1, name: "Carlos Ruiz", email: "carlos.ruiz@nexus.com", role: "Senior Developer", department: "Tecnología", status: "Active" },775 { id: 2, name: "Elena Vega", email: "elena.vega@nexus.com", role: "HR Manager", department: "Recursos Humanos", status: "Active" },776 { id: 3, name: "Miguel Angel", email: "miguel.angel@nexus.com", role: "Sales Lead", department: "Ventas", status: "Vacation" },777 { id: 4, name: "Lucía Méndez", email: "lucia.mendez@nexus.com", role: "UX Designer", department: "Tecnología", status: "Inactive" }778 ];779 780 // Referencias al DOM781 const tableBody = document.getElementById('employeeTableBody');782 const emptyState = document.getElementById('emptyState');783 const searchInput = document.getElementById('searchInput');784 const modalOverlay = document.getElementById('modalOverlay');785 const addBtn = document.getElementById('addBtn');786 const closeModalBtn = document.getElementById('closeModal');787 const cancelBtn = document.getElementById('cancelBtn');788 const saveBtn = document.getElementById('saveBtn');789 const employeeForm = document.getElementById('employeeForm');790 const modalTitle = document.getElementById('modalTitle');791 const statTotal = document.getElementById('statTotal');792 const statActive = document.getElementById('statActive');793 const sidebar = document.getElementById('sidebar');794 const menuToggle = document.getElementById('menuToggle');795 796 // Función para generar iniciales para el avatar797 function getInitials(name) {798 return name.split(' ').map(n => n[0]).join('').substring(0,2).toUpperCase();799 }800 801 // Función para renderizar la tabla802 function renderTable(data = employees) {803 tableBody.innerHTML = '';804 805 // Actualizar estadísticas806 statTotal.textContent = employees.length;807 statActive.textContent = employees.filter(e => e.status === 'Active').length;808 809 if (data.length === 0) {810 emptyState.style.display = 'block';811 return;812 } else {813 emptyState.style.display = 'none';814 }815 816 data.forEach(emp => {817 const tr = document.createElement('tr');818 819 // Definir clase del badge según estado820 let statusClass = 'status-inactive';821 let statusText = 'Inactivo';822 if(emp.status === 'Active') { statusClass = 'status-active'; statusText = 'Activo'; }823 if(emp.status === 'Vacation') { statusClass = 'status-vacation'; statusText = 'Vacaciones'; }824 825 tr.innerHTML = `826 <td>827 <div class="user-cell">828 <div class="avatar">${getInitials(emp.name)}</div>829 <div class="user-info">830 <div>${emp.name}</div>831 <div>${emp.email}</div>832 </div>833 </div>834 </td>835 <td>${emp.role}</td>836 <td>${emp.department}</td>837 <td><span class="status-badge ${statusClass}">${statusText}</span></td>838 <td style="text-align: right;">839 <button class="btn-icon" onclick="editEmployee(${emp.id})" title="Editar">840 <i class="ri-edit-line"></i>841 </button>842 <button class="btn-icon delete" onclick="deleteEmployee(${emp.id})" title="Eliminar">843 <i class="ri-delete-bin-line"></i>844 </button>845 </td>846 `;847 tableBody.appendChild(tr);848 });849 }850 851 // Guardar en LocalStorage852 function saveToLocal() {853 localStorage.setItem('nexus_employees', JSON.stringify(employees));854 renderTable(); // Re-renderizar para actualizar estadísticas855 }856 857 // Abrir Modal (Modo Crear)858 function openModal() {859 employeeForm.reset();860 document.getElementById('employeeId').value = '';861 modalTitle.textContent = "Añadir Empleado";862 modalOverlay.classList.add('open');863 }864 865 // Cerrar Modal866 function closeModal() {867 modalOverlay.classList.remove('open');868 }869 870 // Editar Empleado871 window.editEmployee = function(id) {872 const emp = employees.find(e => e.id === id);873 if(emp) {874 document.getElementById('employeeId').value = emp.id;875 document.getElementById('fullName').value = emp.name;876 document.getElementById('email').value = emp.email;877 document.getElementById('role').value = emp.role;878 document.getElementById('department').value = emp.department;879 document.getElementById('status').value = emp.status;880 881 modalTitle.textContent = "Editar Empleado";882 modalOverlay.classList.add('open');883 }884 }885 886 // Eliminar Empleado887 window.deleteEmployee = function(id) {888 if(confirm('¿Está seguro de que desea eliminar este registro? Esta acción no se puede deshacer.')) {889 employees = employees.filter(e => e.id !== id);890 saveToLocal();891 showToast('Empleado eliminado correctamente', 'success');892 }893 }894 895 // Guardar (Crear o Editar)896 function handleSave(e) {897 e.preventDefault();898 899 const idVal = document.getElementById('employeeId').value;900 const name = document.getElementById('fullName').value;901 const email = document.getElementById('email').value;902 const role = document.getElementById('role').value;903 const department = document.getElementById('department').value;904 const status = document.getElementById('status').value;905 906 if(idVal) {907 // Editar existente908 const index = employees.findIndex(e => e.id == idVal);909 if(index !== -1) {910 employees[index] = { ...employees[index], name, email, role, department, status };911 showToast('Información actualizada', 'success');912 }913 } else {914 // Crear nuevo915 const newId = employees.length > 0 ? Math.max(...employees.map(e => e.id)) + 1 : 1;916 employees.push({ id: newId, name, email, role, department, status });917 showToast('Empleado añadido con éxito', 'success');918 }919 920 saveToLocal();921 closeModal();922 }923 924 // Sistema de Notificaciones Toast925 function showToast(message, type = 'success') {926 const container = document.getElementById('toastContainer');927 const toast = document.createElement('div');928 toast.className = `toast ${type}`;929 930 const icon = type === 'success' ? 'ri-checkbox-circle-line' : 'ri-error-warning-line';931 932 toast.innerHTML = `933 <i class="${icon}"></i>934 <span>${message}</span>935 `;936 937 container.appendChild(toast);938 939 // Eliminar después de 3 segundos940 setTimeout(() => {941 toast.style.animation = 'fadeOut 0.3s ease forwards';942 toast.addEventListener('animationend', () => {943 toast.remove();944 });945 }, 3000);946 }947 948 // Filtrado de búsqueda949 searchInput.addEventListener('keyup', (e) => {950 const term = e.target.value.toLowerCase();951 const filtered = employees.filter(emp => 952 emp.name.toLowerCase().includes(term) || 953 emp.role.toLowerCase().includes(term) ||954 emp.email.toLowerCase().includes(term)955 );956 renderTable(filtered);957 });958 959 // Event Listeners960 addBtn.addEventListener('click', openModal);961 closeModalBtn.addEventListener('click', closeModal);962 cancelBtn.addEventListener('click', closeModal);963 saveBtn.addEventListener('click', handleSave);964 965 // Cerrar modal al hacer click fuera966 modalOverlay.addEventListener('click', (e) => {967 if (e.target === modalOverlay) closeModal();968 });969 970 // Toggle Sidebar Móvil971 menuToggle.addEventListener('click', () => {972 sidebar.classList.toggle('active');973 });974 975 // Click fuera de sidebar en móvil para cerrar976 document.addEventListener('click', (e) => {977 if(window.innerWidth <= 768) {978 if(!sidebar.contains(e.target) && !menuToggle.contains(e.target) && sidebar.classList.contains('active')) {979 sidebar.classList.remove('active');980 }981 }982 });983 984 // Inicializar985 renderTable();986 987 </script>988</body>989</html>