Add function Language
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-16 16:44:04 +07:00
parent 1156e1ab29
commit a2e87aeb29
11 changed files with 1790 additions and 474 deletions

View File

@@ -1,14 +1,12 @@
(() => {
const STORAGE_KEY = "phenikaax_dashboard_v1";
const WIDGET_LABELS = {
mission_button: "Mission button",
mission_group: "Mission group",
mission_queue: "Mission queue",
pause_continue: "Pause / Continue",
};
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");
@@ -60,7 +58,7 @@
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: "Mission queue" },
{ id: newId(), type: "mission_queue", title: "" },
{ id: newId(), type: "pause_continue", title: "" },
];
persistStore();
@@ -76,7 +74,7 @@
function widgetTitle(widget) {
if (widget.title) return widget.title;
return WIDGET_LABELS[widget.type] || widget.type;
return widgetTypeLabel(widget.type);
}
function missionOptions(selected) {
@@ -102,33 +100,33 @@
if (type === "mission_button") {
container.innerHTML = `
<div class="row rowWide">
<label>Mission</label>
<label>${t("dashboard.widget.field.mission")}</label>
<select data-field="mission_id">${missionOptions(widget.mission_id || "")}</select>
</div>
<div class="row rowWide">
<label>Tiêu đề widget (tùy chọn)</label>
<input data-field="title" type="text" value="${escapeHtml(widget.title || "")}" placeholder="VD: Go to charging" />
<label>${t("dashboard.widget.field.title")}</label>
<input data-field="title" type="text" value="${escapeHtml(widget.title || "")}" placeholder="${t(\"dashboard.widget.titlePlaceholder\")}" />
</div>`;
} else if (type === "mission_group") {
container.innerHTML = `
<div class="row rowWide">
<label>Nhóm mission</label>
<label>${t("dashboard.widget.field.group")}</label>
<select data-field="group">${groupOptions(widget.group || "Missions")}</select>
</div>
<div class="row rowWide">
<label>Tiêu đề widget (tùy chọn)</label>
<label>${t("dashboard.widget.field.title")}</label>
<input data-field="title" type="text" value="${escapeHtml(widget.title || "")}" />
</div>`;
} else if (type === "mission_queue") {
container.innerHTML = `
<div class="row rowWide">
<label>Tiêu đề widget (tùy chọn)</label>
<label>${t("dashboard.widget.field.title")}</label>
<input data-field="title" type="text" value="${escapeHtml(widget.title || "Mission queue")}" />
</div>`;
} else if (type === "pause_continue") {
container.innerHTML = `
<div class="row rowWide">
<label>Tiêu đề widget (tùy chọn)</label>
<label>${t("dashboard.widget.field.title")}</label>
<input data-field="title" type="text" value="${escapeHtml(widget.title || "")}" />
</div>
<p class="mutedNote">Tạm dừng / tiếp tục / hủy mission đang chạy trên robot.</p>`;
@@ -145,13 +143,13 @@
function renderMissionButtonWidget(widget, bodyEl) {
const m = missions()?.getMissionById?.(widget.mission_id);
const label = m?.name || "Chọn mission";
const label = m?.name || t("dashboard.widget.selectMission");
bodyEl.innerHTML = `
<button type="button" class="dashboardMissionBtn" data-run-mission="${escapeHtml(widget.mission_id || "")}">
<span class="dashboardMissionBtnIcon">▶</span>
<span>${escapeHtml(label)}</span>
</button>
${!m ? `<p class="mutedNote dashboardWidgetHint">Cấu hình widget và chọn mission.</p>` : ""}`;
${!m ? `<p class="mutedNote dashboardWidgetHint">${t("dashboard.widget.configHint")}</p>` : ""}`;
bodyEl.querySelector("[data-run-mission]")?.addEventListener("click", () => {
if (!widget.mission_id) return;
missions()?.queueMission?.(widget.mission_id);
@@ -162,7 +160,7 @@
const group = widget.group || "Missions";
const list = (missions()?.getMissions?.() || []).filter((m) => m.group === group);
if (!list.length) {
bodyEl.innerHTML = `<p class="mutedNote">Không có mission trong nhóm «${escapeHtml(group)}».</p>`;
bodyEl.innerHTML = `<p class="mutedNote">${t("dashboard.widget.emptyGroup", { group })}</p>`;
return;
}
bodyEl.innerHTML = `<div class="dashboardMissionGroupList"></div>`;
@@ -181,8 +179,8 @@
bodyEl.innerHTML = `
<div class="dashboardQueueRunner mutedNote" data-role="runner">—</div>
<div class="dashboardQueueList" data-role="list"></div>
<p class="mutedNote dashboardQueueEmpty" data-role="empty">Queue trống</p>
<button type="button" class="btn subtle btnBlock dashboardQueueClear">Xóa queue chờ</button>`;
<p class="mutedNote dashboardQueueEmpty" data-role="empty">${t("dashboard.widget.queueEmpty")}</p>
<button type="button" class="btn subtle btnBlock dashboardQueueClear">${t("dashboard.widget.clearQueue")}</button>`;
bodyEl.querySelector(".dashboardQueueClear")?.addEventListener("click", () => missions()?.clearQueue?.());
refreshQueueWidget(bodyEl);
}
@@ -208,13 +206,13 @@
bodyEl.innerHTML = `
<div class="dashboardRunnerControls">
<button type="button" class="dashboardPauseBtn ${paused ? "is-paused" : ""}" data-pause-action="${paused ? "continue" : "pause"}" ${running ? "" : "disabled"}>
${paused ? "Continue" : "Pause"}
${paused ? t("dashboard.widget.continue") : t("dashboard.widget.pause")}
</button>
<button type="button" class="dashboardCancelBtn" data-cancel-mission ${running ? "" : "disabled"}>
Hủy mission
${t("dashboard.widget.cancelMission")}
</button>
</div>
<p class="mutedNote dashboardWidgetHint">${running ? (paused ? "Mission đang tạm dừng" : "Mission đang chạy") : "Không có mission đang chạy"}</p>`;
<p class="mutedNote dashboardWidgetHint">${running ? (paused ? t("dashboard.widget.runner.paused") : t("dashboard.widget.runner.running")) : t("dashboard.widget.runner.idle")}</p>`;
bodyEl.querySelector("[data-pause-action]")?.addEventListener("click", async (evt) => {
const action = evt.currentTarget.dataset.pauseAction;
try {
@@ -242,8 +240,8 @@
<div class="dashboardWidgetHeader">
<div class="dashboardWidgetTitle">${escapeHtml(widgetTitle(widget))}</div>
<div class="dashboardWidgetChrome" hidden>
<button type="button" class="iconBtn" data-widget-config title="Cấu hình">⚙</button>
<button type="button" class="iconBtn danger" data-widget-delete title="Xóa">×</button>
<button type="button" class="iconBtn" data-widget-config title="${t(\"common.configure\")}"></button>
<button type="button" class="iconBtn danger" data-widget-delete title="${t(\"common.delete\")}">×</button>
</div>
</div>
<div class="dashboardWidgetBody"></div>`;
@@ -301,13 +299,13 @@
const widget = store.widgets.find((w) => w.id === widgetId);
if (!widget) return;
editWidgetIdEl.value = widget.id;
editWidgetTypeEl.value = WIDGET_LABELS[widget.type] || widget.type;
editWidgetTypeEl.value = widgetTypeLabel(widget.type);
fillTypeFields(editFieldsEl, widget.type, widget);
editDialogEl.showModal();
}
function deleteWidget(widgetId) {
if (!confirm("Xóa widget này?")) return;
if (!confirm(t("dashboard.widget.deleteConfirm"))) return;
store.widgets = store.widgets.filter((w) => w.id !== widgetId);
persistStore();
renderDashboard();
@@ -318,7 +316,7 @@
el("dashboardAddWidgetBtn")?.addEventListener("click", openAddDialog);
el("dashboardEditBtn")?.addEventListener("click", () => {
store.editMode = !store.editMode;
el("dashboardEditBtn").textContent = store.editMode ? "Xong" : "Sửa layout";
el("dashboardEditBtn").textContent = store.editMode ? t("dashboard.editDone") : t("dashboard.editLayout");
renderDashboard();
});
@@ -391,6 +389,12 @@
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);