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/lib/venue-filter.ts | 28 ++++++++++++++++++++++++++++ app/routes/events._index.tsx | 23 ++++++++++++++++++++--- app/routes/venues.tsx | 27 ++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 app/lib/venue-filter.ts diff --git a/app/lib/venue-filter.ts b/app/lib/venue-filter.ts new file mode 100644 index 0000000..db41f0e --- /dev/null +++ b/app/lib/venue-filter.ts @@ -0,0 +1,28 @@ +import { useState, useEffect } from "react"; + +const STORAGE_KEY = "hidden_venue_ids"; + +export function useVenueFilter() { + const [hiddenIds, setHiddenIds] = useState>(() => new Set()); + + useEffect(() => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) setHiddenIds(new Set(JSON.parse(stored) as string[])); + } catch {} + }, []); + + function toggle(venueId: string) { + setHiddenIds((prev) => { + const next = new Set(prev); + if (next.has(venueId)) next.delete(venueId); + else next.add(venueId); + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify([...next])); + } catch {} + return next; + }); + } + + return { hiddenIds, toggle }; +} 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 (