From 7c5860ce57a334b7ab4a501de78f1a7f41c8cdcc Mon Sep 17 00:00:00 2001 From: yyamashita Date: Tue, 19 May 2026 22:32:14 +0900 Subject: Add venue visibility toggles with localStorage persistence Each venue on /venues can be toggled on/off; state persists in localStorage. Hidden venues are filtered out of the event list, with a notice linking back to the venues page to manage the setting. Co-Authored-By: Claude Sonnet 4.6 --- app/routes/events._index.tsx | 23 ++++++++++++++++++++--- app/routes/venues.tsx | 27 ++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) (limited to 'app/routes') diff --git a/app/routes/events._index.tsx b/app/routes/events._index.tsx index 890e0fd..33568df 100644 --- a/app/routes/events._index.tsx +++ b/app/routes/events._index.tsx @@ -5,6 +5,7 @@ import EventCard from "~/components/EventCard"; import EventListRow from "~/components/EventListRow"; import FilterBar from "~/components/FilterBar"; import ScrapeButton from "~/components/ScrapeButton"; +import { useVenueFilter } from "~/lib/venue-filter"; function defaultWindow() { const today = new Date(); @@ -41,6 +42,13 @@ export default function EventsIndex() { const { events, venues, areas, page, hasMore, date_from, date_to } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const view = (searchParams.get("view") ?? "card") as "card" | "list"; + const { hiddenIds } = useVenueFilter(); + + const venueFiltered = searchParams.get("venue_id"); + const visibleEvents = venueFiltered + ? events + : events.filter((e) => !hiddenIds.has(e.venue_id)); + const hiddenEventCount = events.length - visibleEvents.length; function switchView(next: "card" | "list") { const next_params = new URLSearchParams(searchParams); @@ -88,20 +96,29 @@ export default function EventsIndex() { - {events.length === 0 ? ( + {hiddenEventCount > 0 && ( +

+ {hiddenIds.size} 会場が非表示のため {hiddenEventCount} 件を除外中 —{" "} + + 会場一覧で変更 + +

+ )} + + {visibleEvents.length === 0 ? (

イベントが見つかりません

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

) : view === "list" ? (
- {events.map((event) => ( + {visibleEvents.map((event) => ( ))}
) : (
- {events.map((event) => ( + {visibleEvents.map((event) => ( ))}
diff --git a/app/routes/venues.tsx b/app/routes/venues.tsx index f2a7d54..9b8093a 100644 --- a/app/routes/venues.tsx +++ b/app/routes/venues.tsx @@ -2,6 +2,7 @@ import { useLoaderData, Link } from "react-router"; import type { Route } from "./+types/venues"; import { getVenues, getLastScrapePerVenue, type ScrapeLog } from "~/lib/db.server"; import ScrapeButton from "~/components/ScrapeButton"; +import { useVenueFilter } from "~/lib/venue-filter"; export async function loader(_: Route.LoaderArgs) { const venues = getVenues(); @@ -12,6 +13,8 @@ export async function loader(_: Route.LoaderArgs) { export default function Venues() { const { venues, scrapeStatus } = useLoaderData(); const statusMap = new Map(scrapeStatus.map((s) => [s.venue_id, s])); + const { hiddenIds, toggle } = useVenueFilter(); + const hiddenCount = hiddenIds.size; return (
@@ -32,6 +35,9 @@ export default function Venues() {

会場一覧

現在 {venues.length} 会場が登録されています。 + {hiddenCount > 0 && ( + {hiddenCount} 会場が非表示 + )}

@@ -43,10 +49,15 @@ export default function Venues() {
{venues.map((v) => { const log = statusMap.get(v.id); + const hidden = hiddenIds.has(v.id); return (