feat: move diary export to settings page with CSV/JSON options
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { useCallback, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { BookOpen, ChevronLeft, ChevronRight, Download } from "lucide-react"
|
||||
import { BookOpen, ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { format, startOfMonth, subMonths } from "date-fns"
|
||||
import { MovieCard } from "@/components/movie-card"
|
||||
import { EmptyState } from "@/components/empty-state"
|
||||
@@ -10,8 +10,6 @@ import { VirtualList } from "@/components/virtual-list"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { useInfiniteDiary, useDeleteReview } from "@/hooks/use-diary"
|
||||
import { API_URL } from "@/lib/api/client"
|
||||
import { getToken } from "@/lib/auth"
|
||||
import type { DiaryEntryDto } from "@/lib/api/common"
|
||||
|
||||
export const Route = createFileRoute("/_app/diary")({
|
||||
@@ -75,28 +73,7 @@ function DiaryPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-lg font-bold">{t("diary.title")}</h1>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={async () => {
|
||||
const res = await fetch(`${API_URL}/api/v1/diary/export?format=csv`, {
|
||||
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.csv"
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}}
|
||||
>
|
||||
<Download className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<h1 className="text-lg font-bold">{t("diary.title")}</h1>
|
||||
|
||||
<div className="flex items-center justify-between rounded-xl bg-card px-3 py-2">
|
||||
<Button variant="ghost" size="icon" onClick={goBack} disabled={!canGoBack}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import {
|
||||
@@ -11,11 +12,14 @@ import {
|
||||
ShieldBan,
|
||||
Sparkles,
|
||||
Target,
|
||||
Upload,
|
||||
User,
|
||||
} from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
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 { useSettings, useUpdateSettings } from "@/hooks/use-goals"
|
||||
|
||||
@@ -94,6 +98,7 @@ function SettingsPage() {
|
||||
|
||||
<SettingsGroup label={t("settings.account")} items={account} />
|
||||
<SettingsGroup label={t("settings.data")} items={data} />
|
||||
<ExportSection />
|
||||
<SettingsGroup label={t("settings.integrations")} items={integrations} />
|
||||
<SettingsGroup label={t("settings.socialGroup")} items={social} />
|
||||
|
||||
@@ -179,6 +184,68 @@ 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({
|
||||
label,
|
||||
items,
|
||||
|
||||
Reference in New Issue
Block a user