diff --git a/apps/web/app/ledamot/[id]/profile.tsx b/apps/web/app/ledamot/[id]/profile.tsx
index 1004945ac..183e70850 100644
--- a/apps/web/app/ledamot/[id]/profile.tsx
+++ b/apps/web/app/ledamot/[id]/profile.tsx
@@ -12,7 +12,7 @@ export default function Profile({ member }: ProfileProps) {
{member.status}
diff --git a/apps/web/app/ledamot/member-card.tsx b/apps/web/app/ledamot/member-card.tsx
index 44f2d6e04..dd086aa38 100644
--- a/apps/web/app/ledamot/member-card.tsx
+++ b/apps/web/app/ledamot/member-card.tsx
@@ -27,7 +27,7 @@ export default function MemberCard({ member }: Props) {
{member.party !== "-" && (
diff --git a/apps/web/app/parti/[party]/page.tsx b/apps/web/app/parti/[party]/page.tsx
index 64093c622..7d195859b 100644
--- a/apps/web/app/parti/[party]/page.tsx
+++ b/apps/web/app/parti/[party]/page.tsx
@@ -49,7 +49,11 @@ export default async function PartyPage({
(
-
+
)}
>
{party.name}
diff --git a/apps/web/app/votering/[id]/[bet]/page.tsx b/apps/web/app/votering/[id]/[bet]/page.tsx
new file mode 100644
index 000000000..48698bbf8
--- /dev/null
+++ b/apps/web/app/votering/[id]/[bet]/page.tsx
@@ -0,0 +1,26 @@
+import Container from "@components/common/container";
+import PageTitle from "@components/common/page-title";
+
+interface Props {
+ params: {
+ id: string;
+ bet: string;
+ };
+}
+
+export function generateMetadata({ params: { id, bet } }: Props) {
+ return {
+ title: `${id} ${bet} | Votering | Partiguiden`,
+ description: `Hur har partiernat röstat i voteringen ${id}`,
+ };
+}
+
+export default function Vote() {
+ // TODO: Implement
+ return (
+
+
+
+
+ );
+}
diff --git a/apps/web/app/votering/page.tsx b/apps/web/app/votering/page.tsx
new file mode 100644
index 000000000..a01671365
--- /dev/null
+++ b/apps/web/app/votering/page.tsx
@@ -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 {
+ return Object.values(Committee).reduce(
+ (prev, committee) => ({
+ ...prev,
+ [committee]: {
+ title: committeeInfo[committee].desc,
+ value: committees.includes(committee),
+ },
+ }),
+ {} as FilterToggle,
+ );
+}
+
+interface Props {
+ searchParams: {
+ sok?: string | string[];
+ sida?: string | string[];
+ utskott?: string | string[];
+ };
+}
+
+export default async function Votes({ searchParams }: Props) {
+ const page = parseNumberSearchParam(searchParams.sida) ?? 1;
+ const search = parseStringSearchParam(searchParams.sok);
+ const committees = parseStringArraySearchParam(searchParams.utskott);
+ const votes = await getVotes({ search, page, committees });
+
+ const filterToggles = initialFilterToggles(committees);
+
+ return (
+
+ Voteringar
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/votering/vote-list.tsx b/apps/web/app/votering/vote-list.tsx
new file mode 100644
index 000000000..1c60d0a60
--- /dev/null
+++ b/apps/web/app/votering/vote-list.tsx
@@ -0,0 +1,85 @@
+"use client";
+import type { FilterToggle } from "@components/filter/filter-context";
+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";
+import Pagination from "@components/common/pagination";
+
+interface QueryParameters {
+ search: string;
+ toggles: FilterToggle;
+ page?: number;
+}
+
+function buildQueryParameters({ search, toggles, page }: QueryParameters) {
+ 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);
+ }
+
+ if (page) {
+ query.set("sida", page.toString());
+ }
+
+ return query;
+}
+
+interface Props {
+ currentPage: number;
+ votes: VoteList;
+}
+
+export default function VoteList({ votes, currentPage }: Props) {
+ const router = useRouter();
+ const { search, toggles } = useFilterContext();
+
+ useEffect(() => {
+ const debounce = setTimeout(() => {
+ const query = buildQueryParameters({ search, toggles });
+ router.replace(`${routes.votes}?${query}`);
+ }, 500);
+ return () => {
+ clearTimeout(debounce);
+ };
+ }, [router, search, toggles]);
+
+ function onChangePage(newPage: number) {
+ const query = buildQueryParameters({ search, toggles, page: newPage });
+ router.push(`${routes.votes}?${query}`);
+ }
+
+ if (votes.pages === 0) {
+ return (
+
+ Inga voteringar hittades
+
+ );
+ }
+
+ return (
+
+
+ {votes.votes.map((vote) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/app/votering/vote-result.tsx b/apps/web/app/votering/vote-result.tsx
new file mode 100644
index 000000000..c8e94764e
--- /dev/null
+++ b/apps/web/app/votering/vote-result.tsx
@@ -0,0 +1,60 @@
+import PartyIcon from "@components/party/icon";
+import type { VotingResult } from "@lib/api/vote/types";
+import type { Party } from "@partiguiden/party-data/types";
+
+import { twMerge } from "tailwind-merge";
+
+interface ResultColumnProps {
+ votes: Party[];
+ title: string;
+ className: string;
+}
+
+function ResultColumn({ votes, title, className }: ResultColumnProps) {
+ return (
+
+
{title}
+
+ {votes.map((party) => (
+
+ ))}
+
+
+ );
+}
+
+interface Props {
+ votes: VotingResult;
+}
+
+export default function VoteResult({ votes }: Props) {
+ if (!votes.no.length || !votes.yes.length) {
+ return Ingen voteringsdata hittades
;
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/apps/web/app/votering/vote.tsx b/apps/web/app/votering/vote.tsx
new file mode 100644
index 000000000..1b5f6b95d
--- /dev/null
+++ b/apps/web/app/votering/vote.tsx
@@ -0,0 +1,26 @@
+import { Card, CommitteeHeader } from "@components/common/card";
+import type { VoteListEntry } from "@lib/api/vote/types";
+import VoteResult from "./vote-result";
+import Link from "next/link";
+import { routes } from "@lib/navigation";
+
+interface Props {
+ vote: VoteListEntry;
+}
+
+export default function Vote({ vote }: Props) {
+ return (
+
+
+
+
+
+ {vote.title}
+
+
{vote.subtitle}
+
+
+
+
+ );
+}
diff --git a/apps/web/components/parliament/member-image.tsx b/apps/web/components/parliament/member-image.tsx
index bb769d1cc..772d199c1 100644
--- a/apps/web/components/parliament/member-image.tsx
+++ b/apps/web/components/parliament/member-image.tsx
@@ -21,7 +21,7 @@ export default function MemberImage({
member,
className,
children,
- sizes = "(min-width: 640px) 10rem, 6rem",
+ sizes = "(min-width: 640px) 160px, 96px",
}: MemberImageProps) {
const [fallback, setFallback] = useState(false);
diff --git a/apps/web/components/party/icon.tsx b/apps/web/components/party/icon.tsx
index cdf124b00..f1959a05c 100644
--- a/apps/web/components/party/icon.tsx
+++ b/apps/web/components/party/icon.tsx
@@ -1,25 +1,28 @@
import Image from "next/image";
import { partyLogo } from "@lib/assets";
import type { Party } from "@partiguiden/party-data/types";
+import { twMerge } from "tailwind-merge";
interface PartyIconProps {
party: Party;
- size?: number;
className?: string;
+ sizes?: string;
}
export default function PartyIcon({
party,
- size = 25,
className,
+ sizes = "24px",
}: PartyIconProps) {
return (
-
+
+
+
);
}
diff --git a/apps/web/lib/api/vote/get-vote-results.ts b/apps/web/lib/api/vote/get-vote-results.ts
new file mode 100644
index 000000000..56539a593
--- /dev/null
+++ b/apps/web/lib/api/vote/get-vote-results.ts
@@ -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 {
+ 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);
+}
diff --git a/apps/web/lib/api/vote/get-votes.ts b/apps/web/lib/api/vote/get-votes.ts
new file mode 100644
index 000000000..e5431b083
--- /dev/null
+++ b/apps/web/lib/api/vote/get-votes.ts
@@ -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);
+}
diff --git a/apps/web/lib/api/vote/parsers/vote-result.ts b/apps/web/lib/api/vote/parsers/vote-result.ts
new file mode 100644
index 000000000..93eff5572
--- /dev/null
+++ b/apps/web/lib/api/vote/parsers/vote-result.ts
@@ -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 ((votingTable).tbody !== undefined) {
+ return (votingTable).tbody.tr;
+ }
+ return (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,
+ };
+}
diff --git a/apps/web/lib/api/vote/parsers/votes.ts b/apps/web/lib/api/vote/parsers/votes.ts
new file mode 100644
index 000000000..33e719ea5
--- /dev/null
+++ b/apps/web/lib/api/vote/parsers/votes.ts
@@ -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 {
+ 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 {
+ 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,
+ }));
+}
diff --git a/apps/web/lib/api/vote/types.ts b/apps/web/lib/api/vote/types.ts
new file mode 100644
index 000000000..a3fd9ae67
--- /dev/null
+++ b/apps/web/lib/api/vote/types.ts
@@ -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;
+
+export type VotingGroup = Party | "noParty" | "total";
+
+export type VotingDict = Record;
+
+export type VotingResult = {
+ yes: Party[];
+ no: Party[];
+ 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;
+}
diff --git a/apps/web/lib/api/vote/utilts/create-references.ts b/apps/web/lib/api/vote/utilts/create-references.ts
new file mode 100644
index 000000000..701e665e0
--- /dev/null
+++ b/apps/web/lib/api/vote/utilts/create-references.ts
@@ -0,0 +1,68 @@
+import type { DocumentReference } from "@lib/api/parliament/types";
+import type { ProcessedDocument } from "../types";
+
+interface ReferencesResponse {
+ processedDocuments: ProcessedDocument[];
+ propositionText: string;
+}
+
+export default function createReferences(
+ unparsedProposition: string,
+ references: DocumentReference[],
+): ReferencesResponse {
+ /* Remove newlines */
+ let proposition = unparsedProposition.replace(/(
)|
/gm, " ");
+ /* Regex to find references in suggestion text */
+ /* Matches for example: 2019/20:3635 */
+ const referenceRegex = /[0-9]{4}\/[0-9]{2}:[A-ö]{0,4}[0-9]{0,4}/gm;
+
+ const referencedDocuments: Array = [];
+
+ let match;
+ // eslint-disable-next-line no-cond-assign
+ while ((match = referenceRegex.exec(proposition))) {
+ if (!referencedDocuments.includes(match[0])) {
+ referencedDocuments.push(match[0]);
+ }
+ }
+
+ const processedDocuments: Array = [];
+
+ for (let i = 0; i < referencedDocuments.length; i += 1) {
+ const sectionStart = proposition.indexOf(referencedDocuments[i]);
+ const sectionEnd =
+ i < referencedDocuments.length - 1
+ ? proposition.indexOf(referencedDocuments[i + 1])
+ : proposition.length;
+
+ const id =
+ references.find(
+ (reference) =>
+ `${reference.ref_dok_rm}:${reference.ref_dok_bet}` ===
+ referencedDocuments[i],
+ )?.ref_dok_id ?? "";
+
+ const section = proposition.slice(sectionStart, sectionEnd);
+
+ if (section.includes(")")) {
+ /* Replace EX: "2019/20:3642 av Helena Lindahl m.fl. (C)"" */
+ const endIndex = section.indexOf(")") + 1;
+ const label = section.substring(0, endIndex);
+ processedDocuments.push({ id, label });
+
+ proposition = proposition.split(label).join(`[${i}]`);
+ } else {
+ /* Just replace the ID, EX: "2019/20:3642" */
+ processedDocuments.push({
+ id,
+ label: referencedDocuments[i],
+ });
+ proposition = proposition.split(referencedDocuments[i]).join(`[${i}]`);
+ }
+ }
+
+ return {
+ processedDocuments,
+ propositionText: proposition,
+ };
+}
diff --git a/apps/web/lib/api/vote/utilts/extract-votes.ts b/apps/web/lib/api/vote/utilts/extract-votes.ts
new file mode 100644
index 000000000..23124a457
--- /dev/null
+++ b/apps/web/lib/api/vote/utilts/extract-votes.ts
@@ -0,0 +1,90 @@
+import type { NewVotingRow, OldVotingRow } from "@lib/api/parliament/types";
+import type { VotingDict, VotingEntry, VotingGroup } from "../types";
+import { Party } from "@partiguiden/party-data/types";
+
+function votingGroupRemap(partyName: string): VotingGroup {
+ switch (partyName) {
+ case "fp":
+ return Party.L;
+ case "-":
+ return "noParty";
+ case "Totalt":
+ return "total";
+ default:
+ return partyName.toUpperCase() as Party;
+ }
+}
+
+const votingGroup = [...Object.values(Party), "noParty", "total"] as const;
+
+const defaultVotingEntry: VotingEntry = {
+ yes: 0,
+ no: 0,
+ absent: 0,
+ refrain: 0,
+} as const;
+
+const defaultVotes: VotingDict = votingGroup.reduce(
+ (prev, curr) => ({ ...prev, [curr]: defaultVotingEntry }),
+ {} as VotingDict,
+);
+
+function extractVotesNew(row: NewVotingRow[]): VotingDict {
+ const voting = {} as VotingDict;
+
+ const total: VotingEntry = {
+ yes: 0,
+ no: 0,
+ absent: 0,
+ refrain: 0,
+ };
+
+ row.forEach(({ th, td }) => {
+ const votingGroupName = votingGroupRemap(th);
+ const partyVotes = {
+ yes: +td[0],
+ no: +td[1],
+ refrain: +td[2],
+ absent: +td[3],
+ };
+ total["yes"] = total["yes"] + partyVotes["yes"];
+ total["no"] = total["no"] + partyVotes["no"];
+ total["refrain"] = total["refrain"] + partyVotes["refrain"];
+ total["absent"] = total["absent"] + partyVotes["absent"];
+ voting[votingGroupName] = partyVotes;
+ });
+ voting["total"] = total;
+ return voting;
+}
+
+export default function extractVotes(
+ row: NewVotingRow[] | OldVotingRow | undefined,
+): VotingDict {
+ if (!row) {
+ return defaultVotes;
+ }
+ // New only contains `td`
+ if (row.every((col) => Object.hasOwn(col, "td"))) {
+ return extractVotesNew(row);
+ }
+ const voting = {} as VotingDict;
+ const [, , ...entries] = row;
+
+ entries.forEach((entry) => {
+ const { td } = entry;
+
+ if (Array.isArray(td)) {
+ const votingGroupName = votingGroupRemap(td[0]);
+
+ const partyVotes = {
+ yes: +td[1],
+ no: +td[2],
+ refrain: +td[3],
+ absent: +td[4],
+ };
+ voting[votingGroupName] = partyVotes;
+ }
+ });
+
+ return voting;
+}
diff --git a/apps/web/lib/api/vote/utilts/get-max-vote.ts b/apps/web/lib/api/vote/utilts/get-max-vote.ts
new file mode 100644
index 000000000..88332eb99
--- /dev/null
+++ b/apps/web/lib/api/vote/utilts/get-max-vote.ts
@@ -0,0 +1,36 @@
+import { Party } from "@partiguiden/party-data/types";
+import type { VoteDescription, VotingDict, VotingResult } from "../types";
+
+const decisions: VoteDescription[] = ["yes", "no", "refrain"];
+
+export const getMaxVote = (votes: VotingDict): VotingResult => {
+ const result: VotingResult = { yes: [], no: [], winner: "draw" };
+
+ // Want to isolate so just the parties are in the parties constant
+
+ const yesTotal = votes.total.yes;
+ const noTotal = votes.total.no;
+
+ /* Get the winner */
+ if (yesTotal !== noTotal) {
+ result.winner = yesTotal > noTotal ? "yes" : "no";
+ }
+
+ /* Decide on what parties voted for */
+ for (const party of Object.values(Party)) {
+ if (!(party in votes)) {
+ continue;
+ }
+
+ const partyVotes = votes[party];
+ const decision = decisions.reduce((a, b) =>
+ partyVotes[a] > partyVotes[b] ? a : b,
+ );
+
+ if (decision === "yes" || decision === "no") {
+ result[decision].push(party);
+ }
+ }
+
+ return result;
+};
diff --git a/apps/web/lib/api/vote/utilts/title-trim.ts b/apps/web/lib/api/vote/utilts/title-trim.ts
new file mode 100644
index 000000000..2abac73c1
--- /dev/null
+++ b/apps/web/lib/api/vote/utilts/title-trim.ts
@@ -0,0 +1,3 @@
+export default function titleTrim(title: string) {
+ return title.split(/([0-9]{4}\/[0-9]{2}:[A-ö]{0,4}[0-9]{0,4})/)[2].trim();
+}
diff --git a/apps/web/lib/navigation.tsx b/apps/web/lib/navigation.tsx
index 2c89364f3..5ef2c7fa2 100644
--- a/apps/web/lib/navigation.tsx
+++ b/apps/web/lib/navigation.tsx
@@ -18,8 +18,10 @@ export const routes = {
cookiePolicy: "/cookie-policy",
aboutUs: "/om-oss",
polls: "/polls",
- votes: "/voteringar",
- vote: "/vote/[id]/[bet]",
+ votes: "/votering",
+ vote(id: string, bet: number) {
+ return `/votering/${id}/${bet}`;
+ },
decisions: "/decisions",
standpoints: "/standpunkter",
standpoint(id: string) {
diff --git a/apps/web/lib/utils/search-params.ts b/apps/web/lib/utils/search-params.ts
new file mode 100644
index 000000000..06f5cf6e9
--- /dev/null
+++ b/apps/web/lib/utils/search-params.ts
@@ -0,0 +1,36 @@
+export function parseNumberSearchParam(
+ param?: string | string[],
+): number | undefined {
+ if (!param) {
+ return undefined;
+ }
+ const parsed = parseInt(param.toString());
+ if (Number.isNaN(parsed)) {
+ return undefined;
+ }
+ return parsed;
+}
+
+export function parseStringSearchParam(
+ param?: string | string[],
+): string | undefined {
+ if (!param) {
+ return undefined;
+ }
+ if (Array.isArray(param)) {
+ return param[0];
+ }
+ return param;
+}
+
+export function parseStringArraySearchParam(
+ param?: string | string[],
+): string[] {
+ if (!param) {
+ return [];
+ }
+ if (Array.isArray(param)) {
+ return param;
+ }
+ return [param];
+}
diff --git a/apps/web/src/api/helpers/voteUtils.ts b/apps/web/src/api/helpers/voteUtils.ts
index 506fc73e0..c3ef4e151 100644
--- a/apps/web/src/api/helpers/voteUtils.ts
+++ b/apps/web/src/api/helpers/voteUtils.ts
@@ -131,7 +131,6 @@ export const extractVotesNew = (row: NewVotingRow[]): VotingDict => {
voting[votingGroupName] = partyVotes;
});
voting["total"] = total;
- console.log(voting["total"]);
return voting;
};