[fe-02] feat(componente): Cria o componente que manipula as respostas vindas do servidor

This commit is contained in:
Keven Willian Pereira de Souza 2025-08-26 18:30:19 -03:00
parent 1407f0df90
commit 7ec1259ba0
21 changed files with 326 additions and 64 deletions

View file

@ -1,39 +1,31 @@
'use client'
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { useParams } from "next/navigation";
import {
toast
} from "sonner";
import {
Card,
CardContent
} from "@/components/ui/card";
import GUsuarioRead from "@/app/(protected)/(administrativo)/_services/g_usuario/GUsuarioRead";
import { useGUsuarioReadHooks } from "@/app/(protected)/(administrativo)/_hooks/g_usuario/useGUsuarioReadHooks";
import Usuario from "@/app/(protected)/(administrativo)/_interfaces/IGUsuario";
import Loading from "@/app/_components/loading/loading";
export default function UsuarioDetalhes() {
const params = useParams();
const [usuario, setUsuario] = useState<any>(null);
const { usuario, fetchUsuario } = useGUsuarioReadHooks();
useEffect(() => {
async function fetchUsuarios() {
const data = await GUsuarioRead(Number(params.id));
if (data.code == 400) {
toast(data.message);
}
setUsuario(data);
if (params.id) {
fetchUsuario({ usuario_id: Number(params.id) } as Usuario);
}
fetchUsuarios();
}, []);
if (!usuario) return <Loading type={1} />;
return (
<div>
<Card>
@ -44,7 +36,7 @@ export default function UsuarioDetalhes() {
Nome
</div>
<div className="text-xl">
{usuario?.data?.nome_completo}
{usuario?.nome_completo}
</div>
</div>
<div>
@ -52,7 +44,7 @@ export default function UsuarioDetalhes() {
CPF
</div>
<div className="text-xl">
{usuario?.data?.cpf}
{usuario?.cpf}
</div>
</div>
<div>
@ -60,7 +52,7 @@ export default function UsuarioDetalhes() {
Função
</div>
<div className="text-xl">
{usuario?.data?.funcao}
{usuario?.funcao}
</div>
</div>
<div>
@ -68,7 +60,7 @@ export default function UsuarioDetalhes() {
Email
</div>
<div className="text-xl">
{usuario?.data?.email}
{usuario?.email}
</div>
</div>
</div>

View file

@ -9,6 +9,7 @@ import { UsuarioFormSchema } from "../../../_schemas/GUsuarioSchema"
import {
Button
} from "@/components/ui/button"
import {
Card,
CardContent
@ -23,13 +24,14 @@ import {
FormMessage,
} from "@/components/ui/form"
import GUsuarioSave from "../../../_services/g_usuario/GUsuarioSave"
import { toast } from "sonner"
import { useGUsuarioSaveHook } from "../../../_hooks/g_usuario/useGUsuarioSaveHook"
type FormValues = z.infer<typeof UsuarioFormSchema>
export default function UsuarioFormularioPage() {
const { usuario, saveUsuario } = useGUsuarioSaveHook();
const form = useForm<FormValues>({
resolver: zodResolver(UsuarioFormSchema),
defaultValues: {
@ -42,16 +44,7 @@ export default function UsuarioFormularioPage() {
});
async function onSubmit(values: FormValues) {
const response = await GUsuarioSave(values);
if(response.message)
{
toast("Event has been created.");
}
console.log(response);
saveUsuario(values);
}
return (

View file

@ -1,7 +1,5 @@
'use client'
import GUsuarioIndex from "@/app/(protected)/(administrativo)/_services/g_usuario/GUsuarioIndex";
import {
Card,
CardContent,
@ -19,25 +17,19 @@ import {
import Usuario from "../../_interfaces/IGUsuario";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useGUsuarioIndexHook } from "../../_hooks/g_usuario/useGUsuarioIndexHook";
import { useEffect } from "react";
import Loading from "@/app/_components/loading/loading";
export default function UsuarioPage() {
const [usuarios, setUsuarios] = useState<any>(null);
function RemoveUser(usuarioId: number) {
console.log('asdasd')
}
const { usuarios, fetchUsuarios } = useGUsuarioIndexHook();
useEffect(() => {
async function fetchUsuarios() {
const data = await GUsuarioIndex();
setUsuarios(data);
}
fetchUsuarios();
}, []);
if (!usuarios) return <div>Carregando...</div>;
if (!usuarios) return <Loading type={2} />;
return (
<div>
@ -78,7 +70,7 @@ export default function UsuarioPage() {
</TableRow>
</TableHeader>
<TableBody>
{usuarios.data.map((usuario: Usuario) => (
{usuarios.map((usuario: Usuario) => (
<TableRow key={usuario.usuario_id} className="cursor-pointer">
<TableCell className="text-center">
{usuario.usuario_id}
@ -109,11 +101,6 @@ export default function UsuarioPage() {
</Link>
</Button>
</TableCell>
<TableCell>
<Button onClick={() => RemoveUser(usuario.usuario_id)}>
Remover
</Button>
</TableCell>
</TableRow>
))}
</TableBody>

View file

@ -1,3 +1,5 @@
'use server'
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";

View file

@ -1,3 +1,5 @@
'use server'
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";

View file

@ -1,3 +1,5 @@
'use server'
import { Methods } from "@/services/api/enums/ApiMethodEnum";
import API from "@/services/api/Api";

View file

@ -1,3 +1,5 @@
'use server'
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";

View file

@ -1,3 +1,5 @@
'use server'
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";

View file

@ -0,0 +1,27 @@
'use client'
import { useState } from "react"
import Usuario from "../../_interfaces/IGUsuario"
import GUsuarioIndex from "../../_services/g_usuario/GUsuarioIndex";
import { useResponse } from "@/app/_response/ResponseContext";
export const useGUsuarioIndexHook = () => {
const { setResponse } = useResponse();
const [usuarios, setUsuarios] = useState<Usuario[] | null>(null);
const fetchUsuarios = async () => {
const response = await GUsuarioIndex();
setUsuarios(response.data);
// Define os dados do componente de resposta (toast, modal, etc)
setResponse(response);
}
return { usuarios, fetchUsuarios }
}

View file

@ -0,0 +1,26 @@
'use client'
import { useState } from "react"
import Usuario from "../../_interfaces/IGUsuario"
import GUsuarioRead from "../../_services/g_usuario/GUsuarioRead";
import { useResponse } from "@/app/_response/ResponseContext";
export const useGUsuarioReadHooks = () => {
const { setResponse } = useResponse();
const [usuario, setUsuario] = useState<Usuario>();
const fetchUsuario = async (Usuario: Usuario) => {
const response = await GUsuarioRead(Usuario.usuario_id);
setUsuario(response.data);
setResponse(response);
}
return { usuario, fetchUsuario }
}

View file

@ -0,0 +1,26 @@
'use client'
import { useState } from "react"
import Usuario from "../../_interfaces/IGUsuario"
import GUsuarioSave from "../../_services/g_usuario/GUsuarioSave";
import { useResponse } from "@/app/_response/ResponseContext";
export const useGUsuarioSaveHook = () => {
const { setResponse } = useResponse();
const [usuario, setUsuario] = useState<Usuario>();
const saveUsuario = async (Usuario: any) => {
const response = await GUsuarioSave(Usuario);
setUsuario(response.data);
setResponse(response);
}
return { usuario, saveUsuario }
}

View file

@ -4,6 +4,8 @@ import GUsuarioIndexData from "../../_data/g_usuario/GUsuarioIndexData"
export default async function GUsuarioIndex() {
return await GUsuarioIndexData();
const response = await GUsuarioIndexData();
return response;
}

View file

@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "../globals.css";
import { ResponseProvider } from '../_response/ResponseContext';
import { AppSidebar } from "@/components/app-sidebar"
import {
Breadcrumb,
@ -18,6 +19,7 @@ import {
SidebarTrigger,
} from "@/components/ui/sidebar"
import { Toaster } from "@/components/ui/sonner";
import Response from "../_response/response";
const geistSans = Geist({
variable: "--font-geist-sans",
@ -69,10 +71,13 @@ export default function RootLayout({
</Breadcrumb>
</div>
</header>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
{children}
<Toaster position="top-center" />
</div>
<ResponseProvider>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
{children}
<Toaster position="top-center" />
<Response />
</div>
</ResponseProvider>
</SidebarInset>
</SidebarProvider>
</body>

View file

@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "../../globals.css";
import { Suspense } from "react";
const geistSans = Geist({
variable: "--font-geist-sans",
@ -24,9 +25,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
{children}
</body>
</html>

View file

@ -0,0 +1,18 @@
import React from "react";
import SkeletonCard from "./skeletonCard";
import SkeletonTable from "./skeletonTable";
export default function Loading({ type }: any) {
switch (type) {
case 1:
return <SkeletonCard />;
break;
case 2:
return <SkeletonTable />
break;
}
}

View file

@ -0,0 +1,35 @@
export default function SkeletonCard() {
return (
<div className="space-y-6">
<div className="space-y-2">
<div className="h-4 w-24 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
<div className="h-10 w-full bg-gray-200 dark:bg-slate-700 rounded-md animate-pulse"></div>
</div>
<div className="space-y-2">
<div className="h-4 w-32 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
<div className="h-10 w-full bg-gray-200 dark:bg-slate-700 rounded-md animate-pulse"></div>
</div>
<div className="space-y-2">
<div className="h-4 w-28 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
<div className="h-20 w-full bg-gray-200 dark:bg-slate-700 rounded-md animate-pulse"></div>
</div>
<div className="space-y-2">
<div className="h-4 w-20 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
<div className="h-10 w-full bg-gray-200 dark:bg-slate-700 rounded-md animate-pulse"></div>
</div>
<div>
<div className="h-10 w-32 bg-gray-200 dark:bg-slate-700 rounded-full animate-pulse"></div>
</div>
</div>
);
}

View file

@ -0,0 +1,81 @@
export default function SkeletonTable() {
return (
<div>
<div role="status" aria-busy="true" aria-label="Carregando tabela" className="p-4 w-full">
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr>
<th className="px-4 py-2 text-left">
<div className="h-4 w-20 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</th>
<th className="px-4 py-2 text-left">
<div className="h-4 w-28 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</th>
<th className="px-4 py-2 text-left">
<div className="h-4 w-24 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</th>
<th className="px-4 py-2 text-left">
<div className="h-4 w-16 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</th>
</tr>
</thead>
<tbody>
<tr className="border-t">
<td className="px-4 py-3">
<div className="h-4 w-24 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-32 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-20 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-16 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3">
<div className="h-4 w-20 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-24 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-28 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-16 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
</tr>
<tr className="border-t">
<td className="px-4 py-3">
<div className="h-4 w-28 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-20 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-24 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
<td className="px-4 py-3">
<div className="h-4 w-16 bg-gray-200 dark:bg-slate-700 rounded animate-pulse"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,36 @@
'use client';
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface ResponseState {
message: string;
type: 'toast' | 'modal' | 'alert' | null;
status: number;
}
interface ResponseContextProps {
response: ResponseState;
setResponse: (value: ResponseState) => void;
clearResponse: () => void;
}
const ResponseContext = createContext<ResponseContextProps | undefined>(undefined);
export const ResponseProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [response, setResponseState] = useState<ResponseState>({ message: '', type: null, status : 0});
const setResponse = (value: ResponseState) => setResponseState(value);
const clearResponse = () => setResponseState({ message: '', type: null, status : 0});
return (
<ResponseContext.Provider value={{ response, setResponse, clearResponse }}>
{children}
</ResponseContext.Provider>
);
};
export const useResponse = () => {
const context = useContext(ResponseContext);
if (!context) throw new Error('useResponse must be used within ResponseProvider');
return context;
};

View file

@ -0,0 +1,20 @@
// app/src/app/_response/response.tsx
"use client";
import { useResponse } from "./ResponseContext";
import { useEffect } from "react";
import { toast } from "sonner";
export default function Response() {
const { response, clearResponse } = useResponse();
useEffect(() => {
switch (Number(response?.status)) {
case 200:
toast(response.message);
break;
}
}, [response, clearResponse]);
return <div></div>;
}

6
src/app/loading.tsx Normal file
View file

@ -0,0 +1,6 @@
// components/Loading.tsx
export default function Loading() {
return (
<span className="loading loading-spinner loading-xs"></span>
)
}

View file

@ -59,11 +59,10 @@ export default class API {
// Converte a reposta para json
const responseData = await response.json();
// Classe para manipular a resposta
const ResponseHandler = new Response();
// Obtem o status da requisição
responseData.status = response.status;
// Manipula as respostas
return ResponseHandler.handler(responseData);
return responseData;
} catch (error) {