(() => { const STORAGE_KEY = "phenikaax_dashboard_v1"; function widgetTypeLabel(type) { return t(`dashboard.widget.${type}`) || type; } const el = (id) => document.getElementById(id); const t = (key, vars) => window.I18n?.t(key, vars) ?? key; const gridEl = el("dashboardGrid"); const emptyEl = el("dashboardEmpty"); const addDialogEl = el("dashboardAddWidgetDialog"); const editDialogEl = el("dashboardEditWidgetDialog"); const addTypeEl = el("dashboardWidgetType"); const addFieldsEl = el("dashboardAddWidgetFields"); const editFieldsEl = el("dashboardEditWidgetFields"); const editWidgetIdEl = el("dashboardEditWidgetId"); const editWidgetTypeEl = el("dashboardEditWidgetType"); const store = { widgets: [], editMode: false, pollActive: false, queueUnsub: null, }; function newId() { if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID(); return `w_${Date.now().toString(36)}`; } function missions() { return window.MissionsApp || null; } function loadStore() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) { bootstrapDefaults(); return; } const data = JSON.parse(raw); store.widgets = Array.isArray(data.widgets) ? data.widgets : []; if (!store.widgets.length) bootstrapDefaults(); } catch { bootstrapDefaults(); } } function persistStore() { localStorage.setItem(STORAGE_KEY, JSON.stringify({ widgets: store.widgets })); } function bootstrapDefaults() { const m = missions()?.getMissions?.() || []; const firstId = m[0]?.id || ""; store.widgets = [ { id: newId(), type: "mission_button", mission_id: firstId, title: "" }, { id: newId(), type: "mission_group", group: "Missions", title: "" }, { id: newId(), type: "mission_queue", title: "" }, { id: newId(), type: "pause_continue", title: "" }, ]; persistStore(); } function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function widgetTitle(widget) { if (widget.title) return widget.title; return widgetTypeLabel(widget.type); } function missionOptions(selected) { const list = missions()?.getMissions?.() || []; return list .map( (m) => `` ) .join(""); } function groupOptions(selected) { const groups = missions()?.getGroups?.() || ["Missions"]; return groups .map((g) => ``) .join(""); } function fillTypeFields(container, type, widget = {}) { if (!container) return; container.innerHTML = ""; if (type === "mission_button") { container.innerHTML = `
`; } else if (type === "mission_group") { container.innerHTML = `
`; } else if (type === "mission_queue") { container.innerHTML = `
`; } else if (type === "pause_continue") { container.innerHTML = `

Tạm dừng / tiếp tục / hủy mission đang chạy trên robot.

`; } } function readFields(container) { const out = {}; container?.querySelectorAll("[data-field]").forEach((node) => { out[node.dataset.field] = node.value; }); return out; } function renderMissionButtonWidget(widget, bodyEl) { const m = missions()?.getMissionById?.(widget.mission_id); const label = m?.name || t("dashboard.widget.selectMission"); bodyEl.innerHTML = ` ${!m ? `

${t("dashboard.widget.configHint")}

` : ""}`; bodyEl.querySelector("[data-run-mission]")?.addEventListener("click", () => { if (!widget.mission_id) return; missions()?.queueMission?.(widget.mission_id); }); } function renderMissionGroupWidget(widget, bodyEl) { const group = widget.group || "Missions"; const list = (missions()?.getMissions?.() || []).filter((m) => m.group === group); if (!list.length) { bodyEl.innerHTML = `

${t("dashboard.widget.emptyGroup", { group })}

`; return; } bodyEl.innerHTML = `
`; const listEl = bodyEl.querySelector(".dashboardMissionGroupList"); list.forEach((m) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "dashboardMissionGroupBtn"; btn.innerHTML = `${escapeHtml(m.name)}`; btn.addEventListener("click", () => missions()?.queueMission?.(m.id)); listEl.appendChild(btn); }); } function renderMissionQueueWidget(widget, bodyEl) { bodyEl.innerHTML = `

${t("dashboard.widget.queueEmpty")}

`; bodyEl.querySelector(".dashboardQueueClear")?.addEventListener("click", () => missions()?.clearQueue?.()); refreshQueueWidget(bodyEl); } function refreshQueueWidget(bodyEl) { const snap = missions()?.getQueueSnapshot?.(); if (!snap) return; missions()?.renderQueueInto?.( { listEl: bodyEl.querySelector('[data-role="list"]'), runnerEl: bodyEl.querySelector('[data-role="runner"]'), emptyEl: bodyEl.querySelector('[data-role="empty"]'), }, { compact: true } ); } function renderPauseContinueWidget(widget, bodyEl) { const snap = missions()?.getQueueSnapshot?.(); const state = snap?.runner?.state || "idle"; const paused = state === "paused" || snap?.runner?.paused; const running = state === "running" || paused; bodyEl.innerHTML = `

${running ? (paused ? t("dashboard.widget.runner.paused") : t("dashboard.widget.runner.running")) : t("dashboard.widget.runner.idle")}

`; bodyEl.querySelector("[data-pause-action]")?.addEventListener("click", async (evt) => { const action = evt.currentTarget.dataset.pauseAction; try { if (action === "pause") await missions()?.pauseRunner?.(); else await missions()?.continueRunner?.(); } catch (e) { alert(e.message); } }); bodyEl.querySelector("[data-cancel-mission]")?.addEventListener("click", async () => { try { await missions()?.cancelRunner?.(); } catch (e) { alert(e.message); } }); } function renderWidget(widget) { const card = document.createElement("article"); card.className = `dashboardWidget dashboardWidget--${widget.type}`; card.dataset.widgetId = widget.id; card.innerHTML = `
${escapeHtml(widgetTitle(widget))}
`; const bodyEl = card.querySelector(".dashboardWidgetBody"); switch (widget.type) { case "mission_button": renderMissionButtonWidget(widget, bodyEl); break; case "mission_group": renderMissionGroupWidget(widget, bodyEl); break; case "mission_queue": renderMissionQueueWidget(widget, bodyEl); break; case "pause_continue": renderPauseContinueWidget(widget, bodyEl); break; default: bodyEl.innerHTML = `

Widget không hỗ trợ.

`; } card.querySelector("[data-widget-config]")?.addEventListener("click", () => openEditDialog(widget.id)); card.querySelector("[data-widget-delete]")?.addEventListener("click", () => deleteWidget(widget.id)); return card; } function renderDashboard() { if (!gridEl) return; gridEl.innerHTML = ""; if (emptyEl) emptyEl.hidden = store.widgets.length > 0; gridEl.classList.toggle("dashboardGrid--edit", store.editMode); store.widgets.forEach((w) => gridEl.appendChild(renderWidget(w))); gridEl.querySelectorAll(".dashboardWidgetChrome").forEach((n) => { n.hidden = !store.editMode; }); } function refreshDynamicWidgets() { store.widgets.forEach((widget) => { const card = gridEl?.querySelector(`[data-widget-id="${widget.id}"]`); if (!card) return; const bodyEl = card.querySelector(".dashboardWidgetBody"); if (widget.type === "mission_queue") refreshQueueWidget(bodyEl); if (widget.type === "pause_continue") renderPauseContinueWidget(widget, bodyEl); }); } function openAddDialog() { fillTypeFields(addFieldsEl, addTypeEl.value); addDialogEl.showModal(); } function openEditDialog(widgetId) { const widget = store.widgets.find((w) => w.id === widgetId); if (!widget) return; editWidgetIdEl.value = widget.id; editWidgetTypeEl.value = widgetTypeLabel(widget.type); fillTypeFields(editFieldsEl, widget.type, widget); editDialogEl.showModal(); } function deleteWidget(widgetId) { if (!confirm(t("dashboard.widget.deleteConfirm"))) return; store.widgets = store.widgets.filter((w) => w.id !== widgetId); persistStore(); renderDashboard(); editDialogEl.close(); } function bindEvents() { el("dashboardAddWidgetBtn")?.addEventListener("click", openAddDialog); el("dashboardEditBtn")?.addEventListener("click", () => { store.editMode = !store.editMode; el("dashboardEditBtn").textContent = store.editMode ? t("dashboard.editDone") : t("dashboard.editLayout"); renderDashboard(); }); addTypeEl?.addEventListener("change", () => fillTypeFields(addFieldsEl, addTypeEl.value)); el("dashboardAddWidgetForm")?.addEventListener("submit", (evt) => { evt.preventDefault(); const type = addTypeEl.value; const fields = readFields(addFieldsEl); store.widgets.push({ id: newId(), type, ...fields }); persistStore(); addDialogEl.close(); renderDashboard(); }); el("dashboardEditWidgetForm")?.addEventListener("submit", (evt) => { evt.preventDefault(); const id = editWidgetIdEl.value; const widget = store.widgets.find((w) => w.id === id); if (!widget) return; Object.assign(widget, readFields(editFieldsEl)); persistStore(); editDialogEl.close(); renderDashboard(); }); el("dashboardDeleteWidgetBtn")?.addEventListener("click", () => deleteWidget(editWidgetIdEl.value)); } function startDashboardPoll() { if (window.AuthApp && !window.AuthApp.isReady()) return; stopDashboardPoll(); missions()?.refreshQueue?.(); store.queueUnsub = missions()?.onQueueUpdate?.(() => refreshDynamicWidgets()); missions()?.startQueuePoll?.(); store.pollActive = true; } function stopDashboardPoll() { if (store.pollActive) { missions()?.stopQueuePoll?.(); store.pollActive = false; } if (store.queueUnsub) { store.queueUnsub(); store.queueUnsub = null; } } function init() { loadStore(); bindEvents(); renderDashboard(); } window.DashboardApp = { init, onPageShow() { renderDashboard(); startDashboardPoll(); }, onPageHide() { stopDashboardPoll(); }, refresh() { renderDashboard(); }, }; function boot() { init(); } window.addEventListener("lm:locale-change", () => { renderDashboard(); const editBtn = el("dashboardEditBtn"); if (editBtn) editBtn.textContent = store.editMode ? t("dashboard.editDone") : t("dashboard.editLayout"); }); if (window.AuthApp?.isReady()) boot(); else window.addEventListener("lm:auth-ready", boot, { once: true }); window.addEventListener("lm:auth-logout", stopDashboardPoll); })();