Browse Source

Desevnvolvendo funcionalidade de criar encomenda

master
JailtonAraujo 3 years ago
parent
commit
d4ee7106a4
16 changed files with 749 additions and 28 deletions
  1. +8
    -21
      first-app/src/App.tsx
  2. +38
    -0
      first-app/src/Routes.tsx
  3. +102
    -0
      first-app/src/components/ProdutoForm.tsx
  4. +3
    -4
      first-app/src/components/sideBar.tsx
  5. +6
    -0
      first-app/src/interfaces/ItemCart.ts
  6. +9
    -0
      first-app/src/interfaces/Produto.ts
  7. +0
    -2
      first-app/src/pages/Cliente/Clientes.tsx
  8. +75
    -0
      first-app/src/pages/Encomenda/NewEncomenda.tsx
  9. +18
    -0
      first-app/src/pages/Encomenda/encomenda.module.css
  10. +23
    -0
      first-app/src/pages/Produto/NewProduto.tsx
  11. +203
    -0
      first-app/src/pages/Produto/Produtos.tsx
  12. +20
    -0
      first-app/src/pages/Produto/produto.module.css
  13. +71
    -0
      first-app/src/services/ProdutoService.ts
  14. +51
    -0
      first-app/src/slices/CarrinhoSlice.ts
  15. +117
    -0
      first-app/src/slices/ProdutoSlice.ts
  16. +5
    -1
      first-app/src/store.ts

+ 8
- 21
first-app/src/App.tsx View File

@ -5,28 +5,15 @@ import {
import './App.css'
import { ToastContainer } from "./hooks/useToastfy";
//pages
import PageBase from "./pages/PageBase/PageBase";
import NewCliente from "./pages/Cliente/NewCliente";
import Clientes from "./pages/Cliente/Clientes";
// //pages
// import PageBase from "./pages/PageBase/PageBase";
// import NewCliente from "./pages/Cliente/NewCliente";
// import Clientes from "./pages/Cliente/Clientes";
// import NewProduto from "./pages/Produto/NewProduto";
// import Produtos from "./pages/Produto/Produtos";
import {Routes} from './Routes';
const router = createBrowserRouter([
{
path: "/",
element: <PageBase/>,
children:[
{
path:"/cliente/new",
element: <NewCliente/>
},
{
path:"/cliente/list",
element: <Clientes/>
}
]
},
]);
const router = createBrowserRouter(Routes);
function App() {


+ 38
- 0
first-app/src/Routes.tsx View File

@ -0,0 +1,38 @@
import NewCliente from "./pages/Cliente/NewCliente";
import PageBase from "./pages/PageBase/PageBase";
import Clientes from "./pages/Cliente/Clientes";
import NewProduto from "./pages/Produto/NewProduto";
import Produtos from "./pages/Produto/Produtos";
import NewEncomenda from "./pages/Encomenda/NewEncomenda";
export const Routes = [
{
path: "/",
element: <PageBase />,
children: [
{
path: "/cliente/new",
element: <NewCliente />
},
{
path: "/cliente/list",
element: <Clientes />
},
//produto routes
{
path: "/produto/new",
element: <NewProduto />
},
{
path: "/produto/list",
element: <Produtos />
},
//encomeda routes
{
path: "/encomenda/new",
element: <NewEncomenda/>
}
]
}
]

+ 102
- 0
first-app/src/components/ProdutoForm.tsx View File

@ -0,0 +1,102 @@
import { Input, Select, Button, Form } from "antd";
import { Produto } from "../interfaces/Produto";
import styles from '../pages/Produto/produto.module.css'
import {
getAllCategoria
} from "../slices/ProdutoSlice";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
interface FormProdutoProps {
produto?:Produto,
btnTitle:string,
eventEmitter:any
}
function ProdutoForm( props:FormProdutoProps ) {
const dispatch = useDispatch<any>();
const { categorias, loading } = useSelector((state:any) => state.produto);
const onFinish = (values: any) => {
const produto:Produto = {
nome:values.nome,
preco:values.preco,
categoria:{
uid:values.categoria
}
}
props.eventEmitter(produto);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
dispatch(getAllCategoria())
},[])
return (
<>
<Form
className={styles.form_produto}
name="basic"
layout="vertical"
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="Nome"
name="nome"
initialValue={props.produto?.nome ? props.produto.nome : ''}
rules={[{ required: true, message: 'Nome é obrigatorio!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Preço"
name="preco"
initialValue={props.produto?.preco ? props.produto.preco : ''}
rules={[{ required: true, message: 'preço é obrigatorio!' }]}
>
<Input type='number' prefix="R$"/>
</Form.Item>
<Form.Item
label="Categoria"
name="categoria"
rules={[{ required: true, message: 'É obrigatorio informar a categria!' }]}
>
<Select
options={
categorias.map((categoria: any) => (
{ value: categoria.uid, label: categoria.nome }
))
}
/>
</Form.Item>
<Form.Item>
{(loading === false) && (
<Button type="primary" htmlType="submit">
{props.btnTitle}
</Button>
)}
{(loading === true) && (
<Button type="primary" loading>
Aguarde...
</Button>
)}
</Form.Item>
</Form>
</>
)
}
export default ProdutoForm;

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

@ -36,12 +36,11 @@ function SideBar () {
getItem((<Link to="/cliente/list">Ver todos</Link>), '3'),
]),
getItem('Produtos', 'sub2', <InboxOutlined />, [
getItem('Tom', '5'),
getItem('Bill', '6'),
getItem('Alex', '7'),
getItem((<Link to="/produto/new">Novo Produto</Link>), '5'),
getItem((<Link to="/produto/list">Ver todos</Link>), '6'),
]),
getItem('Encomendas', 'sub3', <ShoppingCartOutlined />, [
getItem('jdj', '8'),
getItem((<Link to="/encomenda/new">Carrinho</Link>), '8'),
getItem('Bill', '9'),
getItem('Alex', '10'),
]),


+ 6
- 0
first-app/src/interfaces/ItemCart.ts View File

@ -0,0 +1,6 @@
import { Produto } from "./Produto";
export interface ItemCart {
produto:Produto,
quantidade:number
}

+ 9
- 0
first-app/src/interfaces/Produto.ts View File

@ -0,0 +1,9 @@
export interface Produto {
uid?:string,
nome:string,
preco:number,
categoria:{
uid:string,
nome?:string
}
}

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

@ -132,8 +132,6 @@ function Clientes() {
/>
</Table>
</div>
)
}


+ 75
- 0
first-app/src/pages/Encomenda/NewEncomenda.tsx View File

@ -0,0 +1,75 @@
import { Button, Collapse, Popconfirm, Space, Table } from 'antd';
import styles from './encomenda.module.css';
import Column from 'antd/es/table/Column';
import { useSelector } from 'react-redux';
import { ItemCart } from '../../interfaces/ItemCart';
import {
DeleteOutlined
} from '@ant-design/icons';
function NewEncomenda() {
const { itens } = useSelector((state: any) => state.cart);
const handleDelete = () => {
}
return (
<div className={styles.container_table}>
<Collapse
size="large"
items={[{
key: '1', label: 'Itens da encomenda.', children: (
<Table
dataSource={
itens.map((item: ItemCart) => (
{ key: item.produto.uid, name: item.produto.nome, quantidade: item.quantidade, preco: item.produto.preco }
))
}
pagination={false}
bordered={true}
scroll={{ x: 300, y: 300 }}
>
<Column title="Nome" dataIndex="name" key="name" />
<Column title="Quantidade" dataIndex="quantidade" key="quantidade" />
<Column title="Preço" dataIndex="preco" key="preco" />
<Column
title="Actions"
key="action"
render={(_: any) => (
<Space size="middle">
<Popconfirm
title="Atenção:"
description="Confirme a ação de delete!"
onConfirm={() => { handleDelete() }}
okText="Sim"
cancelText="Não"
>
<Button value="small"
type="primary"
danger icon={<DeleteOutlined />}
size="large"
title="Deletar"
/>
</Popconfirm>
</Space>
)}
/>
</Table>
)
}]}
/>
</div>
)
}
export default NewEncomenda;

+ 18
- 0
first-app/src/pages/Encomenda/encomenda.module.css View File

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

+ 23
- 0
first-app/src/pages/Produto/NewProduto.tsx View File

@ -0,0 +1,23 @@
import { useDispatch } from "react-redux";
import ProdutoForm from "../../components/ProdutoForm";
import { Produto } from "../../interfaces/Produto";
import {
registerProduto
} from "../../slices/ProdutoSlice";
function NewProduto () {
const dispatch = useDispatch<any>();
const handleSubmit = (produto:Produto) => {
dispatch(registerProduto(produto));
}
return (
<>
<ProdutoForm btnTitle="Cadastrar" eventEmitter={handleSubmit}/>
</>
)
}
export default NewProduto;

+ 203
- 0
first-app/src/pages/Produto/Produtos.tsx View File

@ -0,0 +1,203 @@
import Search from 'antd/es/input/Search'
import styles from './produto.module.css';
import { Button, Popconfirm, Select, Space, Table } from 'antd'
import Column from 'antd/es/table/Column'
import { useDispatch, useSelector } from 'react-redux';
import { ParamsType } from '../../interfaces/ParamsType';
import { useEffect, useState } from 'react';
import { Produto } from '../../interfaces/Produto';
import {
DeleteOutlined,
EditOutlined,
ShoppingCartOutlined
} from '@ant-design/icons';
import {
getAllProdutos,
getAllCategoria,
deleteProduto
} from '../../slices/ProdutoSlice';
import {
addItem
} from '../../slices/CarrinhoSlice';
import { useNavigate } from 'react-router-dom';
import { ItemCart } from '../../interfaces/ItemCart';
function Produtos() {
const [nomeSearch, setNomeSearch] = useState('');
const [categoriaFilter, setCategoriaFilter] = useState('');
const dispatch = useDispatch<any>();
const navigate = useNavigate();
const { produtos, total, loading, categorias } = useSelector((state: any) => state.produto);
const handleSearch = () => {
const params: ParamsType = {
pagination: {
current: 1,
pageSize: 5
},
filter: {
nome: nomeSearch,
categoria: categoriaFilter
}
}
dispatch(getAllProdutos(params));
}
const handleDelete = (uid: string) => {
dispatch(deleteProduto(uid));
}
const onChangePage = (page: any, size: any) => {
const params: ParamsType = {
pagination: {
current: page,
pageSize: size
},
filter: {
nome: nomeSearch != '' ? nomeSearch : '',
categoria: categoriaFilter
}
}
dispatch(getAllProdutos(params));
}
const filterByCategoria = (categoriaUid: string) => {
setCategoriaFilter(categoriaUid);
const params: ParamsType = {
pagination: {
current: 1,
pageSize: 5
},
filter: {
nome: nomeSearch != '' ? nomeSearch : '',
categoria: categoriaUid
}
}
dispatch(getAllProdutos(params))
}
const addItemCart = (produto: any) => {
const item:ItemCart = {
produto:{
uid:produto.key,
nome:produto.name,
preco:produto.preco,
categoria:{
uid:""
}
},
quantidade:1
}
dispatch(addItem(item));
}
useEffect(() => {
dispatch(getAllProdutos(
{ pagination: { current: 1, pageSize: 5 } }
))
dispatch(getAllCategoria());
}, [])
return (
<div className={styles.container_table}>
<div className={styles.container_table__filter}>
<Select
style={{ width: 120 }}
defaultValue=''
options={
categorias.map((categoria: any) => (
{ value: categoria.uid, label: categoria.nome }
)).concat({ value: '', label: 'Todos' })
}
onChange={filterByCategoria}
/>
<Search
placeholder="Buscar por nome..."
enterButton
className={styles.container_table__search}
onSearch={handleSearch}
onChange={(value) => setNomeSearch(value.target.value)}
/>
</div>
<Table
dataSource={
produtos.map((produto: Produto) => (
{ key: produto.uid, name: produto.nome, preco: produto.preco, categoria: produto.categoria.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="Preço" dataIndex="preco" key="preco" />
<Column title="Categoria" dataIndex="categoria" key="categoria" />
<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`) }}
/>
<Button
value="small"
type="primary"
icon={<ShoppingCartOutlined />} size="large"
title="AddCart"
onClick={() => { addItemCart(record) }}
/>
</Space>
)}
/>
</Table>
</div>
)
}
export default Produtos;

+ 20
- 0
first-app/src/pages/Produto/produto.module.css View File

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

+ 71
- 0
first-app/src/services/ProdutoService.ts View File

@ -0,0 +1,71 @@
import { Environments } from "../environments";
import { ParamsType } from "../interfaces/ParamsType";
import { Produto } from "../interfaces/Produto";
const URLApi = Environments.URLApi;
const getAllCategorias = async () => {
const data = await fetch(`${URLApi}/categoria`)
.then((res)=>res.json())
.catch((err)=>err);
return data;
}
const registerProduto = async (produto:Produto) => {
const config = {
method:"POST",
body:JSON.stringify(produto),
headers: {
"Content-type":"application/json",
}
}
try {
const data = await fetch(`${URLApi}/produto`,config)
.then((res)=>res.json())
.catch((err)=>err);
return data;
} catch (error) {
console.info(error);
return error;
}
}
const getAllProdutos = async (params:ParamsType) => {
const config = {
method:"POST",
body:JSON.stringify(params),
headers: {
"Content-type":"application/json",
}
}
const data = await fetch (`${URLApi}/produto/list`,config)
.then((res)=>res.json())
.catch((err)=>err);
return data;
}
const deleteProduto = async (uid:string) => {
try {
const data = await fetch(`${URLApi}/produto?uid=${uid}`,{method:'DELETE'})
.then((res)=>res.json())
.catch((err)=> err);
return data;
} catch (error) {
console.info(error);
}
}
export const ProdutoService = {
getAllCategorias,
registerProduto,
getAllProdutos,
deleteProduto
}

+ 51
- 0
first-app/src/slices/CarrinhoSlice.ts View File

@ -0,0 +1,51 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { ItemCart } from "../interfaces/ItemCart";
import { notify } from "../hooks/useToastfy";
const initialState = {
itens:[],
valorTotal:0
}
const calclQuantidade = (itens:Array<ItemCart>) =>{
let valor = 0;
itens.map((item) => {
valor += Number(item.produto.preco);
})
return valor;
}
export const addItem = createAsyncThunk(
"cart/add",
async (item:ItemCart) => {
return item;
}
)
export const cartSlice = createSlice({
name:"cliente",
initialState,
reducers:{
resetState:(state)=>{
state.itens = [],
state.valorTotal=0
},
},
extraReducers(builder) {
builder
.addCase(addItem.fulfilled,(state,action:any)=>{
state.itens = state.itens.concat(action.payload);
state.valorTotal = calclQuantidade(state.itens);
notify('Produto adicionado com sucessso!','success')
})
},
});
export const { resetState } = cartSlice.actions;
export default cartSlice.reducer;

+ 117
- 0
first-app/src/slices/ProdutoSlice.ts View File

@ -0,0 +1,117 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { ProdutoService } from "../services/ProdutoService";
import { Produto } from "../interfaces/Produto";
import { notify } from "../hooks/useToastfy";
import { ParamsType } from "../interfaces/ParamsType";
const initialState = {
produtos:[] as any,
total: 0,
categorias:[] as any,
loading:false,
error:false,
message:''
}
export const getAllCategoria = createAsyncThunk(
"produto/categorias",
async () =>{
const data = await ProdutoService.getAllCategorias();
return data;
}
)
export const registerProduto = createAsyncThunk(
"produto/new",
async (produto:Produto, thunkApi) =>{
const data = await ProdutoService.registerProduto(produto);
if (data.error) {
return thunkApi.rejectWithValue(data);
}
return data;
}
)
export const getAllProdutos = createAsyncThunk(
"produto/getAll",
async (parans:ParamsType) => {
const data = await ProdutoService.getAllProdutos(parans);
return data;
}
)
export const deleteProduto = createAsyncThunk(
"produto/delete",
async (uid:string, thunkApi) => {
const data = await ProdutoService.deleteProduto(uid);
if (data.error) {
return thunkApi.rejectWithValue(data);
}
return data;
}
)
export const produtoSlice = createSlice({
name:"produto",
initialState,
reducers:{
resetState:(state)=>{
state.loading = false;
state.error = false;
state.message = '';
},
},
extraReducers(builder) {
builder
.addCase(getAllCategoria.pending,(state)=>{
state.loading = true;
}).addCase(getAllCategoria.fulfilled,(state,action)=>{
state.categorias = action.payload.result;
state.loading = false;
})
.addCase(registerProduto.pending,(state)=>{
state.loading = true;
}).addCase(registerProduto.fulfilled,(state)=>{
state.loading = false;
state.message = 'Novo produto cadastrado!';
notify(state.message,'success');
}).addCase(registerProduto.rejected,(state, action:any)=>{
state.loading = false;
state.error = true;
state.message = action.payload.message ? action.payload.message : 'Error ao cadastrar produto, tente novamente mais tarde!';
notify(state.message, 'error');
})
.addCase(getAllProdutos.pending,(state)=>{
state.loading = true;
}).addCase(getAllProdutos.fulfilled,(state,action)=>{
state.produtos = action.payload.result;
state.loading = false;
state.total = action.payload.total;
})
.addCase(deleteProduto.pending,(state)=>{
state.loading = true;
}).addCase(deleteProduto.fulfilled,(state,action:any)=>{
state.loading = false;
state.message = 'Produo deletado com sucesso!';
state.produtos = state.produtos.filter((produto:Produto) => (produto.uid != action.payload.uid));
notify(state.message,'success');
}).addCase(deleteProduto.rejected,(state, action:any)=>{
state.loading = false;
state.error = true;
state.message = action.payload.message ? action.payload.message : 'Error ao deletar produto, tente novamente mais tarde!';
notify(state.message, 'error');
})
},
});
export const { resetState } = produtoSlice.actions;
export default produtoSlice.reducer;

+ 5
- 1
first-app/src/store.ts View File

@ -2,9 +2,13 @@ import { configureStore } from "@reduxjs/toolkit";
//reduces
import ClienteSlice from "./slices/ClienteSlice";
import ProdutoSlice from "./slices/ProdutoSlice";
import CarrinhoSlice from "./slices/CarrinhoSlice";
export default configureStore({
reducer:{
cliente:ClienteSlice
cliente:ClienteSlice,
produto:ProdutoSlice,
cart:CarrinhoSlice
}
})

Loading…
Cancel
Save