Skip to content

Instantly share code, notes, and snippets.

@caiquecastro
Last active July 15, 2022 15:20
Show Gist options
  • Save caiquecastro/c656c0ff06a10bd54854c36b544096e9 to your computer and use it in GitHub Desktop.
Save caiquecastro/c656c0ff06a10bd54854c36b544096e9 to your computer and use it in GitHub Desktop.
Venturus 4Tech
import axios from "axios";
import ICategory from "../models/ICategory";
import { IExpenses, SaveExpenseDto } from "../models/IExpense";
export const api = axios.create({
baseURL: "http://localhost:3333",
});
export const getExpenses = async () => {
const response = await api.get<IExpenses[]>("/expenses");
return response.data;
};
export const getCategories = async () => {
const response = await api.get<ICategory[]>("/categories");
return response.data;
};
export const saveExpense = async (expense: SaveExpenseDto) => {
if (expense.id) {
const response = await api.put(`/expenses/${expense.id}`, expense);
return response.data;
}
const response = await api.post("/expenses", expense);
return response.data;
};
import type { NextPage } from "next";
import { useEffect, useState } from "react";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Box,
Button,
Heading,
IconButton,
} from "@chakra-ui/react";
import { api } from "../services/api";
import { IExpenses } from "../models/IExpense";
import { EditIcon } from "@chakra-ui/icons";
interface Props {
expenses: IExpenses[];
onAddExpense: () => void;
onEditExpense: (expense: IExpenses) => void;
}
const FinancesTable = ({ expenses, onAddExpense, onEditExpense }: Props) => {
const toBRL = (value: number) =>
value.toLocaleString("pt-BR", {
currency: "BRL",
style: "currency",
});
// let total = 0;
// for (let i = 0; i < expenses.length; i++) {
// total += expenses[i].value;
// }
const total = expenses.reduce((acc, cur) => {
return acc + cur.value;
}, 0);
return (
<>
<Box
flexDirection="row"
display="flex"
w="80vw"
justifyContent="space-between"
>
<Heading size="lg">
Total: {toBRL(total)}
</Heading>
<Button
bg="green.400"
color="white"
_hover={{ bg: "green.300" }}
_active={{ bg: "green.600" }}
onClick={() => onAddExpense()}
>
Adicionar despesa
</Button>
</Box>
<Box
mt={5}
border="1px solid"
borderColor="gray.100"
borderWidth={2}
borderRadius="md"
w="80vw"
>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th isNumeric>ID</Th>
<Th>DATA</Th>
<Th>DESCRIÇÃO</Th>
<Th>CATEGORIA</Th>
<Th>VALOR</Th>
<Th>AÇÕES</Th>
</Tr>
</Thead>
<Tbody>
{expenses.map((expense) => (
<Tr key={expense.id}>
<Td isNumeric>{expense.id}</Td>
<Td>{new Date(expense.date).toLocaleDateString("pt-BR")}</Td>
<Td>{expense.description}</Td>
<Td>{expense.category}</Td>
<Td>{toBRL(expense.value)}</Td>
<Td>
<IconButton
aria-label="Editar"
icon={<EditIcon />}
color="yellow.500"
variant="ghost"
onClick={() => {
onEditExpense(expense);
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</>
);
};
export default FinancesTable;
export default interface ICategory {
id: number;
name: string;
}
export interface IExpenses {
id: number;
date: number;
description: string;
category: string;
value: number;
}
export interface SaveExpenseDto extends Omit<IExpenses, 'id'> {
id?: number;
}
import type { NextPage } from "next";
import Head from "next/head";
import styles from "../styles/Home.module.css";
import FinancesTable from "../components/FinancesTable";
import { Heading, useDisclosure } from "@chakra-ui/react";
import NewExpenseModal from "../components/NewExpenseModal";
import { useEffect, useState } from "react";
import { IExpenses } from "../models/IExpense";
import { api, getExpenses } from "../services/api";
const Home: NextPage = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const [expenses, setExpenses] = useState<IExpenses[]>([]);
const [expenseToEdit, setExpenseToEdit] = useState<IExpenses>();
const fetchExpenses = () =>
getExpenses().then(expenseList => setExpenses(expenseList));
useEffect(() => {
fetchExpenses();
}, []);
return (
<div className={styles.container}>
<Head>
<title>My finances</title>
<meta name="description" content="Vnt 4tech 2022" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<Heading as="h2" mb="100px">
My Finances
</Heading>
<FinancesTable
expenses={expenses}
onAddExpense={() => onOpen()}
onEditExpense={(expense) => {
setExpenseToEdit(expense);
onOpen();
}}
/>
<NewExpenseModal
isOpen={isOpen}
expense={expenseToEdit}
onSave={() => {
fetchExpenses();
onClose();
setExpenseToEdit(undefined);
}}
onClose={() => {
onClose();
setExpenseToEdit(undefined);
}}
/>
</main>
<footer className={styles.footer}>
<a
href="https://www.venturus.org.br/"
target="_blank"
rel="noopener noreferrer"
>
Powered by Venturus
</a>
</footer>
</div>
);
};
export default Home;
import { Button, FormControl, FormLabel, Grid, GridItem, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, NumberDecrementStepper, NumberIncrementStepper, NumberInput, NumberInputField, NumberInputStepper, Select, useToast } from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { IExpenses } from "../models/IExpense";
import { getCategories, saveExpense } from "../services/api";
interface Props {
isOpen: boolean;
expense?: IExpenses;
onSave: () => void;
onClose: () => void;
}
function NewExpenseModal({ isOpen, expense, onSave, onClose }: Props) {
const toast = useToast();
const [description, setDescription] = useState("");
const [value, setValue] = useState(1);
const [category, setCategory] = useState<string>();
const [isLoading, setLoading] = useState(false);
const [categories, setCategories] = useState<string[]>([]);
useEffect(() => {
setDescription(expense?.description ?? "");
setValue(expense?.value ?? 1);
setCategory(expense?.category);
}, [expense]);
useEffect(() => {
getCategories()
.then((categories) => setCategories(categories.map(category => category.name)));
}, []);
const handleAddExpense = async () => {
if (!category) {
return;
}
const expense = {
description: description,
value,
category,
date: Date.now(),
};
setLoading(true);
await saveExpense(expense);
setLoading(false);
toast({
title: "Despesa salva",
description: "Despesa foi salva com sucesso",
status: "success",
position: "top-right",
});
onSave();
};
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Adicionar Despesa</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Grid
templateColumns={"repeat(2, 1fr)"}
templateRows={"repeat(2, 1fr)"}
gap={4}
>
<GridItem colSpan={2}>
<FormControl>
<FormLabel>Descrição</FormLabel>
<Input
placeholder="Descrição"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</FormControl>
</GridItem>
<GridItem colSpan={1}>
<FormControl>
<FormLabel htmlFor="value">Valor</FormLabel>
<NumberInput min={1} value={value} onChange={(_, value) => setValue(value)}>
<NumberInputField id="value" />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormControl>
</GridItem>
<GridItem colSpan={1}>
<FormControl>
<FormLabel htmlFor="category">Categoria</FormLabel>
<Select
id="category"
placeholder="Categoria"
value={category}
onChange={(e) => setCategory(e.target.value)}
>
{categories.map(category => (
<option key={category} value={category}>
{category}
</option>
))}
</Select>
</FormControl>
</GridItem>
</Grid>
</ModalBody>
<ModalFooter>
<Button onClick={() => onClose()} mr={3}>
Cancelar
</Button>
<Button
colorScheme="green"
onClick={handleAddExpense}
disabled={isLoading}
>
{expense ? "Editar" : "Adicionar"}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
export default NewExpenseModal;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment