Browse Source

Finalizando listagem de clientes de delete

master
JailtonAraujo 3 years ago
parent
commit
f33e698f78
12 changed files with 355 additions and 110 deletions
  1. +22
    -1
      first-app/package-lock.json
  2. +2
    -1
      first-app/package.json
  3. +7
    -16
      first-app/src/App.tsx
  4. +70
    -63
      first-app/src/components/ClienteForm.tsx
  5. +2
    -3
      first-app/src/components/sideBar.tsx
  6. +0
    -16
      first-app/src/hooks/useNotification.ts
  7. +8
    -0
      first-app/src/hooks/useToastfy.ts
  8. +7
    -0
      first-app/src/interfaces/ParamsType.ts
  9. +17
    -0
      first-app/src/pages/Cliente/Cliente.module.css
  10. +141
    -0
      first-app/src/pages/Cliente/Clientes.tsx
  11. +29
    -2
      first-app/src/services/ClienteService.ts
  12. +50
    -8
      first-app/src/slices/ClienteSlice.ts

+ 22
- 1
first-app/package-lock.json View File

@ -13,7 +13,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.1.1", "react-redux": "^8.1.1",
"react-router-dom": "^6.13.0"
"react-router-dom": "^6.13.0",
"react-toastify": "^9.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.37", "@types/react": "^18.0.37",
@ -1686,6 +1687,14 @@
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
}, },
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -3582,6 +3591,18 @@
"react-dom": ">=16.8" "react-dom": ">=16.8"
} }
}, },
"node_modules/react-toastify": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz",
"integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==",
"dependencies": {
"clsx": "^1.1.1"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/redux": { "node_modules/redux": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",


+ 2
- 1
first-app/package.json View File

@ -15,7 +15,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.1.1", "react-redux": "^8.1.1",
"react-router-dom": "^6.13.0"
"react-router-dom": "^6.13.0",
"react-toastify": "^9.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.37", "@types/react": "^18.0.37",


+ 7
- 16
first-app/src/App.tsx View File

@ -3,13 +3,12 @@ import {
RouterProvider, RouterProvider,
} from "react-router-dom"; } from "react-router-dom";
import './App.css' import './App.css'
import { NotificationProvider } from "./context/NotificationContext";
import { ToastContainer } from "./hooks/useToastfy";
//pages //pages
import PageBase from "./pages/PageBase/PageBase"; import PageBase from "./pages/PageBase/PageBase";
import NewCliente from "./pages/Cliente/NewCliente"; import NewCliente from "./pages/Cliente/NewCliente";
import { notification } from "antd";
import { IconType } from "antd/es/notification/interface";
import Clientes from "./pages/Cliente/Clientes";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@ -19,6 +18,10 @@ const router = createBrowserRouter([
{ {
path:"/cliente/new", path:"/cliente/new",
element: <NewCliente/> element: <NewCliente/>
},
{
path:"/cliente/list",
element: <Clientes/>
} }
] ]
}, },
@ -27,22 +30,10 @@ const router = createBrowserRouter([
function App() { function App() {
const [api, contextHolder] = notification.useNotification();
const openNotification = (message:string, description:string, type:IconType) => {
api.open({
type:type,
message: message,
description: description,
});
};
return ( return (
<div className='App'> <div className='App'>
<NotificationProvider value={{openNotification}} >
{contextHolder}
<ToastContainer/>
<RouterProvider router={router}/> <RouterProvider router={router}/>
</NotificationProvider>
</div> </div>
) )
} }


+ 70
- 63
first-app/src/components/ClienteForm.tsx View File

@ -1,9 +1,9 @@
import { Button, Form, Input, Select } from 'antd'; import { Button, Form, Input, Select } from 'antd';
import styles from '../pages/Cliente/Cliente.module.css'; import styles from '../pages/Cliente/Cliente.module.css';
import { useEffect } from 'react'; import { useEffect } from 'react';
import {
import {
getAllPaises getAllPaises
} from '../slices/ClienteSlice';
} from '../slices/ClienteSlice';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Cliente } from '../interfaces/Cliente'; import { Cliente } from '../interfaces/Cliente';
@ -14,88 +14,95 @@ export interface FormClienteProps {
} }
function ClienteForm(props: FormClienteProps) { function ClienteForm(props: FormClienteProps) {
const {paises} = useSelector((state:any)=>state.cliente);
const { paises, loading} = useSelector((state: any) => state.cliente);
const dispatch = useDispatch<any>(); const dispatch = useDispatch<any>();
const onFinish = (values: any) => { const onFinish = (values: any) => {
const cliente:Cliente = {
nome:values.nome,
email:values.email,
telefone:values.telefone,
pais:{
uid:values.pais,
nome:''
const cliente: Cliente = {
nome: values.nome,
email: values.email,
telefone: values.telefone,
pais: {
uid: values.pais,
nome: ''
} }
} }
props.eventEmiter(cliente); props.eventEmiter(cliente);
}; };
const onFinishFailed = (errorInfo: any) => { const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
console.log('Failed:', errorInfo);
}; };
useEffect(() => { useEffect(() => {
dispatch(getAllPaises())
dispatch(getAllPaises())
}, []); }, []);
return ( return (
<> <>
<Form
className={styles.form_cliente}
name="basic"
layout="vertical"
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="Nome"
name="nome"
initialValue={props.values ? props.values.nome : ''}
rules={[{ required: true, message: 'Nome é obrigatorio!' }]}
<Form
className={styles.form_cliente}
name="basic"
layout="vertical"
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
> >
<Input />
</Form.Item>
<Form.Item
label="Nome"
name="nome"
initialValue={props.values ? props.values.nome : ''}
rules={[{ required: true, message: 'Nome é obrigatorio!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="E-mail"
name="email"
initialValue={props.values ? props.values.email : ''}
rules={[{ required: true, message: 'E-mail é obrigatorio!' }]}
>
<Input type='email' />
</Form.Item>
<Form.Item
label="E-mail"
name="email"
initialValue={props.values ? props.values.email : ''}
rules={[{ required: true, message: 'E-mail é obrigatorio!' }]}
>
<Input type='email' />
</Form.Item>
<Form.Item
label="Telefone"
name="telefone"
initialValue={props.values ? props.values.telefone : ''}
rules={[{ required: true, message: 'Numero de telefone é obrigatorio!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Telefone"
name="telefone"
initialValue={props.values ? props.values.telefone : ''}
rules={[{ required: true, message: 'Numero de telefone é obrigatorio!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Pais"
name="pais"
rules={[{ required: true, message: 'É obrigatorio informar o pais!' }]}
>
<Select
options={
paises.map((cliente:any)=>(
{value:cliente.uid,label:cliente.nome}
))
}
/>
</Form.Item>
<Form.Item
label="Pais"
name="pais"
rules={[{ required: true, message: 'É obrigatorio informar o pais!' }]}
>
<Select
options={
paises.map((cliente: any) => (
{ value: cliente.uid, label: cliente.nome }
))
}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
{props.btnTitle}
</Button>
</Form.Item>
</Form>
<Form.Item>
{(loading === false) && (
<Button type="primary" htmlType="submit">
{props.btnTitle}
</Button>
)}
{(loading === true) && (
<Button type="primary" loading>
Aguarde...
</Button>
)}
</Form.Item>
</Form>
</> </>
) )
} }


+ 2
- 3
first-app/src/components/sideBar.tsx View File

@ -33,8 +33,7 @@ function SideBar () {
const items: MenuItem[] = [ const items: MenuItem[] = [
getItem('Clientes', 'sub1', <UserOutlined />, [ getItem('Clientes', 'sub1', <UserOutlined />, [
getItem((<Link to="/cliente/new">Novo Cliente</Link>), '1'), getItem((<Link to="/cliente/new">Novo Cliente</Link>), '1'),
getItem('Listar todos', '3'),
getItem('Alex', '4'),
getItem((<Link to="/cliente/list">Ver todos</Link>), '3'),
]), ]),
getItem('Produtos', 'sub2', <InboxOutlined />, [ getItem('Produtos', 'sub2', <InboxOutlined />, [
getItem('Tom', '5'), getItem('Tom', '5'),
@ -53,7 +52,7 @@ function SideBar () {
return ( return (
<Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}> <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
<div className="demo-logo-vertical" /> <div className="demo-logo-vertical" />
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items} />
<Menu theme="dark" mode="inline" items={items} />
</Sider> </Sider>
) )
} }


+ 0
- 16
first-app/src/hooks/useNotification.ts View File

@ -1,16 +0,0 @@
import { notification } from "antd";
import { IconType, NotificationInstance } from "antd/es/notification/interface";
const [api, contextHolder] = notification.useNotification();
const openNotification = (message:string, description:string, type:IconType) => {
api.open({
type:type,
message: message,
description: description,
});
};
export {
openNotification
}

+ 8
- 0
first-app/src/hooks/useToastfy.ts View File

@ -0,0 +1,8 @@
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const notify = (message:String,type:any) => toast(message,{type:type,autoClose:2000,theme:'dark'});
export {notify, ToastContainer};

+ 7
- 0
first-app/src/interfaces/ParamsType.ts View File

@ -0,0 +1,7 @@
export interface ParamsType {
pagination?:{
pageSize:number,
current:number
},
filter?:any
}

+ 17
- 0
first-app/src/pages/Cliente/Cliente.module.css View File

@ -1,3 +1,20 @@
.form_cliente{ .form_cliente{
width: 600px; width: 600px;
}
.container_table {
width: 800px;
display: flex;
flex-direction: column;
}
.container_table__filter{
width: 100%;
display: flex;
justify-content: end;
margin-bottom: 1em;
}
.container_table__search{
width: 300px;
} }

+ 141
- 0
first-app/src/pages/Cliente/Clientes.tsx View File

@ -0,0 +1,141 @@
import Table from "antd/es/table";
import {
getAllClientes,
deleteCliente
} from '../../slices/ClienteSlice';
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { Cliente } from "../../interfaces/Cliente";
import Column from "antd/es/table/Column";
import { Button, Input, Popconfirm, Space } from "antd";
import {
DeleteOutlined,
EditOutlined
} from '@ant-design/icons'
import { useNavigate } from "react-router-dom";
import { ParamsType } from "../../interfaces/ParamsType";
import styles from './Cliente.module.css';
const { Search } = Input;
function Clientes() {
const dispatch = useDispatch<any>();
const navigate = useNavigate();
const { clientes, total, loading } = useSelector((state: any) => state.cliente);
const [nomeSearch, setNomeSearch] = useState('');
const onChangePage = (page: any, size: any) => {
const params: ParamsType = {
pagination: {
current: page,
pageSize: size
},
filter: {
nome: nomeSearch != '' ? nomeSearch : ''
}
}
dispatch(getAllClientes(params))
}
const handleDelete = (uid: string) => {
dispatch(deleteCliente(uid));
}
const handleSearch = (nome: string) => {
setNomeSearch(nome);
const params: ParamsType = {
pagination: {
current: 1,
pageSize: 5
},
filter: {
nome: nome
}
}
dispatch(getAllClientes(params))
}
useEffect(() => {
dispatch(getAllClientes(
{ pagination: { current: 1, pageSize: 5 } }
));
}, [])
return (
<div className={styles.container_table}>
<div className={styles.container_table__filter}>
<Search
placeholder="Buscar por nome..."
enterButton
className={styles.container_table__search}
onSearch={handleSearch}
/>
</div>
<Table
dataSource={
clientes.map((cliente: Cliente) => (
{ key: cliente.uid, name: cliente.nome, email: cliente.email, pais: cliente.pais?.nome }
))
}
pagination={{
position: ["bottomCenter"],
total: total,
defaultPageSize: 5,
onChange(page, pageSize) {
onChangePage(page, pageSize)
},
pageSizeOptions: [5, 10, 15],
showSizeChanger: true,
onShowSizeChange(current, size) {
onChangePage(current, size)
},
}}
loading={loading}
bordered={true}
>
<Column title="Nome" dataIndex="name" key="name" />
<Column title="Email" dataIndex="email" key="email" />
<Column title="Pais" dataIndex="pais" key="pais" />
<Column
title="Actions"
key="action"
render={(_: any, record: any) => (
<Space size="middle">
<Popconfirm
title="Atenção:"
description="Confirme a ação de delete!"
onConfirm={() => { handleDelete(record.key) }}
okText="Sim"
cancelText="Não"
>
<Button value="small"
type="primary"
danger icon={<DeleteOutlined />}
size="large"
title="Deletar"
/>
</Popconfirm>
<Button
value="small"
type="primary"
icon={<EditOutlined />} size="large"
title="Atualizar"
onClick={() => { navigate(`/cliente/new`) }}
/>
</Space>
)}
/>
</Table>
</div>
)
}
export default Clientes;

+ 29
- 2
first-app/src/services/ClienteService.ts View File

@ -1,5 +1,6 @@
import { Environments } from "../environments" import { Environments } from "../environments"
import { Cliente } from "../interfaces/Cliente"; import { Cliente } from "../interfaces/Cliente";
import { ParamsType } from "../interfaces/ParamsType";
const { URLApi } = Environments; const { URLApi } = Environments;
@ -29,7 +30,6 @@ const registerCliente = async (cliente:Cliente) => {
const data = await fetch(`${URLApi}/cliente`,config) const data = await fetch(`${URLApi}/cliente`,config)
.then((res)=>res.json()) .then((res)=>res.json())
.catch((err)=>err); .catch((err)=>err);
console.log(data);
return data; return data;
} catch (error) { } catch (error) {
console.info(error); console.info(error);
@ -38,7 +38,34 @@ const registerCliente = async (cliente:Cliente) => {
} }
const getAllClientes = async (params:ParamsType) => {
const config = {
method:"POST",
body:JSON.stringify(params),
headers: {
"Content-type":"application/json",
}
}
const data = await fetch(`${URLApi}/cliente/list`,config)
.then((res)=>res.json())
.catch((err)=>err);
return data;
}
const deleteCliente = async (uid:string) => {
const data = await fetch(`${URLApi}/cliente?uid=${uid}`,{method:"DELETE"})
.then ((res)=>res.json())
.catch((err)=>err);
return data;
}
export const ClienteServices = { export const ClienteServices = {
getAllPaises, getAllPaises,
registerCliente
registerCliente,
getAllClientes,
deleteCliente
} }

+ 50
- 8
first-app/src/slices/ClienteSlice.ts View File

@ -3,12 +3,14 @@ import {
ClienteServices ClienteServices
} from "../services/ClienteService"; } from "../services/ClienteService";
import { Cliente } from "../interfaces/Cliente"; import { Cliente } from "../interfaces/Cliente";
import { notify } from "../hooks/useToastfy";
import { ParamsType } from "../interfaces/ParamsType";
const initialState = { const initialState = {
cliente:{},
paises:[] as any, paises:[] as any,
clientes:[] as any, clientes:[] as any,
loadind:false,
total:0,
loading:false,
error:false, error:false,
success:true, success:true,
message:'' message:''
@ -33,13 +35,29 @@ export const registerCliente = createAsyncThunk(
} }
) )
export const getAllClientes = createAsyncThunk(
"cliente/getAll",
async (params:ParamsType) => {
const data = await ClienteServices.getAllClientes(params);
return data;
}
)
export const deleteCliente = createAsyncThunk(
"cliente/delete",
async (uid:string) => {
const data = await ClienteServices.deleteCliente(uid);
return data;
}
)
export const clienteSlice = createSlice({ export const clienteSlice = createSlice({
name:"cliente", name:"cliente",
initialState, initialState,
reducers:{ reducers:{
resetState:(state)=>{ resetState:(state)=>{
state.loadind = false;
state.loading = false;
state.error = false; state.error = false;
state.message = ''; state.message = '';
}, },
@ -48,22 +66,46 @@ export const clienteSlice = createSlice({
extraReducers(builder) { extraReducers(builder) {
builder builder
.addCase(getAllPaises.pending,(state)=>{ .addCase(getAllPaises.pending,(state)=>{
state.loadind = true;
state.loading = true;
}).addCase(getAllPaises.fulfilled,(state,action)=>{ }).addCase(getAllPaises.fulfilled,(state,action)=>{
state.paises = action.payload.result; state.paises = action.payload.result;
state.loadind = false;
state.loading = false;
}) })
.addCase(registerCliente.pending,(state)=>{ .addCase(registerCliente.pending,(state)=>{
state.loadind = true;
state.loading = true;
}).addCase(registerCliente.fulfilled, (state)=>{ }).addCase(registerCliente.fulfilled, (state)=>{
state.loadind = false;
state.loading = false;
state.success = true; state.success = true;
notify('Novo cliente cadastrado!','success');
}).addCase(registerCliente.rejected,(state,action:any)=>{ }).addCase(registerCliente.rejected,(state,action:any)=>{
state.loadind = false;
state.loading = false;
state.error = true; state.error = true;
state.success = false; state.success = false;
state.message = action.payload ? action.payload.message : 'Error ao cadastrar cliente, revise os dados e tente novamente!'; state.message = action.payload ? action.payload.message : 'Error ao cadastrar cliente, revise os dados e tente novamente!';
notify(state.message,'error');
})
.addCase(getAllClientes.pending,(state)=>{
state.loading = true;
}).addCase(getAllClientes.fulfilled,(state,action)=>{
state.clientes = action.payload.result;
state.total = action.payload.total;
state.loading = false;
})
.addCase(deleteCliente.pending,(state)=>{
state.loading = true;
}).addCase(deleteCliente.fulfilled,(state,action)=>{
state.clientes = state.clientes.filter((cliente:Cliente)=>(cliente.uid !== action.payload.uid));
state.total = state.total - 1;
state.loading = false;
notify('Cliente deletedo com sucesso!','success');
}).addCase(deleteCliente.rejected,(state,action:any)=>{
state.loading = true;
state.error = true;
state.message = action.payload.message ? action.payload.message : 'Erro ao deletar cliente, tente novamente!';
notify(state.message,'error');
}) })
}, },
}); });


Loading…
Cancel
Save