import { NavLink, Navigate, useNavigate, useLocation, useParams } from "react-router-dom"; import Breeds from "@/components/admin/Breeds"; import { useEffect, useState, type FormEvent } from "react"; import { Dog, CalendarDays, CheckCheck, Files, LayoutDashboard, LoaderCircle, LogOut, PawPrint, RefreshCw, Route, Users, X, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { api, clearToken, getToken, setToken, type Row, type Section, type Stats, } from "@/lib/api"; import { labels } from "@/components/admin/format"; import Overview from "@/components/admin/Overview"; import Records from "@/components/admin/Records"; import Details from "@/components/admin/Details"; const icons = { overview: LayoutDashboard, users: Users, pets: PawPrint, breeds: Dog, walks: Route, meetups: CalendarDays, tasks: CheckCheck, files: Files, }; type Tab = Section | "overview" | "breeds"; export default function App() { const routerNavigate = useNavigate(), location = useLocation(), { section } = useParams(); const tab: Tab = section && section in labels ? section as Tab : "overview"; useEffect(() => { setDetail(null); setConfirm(null); setError(""); setNotice(""); setBusy(false); }, [location.pathname]); const [token, setAuth] = useState(getToken()), [username, setUsername] = useState("sundynix"), [password, setPassword] = useState(""); const [revision, refresh] = useState(0), [stats, setStats] = useState(null); const [error, setError] = useState(""), [busy, setBusy] = useState(false), [actionBusy, setActionBusy] = useState(false), [notice, setNotice] = useState(""); const [detail, setDetail] = useState<{ section: Section; row: Row } | null>( null, ), [confirm, setConfirm] = useState<{ section: Section; row: Row } | null>( null, ); useEffect(() => { const expired = () => { setAuth(""); setDetail(null); setConfirm(null); setError("登录已过期,请重新登录"); }; const sync = () => setAuth(getToken()); window.addEventListener("storage", sync); window.addEventListener("admin-auth-expired", expired); return () => { window.removeEventListener("admin-auth-expired", expired); window.removeEventListener("storage", sync); }; }, []); useEffect(() => { if (!token || tab !== "overview") return; const controller = new AbortController(); setBusy(true); setError(""); api .stats(controller.signal) .then((value) => { if (!controller.signal.aborted) setStats(value); }) .catch((e) => { if (!controller.signal.aborted) setError(e.message); }) .finally(() => { if (!controller.signal.aborted) setBusy(false); }); return () => controller.abort(); }, [tab, token, revision]); async function login(e: FormEvent) { e.preventDefault(); if (busy) return; setBusy(true); setError(""); try { const value = await api.login(username.trim(), password); setToken(value); setAuth(value.token); setPassword(""); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } function logout() { void api.logout().catch(() => {}); clearToken(); setAuth(""); setPassword(""); setStats(null); setDetail(null); setConfirm(null); setError(""); setNotice(""); } function navigate(section: Tab) { setBusy(false); routerNavigate(`/admin/${section}`); setError(""); setNotice(""); } async function applyAction() { if (!confirm || actionBusy) return; setActionBusy(true); setError(""); try { if (confirm.section === "users") await api.setUserDisabled(confirm.row.id, !confirm.row.disabled); else await api.cancelMeetup(confirm.row.id); setNotice( confirm.section === "users" ? confirm.row.disabled ? "用户已启用" : "用户已停用,位置分享已关闭" : "活动已取消", ); setConfirm(null); refresh((value) => value + 1); } catch (e) { setError((e as Error).message); } finally { setActionBusy(false); } } const actionTitle = confirm?.section === "users" ? confirm.row.disabled ? "启用用户" : "停用用户" : "取消约遛活动"; if (section !== "login" && !(section && Object.hasOwn(labels, section))) return ; if (!token && section !== "login") return ; if (token && section === "login") { const from = location.state?.from; return ; } if (!token) return (
肉垫计划

PAWPRINT PLAN

每一段陪伴,
都有迹可循。

让宠物的日常更有温度,
让每一次出门都值得记录。

宠物档案 快乐出门 日常照顾

肉垫计划 · 管理后台

欢迎回来

今天也一起,照顾好每一个小日常。

setUsername(e.target.value)} required maxLength={64} /> setPassword(e.target.value)} required /> {error && (

{error}

)} 肉垫计划 · 给陪伴一个温暖的地方
); return (

肉垫计划 / {tab === "overview" ? "今天的日常" : "数据管理"}

{labels[tab]}

{new Date().toLocaleDateString("zh-CN", { month: "long", day: "numeric", weekday: "long", })}
{error && !confirm && (
{error}
)} {notice && (
{notice}
)} {tab === "overview" ? ( ) : tab === "breeds" ? ( ) : ( { setError(""); setConfirm({ section, row }); }} detail={(section, row) => setDetail({ section, row })} actionBusy={actionBusy} /> )}
肉垫计划 · 把每一份陪伴,认真记下来
setDetail(null)} /> { if (!open && !actionBusy) { setConfirm(null); setError(""); } }} > {actionTitle}

{confirm?.section === "users" ? confirm.row.disabled ? `确认恢复「${confirm.row.nickname || "该用户"}」的访问权限?` : `停用「${confirm.row.nickname || "该用户"}」后,将无法使用小程序接口,现有位置分享也会关闭。` : `确认取消「${confirm?.row.title}」?取消后活动将停止接受报名。`}

{error && (

{error}

)}
); }