From ff9a71178a2033399857e8eed683e52ddffa3cb8 Mon Sep 17 00:00:00 2001 From: Tatiana Villa Date: Thu, 14 May 2026 12:27:16 +0200 Subject: [PATCH] Actualizacion de temario y audios --- src/main/resources/static/css/planning.css | 116 +++++++++++++ src/main/resources/static/js/planning.js | 192 +++++++++++++++++++++ src/main/resources/templates/planning.html | 5 +- 3 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/static/js/planning.js diff --git a/src/main/resources/static/css/planning.css b/src/main/resources/static/css/planning.css index 6b9be57..e4b6568 100644 --- a/src/main/resources/static/css/planning.css +++ b/src/main/resources/static/css/planning.css @@ -75,6 +75,119 @@ h1 { font-size: 18pt; text-align: center; margin-bottom: 0.2em; color: var(--acc .leyenda-item { display: flex; align-items: center; gap: 5px; } .leyenda-color { width: 14px; height: 14px; border: 1px solid var(--border); flex-shrink: 0; } +/* ── Hoy ────────────────────────────────────────────────── */ +.dia.hoy { + border-color: var(--accent-2) !important; + box-shadow: 0 0 0 2px rgba(78,201,176,.25); +} +.dia.hoy > .num { color: var(--accent-2) !important; } +.hoy-label { + display: inline-block; + font-size: 7pt; font-weight: bold; letter-spacing: .06em; + color: var(--accent-2); + background: rgba(78,201,176,.12); + border: 1px solid var(--accent-2); + border-radius: 3px; + padding: 0 4px; + margin-left: 4px; margin-bottom: 4px; + vertical-align: middle; +} + +/* ── Filas de tema ──────────────────────────────────────── */ +.tema-row { + display: flex; + align-items: flex-start; + gap: 2px; + margin-bottom: 2px; +} +.tema-row .tema-btn { flex: 1; margin-bottom: 0; } + +/* ── Botón check ────────────────────────────────────────── */ +.check-btn { + flex-shrink: 0; + width: 15px; height: 15px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--text-muted); + cursor: pointer; + font-size: 8pt; line-height: 1; + border-radius: 3px; + display: flex; align-items: center; justify-content: center; + padding: 0; margin-top: 1px; + transition: background .1s, color .1s, border-color .1s; +} +.check-btn:hover { border-color: var(--accent-2); color: var(--accent-2); } +.check-btn.checked { background: var(--accent-2); color: #111; border-color: var(--accent-2); } + +/* ── Botón mover ────────────────────────────────────────── */ +.move-btn { + flex-shrink: 0; + width: 14px; height: 14px; + border: none; background: transparent; + color: var(--text-muted); cursor: pointer; + font-size: 9pt; line-height: 1; + padding: 0; margin-top: 1px; + opacity: 0; transition: opacity .15s; +} +.tema-row:hover .move-btn { opacity: 1; } +.move-btn:hover { color: var(--warning); } + +/* ── Completado ─────────────────────────────────────────── */ +.tema-btn.completado { + text-decoration: line-through; + color: var(--text-muted) !important; + opacity: .45; +} + +/* ── Atrasado ───────────────────────────────────────────── */ +.tema-row.atrasado .tema-btn { color: var(--error) !important; } +.tema-row.atrasado .tema-btn::before { content: '⚠ '; } +.overdue-badge { + display: inline-block; + font-size: 7pt; font-weight: bold; + background: var(--error); color: #fff; + padding: 1px 6px; border-radius: 8px; + margin-bottom: 4px; margin-top: 1px; + -webkit-print-color-adjust: exact; print-color-adjust: exact; +} + +/* ── Picker de día ──────────────────────────────────────── */ +.move-picker { + position: absolute; right: 0; top: 100%; + z-index: 200; + background: var(--bg-alt); + border: 1px solid var(--border); + border-radius: 4px; + padding: 5px; + display: flex; flex-direction: column; gap: 2px; + min-width: 130px; + box-shadow: 0 4px 14px rgba(0,0,0,.55); +} +.mp-label { + font-size: 8pt; color: var(--text-muted); + padding: 0 3px 3px; border-bottom: 1px solid var(--border); + margin-bottom: 2px; +} +.move-picker button { + background: var(--bg); border: 1px solid var(--border); + color: var(--text); font-size: 8pt; padding: 3px 8px; + text-align: left; cursor: pointer; border-radius: 3px; + font-family: inherit; +} +.move-picker button:hover { background: var(--bg-hover); color: var(--accent-2); } +.move-picker .mp-today { border-color: var(--accent-2); color: var(--accent-2); } +.move-picker .mp-reset { color: var(--text-muted); font-size: 7.5pt; margin-top: 2px; } +.move-picker .mp-reset:hover { color: var(--error); } + +/* ── Botón reset planning ───────────────────────────────── */ +.btn-reset-planning { + background: none; border: none; cursor: pointer; + color: var(--text-muted); font-size: 10pt; + padding: 0 4px; vertical-align: middle; line-height: 1; + margin-left: 6px; transition: color .15s; +} +.btn-reset-planning:hover { color: var(--warning); } + @media print { body { background: #fff; color: #111; padding: 0.8cm 1cm; } .dia { background: #fff !important; border-color: #ccc !important; } @@ -84,4 +197,7 @@ h1 { font-size: 18pt; text-align: center; margin-bottom: 0.2em; color: var(--acc .tema-btn { color: #111; } .semana { page-break-after: always; break-after: page; } .semana:last-of-type { page-break-after: auto; } + .check-btn, .move-btn, .move-picker, .overdue-badge, + .hoy-label, .btn-reset-planning { display: none !important; } + .tema-btn.completado { text-decoration: none; opacity: 1; color: inherit !important; } } diff --git a/src/main/resources/static/js/planning.js b/src/main/resources/static/js/planning.js new file mode 100644 index 0000000..1449b1c --- /dev/null +++ b/src/main/resources/static/js/planning.js @@ -0,0 +1,192 @@ +'use strict'; +(function () { + + /* ── Configuración ─────────────────────────────────────── */ + const YEAR = 2026; + const MONTH = 4; // Mayo (0-based) + const USERNAME = document.querySelector('.user-email')?.textContent.trim() || 'guest'; + const KEY = `planning_${YEAR}_${USERNAME}`; + + /* ── Persistencia ──────────────────────────────────────── */ + function load() { + try { + const s = JSON.parse(localStorage.getItem(KEY)); + return { completed: s?.completed || [], moved: s?.moved || {} }; + } catch { return { completed: [], moved: {} }; } + } + function save(s) { localStorage.setItem(KEY, JSON.stringify(s)); } + + /* ── Fecha actual ──────────────────────────────────────── */ + const now = new Date(); + const todayDay = (now.getFullYear() === YEAR && now.getMonth() === MONTH) + ? now.getDate() : null; + + /* ── Mapa de días ──────────────────────────────────────── */ + // dayMap: { 8: diaDom, 9: diaDom, … } + const dayMap = {}; + document.querySelectorAll('.dia').forEach(dia => { + const numEl = dia.querySelector(':scope > .num'); + if (!numEl) return; + const n = parseInt(numEl.textContent.trim(), 10); + if (!isNaN(n)) dayMap[n] = dia; + }); + + /* ── Marcar hoy ────────────────────────────────────────── */ + if (todayDay && dayMap[todayDay]) { + const d = dayMap[todayDay]; + d.classList.add('hoy'); + const numEl = d.querySelector(':scope > .num'); + if (numEl) numEl.insertAdjacentHTML('afterend', 'HOY'); + } + + /* ── Envolver .tema-btn en .tema-row ───────────────────── */ + document.querySelectorAll('.tema-btn').forEach(btn => { + const row = document.createElement('div'); + row.className = 'tema-row'; + btn.before(row); + row.appendChild(btn); + }); + + /* ── Aplicar temas movidos al cargar ───────────────────── */ + const state = load(); + Object.entries(state.moved).forEach(([href, dayNum]) => { + const btn = [...document.querySelectorAll('.tema-btn')] + .find(b => b.getAttribute('href') === href); + const row = btn?.closest('.tema-row'); + const contenido = dayMap[Number(dayNum)]?.querySelector('.contenido'); + if (row && contenido && !contenido.contains(row)) { + contenido.appendChild(row); + } + }); + + /* ── Añadir controles a cada fila ──────────────────────── */ + const completedSet = new Set(state.completed); + + document.querySelectorAll('.tema-row').forEach(row => { + const btn = row.querySelector('.tema-btn'); + if (!btn) return; + const id = btn.getAttribute('href'); + const done = completedSet.has(id); + + // ── Botón ✓ / ○ ── + const chk = document.createElement('button'); + chk.className = 'check-btn' + (done ? ' checked' : ''); + chk.title = done ? 'Marcar como pendiente' : 'Marcar como hecho'; + chk.textContent = done ? '✓' : '○'; + row.insertBefore(chk, btn); + if (done) btn.classList.add('completado'); + + chk.addEventListener('click', e => { + e.preventDefault(); + const s = load(); + const set = new Set(s.completed); + const nowDone = !set.has(id); + nowDone ? set.add(id) : set.delete(id); + s.completed = [...set]; + save(s); + chk.classList.toggle('checked', nowDone); + chk.textContent = nowDone ? '✓' : '○'; + btn.classList.toggle('completado', nowDone); + updateOverdue(); + }); + + // ── Botón mover ── + const mv = document.createElement('button'); + mv.className = 'move-btn'; + mv.title = 'Mover a otro día'; + mv.textContent = '⇌'; + row.appendChild(mv); + mv.addEventListener('click', e => { e.preventDefault(); showPicker(row, id); }); + }); + + /* ── Picker de día ─────────────────────────────────────── */ + function showPicker(row, id) { + document.querySelectorAll('.move-picker').forEach(p => p.remove()); + const picker = document.createElement('div'); + picker.className = 'move-picker'; + picker.insertAdjacentHTML('afterbegin', '
Mover a:
'); + + const days = Object.keys(dayMap).map(Number) + .filter(d => !dayMap[d].classList.contains('vacio') + && !dayMap[d].classList.contains('examen')) + .sort((a, b) => a - b); + + days.forEach(d => { + const b = document.createElement('button'); + b.textContent = `Día ${d}` + (d === todayDay ? ' · hoy' : ''); + if (d === todayDay) b.classList.add('mp-today'); + b.addEventListener('click', e => { + e.stopPropagation(); + const s = load(); + s.moved[id] = d; + save(s); + const c = dayMap[d]?.querySelector('.contenido'); + if (c && !c.contains(row)) c.appendChild(row); + picker.remove(); + updateOverdue(); + }); + picker.appendChild(b); + }); + + const rst = document.createElement('button'); + rst.className = 'mp-reset'; + rst.textContent = '↺ Restaurar original'; + rst.addEventListener('click', e => { + e.stopPropagation(); + const s = load(); + delete s.moved[id]; + save(s); + location.reload(); + }); + picker.appendChild(rst); + + row.style.position = 'relative'; + row.appendChild(picker); + + setTimeout(() => { + document.addEventListener('click', function h(e) { + if (!picker.contains(e.target)) { + picker.remove(); + document.removeEventListener('click', h); + } + }); + }, 0); + } + + /* ── Indicadores de atrasados ──────────────────────────── */ + function updateOverdue() { + const s = load(); + const done = new Set(s.completed); + Object.entries(dayMap).forEach(([dayStr, dia]) => { + const d = Number(dayStr); + dia.querySelectorAll('.tema-row').forEach(row => { + const id = row.querySelector('.tema-btn')?.getAttribute('href'); + const overdue = !!id && todayDay != null && d < todayDay && !done.has(id); + row.classList.toggle('atrasado', overdue); + }); + const n = dia.querySelectorAll('.tema-row.atrasado').length; + let badge = dia.querySelector('.overdue-badge'); + if (n > 0) { + if (!badge) { + badge = document.createElement('span'); + badge.className = 'overdue-badge'; + dia.querySelector(':scope > .num')?.insertAdjacentElement('afterend', badge); + } + badge.textContent = `${n} atrasado${n > 1 ? 's' : ''}`; + } else if (badge) { + badge.remove(); + } + }); + } + + updateOverdue(); + + /* ── Reiniciar progreso ────────────────────────────────── */ + document.getElementById('btn-reset-planning')?.addEventListener('click', () => { + if (confirm('¿Reiniciar todo el progreso guardado?')) { + localStorage.removeItem(KEY); + location.reload(); + } + }); + +})(); diff --git a/src/main/resources/templates/planning.html b/src/main/resources/templates/planning.html index 4a1a71d..016fc96 100644 --- a/src/main/resources/templates/planning.html +++ b/src/main/resources/templates/planning.html @@ -33,7 +33,9 @@

Planning de repaso TAI

-

Mayo 2026 · Examen: sábado 23 de mayo

+

Mayo 2026 · Examen: sábado 23 de mayo + +

@@ -179,5 +181,6 @@
Fin de semana
+ \ No newline at end of file