Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70,214 changes: 35,108 additions & 35,106 deletions mobile/.tamagui/tamagui.config.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { router, useLocalSearchParams } from "expo-router";
import { Screen } from "../../../../../../../components/Screen";
import Header from "../../../../../../../components/Header";
import { Icon } from "../../../../../../../components/Icon";
import { useUserData } from "../../../../../../../contexts/user/UserContext.provider";
import { Typography } from "../../../../../../../components/Typography";
import { ScrollView, Spinner, useWindowDimensions, YStack } from "tamagui";
import { useMemo, useState } from "react";
import { ListView } from "../../../../../../../components/ListView";
import OptionsSheet from "../../../../../../../components/OptionsSheet";
import ChangeLanguageDialog from "../../../../../../../components/ChangeLanguageDialog";
import { useTranslation } from "react-i18next";
import { RefreshControl } from "react-native";
import { ScrollView, Spinner, useWindowDimensions, XStack, YStack } from "tamagui";
import { setFormLanguagePreference } from "../../../../../../../common/language.preferences";
import { useFormById } from "../../../../../../../services/queries/forms.query";
import { useFormSubmissionByFormId } from "../../../../../../../services/queries/form-submissions.query";
import Button from "../../../../../../../components/Button";
import ChangeLanguageDialog from "../../../../../../../components/ChangeLanguageDialog";
import FormOverview from "../../../../../../../components/FormOverview";
import FormQuestionListItem, {
FormQuestionListItemProps,
QuestionStatus,
} from "../../../../../../../components/FormQuestionListItem";
import FormOverview from "../../../../../../../components/FormOverview";
import { useTranslation } from "react-i18next";
import Header from "../../../../../../../components/Header";
import { Icon } from "../../../../../../../components/Icon";
import { ListView } from "../../../../../../../components/ListView";
import OptionsSheet from "../../../../../../../components/OptionsSheet";
import { Screen } from "../../../../../../../components/Screen";
import SearchInput from "../../../../../../../components/SearchInput";
import { Typography } from "../../../../../../../components/Typography";
import WarningDialog from "../../../../../../../components/WarningDialog";
import { useNetInfoContext } from "../../../../../../../contexts/net-info-banner/NetInfoContext";
import { useUserData } from "../../../../../../../contexts/user/UserContext.provider";
import { shouldDisplayQuestion } from "../../../../../../../services/form.parser";
import {
useFormSubmissionMutation,
useMarkFormSubmissionCompletionStatusMutation,
} from "../../../../../../../services/mutations/form-submission.mutation";
import { shouldDisplayQuestion } from "../../../../../../../services/form.parser";
import WarningDialog from "../../../../../../../components/WarningDialog";
import { useAttachments } from "../../../../../../../services/queries/attachments.query";
import { useFormSubmissionByFormId } from "../../../../../../../services/queries/form-submissions.query";
import { useFormById } from "../../../../../../../services/queries/forms.query";
import { useNotesForFormId } from "../../../../../../../services/queries/notes.query";
import { RefreshControl } from "react-native";
import { useNetInfoContext } from "../../../../../../../contexts/net-info-banner/NetInfoContext";

const ESTIMATED_ITEM_SIZE = 100;

Expand All @@ -50,6 +52,8 @@
const [optionSheetOpen, setOptionSheetOpen] = useState(false);
const [clearingForm, setClearingForm] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [search, setSearch] = useState("");

const { width } = useWindowDimensions();

const { mutate: updateSubmission } = useFormSubmissionMutation({
Expand Down Expand Up @@ -238,6 +242,12 @@
}
};

const filteredQuestions = useMemo(() => {
return questions?.filter((q) =>
q.question.toLowerCase().includes(search.trim().toLocaleLowerCase()),
);
}, [questions, search]);

if (isLoadingCurrentForm || isLoadingAnswers) {
return (
<Screen preset="fixed" contentContainerStyle={{ flexGrow: 1 }}>
Expand Down Expand Up @@ -299,18 +309,16 @@
numberOfNotes: number;
}
>
data={questions}
data={filteredQuestions}
ListHeaderComponent={
<YStack gap="$xl" paddingBottom="$xxs">
<YStack marginBottom="$xs">
<FormOverview
completedAnswers={Object.keys(answers || {}).length}
numberOfQuestions={numberOfQuestions}
onFormActionClick={onFormOverviewActionClick}
isCompleted={isCompleted}
/>
<Typography preset="body1" fontWeight="700" gap="$xxs">
{t("questions.title")}
</Typography>
<SearchInput onSearch={setSearch} placeholder="Search questions" />
</YStack>
}
showsVerticalScrollIndicator={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ const Index = () => {
{activeElectionRound && selectedPollingStation?.pollingStationId && psiFormQuestions && (
<FormList
ListHeaderComponent={
<YStack>
<PollingStationGeneral psiData={psiData} psiFormQuestions={psiFormQuestions} />
<Typography preset="body1" fontWeight="700" marginTop="$lg" marginBottom="$xxs">
{t("forms.heading")}
</Typography>
</YStack>
<PollingStationGeneral psiData={psiData} psiFormQuestions={psiFormQuestions} />
}
/>
)}
Expand Down
125 changes: 77 additions & 48 deletions mobile/app/(observer)/(app)/(drawer)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React, { useMemo } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
import { Drawer } from "expo-router/drawer";
import React, { useMemo } from "react";
import { ScrollViewProps } from "react-native";
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
import { useTheme, XStack } from "tamagui";
import { useUserData } from "../../../../contexts/user/UserContext.provider";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTheme, XStack, YStack } from "tamagui";
import { AppMode } from "../../../../contexts/app-mode/AppModeContext.provider";
import { useUserData } from "../../../../contexts/user/UserContext.provider";

import { AppModeSwitchButton } from "../../../../components/AppModeSwitchButton";
import { Icon } from "../../../../components/Icon";
import { Typography } from "../../../../components/Typography";
import { electionRoundSorter } from "../../../../helpers/election-rounds";
import { useRouter } from "expo-router";

type DrawerContentProps = ScrollViewProps & {
children?: React.ReactNode;
Expand All @@ -19,59 +21,86 @@ type DrawerContentProps = ScrollViewProps & {

export const DrawerContent = (props: DrawerContentProps) => {
const { electionRounds, activeElectionRound, setSelectedElectionRoundId } = useUserData();

const router = useRouter();
const startedElectionRounds = useMemo(
() => electionRounds?.filter((electionRound) => electionRound.status === "Started"),
() =>
electionRounds
?.filter((electionRound) => electionRound.status === "Started")
.sort(electionRoundSorter),
[electionRounds],
);

const theme = useTheme();
const insets = useSafeAreaInsets();

return (
<DrawerContentScrollView
contentContainerStyle={{ flexGrow: 1, paddingBottom: insets.bottom + 32 }}
bounces={false}
stickyHeaderIndices={[0]}
{...props}
>
<XStack paddingTop={16} paddingLeft="$md" paddingBottom="$xl">
<Icon icon="vmObserverLogo" width={211} height={65} />
</XStack>
{startedElectionRounds?.map((round, index) => {
return (
<DrawerItem
key={index}
// use a custom component for the label, as the default one only displays one line of text
label={({ color }) => (
<Typography preset="body2" color={color}>
{`${round.status} - ${round.title}`}
</Typography>
)}
focused={activeElectionRound?.id === round.id}
activeTintColor={theme.purple5?.val}
activeBackgroundColor={theme.yellow5?.val}
inactiveTintColor="white"
onPress={() => {
if (activeElectionRound?.id !== round.id) {
setSelectedElectionRoundId(round.id);
}
}}
style={{
paddingVertical: 4,
paddingHorizontal: 16,
marginVertical: 0,
marginHorizontal: 0,
borderRadius: 0,
}}
allowFontScaling={false}
/>
);
})}
<YStack flex={1}>
<DrawerContentScrollView
contentContainerStyle={{ flexGrow: 1 }}
bounces={false}
stickyHeaderIndices={[0]}
{...props}
>
<YStack paddingLeft="$md" backgroundColor={theme.purple5?.val}>
<Icon icon="vmObserverLogo" width={211} height={65} />
<Typography preset="subheading" color="white">
Active elections
</Typography>
</YStack>
{startedElectionRounds?.map((round, index) => {
return (
<DrawerItem
key={index}
// use a custom component for the label, as the default one only displays one line of text
label={({ color }) => (
<Typography preset="body2" color={color}>
{`${round.status} - ${round.title}`}
</Typography>
)}
focused={activeElectionRound?.id === round.id}
activeTintColor={theme.purple5?.val}
activeBackgroundColor={theme.yellow5?.val}
inactiveTintColor="white"
onPress={() => {
if (activeElectionRound?.id !== round.id) {
setSelectedElectionRoundId(round.id);
}
}}
style={{
paddingVertical: 4,
paddingHorizontal: 16,
marginVertical: 0,
marginHorizontal: 0,
borderRadius: 0,
}}
allowFontScaling={false}
/>
);
})}
</DrawerContentScrollView>

{/* app mode switch */}
<AppModeSwitchButton switchToMode={AppMode.CITIZEN} />
</DrawerContentScrollView>
<YStack padding="$md" backgroundColor={theme.purple5?.val}>
<XStack
marginTop="auto"
gap="$xxs"
alignItems="center"
paddingHorizontal="$md"
paddingVertical="$xxxs"
pressStyle={{ opacity: 0.5 }}
onPress={() => router.push("/past-elections")}
>
<Icon icon="appModeSwitch" color={theme.purple5?.val} size={32} />
<Typography color="white" preset="body2" textDecorationLine="underline" flex={1}>
See past elections data
</Typography>
</XStack>
</YStack>
{/* app mode switch */}
<YStack padding="$md" backgroundColor={theme.purple5?.val}>
<AppModeSwitchButton switchToMode={AppMode.CITIZEN} />
</YStack>
</YStack>
);
};

Expand Down
5 changes: 5 additions & 0 deletions mobile/app/(observer)/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const AppLayout = () => {
<Stack.Screen name="change-password" options={{ headerShown: false }} />
<Stack.Screen name="about-votemonitor" options={{ headerShown: false }} />
<Stack.Screen name="guide/[guideId]" options={{ headerShown: false }} />
<Stack.Screen
name="past-elections"
options={{ headerShown: false, gestureEnabled: false }}
/>
<Stack.Screen name="er-statistics/[electionRoundId]" options={{ headerShown: false }} />
</Stack>
</NotificationContextProvider>
</UserContextProvider>
Expand Down
56 changes: 56 additions & 0 deletions mobile/app/(observer)/(app)/er-statistics/[electionRoundId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useLocalSearchParams, useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Header from "../../../../components/Header";
import { Icon } from "../../../../components/Icon";
import { Screen } from "../../../../components/Screen";
import { Typography } from "../../../../components/Typography";
import { useElectionRoundsQuery } from "../../../../services/queries.service";
import { useElectionRoundStatistics } from "../../../../services/queries/er-statistics.query";

type SearchParamsType = {
electionRoundId: string;
};

const ElectionRoundStatistics = () => {
const { electionRoundId } = useLocalSearchParams<SearchParamsType>();
const { data: electionRound, isLoading: isLoadingElectionRound } = useElectionRoundsQuery(
(elections) => elections.find((e) => e.id === electionRoundId),
);
const { t } = useTranslation(["er-statistics", "common"]);
const router = useRouter();
const insets = useSafeAreaInsets();

if (!electionRoundId) {
return <Typography>Incorrect page params</Typography>;
}

const {
data: erStatistics,
isLoading: isLoadingERStatistics,
error: eRStatisticsError,
refetch: refetchERStatistics,
isRefetching: isRefetchingERStatistics,
} = useElectionRoundStatistics(electionRoundId);

if (isLoadingElectionRound || isLoadingERStatistics) {
return <Typography>{t("loading", { ns: "common" })}</Typography>;
}

return (
<Screen
preset="fixed"
contentContainerStyle={{ flexGrow: 1, paddingBottom: 16 + insets.bottom }}
style={{ backgroundColor: "white" }}
>
<Header
title={"Your activity during election" + electionRoundId}
leftIcon={<Icon icon="chevronLeft" color="white" />}
onLeftPress={() => router.back()}
/>
{erStatistics && electionRound && <Typography>hello!</Typography>}
</Screen>
);
};

export default ElectionRoundStatistics;
Loading
Loading