Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(admin): assembly broken edit + ui fix #243

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 18 additions & 1 deletion apps/admin/app/(dashboard)/dashboard/addresses/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,22 @@ export async function getAdresses(): Promise<{ data: Address[]; count: number }>
Authorization: `Bearer ${token}`,
},
});
return res.json();
if (!res.ok) {
throw new Error('Addresses not found');
}
return await res.json();
}

export async function getOneAddress(id: number): Promise<Address> {
const urlApi = process.env.ATHLONIX_API_URL;
const token = cookies().get('access_token')?.value;
const res = await fetch(`${urlApi}/addresses/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!res.ok) {
throw new Error('Address not found');
}
return await res.json();
}
155 changes: 98 additions & 57 deletions apps/admin/app/(dashboard)/dashboard/assemblies/details/assembly.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import {
import { Label } from '@ui/components/ui/label';
import Loading from '@ui/components/ui/loading';
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';

export default function AssemblyDetail(): JSX.Element {
const searchParams = useSearchParams();
Expand All @@ -40,30 +42,53 @@ export default function AssemblyDetail(): JSX.Element {
const [isClosed, setIsClosed] = useState<boolean>(false);
const [openQrCode, setOpenQrCode] = useState<boolean>(false);
const [qrCode, setQrCode] = useState<string>('');
const [started, setStarted] = useState<boolean>(false);
const [location, setLocation] = useState<Address | null>(null);

useEffect(() => {
const fetchAssembly = async () => {
const data = await getAssembly(Number(idPoll));
setAttendees(data.attendees?.length ?? 0);
setAssembly(data);
setIsClosed(data.closed);
const attendeesArray = (data?.attendees ?? []).map((attendee) => attendee.id);
const members = await getAllMembersForAssembly(attendeesArray);
setMembers(members.data);
try {
setLoading(true);
const data = await getAssembly(Number(idPoll));
setAssembly(data);
setAttendees(data.attendees?.length ?? 0);
setIsClosed(data.closed);

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

setStarted(new Date().getTime() > new Date(data.date).getTime());

if (data.location) {
const locationData = await getOneAddress(Number(data.location));
setLocation(locationData);
}
} catch (_error) {
toast.error("Une erreur est survenue lors du chargement des données de l'assemblée");
} finally {
setLoading(false);
}
};
fetchAssembly();
setLoading(false);

if (idPoll) {
fetchAssembly();
}
}, [idPoll]);

async function handleAddAttendee(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const id_user = Number(formData.get('id_user'));
await addAttendee(Number(idPoll), Number(id_user));
setOpenAddAttendee(false);
const data = await getAssembly(Number(idPoll));
setAttendees(data.attendees?.length ?? 0);
setAssembly(data);
try {
await addAttendee(Number(idPoll), id_user);
setOpenAddAttendee(false);
const data = await getAssembly(Number(idPoll));
setAttendees(data.attendees?.length ?? 0);
setAssembly(data);
} catch (_error) {
toast.error("Erreur lors de l'ajout du membre");
}
}

async function handleEndAssembly(event: React.FormEvent<HTMLFormElement>) {
Expand Down Expand Up @@ -96,7 +121,7 @@ export default function AssemblyDetail(): JSX.Element {
<CircleArrowLeft className="w-8 h-8" onClick={() => window.history.back()} cursor={'pointer'} />
<h1 className="text-lg font-semibold md:text-2xl">{assembly?.name}</h1>
<div className="ml-auto flex gap-5">
{!isClosed && (
{!isClosed && started && (
<CloseAssemblyDialog
openCloseAssembly={openCloseAssembly}
setOpenCloseAssembly={setOpenCloseAssembly}
Expand All @@ -110,64 +135,80 @@ export default function AssemblyDetail(): JSX.Element {
<div>
{isClosed ? (
<span className="text-red-500">Terminée</span>
) : new Date(assembly?.date ?? 0).getTime() < Date.now() ? (
) : started ? (
<span className="text-green-500">En cours de déroulement</span>
) : (
<span className="text-blue-500">Débutera le {new Date(assembly?.date ?? 0).toLocaleString()}</span>
)}
</div>
<div className="flex items-center gap-2">
<HomeIcon className="w-6 h-6" />
{`Lieu: ${assembly?.location ? assembly?.location : 'En ligne'}`}
{location ? `${location.road} ${location.city} ${location.postal_code}` : 'En ligne'}
</div>
<div className="flex items-center gap-2">
<BookOpenText className="w-6 h-6" />
{assembly?.description ?? 'Pas de description'}
</div>
</div>
</div>
<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}
/>
{!started && (
<div className="grid gap-4 w-full">
<div className="flex justify-center flex-col gap-4 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg border border-dashed shadow-sm">
<div className="flex items-center gap-2">
<span>
Une fois l'assemblée générale commencée, vous pourrez générer un QR Code pour confirmer la présence des
membres.
</span>
</div>
</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>
</div>
)}
{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>
)}
{assembly?.attendees?.map((member) => (
<TableRow key={member.id}>
<TableCell>{`${member.first_name} ${member.last_name}`}</TableCell>
<TableCell>{member.email}</TableCell>
</TableRow>
)) ?? []}
</TableBody>
</Table>
</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>
)}
{assembly?.attendees?.map((member) => (
<TableRow key={member.id}>
<TableCell>{`${member.first_name} ${member.last_name}`}</TableCell>
<TableCell>{member.email}</TableCell>
</TableRow>
)) ?? []}
</TableBody>
</Table>
</div>
</>
)}
</>
);
}
Expand Down
78 changes: 70 additions & 8 deletions apps/admin/app/(dashboard)/dashboard/assemblies/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ import { type Assembly, createAssembly, getAssemblies, updateAssembly } from './
export default function AssembliesPage(): JSX.Element {
const [loading, setLoading] = useState<boolean>(true);
const [assemblies, setAssemblies] = useState<Assembly[] | null>(null);
const [filteredAssemblies, setFilteredAssemblies] = useState<Assembly[] | null>(null);
const [count, setCount] = useState<number>(0);
const [open, setOpen] = useState<boolean>(false);
const [editAssembly, setEditAssembly] = useState<Assembly | null>(null);
const [location, setLocation] = useState<Address[] | null>(null);
const [filter, setFilter] = useState<string>('all');

useEffect(() => {
const fetchAssemblies = async () => {
const assemblies = await getAssemblies();
setAssemblies(assemblies.data);
setFilteredAssemblies(assemblies.data);
setCount(assemblies.count);
};
const fetchLocations = async () => {
Expand All @@ -44,6 +47,12 @@ export default function AssembliesPage(): JSX.Element {
setLoading(false);
}, []);

useEffect(() => {
if (assemblies) {
filterAssemblies(filter);
}
}, [assemblies, filter]);

async function handleAddAssembly(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
Expand All @@ -62,20 +71,49 @@ export default function AssembliesPage(): JSX.Element {
async function handleUpdateAssembly(event: React.FormEvent<HTMLFormElement>, id: number) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
await updateAssembly(formData, id);
const assemblies = await getAssemblies();
setAssemblies(assemblies.data);
setCount(assemblies.count);
try {
await updateAssembly(formData, id);
const assemblies = await getAssemblies();
setAssemblies(assemblies.data);
setCount(assemblies.count);
setOpen(false);
toast.success("L'assemblée a été modifiée avec succès");
} catch (_error) {
toast.error("Erreur lors de la modification de l'assemblée");
}
setOpen(false);
}

const filterAssemblies = (status: string) => {
if (!assemblies) return;

const now = new Date();
let filtered: Assembly[];

switch (status) {
case 'scheduled':
filtered = assemblies.filter((a) => new Date(a.date) > now && !a.closed);
break;
case 'inProgress':
filtered = assemblies.filter((a) => new Date(a.date) <= now && !a.closed);
break;
case 'finished':
filtered = assemblies.filter((a) => a.closed);
break;
default:
filtered = assemblies;
}

setFilteredAssemblies(filtered);
};

return (
<div className="p-6 space-y-6">
<header className="bg-gray-100 dark:bg-gray-800 px-6 py-4 flex items-center justify-between">
<h1 className="text-xl font-bold">Assemblées Générales</h1>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Programmer une assemblée</Button>
<Button onClick={() => setEditAssembly(null)}>Programmer une assemblée</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
Expand All @@ -88,6 +126,7 @@ export default function AssembliesPage(): JSX.Element {
id="name"
name="name"
defaultValue={editAssembly ? editAssembly.name : ''}
minLength={5}
required
placeholder="Titre de l'assemblée générale"
/>
Expand All @@ -102,11 +141,11 @@ export default function AssembliesPage(): JSX.Element {
defaultValue={editAssembly ? new Date(editAssembly.date).toISOString().slice(0, 16) : ''}
required
/>
<Select name="location" required>
<Select name="location" required defaultValue={editAssembly ? String(editAssembly.location) : '0'}>
<SelectTrigger className="w-full rounded-lg bg-background pl-8 text-black border border-gray-300">
<SelectValue placeholder="Lieu" />
</SelectTrigger>
<SelectContent defaultValue={editAssembly?.location ? String(editAssembly.location) : '0'}>
<SelectContent>
{location?.map((loc) => (
<SelectItem key={loc.id} value={String(loc.id)}>
{loc.road} {loc.number}, {loc.city}
Expand All @@ -133,6 +172,21 @@ export default function AssembliesPage(): JSX.Element {
</DialogContent>
</Dialog>
</header>

<div className="flex items-center justify-between mb-4">
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className="rounded-lg bg-background text-black border border-gray-300 w-[200px]">
<SelectValue placeholder="Filtrer par statut" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toutes les assemblées</SelectItem>
<SelectItem value="scheduled">Programmées</SelectItem>
<SelectItem value="inProgress">En cours</SelectItem>
<SelectItem value="finished">Terminées</SelectItem>
</SelectContent>
</Select>
</div>

<div className="flex items-center gap-5">
<Table>
<TableHeader>
Expand All @@ -145,7 +199,15 @@ export default function AssembliesPage(): JSX.Element {
</TableRow>
</TableHeader>
<TableBody>
{assemblies?.map((assembly) => (
{assemblies?.length === 0 && (
<TableRow>
<TableCell colSpan={5} className="text-center">
Aucune assemblée générale programmée
</TableCell>
</TableRow>
)}

{filteredAssemblies?.map((assembly) => (
<TableRow key={assembly.id} className="hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer">
<TableCell>
<Link href={`/dashboard/assemblies/details?id=${assembly.id}`}>{assembly.name}</Link>
Expand Down
Loading
Loading