refactor: move export to its own settings page under Data group

This commit is contained in:
2026-06-11 12:46:46 +02:00
parent acc20d2f43
commit 9a894c3a95
2 changed files with 94 additions and 66 deletions

View File

@@ -0,0 +1,88 @@
import { createFileRoute, Link } from "@tanstack/react-router"
import { useState } from "react"
import { useTranslation } from "react-i18next"
import { ArrowLeft, Upload } from "lucide-react"
import { Button } from "@/components/ui/button"
import { API_URL } from "@/lib/api/client"
import { getToken } from "@/lib/auth"
import { useDocumentTitle } from "@/hooks/use-document-title"
export const Route = createFileRoute("/_app/settings/export")({
component: ExportPage,
})
function ExportPage() {
const { t } = useTranslation()
useDocumentTitle(t("settings.export"))
const [exporting, setExporting] = useState<string | null>(null)
async function handleExport(format: "csv" | "json") {
setExporting(format)
try {
const res = await fetch(`${API_URL}/api/v1/diary/export?format=${format}`, {
headers: { Authorization: `Bearer ${getToken()}` },
})
const blob = await res.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `diary.${format}`
a.click()
URL.revokeObjectURL(url)
} finally {
setExporting(null)
}
}
return (
<div className="space-y-6 p-4">
<div className="flex items-center gap-3">
<Link to="/settings" className="text-muted-foreground">
<ArrowLeft className="size-5" />
</Link>
<h1 className="text-lg font-bold">{t("settings.export")}</h1>
</div>
<div className="divide-y divide-border rounded-xl bg-card">
<div className="flex items-center gap-3 p-3">
<span className="text-muted-foreground">
<Upload className="size-4" />
</span>
<div className="flex-1">
<p className="text-sm font-medium">{t("settings.exportCsv")}</p>
<p className="text-xs text-muted-foreground">
{t("settings.exportDesc")}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleExport("csv")}
disabled={exporting !== null}
>
{exporting === "csv" ? t("settings.exporting") : t("common.run")}
</Button>
</div>
<div className="flex items-center gap-3 p-3">
<span className="text-muted-foreground">
<Upload className="size-4" />
</span>
<div className="flex-1">
<p className="text-sm font-medium">{t("settings.exportJson")}</p>
<p className="text-xs text-muted-foreground">
{t("settings.exportDesc")}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleExport("json")}
disabled={exporting !== null}
>
{exporting === "json" ? t("settings.exporting") : t("common.run")}
</Button>
</div>
</div>
</div>
)
}

View File

@@ -1,5 +1,4 @@
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router" import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
import { useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useMutation } from "@tanstack/react-query" import { useMutation } from "@tanstack/react-query"
import { import {
@@ -18,8 +17,6 @@ import {
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
import { useAuth, useIsAdmin } from "@/components/auth-provider" import { useAuth, useIsAdmin } from "@/components/auth-provider"
import { API_URL } from "@/lib/api/client"
import { getToken } from "@/lib/auth"
import { reindexSearch } from "@/lib/api/users" import { reindexSearch } from "@/lib/api/users"
import { useSettings, useUpdateSettings } from "@/hooks/use-goals" import { useSettings, useUpdateSettings } from "@/hooks/use-goals"
import { useDocumentTitle } from "@/hooks/use-document-title" import { useDocumentTitle } from "@/hooks/use-document-title"
@@ -58,6 +55,12 @@ function SettingsPage() {
to: "/settings/import", to: "/settings/import",
icon: <Download className="size-4" />, icon: <Download className="size-4" />,
}, },
{
label: t("settings.export"),
description: t("settings.exportDesc"),
to: "/settings/export",
icon: <Upload className="size-4" />,
},
{ {
label: t("settings.yearWrapUp"), label: t("settings.yearWrapUp"),
description: t("settings.yearWrapUpDesc"), description: t("settings.yearWrapUpDesc"),
@@ -100,7 +103,6 @@ function SettingsPage() {
<SettingsGroup label={t("settings.account")} items={account} /> <SettingsGroup label={t("settings.account")} items={account} />
<SettingsGroup label={t("settings.data")} items={data} /> <SettingsGroup label={t("settings.data")} items={data} />
<ExportSection />
<SettingsGroup label={t("settings.integrations")} items={integrations} /> <SettingsGroup label={t("settings.integrations")} items={integrations} />
<SettingsGroup label={t("settings.socialGroup")} items={social} /> <SettingsGroup label={t("settings.socialGroup")} items={social} />
@@ -186,68 +188,6 @@ function AdminActions() {
) )
} }
function ExportSection() {
const { t } = useTranslation()
const [exporting, setExporting] = useState<string | null>(null)
async function handleExport(format: "csv" | "json") {
setExporting(format)
try {
const res = await fetch(`${API_URL}/api/v1/diary/export?format=${format}`, {
headers: { Authorization: `Bearer ${getToken()}` },
})
const blob = await res.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `diary.${format}`
a.click()
URL.revokeObjectURL(url)
} finally {
setExporting(null)
}
}
return (
<div>
<p className="mb-1.5 px-1 text-xs font-medium text-muted-foreground">
{t("settings.export")}
</p>
<div className="divide-y divide-border rounded-xl bg-card">
<div className="flex items-center gap-3 p-3">
<span className="text-muted-foreground">
<Upload className="size-4" />
</span>
<div className="flex-1">
<p className="text-sm font-medium">{t("settings.export")}</p>
<p className="text-xs text-muted-foreground">
{t("settings.exportDesc")}
</p>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleExport("csv")}
disabled={exporting !== null}
>
{exporting === "csv" ? t("settings.exporting") : t("settings.exportCsv")}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleExport("json")}
disabled={exporting !== null}
>
{exporting === "json" ? t("settings.exporting") : t("settings.exportJson")}
</Button>
</div>
</div>
</div>
</div>
)
}
function SettingsGroup({ function SettingsGroup({
label, label,
items, items,