Actualizacion de temario y audios
This commit is contained in:
parent
4fbcee2f66
commit
ff9a71178a
|
|
@ -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-item { display: flex; align-items: center; gap: 5px; }
|
||||||
.leyenda-color { width: 14px; height: 14px; border: 1px solid var(--border); flex-shrink: 0; }
|
.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 {
|
@media print {
|
||||||
body { background: #fff; color: #111; padding: 0.8cm 1cm; }
|
body { background: #fff; color: #111; padding: 0.8cm 1cm; }
|
||||||
.dia { background: #fff !important; border-color: #ccc !important; }
|
.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; }
|
.tema-btn { color: #111; }
|
||||||
.semana { page-break-after: always; break-after: page; }
|
.semana { page-break-after: always; break-after: page; }
|
||||||
.semana:last-of-type { page-break-after: auto; }
|
.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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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', '<span class="hoy-label">HOY</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 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', '<div class="mp-label">Mover a:</div>');
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -33,7 +33,9 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h1>Planning de repaso TAI</h1>
|
<h1>Planning de repaso TAI</h1>
|
||||||
<p class="subtitle">Mayo 2026 · Examen: <strong>sábado 23 de mayo</strong></p>
|
<p class="subtitle">Mayo 2026 · Examen: <strong>sábado 23 de mayo</strong>
|
||||||
|
<button id="btn-reset-planning" class="btn-reset-planning" title="Reiniciar progreso guardado">⚙</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- SEMANA 1: 5–11 mayo -->
|
<!-- SEMANA 1: 5–11 mayo -->
|
||||||
<section class="semana">
|
<section class="semana">
|
||||||
|
|
@ -179,5 +181,6 @@
|
||||||
<div class="leyenda-item"><div class="leyenda-color" style="background:#1a2a35;-webkit-print-color-adjust:exact;print-color-adjust:exact;"></div> Fin de semana</div>
|
<div class="leyenda-item"><div class="leyenda-color" style="background:#1a2a35;-webkit-print-color-adjust:exact;print-color-adjust:exact;"></div> Fin de semana</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script th:src="@{/js/planning.js}" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue