diff options
| author | yyamashita <yyamashita@mosquit.one> | 2026-05-10 22:46:59 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@mosquit.one> | 2026-05-10 22:46:59 +0900 |
| commit | ec2417fc3e7029cb6fa84aa184daac2768ddad85 (patch) | |
| tree | 3d4b0d8ad3b90d3bb99f9c2932637f756090b57a /app/lib/db.server.ts | |
| parent | 184e6947707ecdf07dfa3a5cbc6e51cf9440e93a (diff) | |
Separate current/former members with calculable period dates
- Add MemberGroup/BandGroup types and groupBandMembers/groupArtistMembers helpers
- Calculate membership duration in months from YYYY-MM since/until values
- Band view splits members into 在籍中 / 元メンバー sections with duration label
- Artist view splits bands into 在籍中 / 元在籍 sections with duration label
- Change since/until inputs to type="month" for structured data entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'app/lib/db.server.ts')
| -rw-r--r-- | app/lib/db.server.ts | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/app/lib/db.server.ts b/app/lib/db.server.ts index cb51028..d3e5469 100644 --- a/app/lib/db.server.ts +++ b/app/lib/db.server.ts @@ -200,6 +200,86 @@ export interface ArtistRevision { created_at: string; } +export interface MemberGroup { + artist_id: string; + artist_name: string; + artist_slug: string; + periods: BandMemberRow[]; + is_current: boolean; + duration_months: number | null; +} + +export interface BandGroup { + band_id: string; + band_name: string; + band_slug: string; + periods: ArtistMemberRow[]; + is_current: boolean; + duration_months: number | null; +} + +function parseYearMonth(s: string): { year: number; month: number } | null { + const m = s.match(/^(\d{4})-(\d{2})$/); + if (!m) return null; + return { year: parseInt(m[1]), month: parseInt(m[2]) }; +} + +function calcDurationMonths(since: string, until: string): number | null { + const from = parseYearMonth(since); + if (!from) return null; + const today = new Date(); + const to = parseYearMonth(until) ?? { year: today.getFullYear(), month: today.getMonth() + 1 }; + return Math.max(0, (to.year * 12 + to.month) - (from.year * 12 + from.month)); +} + +export function groupBandMembers(members: BandMemberRow[]): { + current: MemberGroup[]; + former: MemberGroup[]; + all: MemberGroup[]; +} { + const byArtist = new Map<string, BandMemberRow[]>(); + for (const m of members) { + const list = byArtist.get(m.artist_id) ?? []; + list.push(m); + byArtist.set(m.artist_id, list); + } + const all: MemberGroup[] = []; + for (const [artistId, periods] of byArtist) { + const first = periods[0]; + const is_current = periods.some((p) => !p.until); + const duration_months = periods.reduce<number | null>((acc, p) => { + const d = calcDurationMonths(p.since, p.until); + return d === null ? acc : (acc ?? 0) + d; + }, null); + all.push({ artist_id: artistId, artist_name: first.artist_name, artist_slug: first.artist_slug, periods, is_current, duration_months }); + } + return { current: all.filter((g) => g.is_current), former: all.filter((g) => !g.is_current), all }; +} + +export function groupArtistMembers(members: ArtistMemberRow[]): { + current: BandGroup[]; + former: BandGroup[]; + all: BandGroup[]; +} { + const byBand = new Map<string, ArtistMemberRow[]>(); + for (const m of members) { + const list = byBand.get(m.band_id) ?? []; + list.push(m); + byBand.set(m.band_id, list); + } + const all: BandGroup[] = []; + for (const [bandId, periods] of byBand) { + const first = periods[0]; + const is_current = periods.some((p) => !p.until); + const duration_months = periods.reduce<number | null>((acc, p) => { + const d = calcDurationMonths(p.since, p.until); + return d === null ? acc : (acc ?? 0) + d; + }, null); + all.push({ band_id: bandId, band_name: first.band_name, band_slug: first.band_slug, periods, is_current, duration_months }); + } + return { current: all.filter((g) => g.is_current), former: all.filter((g) => !g.is_current), all }; +} + export function getIpAddress(request: Request): string { return ( request.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? |
