From 4687733d12e059965e71fd1cc6f64d8df4b4580f Mon Sep 17 00:00:00 2001 From: Simon ZHANG <102216594+userMeh@users.noreply.github.com> Date: Thu, 2 May 2024 13:19:47 +0200 Subject: [PATCH] feat: Dashboard addresses (#107) * feat: addresses page * feat: search param * feat: add address * feat: edit addresses --- .../(dashboard)/dashboard/addresses/page.tsx | 188 ++++++++++++++ .../app/(dashboard)/dashboard/users/page.tsx | 33 ++- apps/admin/app/(dashboard)/layout.tsx | 7 + apps/admin/app/ui/LoginForm.tsx | 23 +- .../app/ui/dashboard/addresses/AddAddress.tsx | 236 ++++++++++++++++++ .../app/ui/dashboard/addresses/AddressRow.tsx | 146 +++++++++++ .../ui/dashboard/addresses/AddressesList.tsx | 26 ++ .../app/ui/dashboard/addresses/EditForm.tsx | 216 ++++++++++++++++ apps/admin/app/ui/dashboard/users/AddUser.tsx | 9 +- .../admin/app/ui/dashboard/users/EditForm.tsx | 28 ++- apps/admin/app/ui/dashboard/users/UserRow.tsx | 9 +- apps/api/src/handlers/auth.ts | 2 +- apps/api/src/handlers/location.ts | 16 +- apps/api/src/handlers/users.ts | 30 +-- apps/api/src/routes/locations.ts | 5 +- apps/api/src/routes/users.ts | 30 --- 16 files changed, 918 insertions(+), 86 deletions(-) create mode 100644 apps/admin/app/(dashboard)/dashboard/addresses/page.tsx create mode 100644 apps/admin/app/ui/dashboard/addresses/AddAddress.tsx create mode 100644 apps/admin/app/ui/dashboard/addresses/AddressRow.tsx create mode 100644 apps/admin/app/ui/dashboard/addresses/AddressesList.tsx create mode 100644 apps/admin/app/ui/dashboard/addresses/EditForm.tsx diff --git a/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx b/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx new file mode 100644 index 00000000..98d69d97 --- /dev/null +++ b/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx @@ -0,0 +1,188 @@ +'use client'; + +import AddAddress from '@/app/ui/dashboard/addresses/AddAddress'; +import AddressesList from '@/app/ui/dashboard/addresses/AddressesList'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@repo/ui/components/ui/card'; +import { Input } from '@repo/ui/components/ui/input'; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from '@repo/ui/components/ui/pagination'; +import { Table, TableBody, TableHead, TableHeader, TableRow } from '@repo/ui/components/ui/table'; +import { Tabs, TabsContent } from '@repo/ui/components/ui/tabs'; +import { Toaster } from '@repo/ui/components/ui/toaster'; +import { useSearchParams } from 'next/navigation'; +import { useRouter } from 'next/navigation'; +import { Suspense, useEffect, useState } from 'react'; + +type Address = { + id: number; + road: string; + postal_code: string; + complement: string; + city: string; + number: number; + name: string; + id_lease: number; +}; + +type AddressData = { + data: Address[]; + count: number; +}; + +function ShowContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + let page = searchParams.get('page') || 1; + if (typeof page === 'string') { + page = Number.parseInt(page); + } + + const [maxPage, setMaxPage] = useState(1); + const [addresses, setAddresses] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + + const timer = setTimeout(() => { + const queryParams = new URLSearchParams({ + skip: `${page - 1}`, + take: '10', + search: searchTerm, + }); + + fetch(`${urlApi}/addresses?${queryParams}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .then((data: AddressData) => { + setAddresses(data.data); + setMaxPage(Math.ceil(data.count / 10)); + }) + .catch((error: Error) => { + console.log(error); + }); + }, 500); + + return () => clearTimeout(timer); + }, [page, searchTerm, router]); + + const handleSearch = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + }; + + return ( + + + + Adresses + + + +
+ +
+ + + + Alias + Adresse + Ville + Code postal + + Actions + + + + + + +
+
+ + + + {page > 1 && ( + + + + )} + {page > 3 && ( + + {page - 2} + + )} + {page > 2 && ( + + {page - 1} + + )} + + + {page} + + + {page < maxPage && ( + + {page + 1} + + )} + {page < maxPage - 1 && ( + + {page + 2} + + )} + {page < maxPage && ( + + + + )} + + + +
+
+ ); +} + +export default function Page(): JSX.Element { + return ( +
+
+
+
+
+ + + + + +
+
+
+
+ +
+ ); +} diff --git a/apps/admin/app/(dashboard)/dashboard/users/page.tsx b/apps/admin/app/(dashboard)/dashboard/users/page.tsx index 18106faf..3bcda11e 100644 --- a/apps/admin/app/(dashboard)/dashboard/users/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/users/page.tsx @@ -16,6 +16,7 @@ import { Table, TableBody, TableHead, TableHeader, TableRow } from '@repo/ui/com import { Tabs, TabsContent } from '@repo/ui/components/ui/tabs'; import { Toaster } from '@repo/ui/components/ui/toaster'; import { useSearchParams } from 'next/navigation'; +import { useRouter } from 'next/navigation'; import { Suspense, useEffect, useState } from 'react'; type User = { @@ -29,6 +30,11 @@ type User = { roles: { id: number; name: string }[]; }; +type UserData = { + data: User[]; + count: number; +}; + function ShowContent() { const searchParams = useSearchParams(); let page = searchParams.get('page') || 1; @@ -39,6 +45,7 @@ function ShowContent() { const [maxPage, setMaxPage] = useState(1); const [users, setUsers] = useState([]); const [searchTerm, setSearchTerm] = useState(''); + const router = useRouter(); useEffect(() => { const urlApi = process.env.NEXT_PUBLIC_API_URL; @@ -57,23 +64,15 @@ function ShowContent() { Authorization: `Bearer ${localStorage.getItem('access_token')}`, }, }) - .then((response) => response.json()) - .then((data: User[]) => { - setUsers(data); + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); }) - .catch((error: Error) => { - console.log(error); - }); - - fetch(`${urlApi}/users/count?search=${searchTerm}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('access_token')}`, - }, - }) - .then((response) => response.json()) - .then((data: { count: number }) => { + .then((data: UserData) => { + console.log(data); + setUsers(data.data); setMaxPage(Math.ceil(data.count / 10)); }) .catch((error: Error) => { @@ -82,7 +81,7 @@ function ShowContent() { }, 500); return () => clearTimeout(timer); - }, [page, searchTerm]); + }, [page, searchTerm, router]); const handleSearch = (event: React.ChangeEvent) => { setSearchTerm(event.target.value); diff --git a/apps/admin/app/(dashboard)/layout.tsx b/apps/admin/app/(dashboard)/layout.tsx index fa0d1553..99f55198 100644 --- a/apps/admin/app/(dashboard)/layout.tsx +++ b/apps/admin/app/(dashboard)/layout.tsx @@ -90,6 +90,13 @@ export default function RootLayout({ Statistique + + + Gestion des adresses + diff --git a/apps/admin/app/ui/LoginForm.tsx b/apps/admin/app/ui/LoginForm.tsx index e92510f0..a312bb95 100644 --- a/apps/admin/app/ui/LoginForm.tsx +++ b/apps/admin/app/ui/LoginForm.tsx @@ -9,6 +9,17 @@ import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +type User = { + id: number; + email: string; + username: string; + first_name: string; + last_name: string; + id_referer: number | null; + date_validity: string | null; + roles: { id: number; name: string }[]; +}; + export function LoginForm(): JSX.Element { const router = useRouter(); const urlApi = process.env.NEXT_PUBLIC_API_URL; @@ -38,10 +49,14 @@ export function LoginForm(): JSX.Element { }), }) .then((response) => response.json()) - .then((data: { user: Record; token: string }) => { - localStorage.setItem('user', JSON.stringify(data.user)); - localStorage.setItem('access_token', data.token); - router.push('/dashboard'); + .then((data: { user: User; token: string }) => { + const roles = data.user.roles; + + if (roles.some((role: { id: number }) => role.id === 5)) { + localStorage.setItem('user', JSON.stringify(data.user)); + localStorage.setItem('access_token', data.token); + router.push('/dashboard'); + } }) .catch((error: Error) => { console.log(error); diff --git a/apps/admin/app/ui/dashboard/addresses/AddAddress.tsx b/apps/admin/app/ui/dashboard/addresses/AddAddress.tsx new file mode 100644 index 00000000..c8f1cd60 --- /dev/null +++ b/apps/admin/app/ui/dashboard/addresses/AddAddress.tsx @@ -0,0 +1,236 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/ui/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@repo/ui/components/ui/dialog'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/components/ui/form'; +import { Input } from '@repo/ui/components/ui/input'; +import { Label } from '@repo/ui/components/ui/label'; +import { useToast } from '@repo/ui/hooks/use-toast'; +import { PlusCircle } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +type Address = { + id: number; + road: string; + postal_code: string; + complement: string | null; + city: string; + number: number; + name: string | null; + id_lease: number | null; +}; + +function addAddress({ addresses }: { addresses: Address[] }) { + const [open, setOpen] = useState(false); + const { toast } = useToast(); + const router = useRouter(); + + const formSchema = z.object({ + road: z.string().min(1, { message: 'Le champ est requis' }), + postal_code: z + .string({ message: 'Le champ est requis' }) + .max(5, { message: 'Le code postal doit contenir 5 chiffres' }) + .min(5, { message: 'Le code postal doit contenir 5 chiffres' }), + complement: z.string().optional(), + city: z.string().min(1, { message: 'Le champ est requis' }), + number: z.coerce + .number({ message: 'Le numéro doit être un nombre' }) + .int() + .min(1, { message: 'Le champ est requis' }), + name: z.string().optional(), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + road: '', + postal_code: '', + complement: '', + city: '', + number: undefined, + name: '', + }, + }); + + async function submitEdit(values: z.infer) { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + + fetch(`${urlApi}/addresses`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + body: JSON.stringify({ + road: values.road, + postal_code: values.postal_code, + complement: values.complement, + city: values.city, + number: values.number, + name: values.name, + id_lease: null, + }), + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } else if (response.status === 400) { + console.log(response); + } + return response.json(); + }) + .then((data: { id: number }) => { + toast({ title: 'Adresse ajouté', description: "L'adresse a été ajouté avec succès" }); + addresses.push({ + id: data.id, + road: values.road, + postal_code: values.postal_code, + complement: values.complement ?? null, + city: values.city, + number: values.number, + name: values.name ?? null, + id_lease: null, + }); + }) + .catch((error: Error) => { + toast({ title: 'Erreur', description: error?.message }); + }); + + setOpen(false); + } + + return ( + + + + + + + Ajout d'adresse + +
+ +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+
+ + +
+
+ +
+
+
+
+ ); +} + +export default addAddress; diff --git a/apps/admin/app/ui/dashboard/addresses/AddressRow.tsx b/apps/admin/app/ui/dashboard/addresses/AddressRow.tsx new file mode 100644 index 00000000..c09c539f --- /dev/null +++ b/apps/admin/app/ui/dashboard/addresses/AddressRow.tsx @@ -0,0 +1,146 @@ +'use client'; + +import EditForm from '@/app/ui/dashboard/addresses/EditForm'; +import { Button } from '@repo/ui/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@repo/ui/components/ui/dialog'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuTrigger, +} from '@repo/ui/components/ui/dropdown-menu'; +import { TableCell, TableRow } from '@repo/ui/components/ui/table'; +import { useToast } from '@repo/ui/hooks/use-toast'; +import { MoreHorizontal } from 'lucide-react'; +import { useState } from 'react'; + +interface AddressProps { + id: number; + road: string; + postal_code: string; + complement: string; + city: string; + number: number; + name: string; + id_lease: number; +} + +function AddressRow(address: AddressProps) { + const { toast } = useToast(); + const [openEdit, setOpenEdit] = useState(false); + const [openDelete, setOpenDelete] = useState(false); + + const [road, setRoad] = useState(address.road); + const [complement, setComplement] = useState(address.complement); + const [postalCode, setPostalCode] = useState(address.postal_code); + const [city, setCity] = useState(address.city); + const [number, setNumber] = useState(address.number); + const [name, setName] = useState(address.name); + + const setter = { + road: setRoad, + complement: setComplement, + postalCode: setPostalCode, + city: setCity, + number: setNumber, + name: setName, + }; + + async function deleteAddress() { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + fetch(`${urlApi}/addresses/${address.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + }) + .then((response) => response.json()) + .then(() => { + toast({ title: 'Succès', description: "L'adresse a été supprimé avec succès" }); + }) + .catch((error: Error) => { + toast({ title: 'Erreur', description: error?.message }); + }); + + setOpenDelete(false); + } + + return ( + + {name} + + {number} {road} + {complement && `, ${complement}`} + + {city} + {postalCode} + + + + + + + Actions + + + + + + + + + + + + + + ); +} + +export default AddressRow; diff --git a/apps/admin/app/ui/dashboard/addresses/AddressesList.tsx b/apps/admin/app/ui/dashboard/addresses/AddressesList.tsx new file mode 100644 index 00000000..0f0ff711 --- /dev/null +++ b/apps/admin/app/ui/dashboard/addresses/AddressesList.tsx @@ -0,0 +1,26 @@ +'use client'; + +import AddressRow from '@/app/ui/dashboard/addresses/AddressRow'; + +type Address = { + id: number; + road: string; + postal_code: string; + complement: string; + city: string; + number: number; + name: string; + id_lease: number; +}; + +function UsersList({ addresses }: { addresses: Address[] }) { + return ( + <> + {addresses.map((address: Address) => ( + + ))} + + ); +} + +export default UsersList; diff --git a/apps/admin/app/ui/dashboard/addresses/EditForm.tsx b/apps/admin/app/ui/dashboard/addresses/EditForm.tsx new file mode 100644 index 00000000..160d368c --- /dev/null +++ b/apps/admin/app/ui/dashboard/addresses/EditForm.tsx @@ -0,0 +1,216 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/ui/components/ui/button'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/components/ui/form'; +import { Input } from '@repo/ui/components/ui/input'; +import { Label } from '@repo/ui/components/ui/label'; +import { useToast } from '@repo/ui/hooks/use-toast'; +import { useRouter } from 'next/navigation'; +import type { Dispatch, SetStateAction } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +interface Setter { + road: Dispatch>; + complement: Dispatch>; + postalCode: Dispatch>; + city: Dispatch>; + number: Dispatch>; + name: Dispatch>; +} + +interface EditFormProps { + id: number; + road: string; + postal_code: string; + complement: string | null; + city: string; + number: number; + name: string | null; + closeDialog: () => void; + setter: Setter; +} + +function EditForm(props: EditFormProps): JSX.Element { + const formSchema = z.object({ + road: z.string().min(1, { message: 'Le champ est requis' }), + postal_code: z + .string({ message: 'Le champ est requis' }) + .max(5, { message: 'Le code postal doit contenir 5 chiffres' }) + .min(5, { message: 'Le code postal doit contenir 5 chiffres' }), + complement: z.string().optional(), + city: z.string().min(1, { message: 'Le champ est requis' }), + number: z.coerce + .number({ message: 'Le numéro doit être un nombre' }) + .int() + .min(1, { message: 'Le champ est requis' }), + name: z.string().optional(), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + road: props.road, + postal_code: props.postal_code, + complement: props.complement || '', + city: props.city, + number: props.number, + name: props.name || '', + }, + }); + + const { toast } = useToast(); + const router = useRouter(); + + async function submitEdit(values: z.infer) { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + + fetch(`${urlApi}/addresses/${props.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + body: JSON.stringify({ + road: values.road, + postal_code: values.postal_code, + complement: values.complement, + city: values.city, + number: values.number, + name: values.name, + }), + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .catch((error: Error) => { + toast({ title: 'Erreur', description: error?.message }); + }); + + props.setter.road(values.road); + props.setter.complement(values.complement || ''); + props.setter.postalCode(values.postal_code); + props.setter.city(values.city); + props.setter.number(values.number); + props.setter.name(values.name || ''); + + toast({ title: 'Succès', description: 'Adresse modifié avec succès' }); + + props.closeDialog(); + } + + return ( +
+ +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+
+ + +
+
+ + ); +} + +export default EditForm; diff --git a/apps/admin/app/ui/dashboard/users/AddUser.tsx b/apps/admin/app/ui/dashboard/users/AddUser.tsx index 2bc9afe6..e4e37707 100644 --- a/apps/admin/app/ui/dashboard/users/AddUser.tsx +++ b/apps/admin/app/ui/dashboard/users/AddUser.tsx @@ -15,6 +15,7 @@ import { Input } from '@repo/ui/components/ui/input'; import { Label } from '@repo/ui/components/ui/label'; import { useToast } from '@repo/ui/hooks/use-toast'; import { PlusCircle } from 'lucide-react'; +import { useRouter } from 'next/navigation'; import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -33,6 +34,7 @@ type User = { function AddUser({ users }: { users: User[] }) { const [open, setOpen] = useState(false); const { toast } = useToast(); + const router = useRouter(); const formSchema = z.object({ email: z.string().email({ message: 'Email invalide' }), @@ -69,7 +71,12 @@ function AddUser({ users }: { users: User[] }) { last_name: values.lastName, }), }) - .then((response) => response.json()) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) .then((data: { id: number }) => { toast({ title: 'Utilisateur créé', description: "L'utilisateur a été créé avec succès" }); users.push({ diff --git a/apps/admin/app/ui/dashboard/users/EditForm.tsx b/apps/admin/app/ui/dashboard/users/EditForm.tsx index 2abe6483..56059d2b 100644 --- a/apps/admin/app/ui/dashboard/users/EditForm.tsx +++ b/apps/admin/app/ui/dashboard/users/EditForm.tsx @@ -6,6 +6,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from ' import { Input } from '@repo/ui/components/ui/input'; import { Label } from '@repo/ui/components/ui/label'; import { useToast } from '@repo/ui/hooks/use-toast'; +import { useRouter } from 'next/navigation'; import type { Dispatch, SetStateAction } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -92,6 +93,7 @@ function EditForm(props: EditFormProps): JSX.Element { }); const { toast } = useToast(); + const router = useRouter(); async function submitEdit(values: z.infer) { const urlApi = process.env.NEXT_PUBLIC_API_URL; @@ -107,9 +109,16 @@ function EditForm(props: EditFormProps): JSX.Element { first_name: values.firstName, last_name: values.lastName, }), - }).catch((error: Error) => { - toast({ title: 'Erreur', description: error?.message }); - }); + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .catch((error: Error) => { + toast({ title: 'Erreur', description: error?.message }); + }); fetch(`${urlApi}/users/${props.id}/roles`, { method: 'PUT', @@ -120,9 +129,16 @@ function EditForm(props: EditFormProps): JSX.Element { body: JSON.stringify({ roles: values.roles, }), - }).catch((error: Error) => { - toast({ title: 'Erreur', description: error?.message }); - }); + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .catch((error: Error) => { + toast({ title: 'Erreur', description: error?.message }); + }); props.setter.username(values.username); props.setter.firstName(values.firstName); diff --git a/apps/admin/app/ui/dashboard/users/UserRow.tsx b/apps/admin/app/ui/dashboard/users/UserRow.tsx index d67a66ab..a2a408c8 100644 --- a/apps/admin/app/ui/dashboard/users/UserRow.tsx +++ b/apps/admin/app/ui/dashboard/users/UserRow.tsx @@ -20,6 +20,7 @@ import { import { TableCell, TableRow } from '@repo/ui/components/ui/table'; import { useToast } from '@repo/ui/hooks/use-toast'; import { MoreHorizontal } from 'lucide-react'; +import { useRouter } from 'next/navigation'; import { useState } from 'react'; interface UserProps { @@ -47,6 +48,7 @@ const RoleBadge: Record = { function UserRow(user: UserProps) { const { toast } = useToast(); + const router = useRouter(); const [openEdit, setOpenEdit] = useState(false); const [openDelete, setOpenDelete] = useState(false); @@ -66,7 +68,12 @@ function UserRow(user: UserProps) { Authorization: `Bearer ${localStorage.getItem('access_token')}`, }, }) - .then((response) => response.json()) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) .then(() => { toast({ title: 'Succès', description: "L'utilisateur a été supprimé avec succès" }); }) diff --git a/apps/api/src/handlers/auth.ts b/apps/api/src/handlers/auth.ts index abebd1d6..500e6d1f 100644 --- a/apps/api/src/handlers/auth.ts +++ b/apps/api/src/handlers/auth.ts @@ -59,7 +59,7 @@ auth.openapi(loginUser, async (c) => { const { data: user, error: userError } = await supabase .from('USERS') - .select('*') + .select('*, roles:ROLES (id, name)') .eq('id_auth', data.user.id) .single(); diff --git a/apps/api/src/handlers/location.ts b/apps/api/src/handlers/location.ts index d784f0ae..87c5d620 100644 --- a/apps/api/src/handlers/location.ts +++ b/apps/api/src/handlers/location.ts @@ -11,15 +11,25 @@ export const location = new OpenAPIHono<{ Variables: Variables }>({ }); location.openapi(getAllAddresses, async (c) => { - const { skip, take } = c.req.valid('query'); + const { search, skip, take } = c.req.valid('query'); const { from, to } = getPagination(skip, take - 1); - const { data, error } = await supabase.from('ADDRESSES').select('*').range(from, to); + const { data, error, count } = await supabase + .from('ADDRESSES') + .select('*', { count: 'exact' }) + .range(from, to) + .order('id', { ascending: true }) + .ilike('road', `%${search}%`); if (error) { return c.json({ error: error.message }, 500); } - return c.json(data, 200); + const responseData = { + data: data || [], + count: count || 0, + }; + + return c.json(responseData, 200); }); location.openapi(getOneAddress, async (c) => { diff --git a/apps/api/src/handlers/users.ts b/apps/api/src/handlers/users.ts index b6b992b7..d0e1026d 100644 --- a/apps/api/src/handlers/users.ts +++ b/apps/api/src/handlers/users.ts @@ -7,7 +7,6 @@ import { getAllUsers, getOneUser, getUsersActivities, - getUsersCount, removeUserRole, softDeleteUser, updateUser, @@ -29,37 +28,24 @@ users.openapi(getAllUsers, async (c) => { const searchTerm = search !== undefined ? search : ''; const { from, to } = getPagination(skip, take); - const { data, error } = await supabase + const { data, error, count } = await supabase .from('USERS') - .select('*, roles:ROLES (id, name)') + .select('*, roles:ROLES (id, name)', { count: 'exact' }) .range(from, to) .order('created_at', { ascending: true }) .filter('deleted_at', 'is', null) - .like('username', `%${searchTerm}%`); + .ilike('username', `%${searchTerm}%`); if (error) { return c.json({ error: error.message }, 500); } - return c.json(data, 200); -}); - -users.openapi(getUsersCount, async (c) => { - const roles = c.get('user').roles || []; - await checkRole(roles, false, [Role.ADMIN]); - const { search } = c.req.valid('query'); - const searchTerm = search !== undefined ? search : ''; - const { data, error } = await supabase - .from('USERS') - .select('id') - .filter('deleted_at', 'is', null) - .like('username', `%${searchTerm}%`); - - if (error) { - return c.json({ error: error.message }, 500); - } + const responseData = { + data: data || [], + count: count || 0, + }; - return c.json({ count: data?.length }, 200); + return c.json(responseData, 200); }); users.openapi(getOneUser, async (c) => { diff --git a/apps/api/src/routes/locations.ts b/apps/api/src/routes/locations.ts index a7df54ca..48c52030 100644 --- a/apps/api/src/routes/locations.ts +++ b/apps/api/src/routes/locations.ts @@ -50,7 +50,10 @@ export const getAllAddresses = createRoute({ summary: 'Get all addresses', description: 'Get all addresses', request: { - query: paginationSchema, + query: z.object({ + search: z.string().optional(), + ...paginationSchema.shape, + }), }, responses: { 200: { diff --git a/apps/api/src/routes/users.ts b/apps/api/src/routes/users.ts index 5b8e076b..ab30c7a4 100644 --- a/apps/api/src/routes/users.ts +++ b/apps/api/src/routes/users.ts @@ -256,36 +256,6 @@ export const softDeleteUser = createRoute({ tags: ['user'], }); -export const getUsersCount = createRoute({ - method: 'get', - path: '/users/count', - summary: 'Get users count', - description: 'Get users count', - security: [{ Bearer: [] }], - middleware: authMiddleware, - request: { - query: z.object({ - search: z.string().optional(), - }), - }, - responses: { - 200: { - description: 'Successful response', - content: { - 'application/json': { - schema: { - data: z.object({ - count: z.number(), - }), - }, - }, - }, - }, - 500: serverErrorSchema, - }, - tags: ['user'], -}); - export const getUsersActivities = createRoute({ method: 'get', path: '/users/{id}/activities',