feat(ui): routines tab editor
This commit is contained in:
+119
-3
@@ -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 & 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();
|
||||
|
||||
Reference in New Issue
Block a user