add location pin toggle in header to switch between local/all representatives

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 23:12:21 +01:00
parent 685f99412c
commit 862f04ca88
6 changed files with 64 additions and 12 deletions

View File

@@ -9,6 +9,7 @@ export function BundestagConfigure() {
const db = useDb()
const politicianSearch = useBundestagUI((s) => s.politicianSearch)
const setPoliticianSearch = useBundestagUI((s) => s.setPoliticianSearch)
const showAll = useBundestagUI((s) => s.showAll)
const [mandates, setMandates] = useState<MandateWithPolitician[]>([])
const [userCity, setUserCity] = useState<string | null>(null)
@@ -34,6 +35,7 @@ export function BundestagConfigure() {
<RepresentativeList
mandates={mandates}
userCity={userCity}
showAll={showAll}
searchQuery={politicianSearch}
onSearchChange={setPoliticianSearch}
/>

View File

@@ -3,9 +3,13 @@ import { create } from "zustand"
interface BundestagUIState {
politicianSearch: string
setPoliticianSearch: (query: string) => void
showAll: boolean
toggleShowAll: () => void
}
export const useBundestagUI = create<BundestagUIState>((set) => ({
politicianSearch: "",
setPoliticianSearch: (query) => set({ politicianSearch: query }),
showAll: false,
toggleShowAll: () => set((s) => ({ showAll: !s.showAll })),
}))

View File

@@ -10,6 +10,7 @@ export function LandtagConfigure() {
const db = useDb()
const search = useLandtagUI((s) => s.politicianSearch)
const setSearch = useLandtagUI((s) => s.setPoliticianSearch)
const showAll = useLandtagUI((s) => s.showAll)
const [mandates, setMandates] = useState<MandateWithPolitician[]>([])
const [userCity, setUserCity] = useState<string | null>(null)
const [loaded, setLoaded] = useState(false)
@@ -44,7 +45,13 @@ export function LandtagConfigure() {
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
) : (
<RepresentativeList mandates={mandates} userCity={userCity} searchQuery={search} onSearchChange={setSearch} />
<RepresentativeList
mandates={mandates}
userCity={userCity}
showAll={showAll}
searchQuery={search}
onSearchChange={setSearch}
/>
)}
</div>
)

View File

@@ -3,9 +3,13 @@ import { create } from "zustand"
interface LandtagUIState {
politicianSearch: string
setPoliticianSearch: (query: string) => void
showAll: boolean
toggleShowAll: () => void
}
export const useLandtagUI = create<LandtagUIState>((set) => ({
politicianSearch: "",
setPoliticianSearch: (query) => set({ politicianSearch: query }),
showAll: false,
toggleShowAll: () => set((s) => ({ showAll: !s.showAll })),
}))

View File

@@ -1,3 +1,5 @@
import { useBundestagUI } from "@/features/bundestag/store"
import { useLandtagUI } from "@/features/landtag/store"
import { DbProvider } from "@/shared/db/provider"
import { Link, Outlet, createFileRoute, useMatches, useNavigate } from "@tanstack/react-router"
import { Suspense } from "react"
@@ -61,6 +63,14 @@ function AppLayout() {
const isBundestag = currentPath.startsWith("/app/bundestag")
const isLandtag = currentPath.startsWith("/app/landtag")
const bundestagShowAll = useBundestagUI((s) => s.showAll)
const toggleBundestag = useBundestagUI((s) => s.toggleShowAll)
const landtagShowAll = useLandtagUI((s) => s.showAll)
const toggleLandtag = useLandtagUI((s) => s.toggleShowAll)
const showAll = isBundestag ? bundestagShowAll : landtagShowAll
const toggleShowAll = isBundestag ? toggleBundestag : toggleLandtag
// Determine parent path for back navigation from configure routes
const parentPath = isConfigureRoute ? currentPath.replace(/\/configure$/, "") : null
@@ -94,6 +104,32 @@ function AppLayout() {
<h1 className={`text-base font-semibold text-card-foreground ${isConfigureRoute ? "ml-2" : ""}`}>
{isConfigureRoute ? "Abgeordnete" : currentTab.label}
</h1>
{isConfigureRoute && (
<button
type="button"
onClick={toggleShowAll}
className="ml-auto p-1.5 rounded-md hover:bg-muted transition-colors"
aria-label={showAll ? "Nur lokale anzeigen" : "Alle anzeigen"}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`w-5 h-5 ${showAll ? "text-muted-foreground" : "text-primary"}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={1.5}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"
/>
<circle cx="12" cy="9" r="2.5" />
{showAll && <line x1="3" y1="21" x2="21" y2="3" strokeWidth={2} />}
</svg>
</button>
)}
{!isConfigureRoute && configureTarget && (
<Link
to={configureTarget}

View File

@@ -100,14 +100,20 @@ function ChevronRight() {
interface RepresentativeListProps {
mandates: MandateWithPolitician[]
userCity?: string | null
showAll?: boolean
searchQuery: string
onSearchChange: (query: string) => void
}
export function RepresentativeList({ mandates, userCity, searchQuery, onSearchChange }: RepresentativeListProps) {
export function RepresentativeList({
mandates,
userCity,
showAll,
searchQuery,
onSearchChange,
}: RepresentativeListProps) {
const { isFollowing, follow, unfollow } = useFollows()
// When userCity is set, default to showing only nearby. User can click "Alle anzeigen" to expand.
const [showAll, setShowAll] = useState(!userCity)
const effectiveShowAll = showAll ?? !userCity
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({})
const filtered = searchQuery
@@ -149,8 +155,7 @@ export function RepresentativeList({ mandates, userCity, searchQuery, onSearchCh
const meta = getPartyMeta(group.partyLabel)
const isCollapsed = collapsed[group.partyLabel] ?? false
const nearbyMembers = userCity ? group.members.filter((m) => isLocalConstituency(m, userCity)) : []
// Default view (showAll=false): only nearby. After "Alle anzeigen": full list.
const visibleMembers = isCollapsed ? [] : showAll ? group.members : nearbyMembers
const visibleMembers = isCollapsed ? [] : effectiveShowAll ? group.members : nearbyMembers
return (
<Card key={group.partyLabel} className="py-0 gap-0 overflow-hidden">
@@ -213,12 +218,6 @@ export function RepresentativeList({ mandates, userCity, searchQuery, onSearchCh
)
})}
{!showAll && userCity && (
<Button variant="outline" className="w-full" onClick={() => setShowAll(true)}>
Alle anzeigen
</Button>
)}
{groups.length === 0 && searchQuery && (
<p className="text-center text-sm text-muted-foreground py-6">Keine Abgeordneten für {searchQuery}"</p>
)}