feat(archive): implement sortable columns in archive table with toggle functionality
This commit is contained in:
parent
2889a38ab6
commit
5b02eb1226
@ -530,6 +530,16 @@ kbd {
|
||||
background: rgba(255,255,255,0.04);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.archive-head-btn {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
color: var(--subtle);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.archive-head-btn:hover { color: var(--text); }
|
||||
.archive-row {
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
|
||||
@ -5,6 +5,9 @@ import { useAuth } from "../auth/AuthProvider";
|
||||
import { toastError } from "../utils/toastBus";
|
||||
import { logApiError } from "../utils/logger";
|
||||
|
||||
type SortKey = "id" | "ep_num" | "ep_title" | "season_name" | "start_time" | "playback_length" | "date_created" | "date_archived";
|
||||
type SortDir = "asc" | "desc" | null;
|
||||
|
||||
function formatTimestamp(ts: string) {
|
||||
if (!ts) return "";
|
||||
const d = new Date(ts);
|
||||
@ -17,6 +20,7 @@ export default function ArchivePage() {
|
||||
const [items, setItems] = React.useState<ArchiveItem[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [sort, setSort] = React.useState<{ key: SortKey | null; dir: SortDir }>({ key: null, dir: null });
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
if (!idToken) {
|
||||
@ -46,6 +50,51 @@ export default function ArchivePage() {
|
||||
}
|
||||
}, [enabled, isAdmin, idToken, load]);
|
||||
|
||||
const sortedItems = React.useMemo(() => {
|
||||
const { key, dir } = sort;
|
||||
if (!key || !dir) return items;
|
||||
const next = [...items];
|
||||
next.sort((a, b) => {
|
||||
let av: string | number = "";
|
||||
let bv: string | number = "";
|
||||
switch (key) {
|
||||
case "id":
|
||||
case "ep_num":
|
||||
av = a[key];
|
||||
bv = b[key];
|
||||
break;
|
||||
case "date_created":
|
||||
case "date_archived":
|
||||
av = new Date(a[key]).getTime();
|
||||
bv = new Date(b[key]).getTime();
|
||||
break;
|
||||
default:
|
||||
av = (a as Record<string, unknown>)[key] as string | number | undefined ?? "";
|
||||
bv = (b as Record<string, unknown>)[key] as string | number | undefined ?? "";
|
||||
}
|
||||
if (av === bv) return 0;
|
||||
const asc = av > bv ? 1 : -1;
|
||||
return dir === "asc" ? asc : -asc;
|
||||
});
|
||||
return next;
|
||||
}, [items, sort]);
|
||||
|
||||
function toggleSort(key: SortKey) {
|
||||
setSort((prev) => {
|
||||
if (prev.key !== key) return { key, dir: "asc" };
|
||||
if (prev.dir === "asc") return { key, dir: "desc" };
|
||||
if (prev.dir === "desc") return { key: null, dir: null };
|
||||
return { key, dir: "asc" };
|
||||
});
|
||||
}
|
||||
|
||||
const sortIndicator = (key: SortKey) => {
|
||||
if (sort.key !== key) return "";
|
||||
if (sort.dir === "asc") return "▲";
|
||||
if (sort.dir === "desc") return "▼";
|
||||
return "";
|
||||
};
|
||||
|
||||
if (!enabled) {
|
||||
return <div className="subtle">認証が無効です。</div>;
|
||||
}
|
||||
@ -77,16 +126,32 @@ export default function ArchivePage() {
|
||||
<div className="archive-scroll">
|
||||
<div className="archive-table">
|
||||
<div className="archive-header">
|
||||
<span>ID</span>
|
||||
<span>話数</span>
|
||||
<span>タイトル</span>
|
||||
<span>シーズン</span>
|
||||
<span>開始時刻</span>
|
||||
<span>再生時間</span>
|
||||
<span>登録</span>
|
||||
<span>アーカイブ</span>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("id")}>
|
||||
ID {sortIndicator("id")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("ep_num")}>
|
||||
話数 {sortIndicator("ep_num")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("ep_title")}>
|
||||
タイトル {sortIndicator("ep_title")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("season_name")}>
|
||||
シーズン {sortIndicator("season_name")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("start_time")}>
|
||||
開始時刻 {sortIndicator("start_time")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("playback_length")}>
|
||||
再生時間 {sortIndicator("playback_length")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("date_created")}>
|
||||
登録 {sortIndicator("date_created")}
|
||||
</button>
|
||||
<button className="archive-head-btn" onClick={() => toggleSort("date_archived")}>
|
||||
アーカイブ {sortIndicator("date_archived")}
|
||||
</button>
|
||||
</div>
|
||||
{items.map((it) => (
|
||||
{sortedItems.map((it) => (
|
||||
<div key={it.id} className="archive-row">
|
||||
<span>#{it.id}</span>
|
||||
<span>第{it.ep_num}話</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user