Skip to content

Commit

Permalink
feat(admin): handle assemblies in votes (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jayllyz authored Jun 11, 2024
1 parent 268aff4 commit cdbc753
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 948 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:
continue-on-error: true
run: |
supabase link --project-ref ${{ env.SUPABASE_PROJECT_ID }}
supabase db push --include-all --include-seed --password ${{ env.SUPABASE_DB_PASSWORD }}
supabase db push --include-all --password ${{ env.SUPABASE_DB_PASSWORD }}
8 changes: 5 additions & 3 deletions apps/admin/app/(dashboard)/dashboard/votes/details/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { Vote } from '@/app/(dashboard)/dashboard/votes/page';
import { Progress } from '@repo/ui/components/ui/progress';
import { CircleArrowLeft } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useState } from 'react';

Expand Down Expand Up @@ -45,16 +46,17 @@ function ShowContent() {
return (
<>
<div className="flex items-center gap-5">
<h1 className="text-lg font-semibold md:text-2xl">Vote: {title}</h1>
<CircleArrowLeft className="w-8 h-8" onClick={() => window.history.back()} cursor={'pointer'} />
<h1 className="text-lg font-semibold md:text-2xl">Résultats du vote : {title}</h1>
</div>
<div className="flex flex-1 rounded-lg border border-dashed shadow-sm p-4" x-chunk="dashboard-02-chunk-1">
<div className="grid gap-4 w-full">
{results.map((result) => (
<div key={result.id} className="flex justify-center flex-col gap-4 p-4">
<div>
{result.content} ({((result.votes / totalVotes) * 100).toFixed(2)} %)
{result.content} | {result.votes || 0} votes ({((result.votes ?? 0 / totalVotes) * 100).toFixed(2)} %)
</div>
<Progress value={(result.votes / totalVotes) * 100} />
<Progress value={(result.votes ?? 0 / totalVotes) * 100} />
</div>
))}
</div>
Expand Down
13 changes: 11 additions & 2 deletions apps/admin/app/(dashboard)/dashboard/votes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Tabs, TabsContent } from '@repo/ui/components/ui/tabs';
import { useSearchParams } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { Suspense, useEffect, useState } from 'react';
import { type Assembly, getAssemblies } from '../assemblies/utils';

export type Vote = {
id: number;
Expand All @@ -19,6 +20,7 @@ export type Vote = {
max_choices: number;
start_at: string;
end_at: string;
assembly: number | null;
results: { id: number; votes: number; content: string }[];
};

Expand All @@ -37,10 +39,16 @@ function ShowContent() {

const [maxPage, setMaxPage] = useState<number>(1);
const [votes, setVotes] = useState<Vote[]>([]);
const [assemblies, setAssemblies] = useState<Assembly[]>([]);
const [searchTerm, setSearchTerm] = useState<string>('');

useEffect(() => {
const urlApi = process.env.NEXT_PUBLIC_API_URL;
const fetchAssemblies = async () => {
const assemblies = await getAssemblies();
assemblies.data.filter((assembly) => assembly.closed === false);
setAssemblies(assemblies.data);
};

setTimeout(() => {
const queryParams = new URLSearchParams({
Expand Down Expand Up @@ -70,6 +78,7 @@ function ShowContent() {
console.error(error);
});
}, 500);
fetchAssemblies();
}, [page, searchTerm, router]);

const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -81,7 +90,7 @@ function ShowContent() {
<Card x-chunk="dashboard-06-chunk-0">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Gestion des votes</CardTitle>
<AddVote votes={votes} setVotes={setVotes} />
<AddVote votes={votes} setVotes={setVotes} assemblies={assemblies} />
</CardHeader>
<CardContent>
<div className="ml-auto flex items-center gap-2">
Expand All @@ -105,7 +114,7 @@ function ShowContent() {
</TableRow>
</TableHeader>
<TableBody>
<VotesList votes={votes} />
<VotesList votes={votes} setVotes={setVotes} assemblies={assemblies} />
</TableBody>
</Table>
</CardContent>
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function RootLayout({
children: React.ReactNode;
}): JSX.Element {
return (
<html lang="en">
<html lang="fr">
<body className={cn('min-h-screen bg-background font-sans antialiased', fontSans.variable)}>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem disableTransitionOnChange>
{children}
Expand Down
40 changes: 39 additions & 1 deletion apps/admin/app/ui/dashboard/votes/AddVote.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import type { Assembly } from '@/app/(dashboard)/dashboard/assemblies/utils';
import type { Vote } from '@/app/(dashboard)/dashboard/votes/page';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/ui/components/ui/button';
Expand All @@ -16,6 +17,7 @@ import { Input } from '@repo/ui/components/ui/input';
import { Label } from '@repo/ui/components/ui/label';
import { ScrollArea } from '@repo/ui/components/ui/scroll-area';
import { toast } from '@repo/ui/components/ui/sonner';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@ui/components/ui/select';
import { PlusCircle } from 'lucide-react';
import { useRouter } from 'next/navigation';
import type React from 'react';
Expand All @@ -26,9 +28,10 @@ import { z } from 'zod';
interface Props {
votes: Vote[];
setVotes: React.Dispatch<React.SetStateAction<Vote[]>>;
assemblies: Assembly[];
}

function AddVote({ votes, setVotes }: Props) {
function AddVote({ votes, setVotes, assemblies }: Props) {
const [open, setOpen] = useState(false);
const router = useRouter();

Expand All @@ -38,6 +41,7 @@ function AddVote({ votes, setVotes }: Props) {
start_at: z.string(),
end_at: z.string(),
max_choices: z.coerce.number().min(1, { message: 'Le nombre de choix doit être supérieur à 0' }),
assembly: z.string().optional(),
options: z.array(
z.object({ content: z.string().min(2, { message: 'Le choix doit contenir au moins 2 caractères' }) }),
),
Expand All @@ -51,6 +55,7 @@ function AddVote({ votes, setVotes }: Props) {
start_at: '',
end_at: '',
max_choices: 1,
assembly: '0', // 0 = null
options: [{ content: '' }],
},
});
Expand All @@ -75,6 +80,7 @@ function AddVote({ votes, setVotes }: Props) {
start_at: new Date(values.start_at).toISOString(),
end_at: new Date(values.end_at).toISOString(),
max_choices: values.max_choices,
assembly: values.assembly === '0' ? null : Number(values.assembly),
options: values.options,
}),
})
Expand Down Expand Up @@ -187,6 +193,38 @@ function AddVote({ votes, setVotes }: Props) {
)}
/>
</div>
<div className="grid">
<FormField
control={form.control}
name="assembly"
render={({ field }) => (
<FormItem>
<Label className="font-bold">Assemblée générale</Label>
<FormControl>
<Select name="assembly" required onValueChange={field.onChange} defaultValue="0">
<SelectTrigger className="w-full rounded-lg bg-background pl-8 text-black border border-gray-300">
<SelectValue {...field} placeholder="Assemblée" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem key={0} value={'0'}>
Aucune assemblée
</SelectItem>
{assemblies?.map((assembly) => (
<SelectItem key={assembly.id} value={String(assembly.id)}>
{assembly.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="grid gap-4">
{fields.map((field, index) => (
<div key={field.id} className="grid gap-2">
Expand Down
18 changes: 3 additions & 15 deletions apps/admin/app/ui/dashboard/votes/DeleteVote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,12 @@ import { useRouter } from 'next/navigation';
import type React from 'react';
import { useState } from 'react';

type Setter = {
title: React.Dispatch<React.SetStateAction<string>>;
description: React.Dispatch<React.SetStateAction<string>>;
maxChoices: React.Dispatch<React.SetStateAction<number>>;
startAt: React.Dispatch<React.SetStateAction<string>>;
endAt: React.Dispatch<React.SetStateAction<string>>;
};

interface Props {
vote: Vote;
setter: Setter;
setVotes: React.Dispatch<React.SetStateAction<Vote[]>>;
}

function DeleteVote({ vote, setter }: Props) {
function DeleteVote({ vote, setVotes }: Props) {
const [open, setOpen] = useState(false);
const router = useRouter();

Expand All @@ -49,11 +41,7 @@ function DeleteVote({ vote, setter }: Props) {
}
})
.then(() => {
setter.title('Supprimé');
setter.description('');
setter.maxChoices(0);
setter.startAt('');
setter.endAt('');
setVotes((prevVotes) => prevVotes.filter((v) => v.id !== vote.id));
toast.success('Le vote a bien été supprimé', { duration: 5000 });
});
}
Expand Down
44 changes: 43 additions & 1 deletion apps/admin/app/ui/dashboard/votes/EditVote.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import type { Assembly } from '@/app/(dashboard)/dashboard/assemblies/utils';
import type { Vote } from '@/app/(dashboard)/dashboard/votes/page';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/ui/components/ui/button';
Expand All @@ -15,6 +16,7 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/co
import { Input } from '@repo/ui/components/ui/input';
import { Label } from '@repo/ui/components/ui/label';
import { toast } from '@repo/ui/components/ui/sonner';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@ui/components/ui/select';
import { MoreHorizontal } from 'lucide-react';
import { useRouter } from 'next/navigation';
import type React from 'react';
Expand All @@ -33,16 +35,18 @@ type Setter = {
interface Props {
vote: Vote;
setter: Setter;
assemblies: Assembly[];
}

function EditVote({ vote, setter }: Props) {
function EditVote({ vote, setter, assemblies }: Props) {
const [open, setOpen] = useState(false);
const router = useRouter();

const formSchema = z.object({
title: z.string().min(2, { message: 'Le titre doit contenir au moins 2 caractères' }),
description: z.string().optional(),
max_choices: z.coerce.number().min(1, { message: 'Le nombre de choix doit être supérieur à 0' }),
assembly: z.string().nullable(),
});

const form = useForm<z.infer<typeof formSchema>>({
Expand All @@ -51,6 +55,7 @@ function EditVote({ vote, setter }: Props) {
title: vote.title,
description: vote.description,
max_choices: vote.max_choices,
assembly: String(vote.assembly) === 'null' ? '0' : String(vote.assembly),
},
});

Expand All @@ -67,6 +72,7 @@ function EditVote({ vote, setter }: Props) {
title: values.title,
description: values.description,
max_choices: values.max_choices,
assembly: values.assembly === '0' ? null : Number(values.assembly),
}),
})
.then((response) => {
Expand Down Expand Up @@ -147,6 +153,42 @@ function EditVote({ vote, setter }: Props) {
/>
</div>
</div>
<div className="grid">
<FormField
control={form.control}
name="assembly"
render={({ field }) => (
<FormItem>
<Label className="font-bold">Assemblée générale</Label>
<FormControl>
<Select
name="assembly"
required
onValueChange={field.onChange}
defaultValue={field.value || '0'}
>
<SelectTrigger className="w-full rounded-lg bg-background pl-8 text-black border border-gray-300">
<SelectValue {...field} placeholder="Assemblée" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem key={0} value={'0'}>
Aucune assemblée
</SelectItem>
{assemblies?.map((assembly) => (
<SelectItem key={assembly.id} value={String(assembly.id)}>
{assembly.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex gap-4 mt-4">
<Button type="submit" className="w-full">
Modifier
Expand Down
9 changes: 6 additions & 3 deletions apps/admin/app/ui/dashboard/votes/VoteRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import type { Assembly } from '@/app/(dashboard)/dashboard/assemblies/utils';
import type { Vote } from '@/app/(dashboard)/dashboard/votes/page';
import DeleteVote from '@/app/ui/dashboard/votes/DeleteVote';
import EditVote from '@/app/ui/dashboard/votes/EditVote';
Expand All @@ -9,9 +10,11 @@ import { useState } from 'react';

interface Props {
vote: Vote;
setVotes: React.Dispatch<React.SetStateAction<Vote[]>>;
assemblies: Assembly[];
}

function VoteRow({ vote }: Props) {
function VoteRow({ vote, setVotes, assemblies }: Props) {
const [title, setTitle] = useState(vote.title);
const [description, setDescription] = useState(vote.description);
const [maxChoices, setMaxChoices] = useState(vote.max_choices);
Expand Down Expand Up @@ -51,8 +54,8 @@ function VoteRow({ vote }: Props) {
<TableCell>{maxChoices}</TableCell>
{title !== 'Supprimé' && (
<TableCell className="flex gap-2">
<EditVote vote={vote} setter={setter} />
<DeleteVote vote={vote} setter={setter} />
<EditVote vote={vote} setter={setter} assemblies={assemblies} />
<DeleteVote vote={vote} setVotes={setVotes} />
</TableCell>
)}
</TableRow>
Expand Down
7 changes: 5 additions & 2 deletions apps/admin/app/ui/dashboard/votes/VotesList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
'use client';

import type { Assembly } from '@/app/(dashboard)/dashboard/assemblies/utils';
import type { Vote } from '@/app/(dashboard)/dashboard/votes/page';
import VoteRow from '@/app/ui/dashboard/votes/VoteRow';

interface Props {
votes: Vote[];
setVotes: React.Dispatch<React.SetStateAction<Vote[]>>;
assemblies: Assembly[];
}

function ActivitiesList({ votes }: Props) {
function ActivitiesList({ votes, setVotes, assemblies }: Props) {
return (
<>
{votes.map((vote: Vote) => (
<VoteRow key={vote.id} vote={vote} />
<VoteRow key={vote.id} vote={vote} setVotes={setVotes} assemblies={assemblies} />
))}
</>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/routes/votes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const createPollSchema = z.object({
start_at: z.string().datetime(),
end_at: z.string().datetime(),
max_choices: z.number().min(1),
assembly: z.number().min(1).optional(),
assembly: z.number().min(1).nullable().optional(),
options: z.array(z.object({ content: z.string() })),
});

Expand Down
2 changes: 1 addition & 1 deletion apps/client/app/(auth)/(members)/members/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function RootLayout({
children: React.ReactNode;
}): JSX.Element {
return (
<html lang="en">
<html lang="fr">
<body className="min-h-screen bg-background font-sans antialiased">
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
<div className="hidden border-r bg-muted/40 md:block">
Expand Down
Loading

0 comments on commit cdbc753

Please sign in to comment.