diff options
| author | yyamashita <yyamashita@mosquit.one> | 2026-05-10 00:21:04 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@mosquit.one> | 2026-05-10 00:21:04 +0900 |
| commit | 184e6947707ecdf07dfa3a5cbc6e51cf9440e93a (patch) | |
| tree | 77a75c2225ad7beafecac15ef90d0cc6cfe5871b /app/routes/artist-by-uuid.tsx | |
| parent | 0e12e7238f48ffc2a5d35dae059c2f00c7250f3b (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'app/routes/artist-by-uuid.tsx')
| -rw-r--r-- | app/routes/artist-by-uuid.tsx | 53 |
1 files changed, 42 insertions, 11 deletions
diff --git a/app/routes/artist-by-uuid.tsx b/app/routes/artist-by-uuid.tsx index 6eb06a7..a65525b 100644 --- a/app/routes/artist-by-uuid.tsx +++ b/app/routes/artist-by-uuid.tsx @@ -1,18 +1,29 @@ import { data, Link, useLoaderData } from "react-router"; import type { LoaderFunctionArgs } from "react-router"; -import { getArtistBands, getArtistById, getArtistLinks, getArtistRevisions } from "~/lib/db.server"; +import { getArtistById, getArtistLinks, getArtistMembers, getArtistRevisions, type ArtistMemberRow } from "~/lib/db.server"; export async function loader({ params }: LoaderFunctionArgs) { const artist = getArtistById(params.uuid!); if (!artist) throw data("Not found", { status: 404 }); const links = getArtistLinks(artist.id); - const bands = getArtistBands(artist.id); + const memberships = getArtistMembers(artist.id); const revisions = getArtistRevisions(artist.id); - return { artist, links, bands, latest: revisions[0] ?? null }; + return { artist, links, memberships, latest: revisions[0] ?? null }; +} + +function periodLabel(m: ArtistMemberRow): string | null { + if (!m.since && !m.until) return null; + const from = m.since || "?"; + const to = m.until || "現在"; + return `${from} 〜 ${to}`; } export default function ArtistDetail() { - const { artist, links, bands, latest } = useLoaderData<typeof loader>(); + const { artist, links, memberships, latest } = useLoaderData<typeof loader>(); + + // group by band_id + const bandIds = [...new Set(memberships.map((m) => m.band_id))]; + return ( <main> <div className="detail-header"> @@ -23,16 +34,36 @@ export default function ArtistDetail() { </div> </div> - {bands.length > 0 && ( + {bandIds.length > 0 && ( <section> <h2>バンド</h2> <ul className="member-list"> - {bands.map((b) => ( - <li key={b.band_id}> - <Link to={`/bands/of/${b.band_id}`}>{b.band_name}</Link> - {b.role && <span className="muted">{b.role}</span>} - </li> - ))} + {bandIds.map((bandId) => { + const group = memberships.filter((m) => m.band_id === bandId); + const first = group[0]; + return ( + <li key={bandId}> + <div className="member-main"> + <Link to={`/bands/of/${bandId}`}>{first.band_name}</Link> + {first.role && <span className="muted">{first.role}</span>} + </div> + {group.some((m) => m.since || m.until || m.note) && ( + <ul className="period-list"> + {group.map((m) => { + const label = periodLabel(m); + if (!label && !m.note) return null; + return ( + <li key={m.id} className="period-item"> + {label && <span className="period-range">{label}</span>} + {m.note && <span className="period-note">{m.note}</span>} + </li> + ); + })} + </ul> + )} + </li> + ); + })} </ul> </section> )} |
