import React, { useEffect, useRef, useState } from "react"; import { useBlocker, useBeforeUnload } from "react-router-dom"; import { Alert, Box, Button, Checkbox, FormControlLabel, Stack, Typography } from "@mui/material"; import { arrangeDashboardWidgets, DashboardLayout, defaultDashboardLayout, WIDGETS } from "../dashboardLayout"; import { useI18n } from "../i18n/I18nProvider"; import { useConfirm } from "../confirm"; import { PageSection } from "../layout/PageLayout"; export function DashboardNavigationGuard({ dirty, saving }: { dirty: boolean; saving: boolean }) { const { t } = useI18n(); const { confirm } = useConfirm(); const blocker = useBlocker(dirty || saving); const [waiting, setWaiting] = useState(false); const pending = useRef(false); useBeforeUnload(React.useCallback(event => { if (!dirty) return; event.preventDefault(); event.returnValue = ""; }, [dirty])); useEffect(() => { if (blocker.state !== "blocked") { pending.current = false; return; } if (saving) { setWaiting(true); blocker.reset(); return; } if (pending.current) return; pending.current = true; void confirm({ title: t("dashboardDiscardTitle"), message: t("dashboardDiscardBody"), confirmLabel: t("dashboardDiscard"), cancelLabel: t("workspaceKeepEditing"), destructive: true }) .then(approved => { if (approved) blocker.proceed(); else blocker.reset(); }); }, [blocker, confirm, saving, t]); return waiting && saving ? {t("dashboardWaitForSave")} : null; } type Props = { draft: DashboardLayout; dirty: boolean; saving: boolean; saveError: string | null; conflict: boolean; onChange: (next: DashboardLayout) => void; onSave: () => void; onCancel: () => void; onReload: () => void; }; export default function DashboardLayoutEditor({ draft, dirty, saving, saveError, conflict, onChange, onSave, onCancel, onReload }: Props) { const { t } = useI18n(); const { confirm } = useConfirm(); const panel = useRef(null); useEffect(() => { panel.current?.focus({ preventScroll: true }); panel.current?.scrollIntoView?.({ block: "start" }); }, []); const cancel = async () => { if (!dirty || await confirm({ title: t("dashboardDiscardTitle"), message: t("dashboardDiscardBody"), confirmLabel: t("dashboardDiscard"), destructive: true })) onCancel(); }; const reset = async () => { if (await confirm({ title: t("dashboardReset"), message: t("dashboardResetBody"), confirmLabel: t("dashboardReset") })) onChange({ ...defaultDashboardLayout(), revision: draft.revision }); }; const reload = async () => { if (await confirm({ title: t("dashboardReload"), message: t("dashboardReloadBody"), confirmLabel: t("dashboardReload"), destructive: true })) onReload(); }; return { if (event.key === "Escape" && !saving && !event.defaultPrevented) { event.preventDefault(); void cancel(); } }}> {saveError ? {t(conflict ? "dashboardConflict" : "dashboardSaveFailed")} {conflict ? : null} : null} {t(dirty ? "assetsUnsavedChanges" : "dashboardDraftSaved")} {t("dashboardManageWidgets")} {draft.widgets.map(widget => { const entry = WIDGETS.find(item => item.id === widget.id)!; const name = t(entry.titleKey); return onChange({ ...draft, widgets: arrangeDashboardWidgets(draft.widgets.map(item => item.id === widget.id ? { ...item, visible: event.target.checked } : item)) })} />} />; })} ; }