'use client'; import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; import { btn, btnDanger, card, error as errorClass, formWide, hr, input, label, metaLine, rowActions, sectionBlock, sectionTitle, textarea, title, } from '@/lib/adminUi'; type Char = { id: number; name_id: string; name: string; name_jp: string }; type PlayerRow = { id: number; player_key: string; player_name: string; description: string | null; image: string | null; character_id: string | null; name_id?: string; name_jp?: string; }; const field = `${input} ${textarea}`; export function PlayersAdminClient() { const [players, setPlayers] = useState([]); const [characters, setCharacters] = useState([]); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); const [playerKey, setPlayerKey] = useState(''); const [playerName, setPlayerName] = useState(''); const [description, setDescription] = useState(''); const [image, setImage] = useState(''); const [characterId, setCharacterId] = useState(''); const load = useCallback(async () => { setError(''); const [pr, cr] = await Promise.all([ fetch('/api/admin/players', { credentials: 'include' }), fetch('/api/admin/characters', { credentials: 'include' }), ]); const pj = await pr.json().catch(() => ({})); const cj = await cr.json().catch(() => ({})); if (!pr.ok) { setError((pj as { error?: string }).error ?? 'Failed to load players'); setLoading(false); return; } if (!cr.ok) { setError((cj as { error?: string }).error ?? 'Failed to load characters'); setLoading(false); return; } setPlayers((pj as { players: PlayerRow[] }).players ?? []); setCharacters((cj as { characters: Char[] }).characters ?? []); setLoading(false); }, []); useEffect(() => { load(); }, [load]); async function addPlayer(e: React.FormEvent) { e.preventDefault(); setError(''); const res = await fetch('/api/admin/players', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ player_key: playerKey, player_name: playerName, description, image: image || null, character_id: characterId, }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { setError((data as { error?: string }).error ?? 'Create failed'); return; } setPlayerKey(''); setPlayerName(''); setDescription(''); setImage(''); setCharacterId(''); await load(); } function updateLocal(id: number, patch: Partial) { setPlayers((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch } : r))); } async function save(row: PlayerRow) { setError(''); const cid = row.character_id != null && row.character_id !== '' ? String(row.character_id) : ''; if (!cid) { setError('Character is required'); return; } const res = await fetch(`/api/admin/players/${row.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ player_key: row.player_key, player_name: row.player_name, description: row.description ?? '', image: row.image === '' || row.image == null ? null : row.image, character_id: cid, }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { setError((data as { error?: string }).error ?? 'Save failed'); return; } await load(); } async function remove(id: number) { if (!window.confirm('Delete this player?')) return; setError(''); const res = await fetch(`/api/admin/players/${id}`, { method: 'DELETE', credentials: 'include' }); const data = await res.json().catch(() => ({})); if (!res.ok) { setError((data as { error?: string }).error ?? 'Delete failed'); return; } await load(); } return (

← Dashboard

Players

{error ?

{error}

: null} {loading ?

Loading…

: null} {!loading && ( <> {players.map((p) => (

id: {p.id} {p.name_jp ? ` · ${p.name_jp}` : ''}