fix: improve export filename and import confirmation UX (#232)
Export filename: - Include username for multi-user/instance distinction - Include timestamp with time (YYYYMMDD-HHMM) instead of date only - Example: medassist-export-daniel-20260216-2108.json Import confirmation: - Show friendly 'Import Data?' dialog on empty instances instead of scary 'Replace All Data?' warning with danger button - Only show destructive warning when there is existing data to replace - Use primary button style for empty-state import Closes #231
This commit is contained in:
@@ -475,7 +475,11 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
};
|
||||
|
||||
// Set download headers
|
||||
const filename = `medassist-export-${new Date().toISOString().split("T")[0]}.json`;
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().replace(/[-:]/g, "").replace(/T/, "-").slice(0, 13);
|
||||
const authUser = env.AUTH_ENABLED ? (request.user as unknown as AuthUser | null) : null;
|
||||
const userPart = authUser?.username ? `-${authUser.username}` : "";
|
||||
const filename = `medassist-export${userPart}-${dateStr}.json`;
|
||||
reply.header("Content-Type", "application/json");
|
||||
reply.header("Content-Disposition", `attachment; filename="${filename}"`);
|
||||
|
||||
|
||||
@@ -522,9 +522,11 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
const dateStr = new Date().toISOString().split("T")[0];
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().replace(/[-:]/g, "").replace(/T/, "-").slice(0, 13);
|
||||
const userPart = user?.username ? `-${user.username}` : "";
|
||||
a.href = url;
|
||||
a.download = `${t("exportImport.downloadFilename")}-${dateStr}.json`;
|
||||
a.download = `${t("exportImport.downloadFilename")}${userPart}-${dateStr}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
@@ -500,6 +500,9 @@
|
||||
"confirmImportMessage": "Dies löscht dauerhaft alle deine aktuellen Medikamente, Einnahmehistorie, Einstellungen und Teilen-Links und ersetzt sie durch die importierten Daten.",
|
||||
"confirmImportWarning": "Diese Aktion kann nicht rückgängig gemacht werden!",
|
||||
"confirmButton": "Ja, alles ersetzen",
|
||||
"confirmImportEmpty": "Daten importieren?",
|
||||
"confirmImportEmptyMessage": "Alle Medikamente, Einnahmehistorie, Einstellungen und Teilen-Links aus der ausgewählten Datei werden importiert.",
|
||||
"confirmButtonEmpty": "Importieren",
|
||||
"cancelButton": "Abbrechen",
|
||||
"exportSuccess": "Daten erfolgreich exportiert",
|
||||
"importSuccess": "Daten erfolgreich importiert",
|
||||
|
||||
@@ -500,6 +500,9 @@
|
||||
"confirmImportMessage": "This will permanently delete all your current medications, dose history, settings, and share links, then replace them with the imported data.",
|
||||
"confirmImportWarning": "This action cannot be undone!",
|
||||
"confirmButton": "Yes, Replace All",
|
||||
"confirmImportEmpty": "Import Data?",
|
||||
"confirmImportEmptyMessage": "This will import all medications, dose history, settings, and share links from the selected file.",
|
||||
"confirmButtonEmpty": "Import",
|
||||
"cancelButton": "Cancel",
|
||||
"exportSuccess": "Data exported successfully",
|
||||
"importSuccess": "Data imported successfully",
|
||||
|
||||
@@ -30,8 +30,11 @@ export function SettingsPage() {
|
||||
handleImportConfirm,
|
||||
importResult,
|
||||
setImportResult,
|
||||
meds,
|
||||
} = useAppContext();
|
||||
|
||||
const hasExistingData = meds.length > 0;
|
||||
|
||||
return (
|
||||
<section className="grid">
|
||||
{settingsLoading ? (
|
||||
@@ -799,21 +802,25 @@ export function SettingsPage() {
|
||||
{/* Import Confirmation Modal */}
|
||||
{showImportConfirm && (
|
||||
<ConfirmModal
|
||||
title={t("exportImport.confirmImport")}
|
||||
title={t(hasExistingData ? "exportImport.confirmImport" : "exportImport.confirmImportEmpty")}
|
||||
message={
|
||||
<>
|
||||
<p style={{ marginBottom: "12px" }}>{t("exportImport.confirmImportMessage")}</p>
|
||||
<p className="warning-text">⚠️ {t("exportImport.confirmImportWarning")}</p>
|
||||
</>
|
||||
hasExistingData ? (
|
||||
<>
|
||||
<p style={{ marginBottom: "12px" }}>{t("exportImport.confirmImportMessage")}</p>
|
||||
<p className="warning-text">⚠️ {t("exportImport.confirmImportWarning")}</p>
|
||||
</>
|
||||
) : (
|
||||
<p>{t("exportImport.confirmImportEmptyMessage")}</p>
|
||||
)
|
||||
}
|
||||
confirmLabel={t("exportImport.confirmButton")}
|
||||
confirmLabel={t(hasExistingData ? "exportImport.confirmButton" : "exportImport.confirmButtonEmpty")}
|
||||
cancelLabel={t("exportImport.cancelButton")}
|
||||
onConfirm={handleImportConfirm}
|
||||
onCancel={() => {
|
||||
setShowImportConfirm(false);
|
||||
setPendingImportData(null);
|
||||
}}
|
||||
confirmVariant="danger"
|
||||
confirmVariant={hasExistingData ? "danger" : "primary"}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ const createMockContext = (overrides = {}) => ({
|
||||
handleImportConfirm: vi.fn(),
|
||||
importResult: null,
|
||||
setImportResult: vi.fn(),
|
||||
meds: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user