Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0fe3fbe
fix: make dropdown menus scrollable
imdeaconu Sep 6, 2024
4ca1a44
fix: truncate overflowing table columns
imdeaconu Sep 6, 2024
921aa13
Merge branch 'commitglobal:main' into main
imdeaconu Sep 6, 2024
e5a2869
Merge branch 'commitglobal:main' into main
imdeaconu Sep 6, 2024
7866a67
Merge branch 'commitglobal:main' into main
imdeaconu Sep 9, 2024
9ea6c42
Merge branch 'commitglobal:main' into main
imdeaconu Sep 10, 2024
1bd449d
Merge branch 'commitglobal:main' into main
imdeaconu Sep 11, 2024
c9874d1
Squashed commit of the following:
imdeaconu Sep 11, 2024
b7715f3
Merge branch 'commitglobal:main' into main
imdeaconu Sep 12, 2024
0facf65
Squashed commit of the following:
imdeaconu Sep 13, 2024
67f681d
chore: remove unused import
imdeaconu Sep 13, 2024
8d73252
chore: delete duplicated / unused classes
imdeaconu Sep 16, 2024
63b21b9
Merge branch 'commitglobal:main' into main
imdeaconu Sep 17, 2024
1892e6e
Merge branch 'commitglobal:main' into main
imdeaconu Sep 18, 2024
abb7c01
feature: add searching to MonitoringObserversTagFilter
imdeaconu Sep 19, 2024
9d0b8ae
Merge branch 'commitglobal:main' into main
imdeaconu Sep 20, 2024
c9fcd3e
chore: update config files
imdeaconu Sep 20, 2024
333ba49
Revert "[NGO Admin] Rewrite the tag selector component (#675)"
imdeaconu Sep 23, 2024
580b68e
Merge branch 'main' of https://github.com/commitglobal/votemonitor
imdeaconu Sep 23, 2024
ba2dad9
Merge branch 'commitglobal:main' into main
imdeaconu Sep 25, 2024
eea4faa
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu Sep 26, 2024
29b8163
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu Sep 26, 2024
68a44ee
Merge branch 'commitglobal-main'
imdeaconu Sep 26, 2024
7cf3244
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu Oct 1, 2024
b6abee7
Merge branch 'commitglobal-main-s1'
imdeaconu Oct 1, 2024
cc71856
Merge branch 'commitglobal:main' into main
imdeaconu Oct 2, 2024
e45ea22
Merge branch 'commitglobal:main' into main
imdeaconu Oct 2, 2024
50d15b6
Merge branch 'commitglobal:main' into main
imdeaconu Oct 2, 2024
1ed8e99
Merge branch 'commitglobal:main' into main
imdeaconu Oct 3, 2024
c2f1395
Merge branch 'commitglobal:main' into main
imdeaconu Oct 7, 2024
c51b40a
add page for creating new observers
imdeaconu Oct 15, 2024
73627ae
WIP: update AddObserver page
imdeaconu Oct 16, 2024
a463fd7
remove required attributes
imdeaconu Oct 16, 2024
a3258b5
remove unused imports
imdeaconu Oct 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

public class MonitoringObserverImportModel
{
public required string Email { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required string PhoneNumber { get; set; }
public required string[] Tags { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string[] Tags { get; set; }

public override int GetHashCode()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { authApi } from '@/common/auth-api';
import Layout from '@/components/layout/Layout';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import TagsSelectFormField from '@/components/ui/tag-selector';
import { toast } from '@/components/ui/use-toast';
import { useCurrentElectionRoundStore } from '@/context/election-round.store';
import { useMonitoringObserversTags } from '@/hooks/tags-queries';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { MonitorObserverBackButton } from './MonitoringObserverBackButton';

export default function CreateMonitoringObserver() {
const { t } = useTranslation('translation', { keyPrefix: 'observers.addObserver' });
const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId);
const { data: availableTags } = useMonitoringObserversTags(currentElectionRoundId);
const navigate = useNavigate();
const queryClient = useQueryClient();
const newObserverSchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string().email(),
phoneNumber: z.string(),
tags: z.any(),
});

type ObserverFormData = z.infer<typeof newObserverSchema>;

const form = useForm<ObserverFormData>({
resolver: zodResolver(newObserverSchema),
});

function onSubmit(values: ObserverFormData) {
newObserverMutation.mutate({ electionRoundId: currentElectionRoundId, values });
}

const newObserverMutation = useMutation({
mutationFn: ({ electionRoundId, values }: { electionRoundId: string; values: ObserverFormData }) => {
return authApi.post(`/election-rounds/${electionRoundId}/monitoring-observers`, { observers: [values] });
},

onSuccess: () => {
toast({
title: 'Success',
description: t('onSuccess'),
});

queryClient.invalidateQueries({ queryKey: ['monitoring-observers', currentElectionRoundId] });

navigate({
to: '/monitoring-observers/$tab',
params: { tab: 'list' },
});
},
onError: () => {
toast({
title: t('onError'),
description: 'Please contact tech support',
variant: 'destructive',
});
},
});
return (
<Layout title={''} backButton={<MonitorObserverBackButton />} enableBreadcrumbs={false}>
<Card className='w-[800px] pt-0'>
<CardHeader className='flex gap-2 flex-column'>
<div className='flex flex-row items-center justify-between'>
<CardTitle className='text-xl'>{t('title')}</CardTitle>
</div>
<Separator />
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'>
<FormField
control={form.control}
name='firstName'
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>{t('firstName')}</FormLabel>
<Input placeholder={t('firstName')} {...field} {...fieldState} />
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name='lastName'
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>{t('lastName')}</FormLabel>
<Input placeholder={t('lastName')} {...field} {...fieldState} />
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name='email'
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>{t('email')}</FormLabel>
<Input placeholder={t('email')} {...field} {...fieldState} />
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name='phoneNumber'
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>{t('phone')}</FormLabel>
<Input placeholder={t('phone')} {...field} {...fieldState} />
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name='tags'
render={({ field }) => (
<FormItem>
<FormLabel className='text-left'>Tags</FormLabel>
<FormControl>
<TagsSelectFormField
options={availableTags?.filter((tag) => !field.value?.includes(tag)) ?? []}
defaultValue={field.value}
onValueChange={field.onChange}
placeholder='Observer tags'
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className='flex justify-between'>
<div className='flex gap-2'>
<Button
variant='outline'
type='button'
onClick={() => {
void navigate({
to: '/monitoring-observers/$tab',
params: { tab: 'list' },
});
}}>
Cancel
</Button>

<Button title={t('addBtnText')} type='submit' className='px-6'>
{t('addBtnText')}
</Button>
</div>
</div>
</form>
</Form>
</CardContent>
</Card>
</Layout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Separator } from '@/components/ui/separator';
import { useDialog } from '@/components/ui/use-dialog';
import { Cog8ToothIcon, EllipsisVerticalIcon, FunnelIcon, PaperAirplaneIcon } from '@heroicons/react/24/outline';
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import { useNavigate, useRouter, useSearch } from '@tanstack/react-router';
import { Link, useNavigate, useRouter, useSearch } from '@tanstack/react-router';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { useState } from 'react';

Expand All @@ -27,9 +27,11 @@ import { toast } from '@/components/ui/use-toast';
import { useCurrentElectionRoundStore } from '@/context/election-round.store';
import { FILTER_KEY } from '@/features/filtering/filtering-enums';
import { useFilteringContainer } from '@/features/filtering/hooks/useFilteringContainer';
import i18n from '@/i18n';
import { isQueryFiltered } from '@/lib/utils';
import { queryClient } from '@/main';
import { format } from 'date-fns';
import { Plus } from 'lucide-react';
import { MonitoringObserversListFilters } from '../../filtering/MonitoringObserversListFilters';
import { MonitoringObserver, MonitoringObserverStatus } from '../../models/monitoring-observer';
import ImportMonitoringObserversDialog from '../MonitoringObserversList/ImportMonitoringObserversDialog';
Expand Down Expand Up @@ -173,9 +175,7 @@ function MonitoringObserversList() {
};

const tagString =
pageParams.tags == undefined
? ''
: pageParams.tags?.map((n: string) => `tags=${n}`).join('&');
pageParams.tags == undefined ? '' : pageParams.tags?.map((n: string) => `tags=${n}`).join('&');

const response = await authApi.get<PageResponse<MonitoringObserver>>(
`/election-rounds/${currentElectionRoundId}/monitoring-observers?${tagString ?? ''}`,
Expand Down Expand Up @@ -276,6 +276,13 @@ function MonitoringObserversList() {
<div className='flex flex-row items-center justify-between px-6'>
<CardTitle className='text-xl'>Monitoring observers list</CardTitle>
<div className='flex flex-row-reverse gap-4 table-actions flex-row-'>
<Link to='/monitoring-observers/new-observer'>
<Button>
<Plus className='mr-2' width={18} height={18} />
{i18n.t('observers.addObserver.addBtnText')}
</Button>
</Link>

{!!importErrorsFileId && (
<ImportMonitoringObserversErrorsDialog
fileId={importErrorsFileId}
Expand Down
12 changes: 12 additions & 0 deletions web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@
"placeholder.defaultLanguage": "Select a default language",
"placeholder.languages": "Select available languages"
},
"observers": {
"addObserver": {
"title": "Add a new observer",
"firstName": "First name",
"lastName": "Last name",
"email": "Email",
"phone": "Phone number",
"addBtnText": "Add observer",
"onSuccess": "Observer created",
"onError": "Error adding observer"
}
},
"ngoAdminDashboard": {
"title": "Dashboard",
"subtitle": "Key indicators.",
Expand Down
12 changes: 12 additions & 0 deletions web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Route as ResetPasswordSuccessImport } from './routes/reset-password/suc
import { Route as ObserversObserverIdImport } from './routes/observers/$observerId'
import { Route as ObserverGuidesNewImport } from './routes/observer-guides/new'
import { Route as NgosNgoIdImport } from './routes/ngos/$ngoId'
import { Route as MonitoringObserversNewObserverImport } from './routes/monitoring-observers/new-observer'
import { Route as MonitoringObserversCreateNewMessageImport } from './routes/monitoring-observers/create-new-message'
import { Route as MonitoringObserversTabImport } from './routes/monitoring-observers/$tab'
import { Route as FormsFormIdImport } from './routes/forms/$formId'
Expand Down Expand Up @@ -133,6 +134,12 @@ const NgosNgoIdRoute = NgosNgoIdImport.update({
getParentRoute: () => rootRoute,
} as any)

const MonitoringObserversNewObserverRoute =
MonitoringObserversNewObserverImport.update({
path: '/monitoring-observers/new-observer',
getParentRoute: () => rootRoute,
} as any)

const MonitoringObserversCreateNewMessageRoute =
MonitoringObserversCreateNewMessageImport.update({
path: '/monitoring-observers/create-new-message',
Expand Down Expand Up @@ -296,6 +303,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MonitoringObserversCreateNewMessageImport
parentRoute: typeof rootRoute
}
'/monitoring-observers/new-observer': {
preLoaderRoute: typeof MonitoringObserversNewObserverImport
parentRoute: typeof rootRoute
}
'/ngos/$ngoId': {
preLoaderRoute: typeof NgosNgoIdImport
parentRoute: typeof rootRoute
Expand Down Expand Up @@ -434,6 +445,7 @@ export const routeTree = rootRoute.addChildren([
FormsFormIdRoute,
MonitoringObserversTabRoute,
MonitoringObserversCreateNewMessageRoute,
MonitoringObserversNewObserverRoute,
NgosNgoIdRoute,
ObserverGuidesNewRoute,
ObserversObserverIdRoute,
Expand Down
14 changes: 14 additions & 0 deletions web/src/routes/monitoring-observers/new-observer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CreateMonitoringObserver from '@/features/monitoring-observers/components/CreateMonitoringObserver';
import { redirectIfNotAuth } from '@/lib/utils';
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/monitoring-observers/new-observer')({
beforeLoad: () => {
redirectIfNotAuth();
},
component: CreateNewObserver,
});

function CreateNewObserver() {
return <CreateMonitoringObserver />;
}