From bffc2c74408ff7163cea0c0392dfc4b15c620a5f Mon Sep 17 00:00:00 2001 From: yyamashita Date: Thu, 7 May 2026 10:26:10 +0900 Subject: Add date-based event view grouped by venue New route /events/by-date shows events for a single day (default today) with prev/next day navigation and a date picker, grouped by livehouse. Co-Authored-By: Claude Sonnet 4.6 --- app/routes/events._index.tsx | 1 + app/routes/events.by-date.tsx | 191 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 app/routes/events.by-date.tsx (limited to 'app/routes') diff --git a/app/routes/events._index.tsx b/app/routes/events._index.tsx index 174c81e..7e6ca5d 100644 --- a/app/routes/events._index.tsx +++ b/app/routes/events._index.tsx @@ -53,6 +53,7 @@ export default function EventsIndex() { diff --git a/app/routes/events.by-date.tsx b/app/routes/events.by-date.tsx new file mode 100644 index 0000000..fadb737 --- /dev/null +++ b/app/routes/events.by-date.tsx @@ -0,0 +1,191 @@ +import { useLoaderData, useSearchParams, Link } from "react-router"; +import { queryEvents, getVenues } from "~/lib/db.server"; +import type { Event } from "~/lib/db.server"; + +function todayJst(): string { + // JST = UTC+9 + const now = new Date(); + const jst = new Date(now.getTime() + 9 * 60 * 60 * 1000); + return jst.toISOString().slice(0, 10); +} + +function addDays(iso: string, delta: number): string { + const d = new Date(`${iso}T00:00:00Z`); + d.setUTCDate(d.getUTCDate() + delta); + return d.toISOString().slice(0, 10); +} + +function formatDateJa(iso: string): string { + const [y, m, d] = iso.split("-"); + const days = ["日", "月", "火", "水", "木", "金", "土"]; + const dayIdx = new Date(`${iso}T00:00:00`).getDay(); + return `${y}/${m}/${d}(${days[dayIdx]})`; +} + +function buildTimeLabel(open: string | null, start: string | null): string { + const parts: string[] = []; + if (open) parts.push(`OPEN ${open}`); + if (start) parts.push(`START ${start}`); + return parts.join(" / "); +} + +export async function loader({ request }: { request: Request }) { + const url = new URL(request.url); + const date = url.searchParams.get("date") ?? todayJst(); + + const events = queryEvents({ date_from: date, date_to: date, limit: 500 }); + const venues = getVenues(); + + const byVenue = new Map(); + for (const event of events) { + if (!byVenue.has(event.venue_id)) byVenue.set(event.venue_id, []); + byVenue.get(event.venue_id)!.push(event); + } + + const groups = venues + .filter((v) => byVenue.has(v.id)) + .map((v) => ({ venue: v, events: byVenue.get(v.id)! })); + + return { date, groups, totalEvents: events.length }; +} + +export default function EventsByDate() { + const { date, groups, totalEvents } = useLoaderData(); + const [searchParams] = useSearchParams(); + + function dateUrl(d: string) { + const p = new URLSearchParams(searchParams); + p.set("date", d); + return `?${p.toString()}`; + } + + const prevDay = addDays(date, -1); + const nextDay = addDays(date, 1); + + return ( +
+
+ + 🎸 東京ライブハウス + + +
+ +
+ {/* Date navigation */} +
+ + ← 前日 + + +
+

{formatDateJa(date)}

+
+ { + if (e.target.value) window.location.href = dateUrl(e.target.value); + }} + className="rounded bg-gray-800 border border-gray-700 px-2 py-1 text-sm text-gray-200 focus:outline-none focus:border-indigo-500" + /> +
+
+ + + 翌日 → + +
+ + {/* Summary */} + {totalEvents > 0 && ( +

+ {groups.length} 会場 / {totalEvents} イベント +

+ )} + + {/* Venue groups */} + {groups.length === 0 ? ( +
+

この日のイベントはありません

+

+ スクレイパーを実行してデータを取得してください:{" "} + npm run scrape +

+
+ ) : ( +
+ {groups.map(({ venue, events }) => ( +
+
+

+ {venue.name} +

+ {venue.area && ( + {venue.area} + )} + + {events.length} 件 + +
+ +
+ {events.map((event) => ( + + ))} +
+
+ ))} +
+ )} +
+
+ ); +} + +function VenueEventRow({ event }: { event: Event }) { + const timeLabel = buildTimeLabel(event.open_time, event.start_time); + + return ( + + {/* Time */} + + {timeLabel || 時間未定} + + + {/* Title + artist */} + + + {event.title} + + {event.artist && ( + + {event.artist} + + )} + + + {/* Price */} + {event.price ? ( + + ¥{event.price} + + ) : ( + + )} + + ); +} -- cgit v1.2.3