import { data, Link, useLoaderData } from "react-router"; import type { LoaderFunctionArgs } from "react-router"; import { getBandById, getBandLinks, getBandMembers, getBandRevisions, type BandMemberRow, } from "~/lib/db.server"; import { LINK_TYPE_LABEL } from "~/lib/constants"; 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 members = getBandMembers(band.id); const revisions = getBandRevisions(band.id); return { band, links, members, latest: revisions[0] ?? null }; } const STATUS_LABEL: Record = { active: "活動中", hiatus: "活動休止", 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, members, latest } = useLoaderData(); // group members by artist_id for display const artistIds = [...new Set(members.map((m) => m.artist_id))]; return (

{band.name}

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

{band.description}

)}
履歴 編集
{artistIds.length > 0 && (

メンバー

    {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}}
    • ); })}
    )}
  • ); })}
)} {links.length > 0 && (

リンク

)}

/bands/of/{band.id}

/bands/named/{band.slug}

{latest && (

最終更新: {latest.created_at} — {latest.message}

)}
); }