feat: added reject image and migrated to tailwindcss

feat: made it smartphone friendly
This commit is contained in:
2026-04-14 00:16:59 +09:00
parent 3582fca2d9
commit abf350aa6b
35 changed files with 788 additions and 1107 deletions

View File

@@ -4,7 +4,22 @@ import { useCallback, useEffect, useState } from 'react';
import Link from 'next/link';
import style from '@/styles/admin.module.scss';
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 = {
@@ -18,6 +33,8 @@ type PlayerRow = {
name_jp?: string;
};
const field = `${input} ${textarea}`;
export function PlayersAdminClient() {
const [players, setPlayers] = useState<PlayerRow[]>([]);
const [characters, setCharacters] = useState<Char[]>([]);
@@ -129,60 +146,57 @@ export function PlayersAdminClient() {
}
return (
<div className={style.card}>
<div className={card}>
<p>
<Link href="/admin"> Dashboard</Link>
</p>
<h1 className={style.title}>Players</h1>
{error ? <p className={style.error}>{error}</p> : null}
<h1 className={title}>Players</h1>
{error ? <p className={errorClass}>{error}</p> : null}
{loading ? <p>Loading</p> : null}
{!loading && (
<>
{players.map((p) => (
<div
key={p.id}
style={{ marginBottom: 24, paddingBottom: 16, borderBottom: '1px solid #eee' }}
>
<p style={{ margin: '0 0 8px', fontSize: '0.85rem', color: '#666' }}>
<div key={p.id} className={sectionBlock}>
<p className={metaLine}>
id: {p.id}
{p.name_jp ? ` · ${p.name_jp}` : ''}
</p>
<label className={style.label}>
<label className={label}>
player_key
<input
className={style.input}
className={input}
value={p.player_key}
onChange={(e) => updateLocal(p.id, { player_key: e.target.value })}
/>
</label>
<label className={style.label}>
<label className={label}>
player_name
<input
className={style.input}
className={input}
value={p.player_name}
onChange={(e) => updateLocal(p.id, { player_name: e.target.value })}
/>
</label>
<label className={style.label}>
<label className={label}>
description
<textarea
className={`${style.input} ${style.textarea}`}
className={field}
value={p.description ?? ''}
onChange={(e) => updateLocal(p.id, { description: e.target.value })}
/>
</label>
<label className={style.label}>
<label className={label}>
image URL (optional)
<input
className={style.input}
className={input}
value={p.image ?? ''}
onChange={(e) => updateLocal(p.id, { image: e.target.value || null })}
/>
</label>
<label className={style.label}>
<label className={label}>
character
<select
className={style.input}
className={input}
value={p.character_id != null && p.character_id !== '' ? String(p.character_id) : ''}
onChange={(e) => updateLocal(p.id, { character_id: e.target.value })}
required
@@ -195,46 +209,40 @@ export function PlayersAdminClient() {
))}
</select>
</label>
<div className={style.rowActions}>
<button type="button" className={style.btn} onClick={() => save(p)}>
<div className={rowActions}>
<button type="button" className={btn} onClick={() => save(p)}>
Save
</button>
<button type="button" className={style.btnDanger} onClick={() => remove(p.id)}>
<button type="button" className={btnDanger} onClick={() => remove(p.id)}>
Delete
</button>
</div>
</div>
))}
<hr className={style.hr} />
<h2 className={style.title} style={{ fontSize: '1.1rem' }}>
Add player
</h2>
<form className={style.form} style={{ maxWidth: 480 }} onSubmit={addPlayer}>
<label className={style.label}>
<hr className={hr} />
<h2 className={sectionTitle}>Add player</h2>
<form className={formWide} onSubmit={addPlayer}>
<label className={label}>
player_key
<input className={style.input} value={playerKey} onChange={(e) => setPlayerKey(e.target.value)} required />
<input className={input} value={playerKey} onChange={(e) => setPlayerKey(e.target.value)} required />
</label>
<label className={style.label}>
<label className={label}>
player_name
<input className={style.input} value={playerName} onChange={(e) => setPlayerName(e.target.value)} required />
<input className={input} value={playerName} onChange={(e) => setPlayerName(e.target.value)} required />
</label>
<label className={style.label}>
<label className={label}>
description
<textarea
className={`${style.input} ${style.textarea}`}
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<textarea className={field} value={description} onChange={(e) => setDescription(e.target.value)} />
</label>
<label className={style.label}>
<label className={label}>
image URL (optional)
<input className={style.input} value={image} onChange={(e) => setImage(e.target.value)} />
<input className={input} value={image} onChange={(e) => setImage(e.target.value)} />
</label>
<label className={style.label}>
<label className={label}>
character
<select
className={style.input}
className={input}
value={characterId}
onChange={(e) => setCharacterId(e.target.value)}
required
@@ -247,7 +255,7 @@ export function PlayersAdminClient() {
))}
</select>
</label>
<button className={style.btn} type="submit">
<button className={btn} type="submit">
Add
</button>
</form>