"use client"; import { useMemo, useOptimistic, useState, useTransition } from "react"; import { useRouter } from "next/navigation"; import type { User } from "@/lib/types"; import { updateUser } from "@/app/actions"; import EditableCell from "./editable-cell"; type Props = { initial: User[]; prefixPattern: string }; type SortDir = "asc" | "desc"; type SortKey = "f_username" | "last_update_time"; type OptimisticPatch = { f_username: string; field: keyof Pick; value: string; }; function timeOf(t: string | null) { if (!t) return 0; const ms = Date.parse(t); return Number.isNaN(ms) ? 0 : ms; } function sortUsers(rows: User[], key: SortKey, dir: SortDir, prefix: string): User[] { return [...rows].sort((a, b) => { const ap = a.f_username.startsWith(prefix); const bp = b.f_username.startsWith(prefix); if (ap && !bp) return -1; if (!ap && bp) return 1; if (key === "f_username") { return dir === "asc" ? a.f_username.localeCompare(b.f_username) : b.f_username.localeCompare(a.f_username); } return dir === "asc" ? timeOf(a.last_update_time) - timeOf(b.last_update_time) : timeOf(b.last_update_time) - timeOf(a.last_update_time); }); } function formatTime(t: string | null) { if (!t) return ; const d = new Date(t); if (Number.isNaN(d.getTime())) return t; return d.toLocaleString(undefined, { year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } export default function UsersTable({ initial, prefixPattern }: Props) { const router = useRouter(); const [sortKey, setSortKey] = useState("last_update_time"); const [sortDir, setSortDir] = useState("desc"); const [editingKey, setEditingKey] = useState(null); const [, startTransition] = useTransition(); const [refreshing, setRefreshing] = useState(false); const [optimistic, applyOptimistic] = useOptimistic( initial, (state, patch) => state.map((row) => row.f_username === patch.f_username ? { ...row, [patch.field]: patch.value } : row, ), ); const sorted = useMemo( () => sortUsers(optimistic, sortKey, sortDir, prefixPattern), [optimistic, sortKey, sortDir, prefixPattern], ); function saveCell( f_username: string, field: OptimisticPatch["field"], value: string, ) { return new Promise<{ ok: boolean; error?: string }>((resolve) => { startTransition(async () => { applyOptimistic({ f_username, field, value }); const row = initial.find((r) => r.f_username === f_username); if (!row) { resolve({ ok: false, error: "row not found" }); return; } const next = { f_username: row.f_username, f_password: row.f_password, t_username: row.t_username, t_password: row.t_password, [field]: value, }; const result = await updateUser(next); if (result.ok) resolve({ ok: true }); else resolve({ ok: false, error: result.error }); }); }); } function refresh() { setRefreshing(true); startTransition(() => { router.refresh(); setTimeout(() => setRefreshing(false), 400); }); } function toggleSort(k: SortKey) { if (sortKey === k) { setSortDir((d) => (d === "asc" ? "desc" : "asc")); } else { setSortKey(k); setSortDir("desc"); } } if (initial.length === 0) { return (

Users

No users yet.

); } function HeaderButton({ k, label }: { k: SortKey; label: string }) { const active = sortKey === k; return ( ); } return (

// table

Users

count {optimistic.length}
{sorted.map((row) => { const k = (f: string) => `${row.f_username}::${f}`; return ( ); })}
from password to username to password
{row.f_username} setEditingKey(k("f_password"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "f_password", v)} /> setEditingKey(k("t_username"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "t_username", v)} /> setEditingKey(k("t_password"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "t_password", v)} /> {formatTime(row.last_update_time)}
{sorted.map((row) => { const k = (f: string) => `${row.f_username}::${f}`; return (
{row.f_username} {formatTime(row.last_update_time)}
from pw
setEditingKey(k("f_password"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "f_password", v)} />
to user
setEditingKey(k("t_username"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "t_username", v)} />
to pw
setEditingKey(k("t_password"))} onEditEnd={() => setEditingKey(null)} onSave={(v) => saveCell(row.f_username, "t_password", v)} />
); })}
); }