예제 소스
장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개
KiboListModal.tsx
"use client";
import { useCallback, useEffect, useState } from "react";
import type { GameListItem } from "@/lib/gameTypes";
import { getOrCreateGuestId } from "@/lib/guest";
import { modeLabel } from "@/lib/kibo";
import styles from "./KiboListModal.module.css";
function formatWhen(iso: string): string {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
const pad = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
type Props = {
open: boolean;
onClose: () => void;
onSelect: (item: GameListItem) => void;
};
export function KiboListModal({ open, onClose, onSelect }: Props) {
const [games, setGames] = useState<GameListItem[]>([]);
const [dbFile, setDbFile] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const gid = getOrCreateGuestId();
const res = await fetch(`/api/game/list?guestId=${encodeURIComponent(gid)}`);
if (!res.ok) {
setError("기보 목록을 불러오지 못했습니다.");
return;
}
const data = (await res.json()) as { games: GameListItem[]; dbFile?: string };
setGames(data.games || []);
setDbFile(data.dbFile || "");
} catch {
setError("기보 서버 연결 실패");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (!open) return;
void load();
}, [open, load]);
if (!open) return null;
return (
<div className={styles.backdrop} role="dialog" aria-modal="true" aria-label="기보 목록">
<div className={styles.modal}>
<h2>기보</h2>
<p>저장된 대국을 고르면 그 시점부터 이어집니다.{dbFile ? ` · ${dbFile}` : ""}</p>
{loading ? <p className={styles.muted}>불러오는 중…</p> : null}
{error ? <p className={styles.err}>{error}</p> : null}
{!loading && !error && games.length === 0 ? (
<p className={styles.muted}>저장된 기보가 없습니다.</p>
) : null}
<ul className={styles.list}>
{games.map((g) => (
<li key={g.code}>
<button
type="button"
className={styles.card}
onClick={() => {
onSelect(g);
onClose();
}}
>
<div className={styles.top}>
<span className={styles.code}>{g.code}</span>
<span className={g.status === "ended" ? styles.badgeEnded : styles.badgePlay}>
{g.status === "ended" ? "종료" : "진행중"}
</span>
</div>
<div className={styles.meta}>
<span>{formatWhen(g.updated_at)}</span>
<span>{g.move_no}수</span>
<span>{modeLabel(g.mode)}</span>
</div>
{g.summary ? <p className={styles.summary}>{g.summary}</p> : null}
</button>
</li>
))}
</ul>
<button type="button" className={styles.close} onClick={onClose}>
닫기
</button>
</div>
</div>
);
}