Skip to content

Commit

Permalink
feat: Add election system when closing assembly (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jayllyz committed Jul 19, 2024
1 parent e214863 commit 961fcfe
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 72 deletions.
266 changes: 214 additions & 52 deletions apps/admin/app/(dashboard)/dashboard/assemblies/details/assembly.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { type Address, getOneAddress } from '@/app/(dashboard)/dashboard/addresses/utils';
import {
type Assembly,
addAttendee,
Expand All @@ -9,7 +10,9 @@ import {
} from '@/app/(dashboard)/dashboard/assemblies/utils';
import type { User } from '@/app/lib/type/User';
import { getAllMembersForAssembly } from '@/app/lib/utils';
import { Badge } from '@ui/components/ui/badge';
import { Button } from '@ui/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@ui/components/ui/card';
import {
Dialog,
DialogContent,
Expand All @@ -21,14 +24,14 @@ import {
} from '@ui/components/ui/dialog';
import { Label } from '@ui/components/ui/label';
import Loading from '@ui/components/ui/loading';
import { ScrollArea } from '@ui/components/ui/scroll-area';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ui/components/ui/select';
import { toast } from '@ui/components/ui/sonner';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ui/components/ui/table';
import { Textarea } from '@ui/components/ui/textarea';
import { BookOpenText, CircleArrowLeft, HomeIcon } from 'lucide-react';
import { useSearchParams } from 'next/navigation';
import { type FormEvent, useEffect, useState } from 'react';
import { type Address, getOneAddress } from '../../addresses/utils';
import { type FormEvent, useEffect, useMemo, useState } from 'react';

export default function AssemblyDetail(): JSX.Element {
const searchParams = useSearchParams();
Expand All @@ -44,6 +47,7 @@ export default function AssemblyDetail(): JSX.Element {
const [qrCode, setQrCode] = useState<string>('');
const [started, setStarted] = useState<boolean>(false);
const [location, setLocation] = useState<Address | null>(null);
const [roles, setRoles] = useState<{ id_role: number; id_user: number }[]>([]);

useEffect(() => {
const fetchAssembly = async () => {
Expand All @@ -54,8 +58,7 @@ export default function AssemblyDetail(): JSX.Element {
setAttendees(data.attendees?.length ?? 0);
setIsClosed(data.closed);

const attendeesArray = (data?.attendees ?? []).map((attendee) => attendee.id);
const membersData = await getAllMembersForAssembly(attendeesArray);
const membersData = await getAllMembersForAssembly();
setMembers(membersData.data);

setStarted(new Date().getTime() > new Date(data.date).getTime());
Expand Down Expand Up @@ -86,19 +89,28 @@ export default function AssemblyDetail(): JSX.Element {
const data = await getAssembly(Number(idPoll));
setAttendees(data.attendees?.length ?? 0);
setAssembly(data);
toast.success("Membre ajouté à l'assemblée");
} catch (_error) {
toast.error("Erreur lors de l'ajout du membre");
}
}

async function handleEndAssembly(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const lawsuit = formData.get('lawsuit') as string;
await closeAssembly(Number(idPoll), lawsuit);
setOpenCloseAssembly(false);
const data = await getAssembly(Number(idPoll));
setAssembly(data);
try {
const formData = new FormData(event.currentTarget);
const lawsuit = formData.get('lawsuit') as string;
await closeAssembly(Number(idPoll), lawsuit, roles);
setOpenCloseAssembly(false);
const data = await getAssembly(Number(idPoll));
setAssembly(data);
const membersData = await getAllMembersForAssembly();
setMembers(membersData.data);
setIsClosed(true);
toast.success("L'assemblée a bien été clôturée");
} catch (_error) {
toast.error("Erreur lors de la clôture de l'assemblée");
}
}

async function requestQrCode() {
Expand All @@ -111,6 +123,14 @@ export default function AssemblyDetail(): JSX.Element {
setOpenQrCode(true);
}

const currentRoles: { id_role: number; id_user: number }[] = members?.reduce(
(acc: { id_role: number; id_user: number }[], member) => {
const roles = member.roles.map((role) => ({ id_role: role.id, id_user: member.id }));
return acc.concat(roles);
},
[],
);

if (loading) {
return <Loading />;
}
Expand Down Expand Up @@ -163,67 +183,87 @@ export default function AssemblyDetail(): JSX.Element {
</div>
</div>
)}
{started && (
<RoleSelectionComponent
users={members}
roles={roles}
setRoles={setRoles}
currentRoles={currentRoles}
isClosed={isClosed}
/>
)}
{started && (
<>
<div className="flex items-center gap-5">
<h1 className="text-lg font-semibold md:text-2xl">Membres de l'assemblée ({attendees})</h1>
{!isClosed && (
<div className="ml-auto flex gap-5">
<AddAttendeeDialog
openAddAttendee={openAddAttendee}
setOpenAddAttendee={setOpenAddAttendee}
members={members}
handleAddAttendee={handleAddAttendee}
/>
<QrCodeDialog
openQrCode={openQrCode}
setOpenQrCode={setOpenQrCode}
qrCode={qrCode}
requestQrCode={requestQrCode}
/>
</div>
)}
</div>
<div className="flex items-center gap-5">
<Table>
<TableHeader>
<TableRow>
<TableHead>Nom et Prénom</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{attendees === 0 && (
<TableRow>
<TableCell colSpan={2}>Aucun membre n'a encore été ajouté à cette assemblée</TableCell>
</TableRow>
<Card className="mt-4">
<CardHeader>
<CardTitle className="flex justify-between items-center">
<span>Membres de l'assemblée ({attendees})</span>
{!isClosed && (
<div className="flex gap-2">
<AddAttendeeDialog
attendees={assembly?.attendees}
openAddAttendee={openAddAttendee}
setOpenAddAttendee={setOpenAddAttendee}
members={members}
handleAddAttendee={handleAddAttendee}
/>
<QrCodeDialog
openQrCode={openQrCode}
setOpenQrCode={setOpenQrCode}
qrCode={qrCode}
requestQrCode={requestQrCode}
/>
</div>
)}
{assembly?.attendees?.map((member) => (
<TableRow key={member.id}>
<TableCell>{`${member.first_name} ${member.last_name}`}</TableCell>
<TableCell>{member.email}</TableCell>
</TableRow>
)) ?? []}
</TableBody>
</Table>
</div>
</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px]">
<Table>
<TableHeader>
<TableRow>
<TableHead>Nom et Prénom</TableHead>
<TableHead>Email</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{attendees === 0 && (
<TableRow>
<TableCell colSpan={2}>Aucun membre n'a encore été ajouté à cette assemblée</TableCell>
</TableRow>
)}
{assembly?.attendees?.map((member) => (
<TableRow key={member.id}>
<TableCell>{`${member.first_name} ${member.last_name}`}</TableCell>
<TableCell>{member.email}</TableCell>
</TableRow>
)) ?? []}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
</>
)}
</>
);
}

function AddAttendeeDialog({
attendees,
openAddAttendee,
setOpenAddAttendee,
members,
handleAddAttendee,
}: {
attendees: Assembly['attendees'];
openAddAttendee: boolean;
setOpenAddAttendee: (value: boolean) => void;
members: User[];
handleAddAttendee: (event: FormEvent<HTMLFormElement>) => void;
}) {
members = members?.filter((member) => !attendees?.some((attendee) => attendee.id === member.id)) ?? [];

return (
<Dialog open={openAddAttendee} onOpenChange={setOpenAddAttendee}>
<DialogTrigger asChild>
Expand Down Expand Up @@ -297,7 +337,7 @@ function CloseAssemblyDialog({
/>
</div>
<DialogFooter>
<Button type="submit">Terminer</Button>
<Button type="submit">Terminer l'assemblée</Button>
</DialogFooter>
</form>
</DialogContent>
Expand Down Expand Up @@ -337,3 +377,125 @@ function QrCodeDialog({
</Dialog>
);
}

enum ElectionRole {
Président = 6,
Vice_président = 9,
Secrétaire = 7,
Trésorier = 8,
Chargé_de_communication = 11,
Chef_de_projet = 12,
}

interface RoleSelection {
id_role: number;
id_user: number;
}

interface RoleSelectionComponentProps {
users: User[];
roles: RoleSelection[];
setRoles: React.Dispatch<React.SetStateAction<RoleSelection[]>>;
currentRoles?: RoleSelection[];
isClosed?: boolean;
}

function RoleSelectionComponent({
users,
roles,
setRoles,
currentRoles = [],
isClosed = false,
}: RoleSelectionComponentProps) {
const handleRoleChange = (roleId: number, userId: number) => {
setRoles((prevRoles) => {
const newRoles = prevRoles.filter((role) => role.id_role !== roleId);
if (userId !== 0 && !newRoles.some((role) => role.id_user === userId)) {
newRoles.push({ id_role: roleId, id_user: userId });
}
return newRoles;
});
};

const clearAllSelections = () => {
setRoles([]);
};

const getUserName = (userId: number) => {
const user = users.find((u) => u.id === userId);
return user ? `${user.first_name} ${user.last_name}` : 'Aucun';
};

const memoizedUsers = useMemo(() => {
return [
{ id: 0, first_name: 'Pas de changement', last_name: '' },
...users.sort((a, b) => a.last_name.localeCompare(b.last_name)),
];
}, [users]);

return (
<Card className="mb-4">
<CardHeader>
<CardTitle className="flex justify-between items-center">
<span>Election des membres du bureau</span>
<Button variant="outline" size="sm" onClick={clearAllSelections} disabled={isClosed}>
Effacer les sélections
</Button>
</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px]">
<table className="w-full">
<thead>
<tr>
<th className="text-left pb-2">Rôle</th>
<th className="text-left pb-2">Membre actuel</th>
<th className="text-left pb-2">Nouveau membre</th>
</tr>
</thead>
<tbody>
{Object.entries(ElectionRole)
.filter(([roleName, roleId]) => typeof roleName === 'string' && typeof roleId === 'number')
.map(([roleName, roleId]) => {
const currentRoleHolder = currentRoles.find((r) => r.id_role === roleId);
const selectedUser = roles.find((r) => r.id_role === roleId)?.id_user;
return (
<tr key={roleId} className="border-t">
<td className="py-2">
<Label htmlFor={`role-${roleId}`}>{roleName.replaceAll('_', ' ')}</Label>
</td>
<td className="py-2">
{currentRoleHolder ? (
<Badge variant="secondary">{getUserName(currentRoleHolder.id_user)}</Badge>
) : (
<Badge variant="outline">Aucun</Badge>
)}
</td>
<td className="py-2">
<Select
onValueChange={(value) => handleRoleChange(Number(roleId), Number(value))}
value={selectedUser?.toString() || '0'}
disabled={isClosed}
>
<SelectTrigger id={`role-${roleId}`} className="w-full">
<SelectValue placeholder="Sélectionner un membre" />
</SelectTrigger>
<SelectContent>
{memoizedUsers.map((user: { id: number; first_name: string; last_name: string }) => (
<SelectItem key={user.id} value={user.id.toString()}>
{user.first_name} {user.last_name}
</SelectItem>
))}
</SelectContent>
</Select>
</td>
</tr>
);
})}
</tbody>
</table>
</ScrollArea>
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Loading from '@ui/components/ui/loading';
import dynamic from 'next/dynamic';
import { Suspense } from 'react';

Expand All @@ -6,7 +7,7 @@ const DynamicAssemblyDetail = dynamic(() => import('./assembly'), { ssr: false }
export default function page(): JSX.Element {
return (
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 h-full">
<Suspense fallback={<div>Chargement...</div>}>
<Suspense fallback={<Loading />}>
<DynamicAssemblyDetail />
</Suspense>
</main>
Expand Down
Loading

0 comments on commit 961fcfe

Please sign in to comment.