From d944f11581553c5e038b33fa4558566713f6d1f4 Mon Sep 17 00:00:00 2001 From: yyamashita Date: Sat, 9 May 2026 00:46:32 +0900 Subject: Add band description/status fields and redesign index page - bands table: add description (TEXT) and status (TEXT DEFAULT 'active') via ALTER TABLE migrations (active / hiatus / disbanded) - Home page: minimal monochrome index listing name, area, status - Band detail: show description below title, status alongside area - Band new/edit forms: textarea for description, select for status Co-Authored-By: Claude Sonnet 4.6 --- app/lib/db.server.ts | 25 +++++++++++++++++++---- app/routes/band-by-uuid.tsx | 14 ++++++++++++- app/routes/band-edit.tsx | 27 ++++++++++++++++++++++++- app/routes/band-new.tsx | 26 +++++++++++++++++++++++- app/routes/home.tsx | 48 +++++++++++++++++++++++++-------------------- 5 files changed, 112 insertions(+), 28 deletions(-) (limited to 'app') diff --git a/app/lib/db.server.ts b/app/lib/db.server.ts index dd9184d..bf63e70 100644 --- a/app/lib/db.server.ts +++ b/app/lib/db.server.ts @@ -74,6 +74,13 @@ function initSchema(db: Database.Database) { ); CREATE INDEX IF NOT EXISTS idx_band_links_band_id ON band_links(band_id); + `); + + // migrations + try { db.exec("ALTER TABLE bands ADD COLUMN description TEXT"); } catch { /* already exists */ } + try { db.exec("ALTER TABLE bands ADD COLUMN status TEXT NOT NULL DEFAULT 'active'"); } catch { /* already exists */ } + + db.exec(` CREATE INDEX IF NOT EXISTS idx_artist_links_artist_id ON artist_links(artist_id); CREATE INDEX IF NOT EXISTS idx_band_artists_band_id ON band_artists(band_id); CREATE INDEX IF NOT EXISTS idx_band_artists_artist_id ON band_artists(artist_id); @@ -87,6 +94,8 @@ export interface Band { slug: string; name: string; area: string | null; + description: string | null; + status: string; created_at: string; } @@ -208,6 +217,8 @@ export interface CreateBandInput { slug: string; name: string; area: string | null; + description: string | null; + status: string; links: { label: string; url: string }[]; artists: { id: string; role: string | null }[]; message: string; @@ -217,8 +228,8 @@ export interface CreateBandInput { export function createBand(input: CreateBandInput): Band { const db = getDb(); return db.transaction(() => { - db.prepare("INSERT INTO bands (id, slug, name, area) VALUES (?, ?, ?, ?)").run( - input.id, input.slug, input.name, input.area + db.prepare("INSERT INTO bands (id, slug, name, area, description, status) VALUES (?, ?, ?, ?, ?, ?)").run( + input.id, input.slug, input.name, input.area, input.description, input.status ); input.links.forEach((link, i) => { db.prepare( @@ -236,6 +247,8 @@ export function createBand(input: CreateBandInput): Band { const snapshot = JSON.stringify({ name: band.name, area: band.area, + description: band.description, + status: band.status, links: links.map((l) => ({ label: l.label, url: l.url })), artists: artists.map((a) => ({ id: a.artist_id, name: a.artist_name, role: a.role })), }); @@ -250,6 +263,8 @@ export interface UpdateBandInput { slug: string; name: string; area: string | null; + description: string | null; + status: string; links: { label: string; url: string }[]; artists: { id: string; role: string | null }[]; message: string; @@ -259,8 +274,8 @@ export interface UpdateBandInput { export function updateBand(id: string, input: UpdateBandInput): void { const db = getDb(); db.transaction(() => { - db.prepare("UPDATE bands SET slug = ?, name = ?, area = ? WHERE id = ?").run( - input.slug, input.name, input.area, id + db.prepare("UPDATE bands SET slug = ?, name = ?, area = ?, description = ?, status = ? WHERE id = ?").run( + input.slug, input.name, input.area, input.description, input.status, id ); db.prepare("DELETE FROM band_links WHERE band_id = ?").run(id); input.links.forEach((link, i) => { @@ -280,6 +295,8 @@ export function updateBand(id: string, input: UpdateBandInput): void { const snapshot = JSON.stringify({ name: band.name, area: band.area, + description: band.description, + status: band.status, links: links.map((l) => ({ label: l.label, url: l.url })), artists: artists.map((a) => ({ id: a.artist_id, name: a.artist_name, role: a.role })), }); diff --git a/app/routes/band-by-uuid.tsx b/app/routes/band-by-uuid.tsx index c55472e..1945a57 100644 --- a/app/routes/band-by-uuid.tsx +++ b/app/routes/band-by-uuid.tsx @@ -16,6 +16,12 @@ export async function loader({ params }: LoaderFunctionArgs) { return { band, links, artists, latest: revisions[0] ?? null }; } +const STATUS_LABEL: Record = { + active: "活動中", + hiatus: "活動休止", + disbanded: "解散", +}; + export default function BandDetail() { const { band, links, artists, latest } = useLoaderData(); return ( @@ -23,7 +29,13 @@ export default function BandDetail() {

{band.name}

- {band.area &&

{band.area}

} +
+ {band.area && {band.area}} + {STATUS_LABEL[band.status] ?? band.status} +
+ {band.description && ( +

{band.description}

+ )}
0) return { errors }; try { - updateBand(band.id, { slug, name, area, links, artists, message, ip_address: getIpAddress(request) }); + updateBand(band.id, { slug, name, area, description, status, links, artists, message, ip_address: getIpAddress(request) }); } catch (e) { if (e instanceof Error && e.message.includes("UNIQUE constraint failed: bands.slug")) { return { errors: { slug: "このslugは既に使用されています" } }; @@ -129,6 +131,29 @@ export default function BandEdit() { />
+
+ + +
+ +
+ +