From 184e6947707ecdf07dfa3a5cbc6e51cf9440e93a Mon Sep 17 00:00:00 2001 From: yyamashita Date: Sun, 10 May 2026 00:21:04 +0900 Subject: Add members table with membership period and note support Replace band_artists + member_periods with a single members table (id, band_id, artist_id, role, since, until, note, order_index). Each row represents one membership period, so rejoining artists get multiple rows. Existing band_artists data is auto-migrated on startup. Export format bumped to version 3. Co-Authored-By: Claude Sonnet 4.6 --- app/routes/band-by-uuid.tsx | 63 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) (limited to 'app/routes/band-by-uuid.tsx') diff --git a/app/routes/band-by-uuid.tsx b/app/routes/band-by-uuid.tsx index 99a8b5c..2335e14 100644 --- a/app/routes/band-by-uuid.tsx +++ b/app/routes/band-by-uuid.tsx @@ -3,8 +3,9 @@ import type { LoaderFunctionArgs } from "react-router"; import { getBandById, getBandLinks, - getBandArtists, + getBandMembers, getBandRevisions, + type BandMemberRow, } from "~/lib/db.server"; import { LINK_TYPE_LABEL } from "~/lib/constants"; @@ -12,9 +13,9 @@ export async function loader({ params }: LoaderFunctionArgs) { const band = getBandById(params.uuid!); if (!band) throw data("Not found", { status: 404 }); const links = getBandLinks(band.id); - const artists = getBandArtists(band.id); + const members = getBandMembers(band.id); const revisions = getBandRevisions(band.id); - return { band, links, artists, latest: revisions[0] ?? null }; + return { band, links, members, latest: revisions[0] ?? null }; } const STATUS_LABEL: Record = { @@ -23,8 +24,19 @@ const STATUS_LABEL: Record = { disbanded: "解散", }; +function periodLabel(m: BandMemberRow): string | null { + if (!m.since && !m.until) return null; + const from = m.since || "?"; + const to = m.until || "現在"; + return `${from} 〜 ${to}`; +} + export default function BandDetail() { - const { band, links, artists, latest } = useLoaderData(); + const { band, links, members, latest } = useLoaderData(); + + // group members by artist_id for display + const artistIds = [...new Set(members.map((m) => m.artist_id))]; + return (
@@ -44,18 +56,43 @@ export default function BandDetail() {
- {artists.length > 0 && ( + {artistIds.length > 0 && (

メンバー

    - {artists.map((a) => ( -
  • - {a.artist_name} - {a.role && a.role.split(", ").filter(Boolean).map((r, i) => ( - {r} - ))} -
  • - ))} + {artistIds.map((artistId) => { + const group = members.filter((m) => m.artist_id === artistId); + const first = group[0]; + return ( +
  • +
    + {first.artist_name} + {group.flatMap((m) => + m.role ? m.role.split(", ").filter(Boolean).map((r, i) => ( + {r} + )) : [] + ).filter((_, i, arr) => { + // deduplicate role badges across periods (keep unique labels) + return true; + })} +
    + {group.some((m) => m.since || m.until || m.note) && ( +
      + {group.map((m) => { + const label = periodLabel(m); + if (!label && !m.note) return null; + return ( +
    • + {label && {label}} + {m.note && {m.note}} +
    • + ); + })} +
    + )} +
  • + ); + })}
)} -- cgit v1.2.3