| | <!DOCTYPE html> |
| | <html lang="es"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>TimeTask - Gestor de Tareas Pomodoro</title> |
| | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
| | <style> |
| | body { |
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | transition: background-color 0.3s, color 0.3s; |
| | } |
| | |
| | .dark-mode { |
| | background-color: #1a1a1a; |
| | color: #f8f9fa; |
| | } |
| | |
| | .dark-mode .card, .dark-mode .modal-content, .dark-mode .dropdown-menu { |
| | background-color: #2d2d2d; |
| | color: #f8f9fa; |
| | border-color: #444; |
| | } |
| | |
| | .dark-mode .form-control, .dark-mode .form-select { |
| | background-color: #3d3d3d; |
| | color: #f8f9fa; |
| | border-color: #555; |
| | } |
| | |
| | .task-card { |
| | transition: transform 0.2s; |
| | cursor: pointer; |
| | } |
| | |
| | .task-card:hover { |
| | transform: translateY(-5px); |
| | } |
| | |
| | .priority-low { |
| | border-left: 5px solid #28a745; |
| | } |
| | |
| | .priority-medium { |
| | border-left: 5px solid #ffc107; |
| | } |
| | |
| | .priority-high { |
| | border-left: 5px solid #dc3545; |
| | } |
| | |
| | .timeline { |
| | position: relative; |
| | padding-left: 50px; |
| | } |
| | |
| | .timeline::before { |
| | content: ''; |
| | position: absolute; |
| | left: 25px; |
| | top: 0; |
| | bottom: 0; |
| | width: 2px; |
| | background-color: #007bff; |
| | } |
| | |
| | .timeline-item { |
| | position: relative; |
| | margin-bottom: 20px; |
| | } |
| | |
| | .timeline-item::before { |
| | content: ''; |
| | position: absolute; |
| | left: -35px; |
| | top: 10px; |
| | width: 16px; |
| | height: 16px; |
| | border-radius: 50%; |
| | background-color: #007bff; |
| | border: 3px solid #fff; |
| | } |
| | |
| | .current-time { |
| | position: absolute; |
| | left: 0; |
| | right: 0; |
| | height: 2px; |
| | background-color: red; |
| | z-index: 100; |
| | } |
| | |
| | .calendar-day { |
| | height: 100px; |
| | border: 1px solid #dee2e6; |
| | padding: 5px; |
| | overflow-y: auto; |
| | } |
| | |
| | .calendar-day.today { |
| | background-color: rgba(0, 123, 255, 0.1); |
| | } |
| | |
| | .calendar-day-header { |
| | font-weight: bold; |
| | margin-bottom: 5px; |
| | } |
| | |
| | .task-dot { |
| | display: inline-block; |
| | width: 10px; |
| | height: 10px; |
| | border-radius: 50%; |
| | margin-right: 5px; |
| | } |
| | |
| | .pomodoro-timer { |
| | font-size: 3rem; |
| | font-weight: bold; |
| | text-align: center; |
| | margin: 20px 0; |
| | } |
| | |
| | .pomodoro-btn { |
| | width: 100px; |
| | margin: 0 5px; |
| | } |
| | |
| | .pomodoro-session { |
| | font-size: 1.2rem; |
| | text-align: center; |
| | margin-bottom: 20px; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="container-fluid"> |
| | <nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4"> |
| | <div class="container-fluid"> |
| | <a class="navbar-brand" href="#">TimeTask</a> |
| | <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> |
| | <span class="navbar-toggler-icon"></span> |
| | </button> |
| | <div class="collapse navbar-collapse" id="navbarNav"> |
| | <ul class="navbar-nav me-auto"> |
| | <li class="nav-item"> |
| | <a class="nav-link active" href="#" onclick="showView('dashboard')">Inicio</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('tasks')">Tareas</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('timeline')">Línea de Tiempo</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('day')">Día</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('week')">Semana</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('calendar')">Calendario</a> |
| | </li> |
| | <li class="nav-item"> |
| | <a class="nav-link" href="#" onclick="showView('metrics')">Métricas</a> |
| | </li> |
| | </ul> |
| | <div class="d-flex align-items-center"> |
| | <div class="me-3 text-white" id="current-time"></div> |
| | <div class="form-check form-switch me-3"> |
| | <input class="form-check-input" type="checkbox" id="darkModeSwitch"> |
| | <label class="form-check-label text-white" for="darkModeSwitch">Modo Oscuro</label> |
| | </div> |
| | <div class="dropdown"> |
| | <button class="btn btn-outline-light dropdown-toggle" type="button" id="profileDropdown" data-bs-toggle="dropdown"> |
| | Perfil |
| | </button> |
| | <ul class="dropdown-menu dropdown-menu-end"> |
| | <li><a class="dropdown-item" href="#" onclick="showView('profile')">Configuración</a></li> |
| | <li><hr class="dropdown-divider"></li> |
| | <li><a class="dropdown-item" href="#" onclick="logout()">Cerrar Sesión</a></li> |
| | </ul> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </nav> |
| |
|
| | |
| | <div id="main-content"> |
| | |
| | <div id="dashboard-view" class="view"> |
| | <div class="row"> |
| | <div class="col-md-8"> |
| | <div class="card mb-4"> |
| | <div class="card-header d-flex justify-content-between align-items-center"> |
| | <h5 class="mb-0">Pomodoro Timer</h5> |
| | <button class="btn btn-sm btn-primary" onclick="showView('tasks')">Ver Tareas</button> |
| | </div> |
| | <div class="card-body"> |
| | <div class="pomodoro-timer" id="pomodoro-timer">25:00</div> |
| | <div class="pomodoro-session" id="pomodoro-session">Sesión de Trabajo</div> |
| | <div class="d-flex justify-content-center"> |
| | <button class="btn btn-success pomodoro-btn" id="start-btn" onclick="startPomodoro()">Iniciar</button> |
| | <button class="btn btn-danger pomodoro-btn" id="stop-btn" onclick="stopPomodoro()" disabled>Detener</button> |
| | <button class="btn btn-warning pomodoro-btn" id="reset-btn" onclick="resetPomodoro()">Reiniciar</button> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="card mb-4"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Tareas de Hoy</h5> |
| | </div> |
| | <div class="card-body" id="today-tasks"> |
| | |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="col-md-4"> |
| | <div class="card mb-4"> |
| | <div class="card-header d-flex justify-content-between align-items-center"> |
| | <h5 class="mb-0">Agregar Tarea Rápida</h5> |
| | </div> |
| | <div class="card-body"> |
| | <form id="quick-task-form"> |
| | <div class="mb-3"> |
| | <input type="text" class="form-control" id="quick-task-name" placeholder="Nombre de la tarea" required> |
| | </div> |
| | <div class="mb-3"> |
| | <select class="form-select" id="quick-task-priority"> |
| | <option value="low">Baja</option> |
| | <option value="medium">Media</option> |
| | <option value="high">Alta</option> |
| | </select> |
| | </div> |
| | <button type="submit" class="btn btn-primary w-100">Agregar</button> |
| | </form> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Estadísticas</h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="mb-3"> |
| | <h6>Tareas completadas hoy: <span id="completed-today">0</span></h6> |
| | </div> |
| | <div class="mb-3"> |
| | <h6>Tiempo trabajado hoy: <span id="time-worked">0h 0m</span></h6> |
| | </div> |
| | <div class="mb-3"> |
| | <h6>Sesiones Pomodoro: <span id="pomodoro-sessions">0</span></h6> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="tasks-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Tareas</h2> |
| | <button class="btn btn-primary" onclick="showTaskModal()">Nueva Tarea</button> |
| | </div> |
| | |
| | <div class="row mb-3"> |
| | <div class="col-md-4"> |
| | <select class="form-select" id="task-sort"> |
| | <option value="date">Ordenar por: Fecha</option> |
| | <option value="priority">Ordenar por: Prioridad</option> |
| | <option value="category">Ordenar por: Categoría</option> |
| | </select> |
| | </div> |
| | <div class="col-md-4"> |
| | <select class="form-select" id="task-filter"> |
| | <option value="all">Todas las tareas</option> |
| | <option value="today">Hoy</option> |
| | <option value="week">Esta semana</option> |
| | <option value="completed">Completadas</option> |
| | <option value="pending">Pendientes</option> |
| | </select> |
| | </div> |
| | <div class="col-md-4"> |
| | <input type="text" class="form-control" id="task-search" placeholder="Buscar tareas..."> |
| | </div> |
| | </div> |
| | |
| | <div class="row" id="tasks-container"> |
| | |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="timeline-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Línea de Tiempo</h2> |
| | <div class="btn-group"> |
| | <button class="btn btn-outline-primary" onclick="changeTimelineRange('day')">Día</button> |
| | <button class="btn btn-outline-primary" onclick="changeTimelineRange('week')">Semana</button> |
| | <button class="btn btn-outline-primary" onclick="changeTimelineRange('month')">Mes</button> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <div class="d-flex justify-content-between align-items-center"> |
| | <h5 class="mb-0" id="timeline-title">Hoy</h5> |
| | <div id="timeline-date"></div> |
| | </div> |
| | </div> |
| | <div class="card-body"> |
| | <div class="timeline" id="timeline-container"> |
| | <div class="current-time" id="current-time-indicator"></div> |
| | |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="day-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Agenda del Día</h2> |
| | <div class="btn-group"> |
| | <button class="btn btn-outline-primary" onclick="changeDay(-1)">Anterior</button> |
| | <button class="btn btn-outline-primary" onclick="changeDay(0)">Hoy</button> |
| | <button class="btn btn-outline-primary" onclick="changeDay(1)">Siguiente</button> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0" id="day-title">Hoy</h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="table-responsive"> |
| | <table class="table"> |
| | <thead> |
| | <tr> |
| | <th style="width: 10%">Hora</th> |
| | <th style="width: 90%">Tareas</th> |
| | </tr> |
| | </thead> |
| | <tbody id="day-schedule"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="week-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Semana</h2> |
| | <div class="btn-group"> |
| | <button class="btn btn-outline-primary" onclick="changeWeek(-1)">Anterior</button> |
| | <button class="btn btn-outline-primary" onclick="changeWeek(0)">Esta semana</button> |
| | <button class="btn btn-outline-primary" onclick="changeWeek(1)">Siguiente</button> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0" id="week-title">Esta semana</h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="table-responsive"> |
| | <table class="table table-bordered" id="week-calendar"> |
| | <thead> |
| | <tr> |
| | <th style="width: 14%">Domingo</th> |
| | <th style="width: 14%">Lunes</th> |
| | <th style="width: 14%">Martes</th> |
| | <th style="width: 14%">Miércoles</th> |
| | <th style="width: 14%">Jueves</th> |
| | <th style="width: 14%">Viernes</th> |
| | <th style="width: 14%">Sábado</th> |
| | </tr> |
| | </thead> |
| | <tbody> |
| | <tr> |
| | <td class="calendar-day" id="week-day-0"></td> |
| | <td class="calendar-day" id="week-day-1"></td> |
| | <td class="calendar-day" id="week-day-2"></td> |
| | <td class="calendar-day" id="week-day-3"></td> |
| | <td class="calendar-day" id="week-day-4"></td> |
| | <td class="calendar-day" id="week-day-5"></td> |
| | <td class="calendar-day" id="week-day-6"></td> |
| | </tr> |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="calendar-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Calendario</h2> |
| | <div class="btn-group"> |
| | <button class="btn btn-outline-primary" onclick="changeMonth(-1)">Mes anterior</button> |
| | <button class="btn btn-outline-primary" onclick="changeMonth(0)">Este mes</button> |
| | <button class="btn btn-outline-primary" onclick="changeMonth(1)">Siguiente mes</button> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0" id="calendar-title">Este mes</h5> |
| | </div> |
| | <div class="card-body"> |
| | <div class="table-responsive"> |
| | <table class="table table-bordered" id="month-calendar"> |
| | <thead> |
| | <tr> |
| | <th style="width: 14%">Dom</th> |
| | <th style="width: 14%">Lun</th> |
| | <th style="width: 14%">Mar</th> |
| | <th style="width: 14%">Mié</th> |
| | <th style="width: 14%">Jue</th> |
| | <th style="width: 14%">Vie</th> |
| | <th style="width: 14%">Sáb</th> |
| | </tr> |
| | </thead> |
| | <tbody id="calendar-body"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="metrics-view" class="view" style="display: none;"> |
| | <div class="d-flex justify-content-between align-items-center mb-4"> |
| | <h2>Métricas</h2> |
| | <div class="btn-group"> |
| | <button class="btn btn-outline-primary" onclick="changeMetricsRange('week')">Semana</button> |
| | <button class="btn btn-outline-primary" onclick="changeMetricsRange('month')">Mes</button> |
| | <button class="btn btn-outline-primary" onclick="changeMetricsRange('year')">Año</button> |
| | </div> |
| | </div> |
| | |
| | <div class="row"> |
| | <div class="col-md-6"> |
| | <div class="card mb-4"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Tareas completadas</h5> |
| | </div> |
| | <div class="card-body"> |
| | <canvas id="completed-chart" height="300"></canvas> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="col-md-6"> |
| | <div class="card mb-4"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Distribución por prioridad</h5> |
| | </div> |
| | <div class="card-body"> |
| | <canvas id="priority-chart" height="300"></canvas> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Productividad</h5> |
| | </div> |
| | <div class="card-body"> |
| | <canvas id="productivity-chart" height="300"></canvas> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="profile-view" class="view" style="display: none;"> |
| | <div class="row justify-content-center"> |
| | <div class="col-md-8"> |
| | <div class="card"> |
| | <div class="card-header"> |
| | <h5 class="mb-0">Perfil de Usuario</h5> |
| | </div> |
| | <div class="card-body"> |
| | <form id="profile-form"> |
| | <div class="mb-3"> |
| | <label for="profile-name" class="form-label">Nombre</label> |
| | <input type="text" class="form-control" id="profile-name" required> |
| | </div> |
| | <div class="mb-3"> |
| | <label for="profile-email" class="form-label">Email</label> |
| | <input type="email" class="form-control" id="profile-email" required> |
| | </div> |
| | <div class="mb-3"> |
| | <label for="profile-password" class="form-label">Nueva Contraseña</label> |
| | <input type="password" class="form-control" id="profile-password" placeholder="Dejar en blanco para no cambiar"> |
| | </div> |
| | <div class="mb-3"> |
| | <label for="profile-confirm-password" class="form-label">Confirmar Nueva Contraseña</label> |
| | <input type="password" class="form-control" id="profile-confirm-password"> |
| | </div> |
| | <div class="mb-3 form-check"> |
| | <input type="checkbox" class="form-check-input" id="profile-dark-mode"> |
| | <label class="form-check-label" for="profile-dark-mode">Modo Oscuro</label> |
| | </div> |
| | <button type="submit" class="btn btn-primary">Guardar Cambios</button> |
| | </form> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="modal fade" id="taskModal" tabindex="-1" aria-hidden="true"> |
| | <div class="modal-dialog modal-lg"> |
| | <div class="modal-content"> |
| | <div class="modal-header"> |
| | <h5 class="modal-title" id="taskModalTitle">Nueva Tarea</h5> |
| | <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
| | </div> |
| | <div class="modal-body"> |
| | <form id="task-form"> |
| | <input type="hidden" id="task-id"> |
| | <div class="mb-3"> |
| | <label for="task-name" class="form-label">Nombre</label> |
| | <input type="text" class="form-control" id="task-name" required> |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <label for="task-priority" class="form-label">Prioridad</label> |
| | <select class="form-select" id="task-priority" required> |
| | <option value="low">Baja</option> |
| | <option value="medium">Media</option> |
| | <option value="high">Alta</option> |
| | </select> |
| | </div> |
| | <div class="col-md-6"> |
| | <label for="task-status" class="form-label">Estado</label> |
| | <select class="form-select" id="task-status"> |
| | <option value="pending">Pendiente</option> |
| | <option value="in-progress">En progreso</option> |
| | <option value="completed">Completada</option> |
| | </select> |
| | </div> |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <label for="task-category" class="form-label">Categoría</label> |
| | <select class="form-select" id="task-category"> |
| | <option value="work">Trabajo</option> |
| | <option value="personal">Personal</option> |
| | <option value="study">Estudio</option> |
| | <option value="other">Otro</option> |
| | </select> |
| | </div> |
| | <div class="col-md-6"> |
| | <label for="task-subcategory" class="form-label">Subcategoría</label> |
| | <input type="text" class="form-control" id="task-subcategory"> |
| | </div> |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <label for="task-start-date" class="form-label">Fecha de inicio</label> |
| | <input type="date" class="form-control" id="task-start-date" required> |
| | </div> |
| | <div class="col-md-6"> |
| | <label for="task-end-date" class="form-label">Fecha de fin</label> |
| | <input type="date" class="form-control" id="task-end-date"> |
| | </div> |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <label for="task-start-time" class="form-label">Hora de inicio</label> |
| | <input type="time" class="form-control" id="task-start-time"> |
| | </div> |
| | <div class="col-md-6"> |
| | <label for="task-end-time" class="form-label">Hora de fin</label> |
| | <input type="time" class="form-control" id="task-end-time"> |
| | </div> |
| | </div> |
| | <div class="mb-3"> |
| | <label for="task-description" class="form-label">Descripción</label> |
| | <textarea class="form-control" id="task-description" rows="5"></textarea> |
| | </div> |
| | </form> |
| | |
| | <div class="mb-3"> |
| | <h6>Comentarios</h6> |
| | <div id="task-comments"> |
| | |
| | </div> |
| | <div class="input-group mt-3"> |
| | <input type="text" class="form-control" id="new-comment" placeholder="Agregar comentario"> |
| | <button class="btn btn-primary" type="button" onclick="addComment()">Agregar</button> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="modal-footer"> |
| | <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button> |
| | <button type="button" class="btn btn-primary" onclick="saveTask()">Guardar Tarea</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="modal fade" id="taskDetailsModal" tabindex="-1" aria-hidden="true"> |
| | <div class="modal-dialog"> |
| | <div class="modal-content"> |
| | <div class="modal-header"> |
| | <h5 class="modal-title" id="taskDetailsTitle">Detalles de la Tarea</h5> |
| | <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
| | </div> |
| | <div class="modal-body" id="task-details-content"> |
| | |
| | </div> |
| | <div class="modal-footer"> |
| | <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button> |
| | <button type="button" class="btn btn-primary" id="edit-task-btn" onclick="editTask()">Editar</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> |
| | <script> |
| | |
| | let tasks = []; |
| | let currentView = 'dashboard'; |
| | let pomodoroInterval; |
| | let pomodoroTime = 25 * 60; |
| | let pomodoroSession = 'work'; |
| | let pomodoroSessionsCompleted = 0; |
| | let darkMode = false; |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', function() { |
| | |
| | loadTasks(); |
| | |
| | |
| | updateCurrentTime(); |
| | setInterval(updateCurrentTime, 1000); |
| | |
| | |
| | showView('dashboard'); |
| | updateTodayTasks(); |
| | updatePomodoroTimer(); |
| | |
| | |
| | document.getElementById('darkModeSwitch').addEventListener('change', toggleDarkMode); |
| | document.getElementById('quick-task-form').addEventListener('submit', addQuickTask); |
| | document.getElementById('profile-form').addEventListener('submit', saveProfile); |
| | document.getElementById('task-sort').addEventListener('change', updateTasksView); |
| | document.getElementById('task-filter').addEventListener('change', updateTasksView); |
| | document.getElementById('task-search').addEventListener('input', updateTasksView); |
| | |
| | |
| | const taskModal = new bootstrap.Modal(document.getElementById('taskModal')); |
| | const taskDetailsModal = new bootstrap.Modal(document.getElementById('taskDetailsModal')); |
| | |
| | |
| | if (localStorage.getItem('darkMode') === 'true') { |
| | document.getElementById('darkModeSwitch').checked = true; |
| | toggleDarkMode(); |
| | } |
| | |
| | |
| | loadProfile(); |
| | }); |
| | |
| | |
| | function showView(viewName) { |
| | |
| | document.querySelectorAll('.view').forEach(view => { |
| | view.style.display = 'none'; |
| | }); |
| | |
| | |
| | document.getElementById(`${viewName}-view`).style.display = 'block'; |
| | currentView = viewName; |
| | |
| | |
| | switch(viewName) { |
| | case 'dashboard': |
| | updateDashboard(); |
| | break; |
| | case 'tasks': |
| | updateTasksView(); |
| | break; |
| | case 'timeline': |
| | updateTimelineView(); |
| | break; |
| | case 'day': |
| | updateDayView(); |
| | break; |
| | case 'week': |
| | updateWeekView(); |
| | break; |
| | case 'calendar': |
| | updateCalendarView(); |
| | break; |
| | case 'metrics': |
| | updateMetricsView(); |
| | break; |
| | case 'profile': |
| | |
| | break; |
| | } |
| | } |
| | |
| | |
| | function loadTasks() { |
| | const savedTasks = localStorage.getItem('tasks'); |
| | if (savedTasks) { |
| | tasks = JSON.parse(savedTasks); |
| | } |
| | } |
| | |
| | function saveTasks() { |
| | localStorage.setItem('tasks', JSON.stringify(tasks)); |
| | } |
| | |
| | function addTask(task) { |
| | tasks.push(task); |
| | saveTasks(); |
| | updateTasksView(); |
| | updateTodayTasks(); |
| | } |
| | |
| | function updateTask(id, updatedTask) { |
| | const index = tasks.findIndex(task => task.id === id); |
| | if (index !== -1) { |
| | tasks[index] = updatedTask; |
| | saveTasks(); |
| | updateTasksView(); |
| | updateTodayTasks(); |
| | return true; |
| | } |
| | return false; |
| | } |
| | |
| | function deleteTask(id) { |
| | const index = tasks.findIndex(task => task.id === id); |
| | if (index !== -1) { |
| | tasks.splice(index, 1); |
| | saveTasks(); |
| | updateTasksView(); |
| | updateTodayTasks(); |
| | return true; |
| | } |
| | return false; |
| | } |
| | |
| | function showTaskModal(taskId = null) { |
| | const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskModal')); |
| | const form = document.getElementById('task-form'); |
| | |
| | if (taskId) { |
| | |
| | const task = tasks.find(t => t.id === taskId); |
| | if (task) { |
| | document.getElementById('taskModalTitle').textContent = 'Editar Tarea'; |
| | document.getElementById('task-id').value = task.id; |
| | document.getElementById('task-name').value = task.name; |
| | document.getElementById('task-priority').value = task.priority; |
| | document.getElementById('task-status').value = task.status || 'pending'; |
| | document.getElementById('task-category').value = task.category || 'work'; |
| | document.getElementById('task-subcategory').value = task.subcategory || ''; |
| | document.getElementById('task-start-date').value = task.startDate || ''; |
| | document.getElementById('task-end-date').value = task.endDate || ''; |
| | document.getElementById('task-start-time').value = task.startTime || ''; |
| | document.getElementById('task-end-time').value = task.endTime || ''; |
| | document.getElementById('task-description').value = task.description || ''; |
| | |
| | |
| | loadComments(task.id); |
| | } |
| | } else { |
| | |
| | document.getElementById('taskModalTitle').textContent = 'Nueva Tarea'; |
| | form.reset(); |
| | document.getElementById('task-id').value = ''; |
| | document.getElementById('task-status').value = 'pending'; |
| | document.getElementById('task-comments').innerHTML = ''; |
| | |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | document.getElementById('task-start-date').value = today; |
| | } |
| | |
| | modal.show(); |
| | } |
| | |
| | function saveTask() { |
| | const form = document.getElementById('task-form'); |
| | if (!form.checkValidity()) { |
| | form.reportValidity(); |
| | return; |
| | } |
| | |
| | const task = { |
| | id: document.getElementById('task-id').value || Date.now().toString(), |
| | name: document.getElementById('task-name').value, |
| | priority: document.getElementById('task-priority').value, |
| | status: document.getElementById('task-status').value, |
| | category: document.getElementById('task-category').value, |
| | subcategory: document.getElementById('task-subcategory').value, |
| | startDate: document.getElementById('task-start-date').value, |
| | endDate: document.getElementById('task-end-date').value, |
| | startTime: document.getElementById('task-start-time').value, |
| | endTime: document.getElementById('task-end-time').value, |
| | description: document.getElementById('task-description').value, |
| | createdAt: new Date().toISOString() |
| | }; |
| | |
| | if (task.id) { |
| | |
| | updateTask(task.id, task); |
| | } else { |
| | |
| | addTask(task); |
| | } |
| | |
| | const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskModal')); |
| | modal.hide(); |
| | } |
| | |
| | function showTaskDetails(taskId) { |
| | const task = tasks.find(t => t.id === taskId); |
| | if (!task) return; |
| | |
| | const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskDetailsModal')); |
| | const content = document.getElementById('task-details-content'); |
| | |
| | |
| | const startDate = task.startDate ? new Date(task.startDate).toLocaleDateString() : 'No especificada'; |
| | const endDate = task.endDate ? new Date(task.endDate).toLocaleDateString() : 'No especificada'; |
| | |
| | |
| | const startTime = task.startTime || 'No especificada'; |
| | const endTime = task.endTime || 'No especificada'; |
| | |
| | |
| | let priorityBadge; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityBadge = '<span class="badge bg-success">Baja</span>'; |
| | break; |
| | case 'medium': |
| | priorityBadge = '<span class="badge bg-warning text-dark">Media</span>'; |
| | break; |
| | case 'high': |
| | priorityBadge = '<span class="badge bg-danger">Alta</span>'; |
| | break; |
| | } |
| | |
| | |
| | let statusBadge; |
| | switch(task.status) { |
| | case 'pending': |
| | statusBadge = '<span class="badge bg-secondary">Pendiente</span>'; |
| | break; |
| | case 'in-progress': |
| | statusBadge = '<span class="badge bg-primary">En progreso</span>'; |
| | break; |
| | case 'completed': |
| | statusBadge = '<span class="badge bg-success">Completada</span>'; |
| | break; |
| | } |
| | |
| | |
| | content.innerHTML = ` |
| | <h5>${task.name}</h5> |
| | <div class="mb-3"> |
| | ${priorityBadge} ${statusBadge} |
| | </div> |
| | <div class="mb-3"> |
| | <strong>Categoría:</strong> ${task.category} ${task.subcategory ? `(${task.subcategory})` : ''} |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <strong>Fecha inicio:</strong> ${startDate} |
| | </div> |
| | <div class="col-md-6"> |
| | <strong>Fecha fin:</strong> ${endDate} |
| | </div> |
| | </div> |
| | <div class="row mb-3"> |
| | <div class="col-md-6"> |
| | <strong>Hora inicio:</strong> ${startTime} |
| | </div> |
| | <div class="col-md-6"> |
| | <strong>Hora fin:</strong> ${endTime} |
| | </div> |
| | </div> |
| | <div class="mb-3"> |
| | <strong>Descripción:</strong> |
| | <p>${task.description || 'No hay descripción.'}</p> |
| | </div> |
| | <div class="mb-3"> |
| | <strong>Comentarios:</strong> |
| | <div id="details-comments"> |
| | ${task.comments && task.comments.length > 0 ? |
| | task.comments.map(c => `<div class="card mb-2"><div class="card-body p-2">${c.text}</div></div>`).join('') : |
| | '<p>No hay comentarios.</p>'} |
| | </div> |
| | </div> |
| | `; |
| | |
| | |
| | document.getElementById('edit-task-btn').setAttribute('data-task-id', task.id); |
| | |
| | modal.show(); |
| | } |
| | |
| | function editTask() { |
| | const taskId = document.getElementById('edit-task-btn').getAttribute('data-task-id'); |
| | const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('taskDetailsModal')); |
| | modal.hide(); |
| | |
| | showTaskModal(taskId); |
| | } |
| | |
| | function addQuickTask(e) { |
| | e.preventDefault(); |
| | |
| | const name = document.getElementById('quick-task-name').value; |
| | const priority = document.getElementById('quick-task-priority').value; |
| | const today = new Date().toISOString().split('T')[0]; |
| | |
| | const task = { |
| | id: Date.now().toString(), |
| | name: name, |
| | priority: priority, |
| | status: 'pending', |
| | category: 'personal', |
| | startDate: today, |
| | createdAt: new Date().toISOString() |
| | }; |
| | |
| | addTask(task); |
| | document.getElementById('quick-task-form').reset(); |
| | } |
| | |
| | function addComment() { |
| | const commentInput = document.getElementById('new-comment'); |
| | const commentText = commentInput.value.trim(); |
| | const taskId = document.getElementById('task-id').value; |
| | |
| | if (!commentText) return; |
| | |
| | const task = tasks.find(t => t.id === taskId); |
| | if (!task) return; |
| | |
| | if (!task.comments) { |
| | task.comments = []; |
| | } |
| | |
| | task.comments.push({ |
| | text: commentText, |
| | date: new Date().toISOString() |
| | }); |
| | |
| | saveTasks(); |
| | loadComments(taskId); |
| | commentInput.value = ''; |
| | } |
| | |
| | function loadComments(taskId) { |
| | const task = tasks.find(t => t.id === taskId); |
| | if (!task) return; |
| | |
| | const commentsContainer = document.getElementById('task-comments'); |
| | commentsContainer.innerHTML = ''; |
| | |
| | if (task.comments && task.comments.length > 0) { |
| | task.comments.forEach(comment => { |
| | const commentElement = document.createElement('div'); |
| | commentElement.className = 'card mb-2'; |
| | commentElement.innerHTML = ` |
| | <div class="card-body p-2"> |
| | ${comment.text} |
| | </div> |
| | `; |
| | commentsContainer.appendChild(commentElement); |
| | }); |
| | } else { |
| | commentsContainer.innerHTML = '<p>No hay comentarios.</p>'; |
| | } |
| | } |
| | |
| | |
| | function updateDashboard() { |
| | updateTodayTasks(); |
| | updatePomodoroTimer(); |
| | updateStats(); |
| | } |
| | |
| | function updateTasksView() { |
| | const container = document.getElementById('tasks-container'); |
| | container.innerHTML = ''; |
| | |
| | const sortBy = document.getElementById('task-sort').value; |
| | const filterBy = document.getElementById('task-filter').value; |
| | const searchText = document.getElementById('task-search').value.toLowerCase(); |
| | |
| | |
| | let filteredTasks = [...tasks]; |
| | |
| | |
| | if (searchText) { |
| | filteredTasks = filteredTasks.filter(task => |
| | task.name.toLowerCase().includes(searchText) || |
| | (task.description && task.description.toLowerCase().includes(searchText)) |
| | ); |
| | } |
| | |
| | |
| | switch(filterBy) { |
| | case 'today': |
| | const today = new Date().toISOString().split('T')[0]; |
| | filteredTasks = filteredTasks.filter(task => task.startDate === today); |
| | break; |
| | case 'week': |
| | const currentWeek = getWeekNumber(new Date()); |
| | filteredTasks = filteredTasks.filter(task => { |
| | const taskDate = new Date(task.startDate); |
| | return getWeekNumber(taskDate) === currentWeek; |
| | }); |
| | break; |
| | case 'completed': |
| | filteredTasks = filteredTasks.filter(task => task.status === 'completed'); |
| | break; |
| | case 'pending': |
| | filteredTasks = filteredTasks.filter(task => task.status !== 'completed'); |
| | break; |
| | |
| | } |
| | |
| | |
| | switch(sortBy) { |
| | case 'date': |
| | filteredTasks.sort((a, b) => new Date(a.startDate) - new Date(b.startDate)); |
| | break; |
| | case 'priority': |
| | const priorityOrder = { high: 1, medium: 2, low: 3 }; |
| | filteredTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]); |
| | break; |
| | case 'category': |
| | filteredTasks.sort((a, b) => a.category.localeCompare(b.category)); |
| | break; |
| | } |
| | |
| | |
| | if (filteredTasks.length === 0) { |
| | container.innerHTML = '<div class="col-12"><p>No hay tareas que mostrar.</p></div>'; |
| | return; |
| | } |
| | |
| | filteredTasks.forEach(task => { |
| | const taskElement = document.createElement('div'); |
| | taskElement.className = 'col-md-6 col-lg-4 mb-4'; |
| | |
| | |
| | let priorityClass; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityClass = 'priority-low'; |
| | break; |
| | case 'medium': |
| | priorityClass = 'priority-medium'; |
| | break; |
| | case 'high': |
| | priorityClass = 'priority-high'; |
| | break; |
| | } |
| | |
| | |
| | let statusBadge; |
| | switch(task.status) { |
| | case 'pending': |
| | statusBadge = '<span class="badge bg-secondary">Pendiente</span>'; |
| | break; |
| | case 'in-progress': |
| | statusBadge = '<span class="badge bg-primary">En progreso</span>'; |
| | break; |
| | case 'completed': |
| | statusBadge = '<span class="badge bg-success">Completada</span>'; |
| | break; |
| | } |
| | |
| | |
| | const taskDate = task.startDate ? new Date(task.startDate).toLocaleDateString() : 'Sin fecha'; |
| | |
| | taskElement.innerHTML = ` |
| | <div class="card task-card ${priorityClass}" onclick="showTaskDetails('${task.id}')"> |
| | <div class="card-body"> |
| | <h5 class="card-title">${task.name}</h5> |
| | <div class="d-flex justify-content-between mb-2"> |
| | <span class="text-muted">${taskDate}</span> |
| | ${statusBadge} |
| | </div> |
| | <p class="card-text">${task.description ? task.description.substring(0, 100) + (task.description.length > 100 ? '...' : '') : 'Sin descripción'}</p> |
| | <div class="d-flex justify-content-between"> |
| | <span class="badge bg-light text-dark">${task.category}</span> |
| | <button class="btn btn-sm btn-outline-danger" onclick="event.stopPropagation(); deleteTask('${task.id}')">Eliminar</button> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | |
| | container.appendChild(taskElement); |
| | }); |
| | } |
| | |
| | function updateTodayTasks() { |
| | const container = document.getElementById('today-tasks'); |
| | container.innerHTML = ''; |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | const todayTasks = tasks.filter(task => task.startDate === today); |
| | |
| | if (todayTasks.length === 0) { |
| | container.innerHTML = '<p>No hay tareas para hoy.</p>'; |
| | return; |
| | } |
| | |
| | todayTasks.forEach(task => { |
| | const taskElement = document.createElement('div'); |
| | taskElement.className = 'mb-3'; |
| | |
| | |
| | let priorityClass; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityClass = 'priority-low'; |
| | break; |
| | case 'medium': |
| | priorityClass = 'priority-medium'; |
| | break; |
| | case 'high': |
| | priorityClass = 'priority-high'; |
| | break; |
| | } |
| | |
| | |
| | let statusBadge; |
| | switch(task.status) { |
| | case 'pending': |
| | statusBadge = '<span class="badge bg-secondary">Pendiente</span>'; |
| | break; |
| | case 'in-progress': |
| | statusBadge = '<span class="badge bg-primary">En progreso</span>'; |
| | break; |
| | case 'completed': |
| | statusBadge = '<span class="badge bg-success">Completada</span>'; |
| | break; |
| | } |
| | |
| | |
| | const timeInfo = task.startTime ? |
| | (task.endTime ? `${task.startTime} - ${task.endTime}` : `Desde ${task.startTime}`) : |
| | 'Sin hora específica'; |
| | |
| | taskElement.innerHTML = ` |
| | <div class="card ${priorityClass}"> |
| | <div class="card-body p-3"> |
| | <div class="d-flex justify-content-between align-items-center"> |
| | <h6 class="mb-0">${task.name}</h6> |
| | ${statusBadge} |
| | </div> |
| | <div class="d-flex justify-content-between mt-2"> |
| | <small class="text-muted">${timeInfo}</small> |
| | <div> |
| | <button class="btn btn-sm btn-outline-primary" onclick="showTaskDetails('${task.id}')">Detalles</button> |
| | <button class="btn btn-sm btn-outline-success" onclick="completeTask('${task.id}')">Completar</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | |
| | container.appendChild(taskElement); |
| | }); |
| | } |
| | |
| | function completeTask(taskId) { |
| | const task = tasks.find(t => t.id === taskId); |
| | if (task) { |
| | task.status = 'completed'; |
| | saveTasks(); |
| | updateTodayTasks(); |
| | updateTasksView(); |
| | } |
| | } |
| | |
| | function updateTimelineView() { |
| | const container = document.getElementById('timeline-container'); |
| | const title = document.getElementById('timeline-title'); |
| | const dateDisplay = document.getElementById('timeline-date'); |
| | |
| | |
| | container.innerHTML = '<div class="current-time" id="current-time-indicator"></div>'; |
| | |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | const todayTasks = tasks.filter(task => |
| | task.startDate === today && task.startTime |
| | ).sort((a, b) => { |
| | |
| | const timeA = a.startTime.split(':').map(Number); |
| | const timeB = b.startTime.split(':').map(Number); |
| | return (timeA[0] * 60 + timeA[1]) - (timeB[0] * 60 + timeB[1]); |
| | }); |
| | |
| | title.textContent = 'Hoy'; |
| | dateDisplay.textContent = new Date().toLocaleDateString(); |
| | |
| | if (todayTasks.length === 0) { |
| | const noTasks = document.createElement('div'); |
| | noTasks.className = 'alert alert-info'; |
| | noTasks.textContent = 'No hay tareas programadas para hoy con hora específica.'; |
| | container.appendChild(noTasks); |
| | return; |
| | } |
| | |
| | todayTasks.forEach(task => { |
| | const item = document.createElement('div'); |
| | item.className = 'timeline-item'; |
| | |
| | |
| | let priorityDot; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityDot = 'bg-success'; |
| | break; |
| | case 'medium': |
| | priorityDot = 'bg-warning'; |
| | break; |
| | case 'high': |
| | priorityDot = 'bg-danger'; |
| | break; |
| | } |
| | |
| | |
| | let statusBadge; |
| | switch(task.status) { |
| | case 'pending': |
| | statusBadge = '<span class="badge bg-secondary">Pendiente</span>'; |
| | break; |
| | case 'in-progress': |
| | statusBadge = '<span class="badge bg-primary">En progreso</span>'; |
| | break; |
| | case 'completed': |
| | statusBadge = '<span class="badge bg-success">Completada</span>'; |
| | break; |
| | } |
| | |
| | |
| | const timeRange = task.endTime ? |
| | `${task.startTime} - ${task.endTime}` : |
| | `Desde ${task.startTime}`; |
| | |
| | item.innerHTML = ` |
| | <div class="card"> |
| | <div class="card-body"> |
| | <div class="d-flex justify-content-between align-items-center mb-2"> |
| | <h6 class="mb-0">${task.name}</h6> |
| | <span class="badge ${priorityDot}">${task.priority}</span> |
| | </div> |
| | <div class="d-flex justify-content-between align-items-center"> |
| | <span>${timeRange}</span> |
| | ${statusBadge} |
| | </div> |
| | ${task.description ? `<p class="mt-2 mb-0">${task.description}</p>` : ''} |
| | </div> |
| | </div> |
| | `; |
| | |
| | container.appendChild(item); |
| | }); |
| | |
| | |
| | updateCurrentTimeIndicator(); |
| | } |
| | |
| | function updateCurrentTimeIndicator() { |
| | const now = new Date(); |
| | const hours = now.getHours(); |
| | const minutes = now.getMinutes(); |
| | const totalMinutes = hours * 60 + minutes; |
| | |
| | |
| | const timelineStart = 8 * 60; |
| | const timelineEnd = 20 * 60; |
| | const timelineHeight = document.getElementById('timeline-container').offsetHeight; |
| | |
| | if (totalMinutes >= timelineStart && totalMinutes <= timelineEnd) { |
| | const position = ((totalMinutes - timelineStart) / (timelineEnd - timelineStart)) * timelineHeight; |
| | document.getElementById('current-time-indicator').style.top = `${position}px`; |
| | document.getElementById('current-time-indicator').style.display = 'block'; |
| | } else { |
| | document.getElementById('current-time-indicator').style.display = 'none'; |
| | } |
| | } |
| | |
| | function updateDayView() { |
| | const container = document.getElementById('day-schedule'); |
| | const title = document.getElementById('day-title'); |
| | |
| | |
| | const today = new Date(); |
| | title.textContent = today.toLocaleDateString('es-ES', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); |
| | |
| | |
| | container.innerHTML = ''; |
| | |
| | |
| | const todayStr = today.toISOString().split('T')[0]; |
| | const todayTasks = tasks.filter(task => task.startDate === todayStr); |
| | |
| | if (todayTasks.length === 0) { |
| | container.innerHTML = '<tr><td colspan="2" class="text-center py-4">No hay tareas programadas para hoy.</td></tr>'; |
| | return; |
| | } |
| | |
| | |
| | const hours = Array.from({ length: 24 }, (_, i) => i); |
| | |
| | hours.forEach(hour => { |
| | const hourTasks = todayTasks.filter(task => { |
| | if (!task.startTime) return false; |
| | const taskHour = parseInt(task.startTime.split(':')[0]); |
| | return taskHour === hour; |
| | }); |
| | |
| | if (hourTasks.length > 0) { |
| | const row = document.createElement('tr'); |
| | |
| | |
| | const hourCell = document.createElement('td'); |
| | hourCell.textContent = `${hour}:00`; |
| | row.appendChild(hourCell); |
| | |
| | |
| | const tasksCell = document.createElement('td'); |
| | |
| | hourTasks.forEach(task => { |
| | const taskElement = document.createElement('div'); |
| | taskElement.className = 'mb-2'; |
| | |
| | |
| | let priorityDot; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityDot = 'bg-success'; |
| | break; |
| | case 'medium': |
| | priorityDot = 'bg-warning'; |
| | break; |
| | case 'high': |
| | priorityDot = 'bg-danger'; |
| | break; |
| | } |
| | |
| | taskElement.innerHTML = ` |
| | <div class="d-flex align-items-center"> |
| | <span class="task-dot ${priorityDot}"></span> |
| | <span>${task.name}</span> |
| | <button class="btn btn-sm btn-outline-primary ms-auto" onclick="showTaskDetails('${task.id}')">Ver</button> |
| | </div> |
| | `; |
| | |
| | tasksCell.appendChild(taskElement); |
| | }); |
| | |
| | row.appendChild(tasksCell); |
| | container.appendChild(row); |
| | } |
| | }); |
| | } |
| | |
| | function updateWeekView() { |
| | const title = document.getElementById('week-title'); |
| | const now = new Date(); |
| | const startOfWeek = getStartOfWeek(now); |
| | |
| | title.textContent = `Semana del ${startOfWeek.toLocaleDateString()} al ${new Date(startOfWeek.getTime() + 6 * 24 * 60 * 60 * 1000).toLocaleDateString()}`; |
| | |
| | |
| | for (let i = 0; i < 7; i++) { |
| | const day = new Date(startOfWeek.getTime() + i * 24 * 60 * 60 * 1000); |
| | const dayStr = day.toISOString().split('T')[0]; |
| | const dayElement = document.getElementById(`week-day-${i}`); |
| | |
| | |
| | dayElement.innerHTML = ''; |
| | |
| | |
| | const header = document.createElement('div'); |
| | header.className = 'calendar-day-header'; |
| | header.textContent = day.toLocaleDateString('es-ES', { weekday: 'short', day: 'numeric' }); |
| | dayElement.appendChild(header); |
| | |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | if (dayStr === today) { |
| | dayElement.classList.add('today'); |
| | } else { |
| | dayElement.classList.remove('today'); |
| | } |
| | |
| | |
| | const dayTasks = tasks.filter(task => task.startDate === dayStr); |
| | |
| | if (dayTasks.length === 0) { |
| | const noTasks = document.createElement('div'); |
| | noTasks.className = 'text-muted small'; |
| | noTasks.textContent = 'Sin tareas'; |
| | dayElement.appendChild(noTasks); |
| | continue; |
| | } |
| | |
| | |
| | dayTasks.forEach(task => { |
| | const taskElement = document.createElement('div'); |
| | taskElement.className = 'small mb-1'; |
| | |
| | |
| | let priorityDot; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityDot = 'bg-success'; |
| | break; |
| | case 'medium': |
| | priorityDot = 'bg-warning'; |
| | break; |
| | case 'high': |
| | priorityDot = 'bg-danger'; |
| | break; |
| | } |
| | |
| | taskElement.innerHTML = ` |
| | <span class="task-dot ${priorityDot}"></span> |
| | ${task.name} |
| | `; |
| | |
| | dayElement.appendChild(taskElement); |
| | }); |
| | } |
| | } |
| | |
| | function updateCalendarView() { |
| | const title = document.getElementById('calendar-title'); |
| | const now = new Date(); |
| | const month = now.getMonth(); |
| | const year = now.getFullYear(); |
| | |
| | title.textContent = now.toLocaleDateString('es-ES', { month: 'long', year: 'numeric' }); |
| | |
| | |
| | const firstDay = new Date(year, month, 1); |
| | const lastDay = new Date(year, month + 1, 0); |
| | |
| | |
| | const daysInMonth = lastDay.getDate(); |
| | |
| | |
| | const startingDay = firstDay.getDay(); |
| | |
| | |
| | const calendarBody = document.getElementById('calendar-body'); |
| | calendarBody.innerHTML = ''; |
| | |
| | let date = 1; |
| | let row; |
| | |
| | |
| | for (let i = 0; i < 6; i++) { |
| | |
| | if (date > daysInMonth) break; |
| | |
| | |
| | row = document.createElement('tr'); |
| | |
| | |
| | for (let j = 0; j < 7; j++) { |
| | const cell = document.createElement('td'); |
| | |
| | |
| | if (i === 0 && j < startingDay) { |
| | cell.className = 'calendar-day'; |
| | row.appendChild(cell); |
| | continue; |
| | } |
| | |
| | |
| | if (date > daysInMonth) { |
| | cell.className = 'calendar-day'; |
| | row.appendChild(cell); |
| | continue; |
| | } |
| | |
| | |
| | cell.className = 'calendar-day'; |
| | |
| | |
| | const today = new Date(); |
| | if (date === today.getDate() && month === today.getMonth() && year === today.getFullYear()) { |
| | cell.classList.add('today'); |
| | } |
| | |
| | |
| | const dayHeader = document.createElement('div'); |
| | dayHeader.className = 'calendar-day-header'; |
| | dayHeader.textContent = date; |
| | cell.appendChild(dayHeader); |
| | |
| | |
| | const dayStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(date).padStart(2, '0')}`; |
| | const dayTasks = tasks.filter(task => task.startDate === dayStr); |
| | |
| | |
| | dayTasks.forEach(task => { |
| | const taskElement = document.createElement('div'); |
| | taskElement.className = 'small mb-1'; |
| | |
| | |
| | let priorityDot; |
| | switch(task.priority) { |
| | case 'low': |
| | priorityDot = 'bg-success'; |
| | break; |
| | case 'medium': |
| | priorityDot = 'bg-warning'; |
| | break; |
| | case 'high': |
| | priorityDot = 'bg-danger'; |
| | break; |
| | } |
| | |
| | taskElement.innerHTML = ` |
| | <span class="task-dot ${priorityDot}"></span> |
| | ${task.name} |
| | `; |
| | |
| | cell.appendChild(taskElement); |
| | }); |
| | |
| | row.appendChild(cell); |
| | date++; |
| | } |
| | |
| | calendarBody.appendChild(row); |
| | } |
| | } |
| | |
| | function updateMetricsView() { |
| | |
| | |
| | |
| | |
| | const currentWeek = getWeekNumber(new Date()); |
| | const weekTasks = tasks.filter(task => { |
| | const taskDate = new Date(task.startDate); |
| | return getWeekNumber(taskDate) === currentWeek; |
| | }); |
| | |
| | const completedThisWeek = weekTasks.filter(task => task.status === 'completed').length; |
| | |
| | |
| | const priorityCounts = { |
| | low: tasks.filter(task => task.priority === 'low').length, |
| | medium: tasks.filter(task => task.priority === 'medium').length, |
| | high: tasks.filter(task => task.priority === 'high').length |
| | }; |
| | |
| | |
| | |
| | console.log('Metrics updated:', { |
| | completedThisWeek, |
| | priorityCounts |
| | }); |
| | } |
| | |
| | |
| | function startPomodoro() { |
| | document.getElementById('start-btn').disabled = true; |
| | document.getElementById('stop-btn').disabled = false; |
| | |
| | pomodoroInterval = setInterval(() => { |
| | pomodoroTime--; |
| | updatePomodoroTimer(); |
| | |
| | if (pomodoroTime <= 0) { |
| | clearInterval(pomodoroInterval); |
| | pomodoroSessionCompleted(); |
| | } |
| | }, 1000); |
| | } |
| | |
| | function stopPomodoro() { |
| | clearInterval(pomodoroInterval); |
| | document.getElementById('start-btn').disabled = false; |
| | document.getElementById('stop-btn').disabled = true; |
| | } |
| | |
| | function resetPomodoro() { |
| | stopPomodoro(); |
| | pomodoroTime = pomodoroSession === 'work' ? 25 * 60 : 5 * 60; |
| | updatePomodoroTimer(); |
| | } |
| | |
| | function pomodoroSessionCompleted() { |
| | pomodoroSessionsCompleted++; |
| | |
| | if (pomodoroSession === 'work') { |
| | |
| | pomodoroSession = 'break'; |
| | pomodoroTime = 5 * 60; |
| | document.getElementById('pomodoro-session').textContent = 'Descanso'; |
| | alert('¡Sesión de trabajo completada! Toma un descanso de 5 minutos.'); |
| | } else { |
| | |
| | pomodoroSession = 'work'; |
| | pomodoroTime = 25 * 60; |
| | document.getElementById('pomodoro-session').textContent = 'Sesión de Trabajo'; |
| | alert('¡Descanso completado! Volvamos al trabajo.'); |
| | } |
| | |
| | updateStats(); |
| | startPomodoro(); |
| | } |
| | |
| | function updatePomodoroTimer() { |
| | const minutes = Math.floor(pomodoroTime / 60); |
| | const seconds = pomodoroTime % 60; |
| | document.getElementById('pomodoro-timer').textContent = |
| | `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; |
| | } |
| | |
| | |
| | function updateCurrentTime() { |
| | const now = new Date(); |
| | document.getElementById('current-time').textContent = now.toLocaleTimeString(); |
| | |
| | |
| | if (currentView === 'timeline') { |
| | updateCurrentTimeIndicator(); |
| | } |
| | } |
| | |
| | function getStartOfWeek(date) { |
| | const d = new Date(date); |
| | const day = d.getDay(); |
| | const diff = d.getDate() - day + (day === 0 ? -6 : 1); |
| | return new Date(d.setDate(diff)); |
| | } |
| | |
| | function getWeekNumber(date) { |
| | const d = new Date(date); |
| | d.setHours(0, 0, 0, 0); |
| | d.setDate(d.getDate() + 3 - (d.getDay() + 6) % 7); |
| | const week1 = new Date(d.getFullYear(), 0, 4); |
| | return 1 + Math.round(((d - week1) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7); |
| | } |
| | |
| | function toggleDarkMode() { |
| | darkMode = document.getElementById('darkModeSwitch').checked; |
| | document.body.classList.toggle('dark-mode', darkMode); |
| | localStorage.setItem('darkMode', darkMode); |
| | } |
| | |
| | function updateStats() { |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | const completedToday = tasks.filter(task => |
| | task.startDate === today && task.status === 'completed' |
| | ).length; |
| | |
| | document.getElementById('completed-today').textContent = completedToday; |
| | |
| | |
| | document.getElementById('pomodoro-sessions').textContent = pomodoroSessionsCompleted; |
| | |
| | |
| | document.getElementById('time-worked').textContent = `${Math.floor(pomodoroSessionsCompleted * 25 / 60)}h ${pomodoroSessionsCompleted * 25 % 60}m`; |
| | } |
| | |
| | function changeDay(offset) { |
| | |
| | console.log('Day changed by', offset); |
| | updateDayView(); |
| | } |
| | |
| | function changeWeek(offset) { |
| | |
| | console.log('Week changed by', offset); |
| | updateWeekView(); |
| | } |
| | |
| | function changeMonth(offset) { |
| | |
| | console.log('Month changed by', offset); |
| | updateCalendarView(); |
| | } |
| | |
| | function changeTimelineRange(range) { |
| | |
| | console.log('Timeline range changed to', range); |
| | updateTimelineView(); |
| | } |
| | |
| | function changeMetricsRange(range) { |
| | |
| | console.log('Metrics range changed to', range); |
| | updateMetricsView(); |
| | } |
| | |
| | |
| | function loadProfile() { |
| | const profile = JSON.parse(localStorage.getItem('profile')) || { |
| | name: 'Usuario', |
| | email: 'usuario@example.com', |
| | darkMode: false |
| | }; |
| | |
| | document.getElementById('profile-name').value = profile.name; |
| | document.getElementById('profile-email').value = profile.email; |
| | document.getElementById('profile-dark-mode').checked = profile.darkMode; |
| | } |
| | |
| | function saveProfile(e) { |
| | e.preventDefault(); |
| | |
| | const profile = { |
| | name: document.getElementById('profile-name').value, |
| | email: document.getElementById('profile-email').value, |
| | darkMode: document.getElementById('profile-dark-mode').checked |
| | }; |
| | |
| | const password = document.getElementById('profile-password').value; |
| | const confirmPassword = document.getElementById('profile-confirm-password').value; |
| | |
| | if (password && password !== confirmPassword) { |
| | alert('Las contraseñas no coinciden.'); |
| | return; |
| | } |
| | |
| | if (password) { |
| | profile.password = password; |
| | } |
| | |
| | localStorage.setItem('profile', JSON.stringify(profile)); |
| | |
| | |
| | if (profile.darkMode !== darkMode) { |
| | darkMode = profile.darkMode; |
| | document.getElementById('darkModeSwitch').checked = darkMode; |
| | document.body.classList.toggle('dark-mode', darkMode); |
| | } |
| | |
| | alert('Perfil actualizado correctamente.'); |
| | } |
| | |
| | function logout() { |
| | |
| | alert('Sesión cerrada (simulado)'); |
| | } |
| | </script> |
| | <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=butztub/timetimer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| | </html> |