fix polls API call, move themen back to tabbar, configure only for MdB/MdL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 21:38:14 +01:00
parent 3e1d542260
commit 5c26e0458c
13 changed files with 90 additions and 128 deletions

View File

@@ -1,5 +1,4 @@
import { RepresentativeList } from "@/shared/components/representative-list"
import { TopicToggleList } from "@/shared/components/topic-toggle-list"
import { useDb } from "@/shared/db/provider"
import type { MandateWithPolitician } from "@/shared/lib/aw-api"
import { useEffect, useState } from "react"
@@ -8,8 +7,6 @@ import { useBundestagUI } from "../store"
export function BundestagConfigure() {
const db = useDb()
const topicSearch = useBundestagUI((s) => s.topicSearch)
const setTopicSearch = useBundestagUI((s) => s.setTopicSearch)
const politicianSearch = useBundestagUI((s) => s.politicianSearch)
const setPoliticianSearch = useBundestagUI((s) => s.setPoliticianSearch)
@@ -25,25 +22,15 @@ export function BundestagConfigure() {
return (
<div className="pb-4">
<section>
<h2 className="px-4 pt-4 pb-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">Themen</h2>
<TopicToggleList searchQuery={topicSearch} onSearchChange={setTopicSearch} />
</section>
<section>
<h2 className="px-4 pt-4 pb-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">
Abgeordnete
</h2>
{loadingMandates ? (
<div className="flex items-center justify-center h-48">
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
) : mandates.length > 0 ? (
<RepresentativeList mandates={mandates} searchQuery={politicianSearch} onSearchChange={setPoliticianSearch} />
) : (
<p className="px-4 text-sm text-muted-foreground">Keine Abgeordneten verfügbar.</p>
)}
</section>
{loadingMandates ? (
<div className="flex items-center justify-center h-48">
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
) : mandates.length > 0 ? (
<RepresentativeList mandates={mandates} searchQuery={politicianSearch} onSearchChange={setPoliticianSearch} />
) : (
<p className="px-4 py-6 text-sm text-muted-foreground text-center">Keine Abgeordneten verfügbar.</p>
)}
</div>
)
}

View File

@@ -2,12 +2,11 @@ import type { FeedItem } from "@/features/feed/lib/assemble-feed"
import {
type Poll,
fetchCandidacyMandates,
fetchPolls,
fetchPollsByIds,
fetchPollsByLegislature,
fetchTopics,
fetchVotes,
} from "@/shared/lib/aw-api"
import { BUNDESTAG_LEGISLATURE_ID } from "@/shared/lib/constants"
import { fetchUpcomingVorgaenge } from "@/shared/lib/dip-api"
export async function assembleBundestagFeed(
@@ -16,7 +15,7 @@ export async function assembleBundestagFeed(
): Promise<FeedItem[]> {
const [topics, polls, vorgaenge] = await Promise.all([
fetchTopics(),
fetchPollsByLegislature(BUNDESTAG_LEGISLATURE_ID, 150),
fetchPolls(150),
fetchUpcomingVorgaenge().catch(() => []),
])

View File

@@ -1,15 +1,11 @@
import { create } from "zustand"
interface BundestagUIState {
topicSearch: string
setTopicSearch: (query: string) => void
politicianSearch: string
setPoliticianSearch: (query: string) => void
}
export const useBundestagUI = create<BundestagUIState>((set) => ({
topicSearch: "",
setTopicSearch: (query) => set({ topicSearch: query }),
politicianSearch: "",
setPoliticianSearch: (query) => set({ politicianSearch: query }),
}))

View File

@@ -1,5 +1,4 @@
import { RepresentativeList } from "@/shared/components/representative-list"
import { TopicToggleList } from "@/shared/components/topic-toggle-list"
import { useDb } from "@/shared/db/provider"
import type { MandateWithPolitician } from "@/shared/lib/aw-api"
import { Link } from "@tanstack/react-router"
@@ -9,10 +8,8 @@ import { useLandtagUI } from "../store"
export function LandtagConfigure() {
const db = useDb()
const topicSearch = useLandtagUI((s) => s.topicSearch)
const setTopicSearch = useLandtagUI((s) => s.setTopicSearch)
const politicianSearch = useLandtagUI((s) => s.politicianSearch)
const setPoliticianSearch = useLandtagUI((s) => s.setPoliticianSearch)
const search = useLandtagUI((s) => s.politicianSearch)
const setSearch = useLandtagUI((s) => s.setPoliticianSearch)
const [mandates, setMandates] = useState<MandateWithPolitician[]>([])
const [loaded, setLoaded] = useState(false)
@@ -23,34 +20,28 @@ export function LandtagConfigure() {
})
}, [db])
if (loaded && mandates.length === 0) {
return (
<div className="text-center mt-12 px-4">
<p className="text-muted-foreground text-sm mb-4">
Noch keine Abgeordneten geladen. Erkenne zuerst deinen Standort in den Einstellungen.
</p>
<Link to="/app/settings" className="text-primary text-sm underline">
Zu den Einstellungen
</Link>
</div>
)
}
return (
<div className="pb-4">
<section>
<h2 className="px-4 pt-4 pb-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">Themen</h2>
<TopicToggleList searchQuery={topicSearch} onSearchChange={setTopicSearch} />
</section>
<section>
<h2 className="px-4 pt-4 pb-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">
Abgeordnete
</h2>
{!loaded ? (
<div className="flex items-center justify-center h-48">
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
) : mandates.length > 0 ? (
<RepresentativeList mandates={mandates} searchQuery={politicianSearch} onSearchChange={setPoliticianSearch} />
) : (
<div className="text-center px-4 py-6">
<p className="text-muted-foreground text-sm mb-4">
Noch keine Abgeordneten geladen. Erkenne zuerst deinen Standort in den Einstellungen.
</p>
<Link to="/app/settings" className="text-primary text-sm underline">
Zu den Einstellungen
</Link>
</div>
)}
</section>
{!loaded ? (
<div className="flex items-center justify-center h-48">
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
) : (
<RepresentativeList mandates={mandates} searchQuery={search} onSearchChange={setSearch} />
)}
</div>
)
}

View File

@@ -1,55 +1,8 @@
import { RepresentativeList } from "@/shared/components/representative-list"
import { useDb } from "@/shared/db/provider"
import type { MandateWithPolitician } from "@/shared/lib/aw-api"
import { Link } from "@tanstack/react-router"
import { useEffect, useState } from "react"
import { type GeoResult, loadCachedResult } from "../../location/lib/geo"
import { useLandtagUI } from "../store"
export function LandtagPage() {
const db = useDb()
const [result, setResult] = useState<GeoResult | null>(null)
const [mandates, setMandates] = useState<MandateWithPolitician[]>([])
const search = useLandtagUI((s) => s.politicianSearch)
const setSearch = useLandtagUI((s) => s.setPoliticianSearch)
useEffect(() => {
loadCachedResult(db).then((cached) => {
if (cached) {
setResult(cached)
setMandates(cached.mandates)
}
})
}, [db])
if (!result || mandates.length === 0) {
return (
<div className="text-center mt-12 px-4">
<p className="text-lg font-medium">Landtag</p>
<p className="text-muted-foreground text-sm mt-2 mb-4">
Erkenne zuerst deinen Standort in den Einstellungen, um deine Landtagsabgeordneten zu sehen.
</p>
<Link to="/app/settings" className="text-primary text-sm underline">
Zu den Einstellungen
</Link>
</div>
)
}
return (
<div className="pb-4">
{result.landtag_label && (
<div className="px-4 py-3 border-b border-border">
<p className="text-sm font-medium">{result.landtag_label}</p>
{result.bundesland && <p className="text-xs text-muted-foreground">{result.bundesland}</p>}
</div>
)}
<RepresentativeList mandates={mandates} searchQuery={search} onSearchChange={setSearch} />
<div className="px-4 py-6 text-center">
<p className="text-sm text-muted-foreground">Abstimmungsdaten folgen in Kürze</p>
</div>
<div className="text-center mt-12 px-4">
<p className="text-lg font-medium">Landtag</p>
<p className="text-sm text-muted-foreground mt-2">Abstimmungsdaten folgen in Kürze.</p>
</div>
)
}

View File

@@ -1,15 +1,11 @@
import { create } from "zustand"
interface LandtagUIState {
topicSearch: string
setTopicSearch: (query: string) => void
politicianSearch: string
setPoliticianSearch: (query: string) => void
}
export const useLandtagUI = create<LandtagUIState>((set) => ({
topicSearch: "",
setTopicSearch: (query) => set({ topicSearch: query }),
politicianSearch: "",
setPoliticianSearch: (query) => set({ politicianSearch: query }),
}))

View File

@@ -0,0 +1,9 @@
import { TopicToggleList } from "@/shared/components/topic-toggle-list"
import { useTopicsUI } from "../store"
export function TopicList() {
const search = useTopicsUI((s) => s.searchQuery)
const setSearch = useTopicsUI((s) => s.setSearchQuery)
return <TopicToggleList searchQuery={search} onSearchChange={setSearch} />
}

View File

@@ -0,0 +1 @@
export { TopicList } from "./components/topic-list"

View File

@@ -0,0 +1,11 @@
import { create } from "zustand"
interface TopicsUIState {
searchQuery: string
setSearchQuery: (query: string) => void
}
export const useTopicsUI = create<TopicsUIState>((set) => ({
searchQuery: "",
setSearchQuery: (query) => set({ searchQuery: query }),
}))

View File

@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as AppRouteRouteImport } from './routes/app/route'
import { Route as IndexRouteImport } from './routes/index'
import { Route as AppTopicsRouteImport } from './routes/app/topics'
import { Route as AppSettingsRouteImport } from './routes/app/settings'
import { Route as AppHomeRouteImport } from './routes/app/home'
import { Route as AppLandtagRouteRouteImport } from './routes/app/landtag/route'
@@ -30,6 +31,11 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const AppTopicsRoute = AppTopicsRouteImport.update({
id: '/topics',
path: '/topics',
getParentRoute: () => AppRouteRoute,
} as any)
const AppSettingsRoute = AppSettingsRouteImport.update({
id: '/settings',
path: '/settings',
@@ -78,6 +84,7 @@ export interface FileRoutesByFullPath {
'/app/landtag': typeof AppLandtagRouteRouteWithChildren
'/app/home': typeof AppHomeRoute
'/app/settings': typeof AppSettingsRoute
'/app/topics': typeof AppTopicsRoute
'/app/bundestag/configure': typeof AppBundestagConfigureRoute
'/app/landtag/configure': typeof AppLandtagConfigureRoute
'/app/bundestag/': typeof AppBundestagIndexRoute
@@ -88,6 +95,7 @@ export interface FileRoutesByTo {
'/app': typeof AppRouteRouteWithChildren
'/app/home': typeof AppHomeRoute
'/app/settings': typeof AppSettingsRoute
'/app/topics': typeof AppTopicsRoute
'/app/bundestag/configure': typeof AppBundestagConfigureRoute
'/app/landtag/configure': typeof AppLandtagConfigureRoute
'/app/bundestag': typeof AppBundestagIndexRoute
@@ -101,6 +109,7 @@ export interface FileRoutesById {
'/app/landtag': typeof AppLandtagRouteRouteWithChildren
'/app/home': typeof AppHomeRoute
'/app/settings': typeof AppSettingsRoute
'/app/topics': typeof AppTopicsRoute
'/app/bundestag/configure': typeof AppBundestagConfigureRoute
'/app/landtag/configure': typeof AppLandtagConfigureRoute
'/app/bundestag/': typeof AppBundestagIndexRoute
@@ -115,6 +124,7 @@ export interface FileRouteTypes {
| '/app/landtag'
| '/app/home'
| '/app/settings'
| '/app/topics'
| '/app/bundestag/configure'
| '/app/landtag/configure'
| '/app/bundestag/'
@@ -125,6 +135,7 @@ export interface FileRouteTypes {
| '/app'
| '/app/home'
| '/app/settings'
| '/app/topics'
| '/app/bundestag/configure'
| '/app/landtag/configure'
| '/app/bundestag'
@@ -137,6 +148,7 @@ export interface FileRouteTypes {
| '/app/landtag'
| '/app/home'
| '/app/settings'
| '/app/topics'
| '/app/bundestag/configure'
| '/app/landtag/configure'
| '/app/bundestag/'
@@ -164,6 +176,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/app/topics': {
id: '/app/topics'
path: '/topics'
fullPath: '/app/topics'
preLoaderRoute: typeof AppTopicsRouteImport
parentRoute: typeof AppRouteRoute
}
'/app/settings': {
id: '/app/settings'
path: '/settings'
@@ -255,6 +274,7 @@ interface AppRouteRouteChildren {
AppLandtagRouteRoute: typeof AppLandtagRouteRouteWithChildren
AppHomeRoute: typeof AppHomeRoute
AppSettingsRoute: typeof AppSettingsRoute
AppTopicsRoute: typeof AppTopicsRoute
}
const AppRouteRouteChildren: AppRouteRouteChildren = {
@@ -262,6 +282,7 @@ const AppRouteRouteChildren: AppRouteRouteChildren = {
AppLandtagRouteRoute: AppLandtagRouteRouteWithChildren,
AppHomeRoute: AppHomeRoute,
AppSettingsRoute: AppSettingsRoute,
AppTopicsRoute: AppTopicsRoute,
}
const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(

View File

@@ -24,6 +24,11 @@ const TABS: TabDef[] = [
label: "Landtag",
icon: "M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z",
},
{
to: "/app/topics",
label: "Themen",
icon: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2",
},
{
to: "/app/settings",
label: "Einstellungen",
@@ -87,7 +92,7 @@ function AppLayout() {
</button>
) : null}
<h1 className={`text-base font-semibold text-card-foreground ${isConfigureRoute ? "ml-2" : ""}`}>
{isConfigureRoute ? `${currentTab.label} konfigurieren` : currentTab.label}
{isConfigureRoute ? "Abgeordnete" : currentTab.label}
</h1>
{!isConfigureRoute && configureTarget && (
<Link

View File

@@ -0,0 +1,6 @@
import { TopicList } from "@/features/topics"
import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/app/topics")({
component: TopicList,
})

View File

@@ -165,19 +165,6 @@ export function fetchVotes(mandateID: number): Promise<Vote[]> {
return request("votes", { mandate: String(mandateID), range_end: "200" }, voteSchema)
}
export function fetchPollsByLegislature(legislatureId: number, rangeEnd = 150): Promise<Poll[]> {
return request(
"polls",
{
parliament_period: String(legislatureId),
range_end: String(rangeEnd),
sort_by: "field_poll_date",
sort_direction: "desc",
},
pollSchema,
)
}
export function fetchMandatesByParliamentPeriod(periodID: number): Promise<MandateWithPolitician[]> {
return request(
"candidacies-mandates",