1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
import { Form, Link, redirect, useLoaderData, useNavigation } from "react-router";
import type { ActionFunctionArgs } from "react-router";
import { createParseJob, getIpAddress, listRecentParseJobs } from "~/lib/db.server";
export function loader() {
return { jobs: listRecentParseJobs(10) };
}
export async function action({ request }: ActionFunctionArgs) {
const fd = await request.formData();
const liveText = (fd.get("liveText") as string | null)?.trim() ?? "";
if (!liveText) return { error: "テキストを入力してください" };
const job = createParseJob(liveText);
void getIpAddress(request); // unused but kept for symmetry
return redirect(`/bands/auto-register/jobs/${job.id}`);
}
const STATUS_LABEL: Record<string, string> = {
pending: "待機中",
processing: "解析中",
done: "完了",
error: "エラー",
};
const STATUS_COLOR: Record<string, string> = {
pending: "#6b7280",
processing: "#fbbf24",
done: "#34d399",
error: "#f87171",
};
export default function BandAutoRegister() {
const { jobs } = useLoaderData<typeof loader>();
const nav = useNavigation();
const busy = nav.state !== "idle";
return (
<main>
<div className="page-header">
<Link to="/bands" className="back">←</Link>
<h1>ライブ情報から自動登録</h1>
</div>
<Form method="post">
<div>
<label>ライブ情報テキスト <span className="req">*</span></label>
<textarea
name="liveText"
rows={10}
placeholder={"出演バンド・アーティストの情報をペーストしてください。\n\n例:\n2024.08.10 @ 渋谷CLUB QUATTRO\nopen 17:30 / start 18:00\n\nband1 / Band Two / バンド三"}
/>
</div>
<div className="actions">
<button type="submit" disabled={busy}>
{busy ? "送信中..." : "キューに追加する"}
</button>
</div>
</Form>
{jobs.length > 0 && (
<section style={{ marginTop: "2rem" }}>
<h2>最近のジョブ</h2>
<div className="job-list">
{jobs.map((job) => (
<Link
key={job.id}
to={`/bands/auto-register/jobs/${job.id}`}
className="job-row"
>
<span
className="job-status"
style={{ color: STATUS_COLOR[job.status] ?? "#6b7280" }}
>
{STATUS_LABEL[job.status] ?? job.status}
</span>
<span className="job-preview">
{job.input_text.slice(0, 60).replace(/\n/g, " ")}
{job.input_text.length > 60 ? "…" : ""}
</span>
<span className="job-time">{job.created_at.slice(0, 16).replace("T", " ")}</span>
</Link>
))}
</div>
</section>
)}
</main>
);
}
|