feat(ui): routines tab editor

This commit is contained in:
L'électron rare
2026-06-21 09:46:35 +02:00
parent 831fd3c45e
commit 281f0ddbfe
+119 -3
View File
@@ -134,6 +134,7 @@
<button id="tab-podcasts" class="active" onclick="showTab('podcasts')">🎧 Podcasts</button>
<button id="tab-fichiers" onclick="showTab('fichiers')">📁 Fichiers</button>
<button id="tab-box" onclick="showTab('box')">📦 Box</button>
<button id="tab-routines" onclick="showTab('routines')">🗓️ Routines</button>
</div>
<!-- ===== PODCASTS ===== -->
@@ -185,6 +186,14 @@
</div>
</div>
</section>
<!-- ===== ROUTINES ===== -->
<section id="panel-routines" class="panel">
<div id="routines"><div class="loading"><span class="spinner"></span> chargement…</div></div>
<div class="row" style="margin-top:14px">
<button id="routines-save" class="btn orange" onclick="saveRoutines()">💾 Enregistrer &amp; pousser</button>
</div>
</section>
</div>
<div id="toast"></div>
@@ -246,14 +255,17 @@ function humanSize(n) {
}
// ---------- tabs ----------
const TABS = ["podcasts", "fichiers", "box"];
const TABS = ["podcasts", "fichiers", "box", "routines", "transfers"];
function showTab(name) {
TABS.forEach(function (t) {
$("panel-" + t).classList.toggle("active", t === name);
$("tab-" + t).classList.toggle("active", t === name);
const panel = $("panel-" + t);
const tab = $("tab-" + t);
if (panel) panel.classList.toggle("active", t === name);
if (tab) tab.classList.toggle("active", t === name);
});
if (name === "fichiers") loadFiles();
if (name === "box") loadBox();
if (name === "routines") loadRoutines();
}
// ---------- Podcasts ----------
@@ -524,6 +536,110 @@ async function pushNow() {
}
}
// ---------- Routines ----------
const EMOJI_PALETTE = ["☀️","🌙","👕","🥣","🦷","🎒","🧸","🛁","👖","🧦","🪥","📖","🚽","🍽️","💤","🧼","👟","🧥"];
let ROUTINES = []; // editable in-memory copy of /api/routines
function routineCard(r, ri) {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = '<h3>' + esc(r.emoji || "") + ' ' + esc(r.title || r.id) + '</h3>'
+ '<div class="steps-host"></div>'
+ '<div class="row" style="margin-top:10px"><button class="btn ghost add-step">+ Ajouter une étape</button></div>';
const host = card.querySelector(".steps-host");
(r.steps || []).forEach(function (st, si) { host.appendChild(stepRow(ri, si)); });
card.querySelector(".add-step").onclick = function () {
ROUTINES[ri].steps.push({ text: "", emoji: "🧸" });
loadRoutines(true);
};
return card;
}
function stepRow(ri, si) {
const st = ROUTINES[ri].steps[si];
const li = document.createElement("div");
li.className = "row";
li.style.borderTop = "1px solid var(--line)";
li.style.padding = "8px 0";
// emoji select
let opts = "";
const inPalette = EMOJI_PALETTE.indexOf(st.emoji) !== -1;
EMOJI_PALETTE.forEach(function (e) {
opts += '<option value="' + esc(e) + '"' + (e === st.emoji ? " selected" : "") + '>' + esc(e) + '</option>';
});
if (!inPalette && st.emoji) opts += '<option value="' + esc(st.emoji) + '" selected>' + esc(st.emoji) + '</option>';
li.innerHTML =
'<select class="st-emoji" style="flex:none;font-size:1.4rem;padding:8px;border-radius:12px;border:2px solid var(--line);background:#fbf8ff">' + opts + '</select>'
+ '<input type="text" class="st-text grow" value="' + esc(st.text) + '" placeholder="Texte de l\'étape">'
+ '<button class="icon st-up" title="Monter">⬆️</button>'
+ '<button class="icon st-down" title="Descendre">⬇️</button>'
+ '<button class="icon st-del" title="Supprimer">🗑</button>';
li.querySelector(".st-text").oninput = function (e) { ROUTINES[ri].steps[si].text = e.target.value; };
li.querySelector(".st-emoji").onchange = function (e) { ROUTINES[ri].steps[si].emoji = e.target.value; };
li.querySelector(".st-up").onclick = function () { moveStep(ri, si, -1); };
li.querySelector(".st-down").onclick = function () { moveStep(ri, si, 1); };
li.querySelector(".st-del").onclick = function () { ROUTINES[ri].steps.splice(si, 1); loadRoutines(true); };
return li;
}
function moveStep(ri, si, d) {
const s = ROUTINES[ri].steps;
const j = si + d;
if (j < 0 || j >= s.length) return;
const t = s[si]; s[si] = s[j]; s[j] = t;
loadRoutines(true);
}
async function loadRoutines(fromMemory) {
const box = $("routines");
if (!fromMemory) {
box.innerHTML = '<div class="loading"><span class="spinner"></span> chargement…</div>';
try {
clearBanner();
const data = await api("/api/routines");
ROUTINES = (data && data.routines) || [];
} catch (e) {
box.innerHTML = '<div class="empty">Impossible de charger les routines.</div>';
showBanner("Routines: " + e.message);
return;
}
}
box.innerHTML = "";
ROUTINES.forEach(function (r, ri) { box.appendChild(routineCard(r, ri)); });
}
async function saveRoutines() {
const btn = $("routines-save");
btn.disabled = true;
const old = btn.textContent;
btn.textContent = "Enregistrement…";
try {
await api("/api/routines", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ routines: ROUTINES }),
});
toast("Routines enregistrées — génération en cours…");
// give the server a moment to regenerate assets, then push
setTimeout(async function () {
try {
const r = await api("/api/routines/push", { method: "POST" });
if (r && r.started) toast("Envoi des routines démarré (" + (r.delta || 0) + " fichier(s))");
else toast("Routines prêtes (rien à pousser ou box absente)");
} catch (e) {
if (e.status === 409) toast("La box ne s'est jamais annoncée", true);
else { toast("Erreur push: " + e.message, true); showBanner("Push routines: " + e.message); }
}
}, 6000);
} catch (e) {
toast("Erreur: " + e.message, true);
showBanner("Enregistrer routines: " + e.message);
} finally {
btn.disabled = false;
btn.textContent = old;
}
}
// ---------- init ----------
window.addEventListener("DOMContentLoaded", function () {
loadFeeds();