refactor(ui): shared DraftSaveBar (Settings + Config save bars)
One Save/Discard bar with an inline (Settings card) and floating (Config page) variant, replacing the two near-identical hand-rolled bars + their state machine.
This commit is contained in:
parent
53c75f69e2
commit
f6b9ac2dd1
3 changed files with 109 additions and 66 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Check, RotateCcw, Save, Send } from "lucide-react";
|
||||
import { RotateCcw, Send } from "lucide-react";
|
||||
import { api, type ConfigItem } from "../lib/api";
|
||||
import { notify } from "../lib/notifications";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Tabs, { usePersistedTab } from "./Tabs";
|
||||
import { Switch } from "./ui/form";
|
||||
import { DraftSaveBar } from "./ui/DraftSaveBar";
|
||||
|
||||
// Admin Configuration page: edit DB-overridable operational settings (registry-driven from the
|
||||
// backend — adding a key there makes it appear here automatically). Edits are drafted and
|
||||
|
|
@ -151,40 +152,21 @@ export default function ConfigPanel() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{(dirty || saveState !== "idle") && (
|
||||
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-40 glass rounded-2xl shadow-lg flex items-center justify-between gap-4 px-4 py-3 w-[min(48rem,calc(100%-2rem))]">
|
||||
<span className="text-sm text-muted">
|
||||
{saveState === "saved" ? (
|
||||
<span className="text-emerald-500 inline-flex items-center gap-1.5">
|
||||
<Check className="w-4 h-4" />
|
||||
{t("config.saved")}
|
||||
</span>
|
||||
) : saveState === "error" ? (
|
||||
<span className="text-red-500">{t("config.saveFailed")}</span>
|
||||
) : (
|
||||
t("config.unsaved")
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={discard}
|
||||
disabled={saveState === "saving" || !dirty}
|
||||
className="glass-card glass-hover flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm disabled:opacity-40 transition"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
{t("config.discard")}
|
||||
</button>
|
||||
<button
|
||||
onClick={save}
|
||||
disabled={saveState === "saving" || !dirty}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-accent text-accent-fg font-medium shadow-md shadow-accent/20 disabled:opacity-40 transition"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
{saveState === "saving" ? t("config.saving") : t("config.save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DraftSaveBar
|
||||
variant="floating"
|
||||
dirty={dirty}
|
||||
state={saveState}
|
||||
onSave={save}
|
||||
onDiscard={discard}
|
||||
labels={{
|
||||
saved: t("config.saved"),
|
||||
failed: t("config.saveFailed"),
|
||||
unsaved: t("config.unsaved"),
|
||||
discard: t("config.discard"),
|
||||
save: t("config.save"),
|
||||
saving: t("config.saving"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue