-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
554 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import PageTitle from "@components/common/page-title"; | ||
import type { FilterToggle } from "@components/filter/filter-context"; | ||
import { FilterContextProvider } from "@components/filter/filter-context"; | ||
import { ScaleIcon } from "@heroicons/react/24/solid"; | ||
import { getVotes } from "@lib/api/vote/get-votes"; | ||
import { | ||
parseNumberSearchParam, | ||
parseStringArraySearchParam, | ||
parseStringSearchParam, | ||
} from "@lib/utils/search-params"; | ||
import VoteList from "./vote-list"; | ||
import Filter from "@components/filter"; | ||
import { Committee, committeeInfo } from "@lib/committes"; | ||
|
||
export const metadata = { | ||
title: "Voteringar | Partiguiden", | ||
description: "Hur har partierna röstat i voteringar? Ta reda på det här", | ||
}; | ||
|
||
function initialFilterToggles(committees: string[]): FilterToggle<Committee> { | ||
return Object.values(Committee).reduce( | ||
(prev, committee) => ({ | ||
...prev, | ||
[committee]: { | ||
title: committeeInfo[committee].desc, | ||
value: committees.includes(committee), | ||
}, | ||
}), | ||
{} as FilterToggle<Committee>, | ||
); | ||
} | ||
|
||
interface Props { | ||
searchParams: { | ||
sok?: string | string[]; | ||
sida?: string | string[]; | ||
utskott?: string | string[]; | ||
}; | ||
} | ||
|
||
export default async function Votes({ searchParams }: Props) { | ||
const page = parseNumberSearchParam(searchParams.sida); | ||
const search = parseStringSearchParam(searchParams.sok); | ||
const committees = parseStringArraySearchParam(searchParams.utskott); | ||
const votes = await getVotes({ search, page, committees }); | ||
|
||
const filterToggles = initialFilterToggles(committees); | ||
|
||
return ( | ||
<main> | ||
<PageTitle Icon={ScaleIcon}>Voteringar</PageTitle> | ||
|
||
<div className="mx-4 mb-4 flex gap-2 2xl:container 2xl:mx-auto"> | ||
<FilterContextProvider initialToggles={filterToggles}> | ||
<VoteList votes={votes} /> | ||
<Filter /> | ||
</FilterContextProvider> | ||
</div> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
"use client"; | ||
import { useFilterContext } from "@components/filter/filter-context"; | ||
import type { VoteList } from "@lib/api/vote/types"; | ||
import { routes } from "@lib/navigation"; | ||
import { useRouter } from "next/navigation"; | ||
import { useEffect } from "react"; | ||
import Vote from "./vote"; | ||
|
||
interface Props { | ||
votes: VoteList; | ||
} | ||
|
||
export default function VoteList({ votes }: Props) { | ||
const router = useRouter(); | ||
const { search, toggles } = useFilterContext(); | ||
|
||
useEffect(() => { | ||
const debounce = setTimeout(() => { | ||
const query = new URLSearchParams(); | ||
const activeToggles = Object.entries(toggles) | ||
.filter(([, value]) => value.value) | ||
.map(([key]) => key); | ||
for (const toggle of activeToggles) { | ||
query.append("utskott", toggle); | ||
} | ||
if (search) { | ||
query.set("sok", search); | ||
} | ||
|
||
router.replace(`${routes.votes}?${query}`); | ||
}, 500); | ||
return () => { | ||
clearTimeout(debounce); | ||
}; | ||
}, [router, search, toggles]); | ||
|
||
if (votes.pages === 0) { | ||
return ( | ||
<p className="flex-1 text-center text-xl sm:text-2xl"> | ||
Inga voteringar hittades | ||
</p> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="grid flex-1 gap-4"> | ||
{votes.votes.map((vote) => ( | ||
<Vote key={`${vote.documentId}:${vote.proposition}`} vote={vote} /> | ||
))} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Card, CommitteeHeader } from "@components/common/card"; | ||
import type { VoteListEntry } from "@lib/api/vote/types"; | ||
|
||
interface Props { | ||
vote: VoteListEntry; | ||
} | ||
|
||
export default function Vote({ vote }: Props) { | ||
return ( | ||
<Card className="p-0"> | ||
<CommitteeHeader committee={vote.committee} /> | ||
<div className="p-4"> | ||
<p className="text-sm text-slate-600 dark:text-slate-400"> | ||
{vote.title} | ||
</p> | ||
<p>{vote.subtitle}</p> | ||
</div> | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { PARLIAMENT_BASE_URL } from "@lib/constants"; | ||
import stripJsonComments from "strip-json-comments"; | ||
import type { VoteResultsResponse } from "./types"; | ||
import parseVoteResult from "./parsers/vote-result"; | ||
|
||
export default async function getVoteResult( | ||
id: string, | ||
num: number, | ||
): Promise<VoteResultsResponse> { | ||
const response = await fetch( | ||
`${PARLIAMENT_BASE_URL}/dokumentstatus/${id}.json`, | ||
); | ||
const text = await response.text(); | ||
const data = JSON.parse(stripJsonComments(text)); | ||
|
||
return parseVoteResult(data, num); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { PARLIAMENT_BASE_URL } from "@lib/constants"; | ||
import { parseVotes } from "./parsers/votes"; | ||
|
||
interface Query { | ||
search?: string; | ||
committees: string[]; | ||
page?: number; | ||
} | ||
|
||
export async function getVotes({ search, committees, page }: Query) { | ||
const query = new URLSearchParams({ | ||
doktyp: "votering", | ||
sortorder: "desc", | ||
utformat: "json", | ||
sok: search || "", | ||
sort: search ? "rel" : "datum", | ||
p: page?.toString() || "", | ||
}); | ||
for (const committe of committees) { | ||
query.append("org", committe); | ||
} | ||
|
||
const response = await fetch( | ||
`${PARLIAMENT_BASE_URL}/dokumentlista/?${query}`, | ||
{ | ||
next: { | ||
revalidate: 60 * 60, | ||
}, | ||
}, | ||
); | ||
|
||
const data = await response.json(); | ||
|
||
return parseVotes(data); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { | ||
NewVotingRow, | ||
NewVotingTable, | ||
OldVotingRow, | ||
OldVotingTable, | ||
VoteDocumentStatus, | ||
VotingTable, | ||
} from "@lib/api/parliament/types"; | ||
import type { VoteResultsResponse } from "../types"; | ||
import { getMaxVote } from "../utilts/get-max-vote"; | ||
import extractVotes from "../utilts/extract-votes"; | ||
|
||
function getVotingRow(votingTable: VotingTable): NewVotingRow[] | OldVotingRow { | ||
if ((<NewVotingTable>votingTable).tbody !== undefined) { | ||
return (<NewVotingTable>votingTable).tbody.tr; | ||
} | ||
return (<OldVotingTable>votingTable).tr; | ||
} | ||
|
||
export default function parseVoteResult( | ||
data: VoteDocumentStatus, | ||
num: number, | ||
): VoteResultsResponse { | ||
const { dokumentstatus } = data; | ||
const { utskottsforslag } = dokumentstatus.dokutskottsforslag; | ||
const voteObject = Array.isArray(utskottsforslag) | ||
? utskottsforslag[num - 1] | ||
: utskottsforslag; | ||
|
||
const { table } = voteObject.votering_sammanfattning_html; | ||
const singleTable = Array.isArray(table) ? table[table.length - 1] : table; | ||
const tableRow = getVotingRow(singleTable); | ||
|
||
return { | ||
results: getMaxVote(extractVotes(tableRow)), | ||
subtitle: voteObject.rubrik, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import type { | ||
DocumentList, | ||
DocumentListEntry, | ||
} from "@lib/api/parliament/types"; | ||
import type { VoteList, VoteListEntry } from "../types"; | ||
import titleTrim from "../utilts/title-trim"; | ||
import getVoteResult from "../get-vote-results"; | ||
import { Committee } from "@lib/committes"; | ||
|
||
async function parseVote(data: DocumentListEntry): Promise<VoteListEntry> { | ||
const { beteckning: denomination, id } = data; | ||
const title: string = titleTrim(data.sokdata.titel); | ||
const proposition = parseInt(data.tempbeteckning, 10); | ||
|
||
const documentId = `${id.substring(0, 2)}01${denomination.split("p")[0]}`; | ||
|
||
const committee = Object.values(Committee).includes(data.organ) | ||
? data.organ | ||
: undefined; | ||
|
||
const { results, subtitle } = await getVoteResult(documentId, proposition); | ||
|
||
return { title, results, committee, documentId, proposition, subtitle }; | ||
} | ||
|
||
export function parseVotes(data: DocumentList): Promise<VoteList> { | ||
const { dokumentlista: document } = data; | ||
|
||
const pages = parseInt(document["@sidor"], 10); | ||
|
||
const votes = document.dokument; | ||
|
||
if (!votes || pages === 0) { | ||
return Promise.resolve({ | ||
pages, | ||
votes: [], | ||
}); | ||
} | ||
|
||
const votesPromises = votes.map(parseVote); | ||
|
||
return Promise.all(votesPromises).then((votes) => ({ | ||
pages, | ||
votes, | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import type { Party } from "@partiguiden/party-data/types"; | ||
import type { DocumentAttachment } from "../parliament/types"; | ||
import type { Committee } from "@lib/committes"; | ||
|
||
export type VoteDescription = "yes" | "no" | "refrain" | "absent"; | ||
|
||
export type VotingEntry = Record<VoteDescription, number>; | ||
|
||
export type VotingGroup = Party | "noParty" | "total"; | ||
|
||
export type VotingDict = Record<VotingGroup, VotingEntry>; | ||
|
||
export type VotingResult = { | ||
yes: string[]; | ||
no: string[]; | ||
winner: "yes" | "no" | "draw"; | ||
}; | ||
|
||
export interface VoteResultsResponse { | ||
results: VotingResult; | ||
subtitle: string; | ||
} | ||
export interface VoteListEntry extends VoteResultsResponse { | ||
title: string; | ||
committee?: Committee; | ||
documentId: string; | ||
proposition: number; | ||
} | ||
|
||
export interface ProcessedDocument { | ||
id: string; | ||
label: string; | ||
proposals?: string; | ||
} | ||
|
||
export interface VoteAppendixItem { | ||
titel: string; | ||
dok_id: string; | ||
fil_url: string; | ||
} | ||
|
||
export interface VoteList { | ||
pages: number; | ||
votes: VoteListEntry[]; | ||
} | ||
|
||
export interface Vote { | ||
title: string; | ||
description: string; | ||
committee: Committee; | ||
propositionText: string; | ||
processedDocuments: ProcessedDocument[]; | ||
appendix: DocumentAttachment[]; | ||
decision: string; | ||
voting: VotingDict; | ||
} |
Oops, something went wrong.