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:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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 })),
|
||||
}))
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 })),
|
||||
}))
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user