| @ -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/> | |||||
| } | |||||
| ] | |||||
| } | |||||
| ] | |||||
| @ -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; | |||||
| @ -0,0 +1,6 @@ | |||||
| import { Produto } from "./Produto"; | |||||
| export interface ItemCart { | |||||
| produto:Produto, | |||||
| quantidade:number | |||||
| } | |||||
| @ -0,0 +1,9 @@ | |||||
| export interface Produto { | |||||
| uid?:string, | |||||
| nome:string, | |||||
| preco:number, | |||||
| categoria:{ | |||||
| uid:string, | |||||
| nome?:string | |||||
| } | |||||
| } | |||||
| @ -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; | |||||
| @ -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; | |||||
| } | |||||
| @ -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; | |||||
| @ -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; | |||||
| @ -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; | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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; | |||||
| @ -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; | |||||