open-navigator / web_app /src /api /decisions.ts
jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
Raw
History Blame Contribute Delete
1.91 kB
// API client for the flat decision browse list (GET /api/decisions). Returns the
// SAME card shape as /api/lenses (Contested-lens cards), so the Topics /
// Questions / States browse pages and search can all render the shared StoryCard.
import api from '../lib/api'
import type { ApiCard } from '../components/StoryLenses'
export type DecisionSort = 'contested' | 'recent' | 'interesting'
export interface DecisionListParams {
/** civicsearch topic id(s) β€” fuzzy text match against decision search_tsv.
* Multiple ids are OR'd server-side (decisions matching ANY topic). */
topicIds?: number[]
/** EveryOrg cause slug β€” keyword match against decision text (cause -> decision). */
causeId?: string
/** policy-question id β€” exact link via question_instance. */
questionId?: string
/** Meeting id β€” drill into a single meeting's decisions. */
meetingId?: number
/** 2-letter state code or full state name. */
state?: string
city?: string
/** Free-text filter over decision title/summary/theme/jurisdiction. */
q?: string
sort?: DecisionSort
limit?: number
offset?: number
}
export interface DecisionListResponse {
items: ApiCard[]
pagination: { total: number; limit: number; offset: number }
}
/** Fetch a page of decision cards, filtered/sorted server-side. Empty `items`
* (with total 0) is an honest "no decisions match" β€” never fabricated. */
export async function fetchDecisions(params: DecisionListParams = {}): Promise<DecisionListResponse> {
const res = await api.get<DecisionListResponse>('/decisions', {
params: {
topic_id: params.topicIds,
cause_id: params.causeId,
question_id: params.questionId,
meeting_id: params.meetingId,
state: params.state,
city: params.city,
q: params.q,
sort: params.sort,
limit: params.limit,
offset: params.offset,
},
})
return res.data
}