Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 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
2c6d5f0
Merge branch 'commitglobal:main' into main
imdeaconu Oct 9, 2024
8c8e18f
Merge branch 'commitglobal:main' into main
imdeaconu Oct 9, 2024
db46a6d
Merge branch 'commitglobal:main' into main
imdeaconu Oct 10, 2024
d4b0263
Merge branch 'commitglobal:main' into main
imdeaconu Oct 12, 2024
79864cd
Merge branch 'commitglobal:main' into main
imdeaconu Oct 14, 2024
5ae7edf
Merge branch 'commitglobal:main' into main
imdeaconu Oct 15, 2024
c0fe98a
Merge branch 'commitglobal:main' into main
imdeaconu Oct 17, 2024
b7b3c5c
Merge branch 'commitglobal:main' into main
imdeaconu Oct 18, 2024
a892349
Merge branch 'commitglobal:main' into main
imdeaconu Oct 19, 2024
2d98793
Merge branch 'commitglobal:main' into main
imdeaconu Oct 20, 2024
66ba8d0
Merge branch 'commitglobal:main' into main
imdeaconu Oct 21, 2024
a86608d
Merge branch 'commitglobal:main' into main
imdeaconu Oct 22, 2024
f8532c4
move the create observer form into a modal
imdeaconu Oct 22, 2024
a5720e2
remove create observer route
imdeaconu Oct 22, 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
@@ -1,28 +1,28 @@
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 { Dialog, DialogClose, DialogContent, DialogFooter, DialogTitle } from '@/components/ui/dialog';
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';
import { monitoringObserversKeys } from '../hooks/monitoring-observers-queries';
import { monitoringObserversKeys } from '../../hooks/monitoring-observers-queries';

export default function CreateMonitoringObserver() {
export interface CreateMonitoringObserverDialogProps {
open: boolean;
onOpenChange: (open: any) => void;
}

function CreateMonitoringObserverDialog({ open, onOpenChange }: CreateMonitoringObserverDialogProps) {
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(),
Expand All @@ -47,18 +47,15 @@ export default function CreateMonitoringObserver() {
return authApi.post(`/election-rounds/${electionRoundId}/monitoring-observers`, { observers: [values] });
},

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

queryClient.invalidateQueries({ queryKey: monitoringObserversKeys.all(electionRoundId) });

navigate({
to: '/monitoring-observers/$tab',
params: { tab: 'list' },
});
form.reset({});
onOpenChange(false);
},
onError: () => {
toast({
Expand All @@ -68,16 +65,19 @@ export default function CreateMonitoringObserver() {
});
},
});

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>
<Dialog open={open} onOpenChange={onOpenChange} modal={true}>
<DialogContent
className='min-w-[650px] min-h-[350px]'
onInteractOutside={(e) => {
e.preventDefault();
}}
onEscapeKeyDown={(e) => {
e.preventDefault();
}}>
<DialogTitle className='mb-3.5'>{t('title')}</DialogTitle>
<div className='flex flex-col gap-3'>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'>
<FormField
Expand Down Expand Up @@ -147,29 +147,22 @@ export default function CreateMonitoringObserver() {
)}
/>

<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' },
});
}}>
<DialogFooter>
<DialogClose asChild>
<Button className='text-purple-900 border border-purple-900 border-input bg-background hover:bg-purple-50 hover:text-purple-600'>
Cancel
</Button>

<Button title={t('addBtnText')} type='submit' className='px-6'>
{t('addBtnText')}
</Button>
</div>
</div>
</DialogClose>
<Button title={t('addBtnText')} type='submit' className='px-6'>
{t('addBtnText')}
</Button>
</DialogFooter>
</form>
</Form>
</CardContent>
</Card>
</Layout>
</div>
</DialogContent>
</Dialog>
);
}

export default CreateMonitoringObserverDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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 } from '@tanstack/react-query';
import { Link, useNavigate, useRouter } from '@tanstack/react-router';
import { useNavigate, useRouter } from '@tanstack/react-router';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { useEffect, useMemo, useState } from 'react';

Expand All @@ -38,98 +38,101 @@ import { MonitoringObserver, MonitoringObserverStatus } from '../../models/monit
import ImportMonitoringObserversDialog from '../MonitoringObserversList/ImportMonitoringObserversDialog';
import ImportMonitoringObserversErrorsDialog from '../MonitoringObserversList/ImportMonitoringObserversErrorsDialog';
import ConfirmResendInvitationDialog from './ConfirmResendInvitationDialog';
import CreateMonitoringObserverDialog from './CreateMonitoringObserverDialog';

function MonitoringObserversList() {
const navigate = useNavigate();
const router = useRouter();
const search = Route.useSearch();
const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId);

const monitoringObserverColDefs: ColumnDef<MonitoringObserver>[] = useMemo(()=>{
const monitoringObserverColDefs: ColumnDef<MonitoringObserver>[] = useMemo(() => {
return [
{
header: ({ column }) => <DataTableColumnHeader title='Name' column={column} />,
accessorKey: 'name',
enableSorting: true,
enableGlobalFilter: true,
cell: ({
row: {
original: { firstName, lastName },
},
}) => (
<p>
{firstName} {lastName}
</p>
),
},
{
header: ({ column }) => <DataTableColumnHeader title='Email' column={column} />,
accessorKey: 'email',
enableSorting: true,
},
{
header: ({ column }) => <DataTableColumnHeader title='Observer tags' column={column} />,
accessorKey: 'tags',
cell: ({
row: {
original: { tags },
},
}) => <TableTagList tags={tags} />,
},
{
header: ({ column }) => <DataTableColumnHeader title='Phone' column={column} />,
accessorKey: 'phoneNumber',
enableSorting: true,
},
{
header: ({ column }) => <DataTableColumnHeader title='Observer status' column={column} />,
accessorKey: 'status',
enableSorting: true,
cell: ({
row: {
original: { status },
},
}) => <Badge className={'badge-' + status}>{status}</Badge>,
},
{
header: ({ column }) => <DataTableColumnHeader title='Latest activity at' column={column} />,
accessorKey: 'latestActivityAt',
enableSorting: true,
cell: ({
row: {
original: { latestActivityAt },
},
}) => <p>{latestActivityAt ? format(latestActivityAt, DateTimeFormat) : '-'}</p>,
},
{
header: '',
accessorKey: 'action',
enableSorting: true,
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<EllipsisVerticalIcon className='w-[24px] h-[24px] text-purple-600' />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => navigateToObserver(row.original.id)}>View</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigateToEdit(row.original.id)}>Edit</DropdownMenuItem>
<DropdownMenuItem
disabled={row.original.status !== MonitoringObserverStatus.Pending}
onClick={() => handleResendInviteToObserver(row.original.id)}>
Resend invitation email
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
];}, [currentElectionRoundId]);
{
header: ({ column }) => <DataTableColumnHeader title='Name' column={column} />,
accessorKey: 'name',
enableSorting: true,
enableGlobalFilter: true,
cell: ({
row: {
original: { firstName, lastName },
},
}) => (
<p>
{firstName} {lastName}
</p>
),
},
{
header: ({ column }) => <DataTableColumnHeader title='Email' column={column} />,
accessorKey: 'email',
enableSorting: true,
},
{
header: ({ column }) => <DataTableColumnHeader title='Observer tags' column={column} />,
accessorKey: 'tags',
cell: ({
row: {
original: { tags },
},
}) => <TableTagList tags={tags} />,
},
{
header: ({ column }) => <DataTableColumnHeader title='Phone' column={column} />,
accessorKey: 'phoneNumber',
enableSorting: true,
},
{
header: ({ column }) => <DataTableColumnHeader title='Observer status' column={column} />,
accessorKey: 'status',
enableSorting: true,
cell: ({
row: {
original: { status },
},
}) => <Badge className={'badge-' + status}>{status}</Badge>,
},
{
header: ({ column }) => <DataTableColumnHeader title='Latest activity at' column={column} />,
accessorKey: 'latestActivityAt',
enableSorting: true,
cell: ({
row: {
original: { latestActivityAt },
},
}) => <p>{latestActivityAt ? format(latestActivityAt, DateTimeFormat) : '-'}</p>,
},
{
header: '',
accessorKey: 'action',
enableSorting: true,
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<EllipsisVerticalIcon className='w-[24px] h-[24px] text-purple-600' />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => navigateToObserver(row.original.id)}>View</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigateToEdit(row.original.id)}>Edit</DropdownMenuItem>
<DropdownMenuItem
disabled={row.original.status !== MonitoringObserverStatus.Pending}
onClick={() => handleResendInviteToObserver(row.original.id)}>
Resend invitation email
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
];
}, [currentElectionRoundId]);

const [searchText, setSearchText] = useState(search.searchText);
const debouncedSearch = useDebounce(search, 300);
const debouncedSearchText = useDebounce(searchText, 300);

const [importErrorsFileId, setImportErrorsFileId] = useState<string | undefined>();
const [monitoringObserverId, setMonitoringObserverId] = useState<string | undefined>();
const createMonitoringObserverDialog = useDialog();
const importMonitoringObserversDialog = useDialog();
const importMonitoringObserverErrorsDialog = useDialog();
const confirmResendInvitesDialog = useDialog();
Expand All @@ -155,7 +158,7 @@ function MonitoringObserversList() {
const params = [
['status', debouncedSearch.monitoringObserverStatus],
['tags', debouncedSearch.tags],
['searchText', debouncedSearch.searchText]
['searchText', debouncedSearch.searchText],
].filter(([_, value]) => value);

return Object.fromEntries(params);
Expand Down Expand Up @@ -184,7 +187,7 @@ function MonitoringObserversList() {
});
},

onSuccess: (_, {electionRoundId}) => {
onSuccess: (_, { electionRoundId }) => {
queryClient.invalidateQueries({ queryKey: monitoringObserversKeys.all(electionRoundId) });
router.invalidate();

Expand Down Expand Up @@ -251,19 +254,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}
{...importMonitoringObserverErrorsDialog.dialogProps}
/>
)}

<ImportMonitoringObserversDialog
{...importMonitoringObserversDialog.dialogProps}
onImportError={(fileId) => {
Expand Down Expand Up @@ -291,6 +288,12 @@ function MonitoringObserversList() {
</svg>
Import observer list
</Button>
<Button variant='secondary' onClick={() => createMonitoringObserverDialog.trigger()}>
<Plus className='mr-2' width={18} height={18} />
{i18n.t('observers.addObserver.addBtnText')}
</Button>
<CreateMonitoringObserverDialog {...createMonitoringObserverDialog.dialogProps} />

<Button
className='text-purple-900 bg-background hover:bg-purple-50 hover:text-purple-500'
onClick={exportMonitoringObservers}>
Expand Down
Loading