Repository: rodrigorgtic/minha-carteira-dashboard
Branch: master
Commit: 85feee3561be
Files: 59
Total size: 70.8 KB
Directory structure:
gitextract_4aoyh11l/
├── .gitignore
├── README.md
├── package.json
├── public/
│ └── index.html
├── src/
│ ├── App.tsx
│ ├── components/
│ │ ├── Aside/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── BarChartBox/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Button/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Content/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ContentHeader/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── HistoryBox/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── HistoryFinanceCard/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Input/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Layout/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── MainHeader/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── MessageBox/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── PieChartBox/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SelectInput/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Toggle/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── WalletBox/
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── hooks/
│ │ ├── auth.tsx
│ │ └── theme.tsx
│ ├── index.tsx
│ ├── pages/
│ │ ├── Dashboard/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── List/
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── SignIn/
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── react-app-env.d.ts
│ ├── repositories/
│ │ ├── expenses.ts
│ │ └── gains.ts
│ ├── routes/
│ │ ├── app.routes.tsx
│ │ ├── auth.routes.tsx
│ │ └── index.tsx
│ ├── styles/
│ │ ├── GlobalStyles.ts
│ │ ├── styled.d.ts
│ │ └── themes/
│ │ ├── dark.ts
│ │ └── light.ts
│ └── utils/
│ ├── emojis.ts
│ ├── formatCurrency.ts
│ ├── formatDate.ts
│ └── months.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: README.md
================================================
<div align="center" >
<img src="./docs/assets/logo.png" width="200">
</div>
Dashboard desenvolvido em **ReactJs** com **TypeScript** inteiramente componentizado com **componentes puros**.
<div align="center" >
<img src="./docs/assets/minhacarteirapreview.gif">
</div>
Não há banco de dados. Os dados são carregados de 2 arquivos que contém arrays simulando os repositórios de dados.
### Layout & Componentes Responsivos
<div align="center" >
<img src="./docs/assets/resposiveview.png">
</div>
- [x] Link do prototipo desenvolvido no [**Figma**](https://www.figma.com/file/nOGmUkhcINJt6nd57R4ENu/Untitled?node-id=0%3A1).
### Layout & Componentes Responsivos
- [x] Para os gráficos, foi utilizada a bibliteca [**Recharts**](http://recharts.org/en-US) que é opensource.
- [x] Para efeito de número crescendo eu utilizei o [**React CountUp**](https://www.npmjs.com/package/react-countup).
<div align="center">
<small>Rodrigo Gonçalves Santana - 2020</small>
</div>
================================================
FILE: package.json
================================================
{
"name": "minha-carteira",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/recharts": "^1.8.14",
"react": "^16.13.1",
"react-countup": "^4.3.3",
"react-dom": "^16.13.1",
"react-icons": "^3.10.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-switch": "^5.0.1",
"recharts": "^1.8.5",
"styled-components": "^5.1.1",
"typescript": "~3.7.2",
"uuidv4": "^6.2.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.1"
}
}
================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="./assets/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<title>Minha Carteira</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
================================================
FILE: src/App.tsx
================================================
import React from 'react';
import { ThemeProvider } from 'styled-components';
import GlobalStyles from './styles/GlobalStyles';
import { useTheme } from './hooks/theme';
import Routes from './routes';
const App: React.FC = () => {
const {theme} = useTheme();
return (
<ThemeProvider theme={theme}>
<GlobalStyles />
<Routes/>
</ThemeProvider>
);
}
export default App;
================================================
FILE: src/components/Aside/index.tsx
================================================
import React, {useState} from 'react';
import Toggle from '../Toggle';
import {
MdDashboard,
MdArrowDownward,
MdArrowUpward,
MdExitToApp,
MdClose,
MdMenu,
} from 'react-icons/md';
import logoImg from '../../assets/logo.svg';
import { useAuth } from '../../hooks/auth';
import { useTheme } from '../../hooks/theme';
import {
Container,
Header,
LogImg,
Title,
MenuContainer,
MenuItemLink,
MenuItemButton,
ToggleMenu,
ThemeToggleFooter,
} from './styles';
const Aside: React.FC = () => {
const { signOut } = useAuth();
const { toggleTheme, theme } = useTheme();
const [toggleMenuIsOpened, setToggleMenuIsOpened ] = useState(false);
const [darkTheme, setDarkTheme] = useState(() => theme.title === 'dark' ? true : false);
const handleToggleMenu = () => {
setToggleMenuIsOpened(!toggleMenuIsOpened);
}
const handleChangeTheme = () => {
setDarkTheme(!darkTheme);
toggleTheme();
}
return (
<Container menuIsOpen={toggleMenuIsOpened}>
<Header>
<ToggleMenu onClick={handleToggleMenu}>
{ toggleMenuIsOpened ? <MdClose /> : <MdMenu /> }
</ToggleMenu>
<LogImg src={logoImg} alt="Logo Minha Carteira" />
<Title>Minha Carteira</Title>
</Header>
<MenuContainer>
<MenuItemLink href="/">
<MdDashboard />
Dashboard
</MenuItemLink>
<MenuItemLink href="/list/entry-balance">
<MdArrowUpward />
Entradas
</MenuItemLink>
<MenuItemLink href="/list/exit-balance">
<MdArrowDownward />
Saídas
</MenuItemLink>
<MenuItemButton onClick={signOut}>
<MdExitToApp />
Sair
</MenuItemButton>
</MenuContainer>
<ThemeToggleFooter menuIsOpen={toggleMenuIsOpened}>
<Toggle
labelLeft="Light"
labelRight="Dark"
checked={darkTheme}
onChange={handleChangeTheme}
/>
</ThemeToggleFooter>
</Container>
);
}
export default Aside;
================================================
FILE: src/components/Aside/styles.ts
================================================
import styled, { css } from 'styled-components';
interface IContainerProps {
menuIsOpen: boolean;
}
interface IThemeToggleFooterProps {
menuIsOpen: boolean;
}
export const Container = styled.div<IContainerProps>`
grid-area: AS;
background-color: ${props => props.theme.colors.secondary};
padding-left: 20px;
border-right: 1px solid ${props => props.theme.colors.gray};
position: relative;
@media(max-width: 600px){
padding-left: 20px;
position: fixed;
z-index: 2;
width: 170px;
height: ${props => props.menuIsOpen ? '100vh' : '70px'};
overflow: hidden;
${props => !props.menuIsOpen && css`
border: none;
border-bottom: 1px solid ${props => props.theme.colors.gray};
`};
}
`;
export const Header = styled.header`
height: 70px;
display: flex;
align-items: center;
`;
export const LogImg = styled.img`
height: 40px;
width: 40px;
@media(max-width: 600px){
display: none;
}
`;
export const Title = styled.h3`
color: ${props => props.theme.colors.white};
margin-left: 10px;
@media(max-width: 600px){
display: none;
}
`;
export const MenuContainer = styled.nav`
display: flex;
flex-direction: column;
margin-top: 50px;
`;
export const MenuItemLink = styled.a`
color: ${props => props.theme.colors.info};
text-decoration: none;
margin: 7px 0;
display: flex;
align-items: center;
transition: opacity .3s;
&:hover {
opacity: .7;
}
> svg {
font-size: 18px;
margin-right: 5px;
}
`;
export const MenuItemButton = styled.button`
font-size: 16px;
color: ${props => props.theme.colors.info};
border: none;
background: none;
margin: 7px 0;
display: flex;
align-items: center;
transition: opacity .3s;
&:hover {
opacity: .7;
}
> svg {
font-size: 18px;
margin-right: 5px;
}
`;
export const ToggleMenu = styled.button`
width: 40px;
height: 40px;
border-radius: 5px;
font-size: 22px;
background-color: ${props => props.theme.colors.warning};
color: ${props => props.theme.colors.white};
transition: opacity .3s;
&:hover{
opacity: 0.7;
}
display: none;
@media(max-width: 600px){
display: flex;
justify-content: center;
align-items: center;
}
`;
export const ThemeToggleFooter = styled.footer<IThemeToggleFooterProps>`
display: none;
position: absolute;
bottom: 30px;
@media(max-width: 470px){
display: ${props => props.menuIsOpen ? 'flex' : 'none'};
}
`;
================================================
FILE: src/components/BarChartBox/index.tsx
================================================
import React from 'react';
import {
ResponsiveContainer,
BarChart,
Bar,
Cell,
Tooltip,
} from 'recharts';
import formatCurrency from '../../utils/formatCurrency';
import {
Container,
SideLeft,
SideRight,
LegendContainer,
Legend,
} from './styles';
interface IBarChartProps {
title: string;
data: {
name: string;
amount: number;
percent: number;
color: string
}[],
}
const BarChartBox: React.FC<IBarChartProps> = ({ title, data }) => (
<Container>
<SideLeft>
<h2>{title}</h2>
<LegendContainer>
{
data.map((indicator) => (
<Legend key={indicator.name} color={indicator.color}>
<div>{indicator.percent}%</div>
<span>{indicator.name}</span>
</Legend>
))
}
</LegendContainer>
</SideLeft>
<SideRight>
<ResponsiveContainer>
<BarChart data={data}>
<Bar dataKey="amount" name="Valor">
{
data.map((indicator) => (
<Cell
key={indicator.name}
fill={indicator.color}
cursor="pointer"
/>
))
}
</Bar>
<Tooltip
cursor={{fill: 'none'}}
formatter={(value) => formatCurrency(Number(value))}
/>
</BarChart>
</ResponsiveContainer>
</SideRight>
</Container>
);
export default BarChartBox;
================================================
FILE: src/components/BarChartBox/styles.ts
================================================
import styled, { keyframes } from 'styled-components';
interface ILegendProps {
color: string;
}
const animate = keyframes`
0% {
transform: translateX(100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.div`
width: 48%;
min-height: 260px;
margin: 10px 0;
background-color: ${props => props.theme.colors.tertiary};
color: ${props => props.theme.colors.white};
border-radius: 7px;
display: flex;
animation: ${animate} .5s;
@media(max-width: 1200px){
display: flex;
flex-direction: column;
width: 100%;
height: auto;
}
`;
export const SideLeft = styled.aside`
flex: 1;
padding: 30px 20px;
> h2 {
padding-left: 16px;
margin-bottom: 10px;
}
`;
export const LegendContainer = styled.ul`
list-style: none;
height: 175px;
padding-right: 15px;
overflow-y: scroll;
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background-color: ${props => props.theme.colors.secondary};
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: ${props => props.theme.colors.tertiary};
}
@media(max-width: 1200px){
display: flex;
height: auto;
}
`;
export const Legend = styled.li<ILegendProps>`
display: flex;
align-items: center;
margin-bottom: 7px;
padding-left: 16px;
> div {
background-color: ${props => props.color};
width: 40px;
height: 40px;
border-radius: 5px;
font-size: 14px;
line-height: 40px;
text-align: center;
}
> span {
margin-left: 5px;
}
@media(max-width: 1200px){
> div {
width: 30px;
height: 30px;
font-size: 10px;
line-height: 30px;
}
}
`;
export const SideRight = styled.main`
flex: 1;
min-height: 150px;
display: flex;
justify-content: center;
padding-top: 35px;
`;
================================================
FILE: src/components/Button/index.tsx
================================================
import React, { ButtonHTMLAttributes } from 'react';
import { Container } from './styles'
type IButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
const Button: React.FC<IButtonProps> = ({children, ...rest }) => (
<Container {...rest}>
{children}
</Container>
);
export default Button;
================================================
FILE: src/components/Button/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.button`
width: 100%;
margin: 7px 0;
padding: 10px;
border-radius: 5px;
font-weight: bold;
color: ${props => props.theme.colors.white};
background-color: ${props => props.theme.colors.warning};
transition: opacity .3s;
&:hover{
opacity: .7;
}
`;
================================================
FILE: src/components/Content/index.tsx
================================================
import React from 'react';
import { Container } from './styles';
const Content: React.FC = ({ children }) => (
<Container>
{children}
</Container>
);
export default Content;
================================================
FILE: src/components/Content/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div`
grid-area: CT;
color: ${props => props.theme.colors.white};
background-color: ${props => props.theme.colors.primary};
padding: 25px;
height: calc(100vh - 70px);
overflow-y: scroll;
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background-color: ${props => props.theme.colors.secondary};
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: ${props => props.theme.colors.tertiary};
}
`;
================================================
FILE: src/components/ContentHeader/index.tsx
================================================
import React from 'react';
import {
Container,
TitleContainer,
Controllers
} from './styles';
interface IContentHeaderProps {
title: string;
lineColor: string;
children: React.ReactNode;
}
const ContentHeader: React.FC<IContentHeaderProps> = ({
title, lineColor, children
}) => (
<Container>
<TitleContainer lineColor={lineColor}>
<h1>{title}</h1>
</TitleContainer>
<Controllers>
{children}
</Controllers>
</Container>
);
export default ContentHeader;
================================================
FILE: src/components/ContentHeader/styles.ts
================================================
import styled from 'styled-components';
interface ITitleContainerProps {
lineColor: string;
}
export const Container = styled.div`
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 25px;
@media(max-width: 320px){
flex-direction: column;
}
`;
export const TitleContainer = styled.div<ITitleContainerProps>`
> h1 {
color: ${props => props.theme.colors.white};
&::after {
content: '';
display: block;
width: 55px;
border-bottom: 10px solid ${props => props.lineColor};
}
}
@media(max-width: 420px){
> h1 {
font-size: 22px;
&::after {
content: '';
display: block;
width: 55px;
border-bottom: 5px solid ${props => props.lineColor};
}
}
}
`;
export const Controllers = styled.div`
display: flex;
@media(max-width: 320px){
width: 100%;
justify-content: space-around;
margin-top: 20px;
}
`;
================================================
FILE: src/components/HistoryBox/index.tsx
================================================
import React from 'react';
import {
ResponsiveContainer,
LineChart,
Line,
XAxis,
CartesianGrid,
Tooltip,
} from 'recharts';
import formatCurrency from '../../utils/formatCurrency';
import {
Container,
ChartContainer,
Header,
LegendContainer,
Legend,
} from './styles';
interface IHistoryBoxProps {
data: {
month: string;
amountEntry: number;
amountOutput: number;
}[],
lineColorAmountEntry: string;
lineColorAmountOutput: string;
}
const HistoryBox: React.FC<IHistoryBoxProps> = ({
data, lineColorAmountEntry, lineColorAmountOutput
}) => (
<Container>
<Header>
<h2>Histórico de saldo</h2>
<LegendContainer>
<Legend color={lineColorAmountEntry}>
<div></div>
<span>Entradas</span>
</Legend>
<Legend color={lineColorAmountOutput}>
<div></div>
<span>Saídas</span>
</Legend>
</LegendContainer>
</Header>
<ChartContainer>
<ResponsiveContainer>
<LineChart data={data} margin={{ top: 5, right: 20, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#cecece" />
<XAxis dataKey="month" stroke="#cecece" />
<Tooltip formatter={(value) => formatCurrency(Number(value))} />
<Line
type="monotone"
dataKey="amountEntry"
name="Entradas"
stroke={lineColorAmountEntry}
strokeWidth={5}
dot={{ r: 5}}
activeDot={{ r: 8}}
/>
<Line
type="monotone"
dataKey="amountOutput"
name="Saídas"
stroke={lineColorAmountOutput}
strokeWidth={5}
dot={{ r: 5}}
activeDot={{ r: 8}}
/>
</LineChart>
</ResponsiveContainer>
</ChartContainer>
</Container>
)
export default HistoryBox;
================================================
FILE: src/components/HistoryBox/styles.ts
================================================
import styled, {keyframes} from 'styled-components';
interface ILegendProps {
color: string;
}
const animate = keyframes`
0% {
transform: translateX(-100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.div`
width: 100%;
display: flex;
flex-direction: column;
background-color: ${props => props.theme.colors.tertiary};
color: ${props => props.theme.colors.white};
margin: 10px 0;
padding: 30px 20px;
border-radius: 7px;
animation: ${animate} .5s;
`;
export const ChartContainer = styled.div`
flex: 1;
height: 260px;
`;
export const Header = styled.header`
width: 100%;
display: flex;
justify-content: space-between;
> h2 {
margin-bottom: 20px;
padding-left: 16px;
}
@media(max-width: 1200px){
flex-direction: column;
}
`;
export const LegendContainer = styled.ul`
list-style: none;
display: flex;
padding-right: 16px;
`;
export const Legend = styled.li<ILegendProps>`
display: flex;
align-items: center;
margin-bottom: 7px;
margin-left: 16px;
> div {
background-color: ${props => props.color};
width: 40px;
height: 40px;
border-radius: 5px;
font-size: 14px;
line-height: 40px;
text-align: center;
}
> span {
margin-left: 5px;
}
@media(max-width: 1280px){
> div {
width: 30px;
height: 30px;
}
}
`;
================================================
FILE: src/components/HistoryFinanceCard/index.tsx
================================================
import React from 'react';
import { Container, Tag } from './styles';
interface IHistoryFinanceCardProps {
tagColor: string;
title: string;
subtitle: string;
amount: string;
}
const HistoryFinanceCard: React.FC<IHistoryFinanceCardProps> = ({
tagColor,
title,
subtitle,
amount
}) => (
<Container>
<Tag color={tagColor} />
<div>
<span>{title}</span>
<small>{subtitle}</small>
</div>
<h3>{amount}</h3>
</Container>
);
export default HistoryFinanceCard;
================================================
FILE: src/components/HistoryFinanceCard/styles.ts
================================================
import styled, { keyframes } from 'styled-components';
interface ITagProps {
color: string;
}
const animate = keyframes`
0% {
transform: translateX(-100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.li`
background-color: ${props => props.theme.colors.tertiary};
list-style: none;
border-radius: 10px;
margin: 10px 0;
padding: 12px 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all .3s;
position: relative;
animation: ${animate} .5s ease;
&:hover {
opacity: .7;
transform: translateX(10px);
}
> div {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 10px;
}
> div span {
font-size: 22px;
font-weight: 500;
}
`;
export const Tag = styled.div<ITagProps>`
width: 13px;
height: 60%;
background-color: ${props => props.color};
position: absolute;
left: 0;
`;
================================================
FILE: src/components/Input/index.tsx
================================================
import React, { InputHTMLAttributes } from 'react';
import { Container } from './styles'
type IInputProps = InputHTMLAttributes<HTMLInputElement>;
const Input: React.FC<IInputProps> = ({ ...rest }) => (
<Container {...rest} />
);
export default Input;
================================================
FILE: src/components/Input/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.input`
width: 100%;
margin: 7px 0;
padding: 10px;
border-radius: 5px;
`;
================================================
FILE: src/components/Layout/index.tsx
================================================
import React from 'react';
import { Grid } from './styles';
import MainHeader from '../MainHeader';
import Aside from '../Aside';
import Content from '../Content';
const Layout: React.FC = ({ children }) => (
<Grid>
<MainHeader />
<Aside />
<Content>
{ children }
</Content>
</Grid>
);
export default Layout;
================================================
FILE: src/components/Layout/styles.ts
================================================
import styled from 'styled-components';
/**
* Layout
* MH = Main Header
* AS = Aside
* CT = Content
*/
export const Grid = styled.div`
display: grid;
grid-template-columns: 250px auto;
grid-template-rows: 70px auto;
grid-template-areas:
'AS MH'
'AS CT';
height: 100vh;
min-width: 315px;
@media(max-width: 600px){
grid-template-columns: 100%;
grid-template-rows: 70px auto;
grid-template-areas:
'MH'
'CT';
}
`;
================================================
FILE: src/components/MainHeader/index.tsx
================================================
import React, { useMemo, useState } from 'react';
import Toggle from '../Toggle';
import emojis from '../../utils/emojis';
import { useTheme } from '../../hooks/theme';
import {
Container,
Profile,
Welcome,
UserName,
} from './styles';
const MainHeader: React.FC = () => {
const { toggleTheme, theme } = useTheme();
const [darkTheme, setDarkTheme] = useState(() => theme.title === 'dark' ? true : false);
const handleChangeTheme = () => {
setDarkTheme(!darkTheme);
toggleTheme();
}
const emoji = useMemo(() => {
const indice = Math.floor(Math.random() * emojis.length);
return emojis[indice];
},[]);
return (
<Container>
<Toggle
labelLeft="Light"
labelRight="Dark"
checked={darkTheme}
onChange={handleChangeTheme}
/>
<Profile>
<Welcome>Olá, {emoji}</Welcome>
<UserName>Rodrigo Gonçalves</UserName>
</Profile>
</Container>
);
}
export default MainHeader;
================================================
FILE: src/components/MainHeader/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div`
grid-area: MH;
background-color: ${props => props.theme.colors.secondary};
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
border-bottom: 1px solid ${props => props.theme.colors.gray};
`;
export const Profile = styled.div`
color: ${props => props.theme.colors.white};
`;
export const Welcome = styled.h3``;
export const UserName = styled.span``;
================================================
FILE: src/components/MessageBox/index.tsx
================================================
import React from 'react';
import { Container } from './styles';
interface IMessageBoxProps {
title: string;
description: string;
footerText: string;
icon: string;
}
const MessageBox: React.FC<IMessageBoxProps> = ({
title,
description,
footerText,
icon,
}) => (
<Container>
<header>
<h1>
{title}
<img src={icon} alt={title}/>
</h1>
<p>{description}</p>
</header>
<footer>
<span>{footerText}</span>
</footer>
</Container>
);
export default MessageBox;
================================================
FILE: src/components/MessageBox/styles.ts
================================================
import styled, {keyframes} from 'styled-components';
const animate = keyframes`
0% {
transform: translateX(-100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.div`
width: 48%;
height: 260px;
background-color: ${props => props.theme.colors.tertiary};
color: ${props => props.theme.colors.white};
border-radius: 7px;
margin: 10px 0;
padding: 30px 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
animation: ${animate} .5s;
> header img {
width: 35px;
margin-left: 7px;
}
> header p {
font-size: 18px;
}
@media(max-width: 770px){
width: 100%;
> header h1 {
font-size: 24px;
img {
height: 20px;
width: 20px;
}
}
> header p, > footer span {
font-size: 14px;
}
}
@media(max-width: 420px){
width: 100%;
height: auto;
> header p {
margin-bottom: 15px;
}
}
`;
================================================
FILE: src/components/PieChartBox/index.tsx
================================================
import React from 'react';
import {
PieChart,
Pie,
Cell,
ResponsiveContainer
} from 'recharts';
import {
Container,
SideLeft,
LegendContainer,
Legend,
SideRight,
} from './styles';
interface IPieChartProps {
data: {
name: string;
value: number;
percent: number;
color: string;
}[];
}
const PieChartBox: React.FC<IPieChartProps> = ({ data }) => (
<Container>
<SideLeft>
<h2>Relação</h2>
<LegendContainer>
{
data.map((indicator) => (
<Legend key={indicator.name} color={indicator.color}>
<div>{indicator.percent}%</div>
<span>{indicator.name}</span>
</Legend>
))
}
</LegendContainer>
</SideLeft>
<SideRight>
<ResponsiveContainer>
<PieChart>
<Pie data={data} dataKey="percent">
{
data.map((indicator) => (
<Cell key={indicator.name} fill={indicator.color} />
))
}
</Pie>
</PieChart>
</ResponsiveContainer>
</SideRight>
</Container>
);
export default PieChartBox;
================================================
FILE: src/components/PieChartBox/styles.ts
================================================
import styled, { keyframes } from 'styled-components';
interface ILegendProps {
color: string;
}
const animate = keyframes`
0% {
transform: translateX(100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.div`
width: 48%;
height: 260px;
margin: 10px 0;
background-color: ${props => props.theme.colors.tertiary};
color: ${props => props.theme.colors.white};
border-radius: 7px;
display: flex;
animation: ${animate} .5s;
@media(max-width: 770px){
display: flex;
width: 100%;
}
`;
export const SideLeft = styled.aside`
padding: 30px 20px;
> h2 {
margin-bottom: 20px;
}
@media(max-width: 1345px){
padding: 0 15px 5px;
margin-bottom: 7px;
> h2 {
margin-top: 15px;
margin-bottom: 7px;
}
}
@media(max-width: 420px){
padding: 15px;
margin-bottom: 7px;
}
`;
export const LegendContainer = styled.ul`
list-style: none;
height: 175px;
padding-right: 15px;
overflow-y: scroll;
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background-color: ${props => props.theme.colors.secondary};
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: ${props => props.theme.colors.tertiary};
}
@media(max-width: 1345px){
display: flex;
flex-direction: column;
}
`;
export const Legend = styled.li<ILegendProps>`
display: flex;
align-items: center;
margin-bottom: 7px;
> div {
background-color: ${props => props.color};
width: 40px;
height: 40px;
border-radius: 5px;
font-size: 14px;
line-height: 40px;
text-align: center;
}
> span {
margin-left: 5px;
}
@media(max-width: 145px){
font-size: 14px;
margin: 3px 0;
> div {
height: 35px;
width: 35px;
line-height: 35px;
}
> span {
margin-left: 7px;
}
}
`;
export const SideRight = styled.main`
display: flex;
flex: 1;
justify-content: center;
@media(max-width: 1345px){
height: 100%;
}
`;
================================================
FILE: src/components/SelectInput/index.tsx
================================================
import React from 'react';
import { Container } from './styles';
interface ISelectInputProps {
options: {
value: string | number;
label: string | number;
}[],
onChange(event: React.ChangeEvent<HTMLSelectElement>): void | undefined;
defaultValue?: string | number;
}
const SelectInput: React.FC<ISelectInputProps> = ({
options,
onChange,
defaultValue
}) => (
<Container>
<select onChange={onChange} defaultValue={defaultValue}>
{
options.map(option => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))
}
</select>
</Container>
);
export default SelectInput;
================================================
FILE: src/components/SelectInput/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div`
> select {
padding: 7px 10px;
border-radius: 5px;
margin-left: 7px;
}
`;
================================================
FILE: src/components/Toggle/index.tsx
================================================
import React from 'react';
import {
Container,
ToggleLabel,
ToggleSelector
} from './styles';
interface IToggleProps {
labelLeft: string;
labelRight: string;
checked: boolean;
onChange(): void;
}
const Toggle: React.FC<IToggleProps> = ({
labelLeft,
labelRight,
checked,
onChange
}) => (
<Container>
<ToggleLabel>{labelLeft}</ToggleLabel>
<ToggleSelector
checked={checked}
uncheckedIcon={false}
checkedIcon={false}
onChange={onChange}
/>
<ToggleLabel>{labelRight}</ToggleLabel>
</Container>
)
export default Toggle;
================================================
FILE: src/components/Toggle/styles.ts
================================================
import styled from 'styled-components';
import Switch, { ReactSwitchProps } from 'react-switch';
export const Container = styled.div`
display: flex;
align-items: center;
`;
export const ToggleLabel = styled.span`
color: ${props => props.theme.colors.white};
`;
export const ToggleSelector = styled(Switch).attrs<ReactSwitchProps>(
({ theme }) => ({
onColor: theme.colors.info,
offColor: theme.colors.warning
}))<ReactSwitchProps>`
margin: 0 7px;
`;
================================================
FILE: src/components/WalletBox/index.tsx
================================================
import React, { useMemo } from 'react';
import CountUp from 'react-countup';
import dolarImg from '../../assets/dolar.svg';
import arrowUpImg from '../../assets/arrow-up.svg';
import arrowDownImg from '../../assets/arrow-down.svg';
import { Container } from './styles';
interface IWalletBoxProps {
title: string;
amount: number;
footerlabel: string;
icon: 'dolar' | 'arrowUp' | 'arrowDown';
color: string;
}
const WalletBox: React.FC<IWalletBoxProps> = ({
title,
amount,
footerlabel,
icon,
color
}) => {
const iconSelected = useMemo(() => {
switch (icon) {
case 'dolar':
return dolarImg;
case 'arrowUp':
return arrowUpImg;
case 'arrowDown':
return arrowDownImg;
default:
return undefined;
}
},[icon]);
return (
<Container color={color}>
<span>{title}</span>
<h1>
<strong>R$ </strong>
<CountUp
end={amount}
separator="."
decimal=","
decimals={2}
/>
</h1>
<small>{footerlabel}</small>
<img src={iconSelected} alt={title} />
</Container>
);
}
export default WalletBox;
================================================
FILE: src/components/WalletBox/styles.ts
================================================
import styled, {keyframes} from 'styled-components';
interface IContainerProps {
color: string;
}
const animate = keyframes`
0%{
transform: translateX(100px);
opacity: 0;
}
50%{
opacity: .3;
}
100%{
transform: translateX(0px);
opacity: 1;
}
`;
export const Container = styled.div<IContainerProps>`
width: 32%;
height: 150px;
margin: 10px 0;
background-color: ${props => props.color};
color: ${props => props.theme.colors.white};
border-radius: 7px;
padding: 10px 20px;
position: relative;
overflow: hidden;
animation: ${animate} .5s;
> img {
height: 110%;
position: absolute;
top: -10px;
right: -30px;
opacity: .3;
}
> span {
font-size: 18px;
font-weight: 500;
}
> small {
font-size: 12px;
position: absolute;
bottom: 10px;
}
@media(max-width: 770px){
> span {
font-size: 14px;
}
> h1 {
word-wrap: break-word;
font-size: 22px;
strong {
display: inline-block;
width: 100%;
font-size: 16px;
}
}
}
@media(max-width: 420px){
width: 100%;
> h1 {
display: flex;
strong {
position: initial;
width: auto;
font-size: 22px;
}
strong:after {
display: inline-block;
content: '';
width: 1px;
}
}
}
`;
================================================
FILE: src/hooks/auth.tsx
================================================
import React, { createContext, useState, useContext } from 'react';
interface IAuthContext {
logged: boolean;
signIn(email: string, password: string): void;
signOut(): void;
}
const AuthContext = createContext<IAuthContext>({} as IAuthContext);
const AuthProvider: React.FC = ({ children }) => {
const [logged, setLogged] = useState<boolean>(() => {
const isLogged = localStorage.getItem('@minha-carteira:logged');
return !!isLogged;
});
const signIn = (email: string, password: string) => {
if(email === 'rodrigo@email.com' && password === '123'){
localStorage.setItem('@minha-carteira:logged', 'true');
setLogged(true);
}else{
alert('Senha ou usuário inválidos!');
}
}
const signOut = () => {
localStorage.removeItem('@minha-carteira:logged');
setLogged(false);
}
return (
<AuthContext.Provider value={{logged, signIn, signOut}}>
{children}
</AuthContext.Provider>
);
}
function useAuth(): IAuthContext {
const context = useContext(AuthContext);
return context;
}
export { AuthProvider, useAuth };
================================================
FILE: src/hooks/theme.tsx
================================================
import React, { createContext, useState, useContext } from 'react';
import dark from '../styles/themes/dark';
import light from '../styles/themes/light';
interface IThemeContext {
toggleTheme(): void;
theme: ITheme;
}
interface ITheme {
title: string;
colors: {
primary: string;
secondary: string;
tertiary: string;
white: string;
black: string;
gray: string;
success: string;
info: string;
warning: string;
}
}
const ThemeContext = createContext<IThemeContext>({} as IThemeContext);
const ThemeProvider: React.FC = ({ children }) => {
const [theme, setTheme] = useState<ITheme>(() => {
const themeSaved = localStorage.getItem('@minha-carteira:theme');
if(themeSaved) {
return JSON.parse(themeSaved);
}else{
return dark;
}
});
const toggleTheme = () => {
if(theme.title === 'dark'){
setTheme(light);
localStorage.setItem('@minha-carteira:theme', JSON.stringify(light));
}else{
setTheme(dark);
localStorage.setItem('@minha-carteira:theme', JSON.stringify(dark));
}
};
return (
<ThemeContext.Provider value={{ toggleTheme, theme }}>
{children}
</ThemeContext.Provider>
)
}
function useTheme(): IThemeContext {
const context = useContext(ThemeContext);
return context;
}
export { ThemeProvider, useTheme };
================================================
FILE: src/index.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import { ThemeProvider } from './hooks/theme';
import { AuthProvider } from './hooks/auth';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<AuthProvider>
<App />
</AuthProvider>
</ThemeProvider>
</React.StrictMode>,
document.getElementById('root')
);
================================================
FILE: src/pages/Dashboard/index.tsx
================================================
import React, { useState, useMemo, useCallback } from 'react';
import ContentHeader from '../../components/ContentHeader';
import SelectInput from '../../components/SelectInput';
import WalletBox from '../../components/WalletBox';
import MessageBox from '../../components/MessageBox';
import PieChartBox from '../../components/PieChartBox';
import HistoryBox from '../../components/HistoryBox';
import BarChartBox from '../../components/BarChartBox'
import expenses from '../../repositories/expenses';
import gains from '../../repositories/gains';
import listOfMonths from '../../utils/months';
import happyImg from '../../assets/happy.svg';
import sadImg from '../../assets/sad.svg';
import grinningImg from '../../assets/grinning.svg';
import opsImg from '../../assets/ops.svg';
import {
Container,
Content,
} from './styles';
const Dashboard: React.FC = () => {
const [monthSelected, setMonthSelected] = useState<number>(new Date().getMonth() + 1);
const [yearSelected, setYearSelected] = useState<number>(new Date().getFullYear());
const years = useMemo(() => {
let uniqueYears: number[] = [];
[...expenses, ...gains].forEach(item => {
const date = new Date(item.date);
const year = date.getFullYear();
if(!uniqueYears.includes(year)){
uniqueYears.push(year)
}
});
return uniqueYears.map(year => {
return {
value: year,
label: year,
}
});
},[]);
const months = useMemo(() => {
return listOfMonths.map((month, index) => {
return {
value: index + 1,
label: month,
}
});
},[]);
const totalExpenses = useMemo(() => {
let total: number = 0;
expenses.forEach(item => {
const date = new Date(item.date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
if(month === monthSelected && year === yearSelected){
try{
total += Number(item.amount)
}catch{
throw new Error('Invalid amount! Amount must be number.')
}
}
});
return total;
},[monthSelected, yearSelected]);
const totalGains = useMemo(() => {
let total: number = 0;
gains.forEach(item => {
const date = new Date(item.date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
if(month === monthSelected && year === yearSelected){
try{
total += Number(item.amount)
}catch{
throw new Error('Invalid amount! Amount must be number.')
}
}
});
return total;
},[monthSelected, yearSelected]);
const totalBalance = useMemo(() => {
return totalGains - totalExpenses;
},[totalGains, totalExpenses]);
const message = useMemo(() => {
if(totalBalance < 0){
return {
title: "Que triste!",
description: "Neste mês, você gastou mais do que deveria.",
footerText: "Verifique seus gastos e tente cortar algumas coisas desnecessárias.",
icon: sadImg
}
}
else if(totalGains === 0 && totalExpenses === 0){
return {
title: "Op's!",
description: "Neste mês, não há registros de entradas ou saídas.",
footerText: "Parece que você não fez nenhum registro no mês e ano selecionado.",
icon: opsImg
}
}
else if(totalBalance === 0){
return {
title: "Ufaa!",
description: "Neste mês, você gastou exatamente o que ganhou.",
footerText: "Tenha cuidado. No próximo tente poupar o seu dinheiro.",
icon: grinningImg
}
}
else{
return {
title: "Muito bem!",
description: "Sua carteira está positiva!",
footerText: "Continue assim. Considere investir o seu saldo.",
icon: happyImg
}
}
},[totalBalance, totalGains, totalExpenses]);
const relationExpensesVersusGains = useMemo(() => {
const total = totalGains + totalExpenses;
const percentGains = Number(((totalGains / total) * 100).toFixed(1));
const percentExpenses = Number(((totalExpenses / total) * 100).toFixed(1));
const data = [
{
name: "Entradas",
value: totalGains,
percent: percentGains ? percentGains : 0,
color: '#E44C4E'
},
{
name: "Saídas",
value: totalExpenses,
percent: percentExpenses ? percentExpenses : 0,
color: '#F7931B'
},
];
return data;
},[totalGains, totalExpenses]);
const historyData = useMemo(() => {
return listOfMonths
.map((_, month) => {
let amountEntry = 0;
gains.forEach(gain => {
const date = new Date(gain.date);
const gainMonth = date.getMonth();
const gainYear = date.getFullYear();
if(gainMonth === month && gainYear === yearSelected){
try{
amountEntry += Number(gain.amount);
}catch{
throw new Error('amountEntry is invalid. amountEntry must be valid number.')
}
}
});
let amountOutput = 0;
expenses.forEach(expense => {
const date = new Date(expense.date);
const expenseMonth = date.getMonth();
const expenseYear = date.getFullYear();
if(expenseMonth === month && expenseYear === yearSelected){
try{
amountOutput += Number(expense.amount);
}catch{
throw new Error('amountOutput is invalid. amountOutput must be valid number.')
}
}
});
return {
monthNumber: month,
month: listOfMonths[month].substr(0, 3),
amountEntry,
amountOutput
}
})
.filter(item => {
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
return (yearSelected === currentYear && item.monthNumber <= currentMonth) || (yearSelected < currentYear)
});
},[yearSelected]);
const relationExpensevesRecurrentVersusEventual = useMemo(() => {
let amountRecurrent = 0;
let amountEventual = 0;
expenses
.filter((expense) => {
const date = new Date(expense.date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
return month === monthSelected && year === yearSelected;
})
.forEach((expense) => {
if(expense.frequency === 'recorrente'){
return amountRecurrent += Number(expense.amount);
}
if(expense.frequency === 'eventual'){
return amountEventual += Number(expense.amount);
}
});
const total = amountRecurrent + amountEventual;
const percentRecurrent = Number(((amountRecurrent / total) * 100).toFixed(1));
const percentEventual = Number(((amountEventual / total) * 100).toFixed(1));
return [
{
name: 'Recorrentes',
amount: amountRecurrent,
percent: percentRecurrent ? percentRecurrent : 0,
color: "#F7931B"
},
{
name: 'Eventuais',
amount: amountEventual,
percent: percentEventual ? percentEventual : 0,
color: "#E44C4E"
}
];
},[monthSelected, yearSelected]);
const relationGainsRecurrentVersusEventual = useMemo(() => {
let amountRecurrent = 0;
let amountEventual = 0;
gains
.filter((gain) => {
const date = new Date(gain.date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
return month === monthSelected && year === yearSelected;
})
.forEach((gain) => {
if(gain.frequency === 'recorrente'){
return amountRecurrent += Number(gain.amount);
}
if(gain.frequency === 'eventual'){
return amountEventual += Number(gain.amount);
}
});
const total = amountRecurrent + amountEventual;
const percentRecurrent = Number(((amountRecurrent / total) * 100).toFixed(1));
const percentEventual = Number(((amountEventual / total) * 100).toFixed(1));
return [
{
name: 'Recorrentes',
amount: amountRecurrent,
percent: percentRecurrent ? percentRecurrent : 0,
color: "#F7931B"
},
{
name: 'Eventuais',
amount: amountEventual,
percent: percentEventual ? percentEventual : 0,
color: "#E44C4E"
}
];
},[monthSelected, yearSelected]);
const handleMonthSelected = useCallback((month: string) => {
try {
const parseMonth = Number(month);
setMonthSelected(parseMonth);
}
catch{
throw new Error('invalid month value. Is accept 0 - 24.')
}
},[]);
const handleYearSelected = useCallback((year: string) => {
try {
const parseYear = Number(year);
setYearSelected(parseYear);
}
catch{
throw new Error('invalid year value. Is accept integer numbers.')
}
},[]);
return (
<Container>
<ContentHeader title="Dashboard" lineColor="#F7931B">
<SelectInput
options={months}
onChange={(e) => handleMonthSelected(e.target.value)}
defaultValue={monthSelected}
/>
<SelectInput
options={years}
onChange={(e) => handleYearSelected(e.target.value)}
defaultValue={yearSelected}
/>
</ContentHeader>
<Content>
<WalletBox
title="saldo"
color="#4E41F0"
amount={totalBalance}
footerlabel="atualizado com base nas entradas e saídas"
icon="dolar"
/>
<WalletBox
title="entradas"
color="#F7931B"
amount={totalGains}
footerlabel="atualizado com base nas entradas e saídas"
icon="arrowUp"
/>
<WalletBox
title="saídas"
color="#E44C4E"
amount={totalExpenses}
footerlabel="atualizado com base nas entradas e saídas"
icon="arrowDown"
/>
<MessageBox
title={message.title}
description={message.description}
footerText={message.footerText}
icon={message.icon}
/>
<PieChartBox data={relationExpensesVersusGains} />
<HistoryBox
data={historyData}
lineColorAmountEntry="#F7931B"
lineColorAmountOutput="#E44C4E"
/>
<BarChartBox
title="Saídas"
data={relationExpensevesRecurrentVersusEventual}
/>
<BarChartBox
title="Entradas"
data={relationGainsRecurrentVersusEventual}
/>
</Content>
</Container>
);
}
export default Dashboard;
================================================
FILE: src/pages/Dashboard/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div``;
export const Content = styled.div`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
`;
================================================
FILE: src/pages/List/index.tsx
================================================
import React, { useMemo, useState, useEffect } from 'react';
import { uuid } from 'uuidv4';
import ContentHeader from '../../components/ContentHeader';
import SelectInput from '../../components/SelectInput';
import HistoryFinanceCard from '../../components/HistoryFinanceCard';
import gains from '../../repositories/gains';
import expenses from '../../repositories/expenses';
import formatCurrency from '../../utils/formatCurrency';
import formatDate from '../../utils/formatDate';
import listOfMonths from '../../utils/months';
import {
Container,
Content,
Filters
} from './styles';
interface IRouteParams {
match: {
params: {
type: string;
}
}
}
interface IData {
id: string;
description: string;
amountFormatted: string;
frequency: string;
dateFormatted: string;
tagColor: string;
}
const List: React.FC<IRouteParams> = ({ match }) => {
const [data, setData] = useState<IData[]>([]);
const [monthSelected, setMonthSelected] = useState<number>(new Date().getMonth() + 1);
const [yearSelected, setYearSelected] = useState<number>(new Date().getFullYear());
const [frequencyFilterSelected, setFrequencyFilterSelected] = useState(['recorrente', 'eventual']);
const movimentType = match.params.type;
const pageData = useMemo(() => {
return movimentType === 'entry-balance' ?
{
title: 'Entradas',
lineColor: '#4E41F0',
data: gains
}
:
{
title: 'Saídas',
lineColor: '#E44C4E',
data: expenses
}
},[movimentType]);
const years = useMemo(() => {
let uniqueYears: number[] = [];
const { data } = pageData;
data.forEach(item => {
const date = new Date(item.date);
const year = date.getFullYear();
if(!uniqueYears.includes(year)){
uniqueYears.push(year)
}
});
return uniqueYears.map(year => {
return {
value: year,
label: year,
}
});
},[pageData]);
const months = useMemo(() => {
return listOfMonths.map((month, index) => {
return {
value: index + 1,
label: month,
}
});
},[]);
const handleFrequencyClick = (frequency: string) => {
const alreadySelected = frequencyFilterSelected.findIndex(item => item === frequency);
if(alreadySelected >= 0){
const filtered = frequencyFilterSelected.filter(item => item !== frequency);
setFrequencyFilterSelected(filtered);
}else{
setFrequencyFilterSelected((prev) => [...prev, frequency]);
}
}
const handleMonthSelected = (month: string) => {
try {
const parseMonth = Number(month);
setMonthSelected(parseMonth);
}
catch{
throw new Error('invalid month value. Is accept 0 - 24.')
}
}
const handleYearSelected = (year: string) => {
try {
const parseYear = Number(year);
setYearSelected(parseYear);
}
catch{
throw new Error('invalid year value. Is accept integer numbers.')
}
}
useEffect(() => {
const { data } = pageData;
const filteredData = data.filter(item => {
const date = new Date(item.date);
const month = date.getMonth() + 1;
const year = date.getFullYear();
return month === monthSelected && year === yearSelected && frequencyFilterSelected.includes(item.frequency);
});
const formattedData = filteredData.map(item => {
return {
id: uuid(),
description: item.description,
amountFormatted: formatCurrency(Number(item.amount)),
frequency: item.frequency,
dateFormatted: formatDate(item.date),
tagColor: item.frequency === 'recorrente' ? '#4E41F0' : '#E44C4E',
}
});
setData(formattedData);
},[pageData, monthSelected, yearSelected, data.length, frequencyFilterSelected]);
return (
<Container>
<ContentHeader title={pageData.title} lineColor={pageData.lineColor}>
<SelectInput
options={months}
onChange={(e) => handleMonthSelected(e.target.value)}
defaultValue={monthSelected}
/>
<SelectInput
options={years}
onChange={(e) => handleYearSelected(e.target.value)}
defaultValue={yearSelected}
/>
</ContentHeader>
<Filters>
<button
type="button"
className={`
tag-filter
tag-filter-recurrent
${frequencyFilterSelected.includes('recorrente') && 'tag-actived'}`}
onClick={() => handleFrequencyClick('recorrente')}
>
Recorrentes
</button>
<button
type="button"
className={`
tag-filter
tag-filter-eventual
${frequencyFilterSelected.includes('eventual') && 'tag-actived'}`}
onClick={() => handleFrequencyClick('eventual')}
>
Eventuais
</button>
</Filters>
<Content>
{
data.map(item => (
<HistoryFinanceCard
key={item.id}
tagColor={item.tagColor}
title={item.description}
subtitle={item.dateFormatted}
amount={item.amountFormatted}
/>
))
}
</Content>
</Container>
);
}
export default List;
================================================
FILE: src/pages/List/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div``;
export const Content = styled.main``;
export const Filters = styled.div`
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 30px;
.tag-filter {
font-size: 18px;
font-weight: 500;
background: none;
color: ${props => props.theme.colors.white};
margin: 0 10px;
opacity: .4;
transition: opacity .3s;
&:hover {
opacity: .7;
}
}
.tag-filter-recurrent::after {
content: '';
display: block;
width: 55px;
margin: 0 auto;
border-bottom: 10px solid ${props => props.theme.colors.success};
}
.tag-filter-eventual::after {
content: '';
display: block;
width: 55px;
margin: 0 auto;
border-bottom: 10px solid ${props => props.theme.colors.warning};
}
.tag-actived {
opacity: 1;
}
`;
================================================
FILE: src/pages/SignIn/index.tsx
================================================
import React, {useState} from 'react';
import logoImg from '../../assets/logo.svg';
import Input from '../../components/Input';
import Button from '../../components/Button';
import { useAuth } from '../../hooks/auth';
import {
Container,
Logo,
Form,
FormTitle,
} from './styles';
const SignIn: React.FC = () => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const { signIn } = useAuth();
return (
<Container>
<Logo>
<img src={logoImg} alt="Minha Carteira" />
<h2>Minha Carteira</h2>
</Logo>
<Form onSubmit={() => signIn(email, password)}>
<FormTitle>Entrar</FormTitle>
<Input
type="email"
placeholder="e-mail"
required
onChange={(e) => setEmail(e.target.value)}
/>
<Input
type="password"
placeholder="senha"
required
onChange={(e) => setPassword(e.target.value)}
/>
<Button type="submit">Acessar</Button>
</Form>
</Container>
);
}
export default SignIn;
================================================
FILE: src/pages/SignIn/styles.ts
================================================
import styled from 'styled-components';
export const Container = styled.div`
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: ${props => props.theme.colors.primary};
`;
export const Logo = styled.div`
display: flex;
align-items: center;
margin-bottom: 30px;
> h2 {
color: ${props => props.theme.colors.white};
margin-left: 7px;
}
> img {
width: 40px;
height: 40px;
}
`;
export const Form = styled.form`
width: 300px;
height: 300px;
padding: 30px;
border-radius: 10px;
background-color: ${props => props.theme.colors.tertiary};
`;
export const FormTitle = styled.h1`
margin-bottom: 40px;
color: ${props => props.theme.colors.white};
&:after {
content: '';
display: block;
width: 55px;
border-bottom: 10px solid ${props => props.theme.colors.warning};
}
`;
================================================
FILE: src/react-app-env.d.ts
================================================
/// <reference types="react-scripts" />
================================================
FILE: src/repositories/expenses.ts
================================================
export default [
{"description":"Energia elétrica", "amount":"150.55","type":"saída","frequency":"recorrente","date":"2020-01-10"},
{"description":"Água", "amount":"75.55", "type":"saída","frequency":"recorrente","date":"2020-01-15"},
{"description": "Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-01-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-01-23"},
{"description":"Compras do mês", "amount":"625.78","type":"saída","frequency":"recorrente","date":"2020-01-23"},
{"description":"Lanche", "amount":"45.70", "type":"saída","frequency":"eventual", "date":"2020-01-23"},
{"description":"Blusa", "amount":"95.70", "type":"saída","frequency":"eventual", "date":"2020-01-23"},
{"description":"Energia elétrica", "amount":"125.55","type":"saída","frequency":"recorrente","date":"2020-02-10"},
{"description":"Água", "amount":"90.15", "type":"saída","frequency":"recorrente","date":"2020-02-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-02-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-02-23"},
{"description":"Compras do mês", "amount":"540.00","type":"saída","frequency":"recorrente","date":"2020-02-25"},
{"description":"Parcela do Celular 1/5","amount":"150.99","type":"saída","frequency":"eventual", "date":"2020-02-26"},
{"description":"Cinema", "amount":"45.00", "type":"saída","frequency":"eventual", "date":"2020-02-23"},
{"description":"Energia elétrica", "amount":"97.00", "type":"saída","frequency":"recorrente","date":"2020-03-10"},
{"description":"Água", "amount":"100.10","type":"saída","frequency":"recorrente","date":"2020-03-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-03-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-03-23"},
{"description":"Compras do mês", "amount":"800.50","type":"saída","frequency":"recorrente","date":"2020-03-17"},
{"description":"Parcela do Celular 2/5","amount":"150.99","type":"saída","frequency":"eventual", "date":"2020-03-18"},
{"description":"Troca de Oléo do carro","amount":"90.00", "type":"saída","frequency":"eventual", "date":"2020-03-23"},
{"description":"Energia elétrica", "amount":"75.99", "type":"saída","frequency":"recorrente","date":"2020-04-10"},
{"description":"Água", "amount":"80.33", "type":"saída","frequency":"recorrente","date":"2020-04-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-04-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-04-23"},
{"description":"Compras do mês", "amount":"600.00","type":"saída","frequency":"recorrente","date":"2020-04-25"},
{"description":"Parcela do Celular 3/5","amount":"150.99","type":"saída","frequency":"eventual", "date":"2020-04-26"},
{"description":"Ebook de React Js", "amount":"85.95", "type":"saída","frequency":"eventual", "date":"2020-04-13"},
{"description":"Energia elétrica", "amount":"125.55","type":"saída","frequency":"recorrente","date":"2020-05-10"},
{"description":"Água", "amount":"90.15", "type":"saída","frequency":"recorrente","date":"2020-05-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-05-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-05-23"},
{"description":"Compras do mês", "amount":"540.00","type":"saída","frequency":"recorrente","date":"2020-05-25"},
{"description":"Parcela do Celular 4/5","amount":"150.99","type":"saída","frequency":"eventual", "date":"2020-05-26"},
{"description":"Blusa Iron Man", "amount":"150.00","type":"saída","frequency":"eventual", "date":"2020-01-23"},
{"description":"Energia elétrica", "amount":"200.00","type":"saída","frequency":"recorrente","date":"2020-06-10"},
{"description":"Água", "amount":"150.00","type":"saída","frequency":"recorrente","date":"2020-06-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-06-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-06-23"},
{"description":"Compras do mês", "amount":"559.15","type":"saída","frequency":"recorrente","date":"2020-06-25"},
{"description":"Parcela do Celular 5/5","amount":"150.99","type":"saída","frequency":"eventual", "date":"2020-06-26"},
{"description":"Perfume", "amount":"250.00","type":"saída","frequency":"eventual", "date":"2020-06-21"},
{"description":"Energia elétrica", "amount":"250.00","type":"saída","frequency":"recorrente","date":"2020-07-10"},
{"description":"Água", "amount":"90.00", "type":"saída","frequency":"recorrente","date":"2020-07-15"},
{"description":"Telefone", "amount":"99.99", "type":"saída","frequency":"recorrente","date":"2020-07-23"},
{"description":"Plano de Saúde", "amount":"300.00","type":"saída","frequency":"recorrente","date":"2020-07-23"},
{"description":"Compras do mês", "amount":"700.00","type":"saída","frequency":"recorrente","date":"2020-07-25"},
{"description":"Cafeteira", "amount":"250.00","type":"saída","frequency":"eventual", "date":"2020-07-26"},
{"description":"Pizza", "amount":"60.00", "type":"saída","frequency":"eventual", "date":"2020-07-19"},
{"description":"Pizza", "amount":"750.01", "type":"saída","frequency":"eventual", "date":"2020-07-19"}
]
================================================
FILE: src/repositories/gains.ts
================================================
export default [
{ "description": "Salário", "amount": "1300.52", "type": "entrada", "frequency": "recorrente", "date": "2020-01-10" },
{ "description": "Freela", "amount": "150.13", "type": "entrada", "frequency": "eventual", "date": "2020-01-17" },
{ "description": "Salário", "amount": "2500.23", "type": "entrada", "frequency": "recorrente", "date": "2020-02-10" },
{ "description": "Freela site", "amount": "900.23", "type": "entrada", "frequency": "eventual", "date": "2020-02-21" },
{ "description": "Freela app", "amount": "950.92", "type": "entrada", "frequency": "eventual", "date": "2020-02-23" },
{ "description": "Salário", "amount": "2500.25", "type": "entrada", "frequency": "recorrente", "date": "2020-03-10" },
{ "description": "Salário", "amount": "2500.18", "type": "entrada", "frequency": "recorrente", "date": "2020-04-10" },
{ "description": "Salário", "amount": "2500.15", "type": "entrada", "frequency": "recorrente", "date": "2020-05-10" },
{ "description": "Salário", "amount": "2500.12", "type": "entrada", "frequency": "recorrente", "date": "2020-06-10" },
{ "description": "Salário", "amount": "2500.00", "type": "entrada", "frequency": "recorrente", "date": "2020-07-10" },
]
================================================
FILE: src/routes/app.routes.tsx
================================================
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Layout from '../components/Layout';
import Dashboard from '../pages/Dashboard';
import List from '../pages/List';
const AppRoutes: React.FC = () => (
<Layout>
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/list/:type" exact component={List} />
</Switch>
</Layout>
);
export default AppRoutes;
================================================
FILE: src/routes/auth.routes.tsx
================================================
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import SignIn from '../pages/SignIn';
const AuthRoutes: React.FC = () => (
<Switch>
<Route path="/" component={SignIn} />
</Switch>
);
export default AuthRoutes;
================================================
FILE: src/routes/index.tsx
================================================
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { useAuth } from '../hooks/auth';
import App from './app.routes';
import Auth from './auth.routes';
const Routes: React.FC = () => {
const { logged } = useAuth();
return (
<BrowserRouter>
{ logged ? <App/> : <Auth /> }
</BrowserRouter>
);
}
export default Routes;
================================================
FILE: src/styles/GlobalStyles.ts
================================================
import { createGlobalStyle } from 'styled-components';
export default createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #root {
height: 100%;
}
*, button, input {
border: 0;
outline: 0;
font-family: 'Roboto', sans-serif;
}
button {
cursor: pointer;
}
`;
================================================
FILE: src/styles/styled.d.ts
================================================
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
title: string;
colors: {
primary: string;
secondary: string;
tertiary: string;
white: string;
black: string;
gray: string;
success: string;
info: string;
warning: string;
},
};
}
================================================
FILE: src/styles/themes/dark.ts
================================================
export default {
title: 'dark',
colors: {
primary: '#1B1F38',
secondary: '#252A48',
tertiary: '#313862',
white: '#FFF',
black: '#000',
gray: '#BFBFBF',
success: '#4E41F0',
info: '#F7931B',
warning: '#E44C4E',
},
};
================================================
FILE: src/styles/themes/light.ts
================================================
export default {
title: 'light',
colors: {
primary: '#DCDCDC',
secondary: '#FFFFFF',
tertiary: '#F5F5F5',
white: '#000',
black: '#FFF',
gray: '#BFBFBF',
success: '#03BB85',
info: '#3B5998',
warning: '#FF6961',
},
};
================================================
FILE: src/utils/emojis.ts
================================================
export default ['🤑', '🤩', '😍', '😎', '🤑', '😃', '😄'];
================================================
FILE: src/utils/formatCurrency.ts
================================================
const formatCurrency = (current: number): string => {
return current.toLocaleString(
'pt-br',
{
style: 'currency',
currency: 'BRL'
});
};
export default formatCurrency;
================================================
FILE: src/utils/formatDate.ts
================================================
const formatDate = (date: string): string => {
const dateFormatted = new Date(date);
const year = dateFormatted.getFullYear();
const day = dateFormatted.getDate() > 9
? dateFormatted.getDate() : `0${dateFormatted.getDate()}`;
const month = dateFormatted.getMonth() + 1 > 9
? dateFormatted.getMonth() + 1 : `0${dateFormatted.getMonth() + 1}`;
return `${day}/${month}/${year}`;
};
export default formatDate;
================================================
FILE: src/utils/months.ts
================================================
export default [
'Janeiro',
'Fevereiro',
'Março',
'Abril',
'Maio',
'Junho',
'Julho',
'Agosto',
'Setembro',
'Outubro',
'Novembro',
'Dezembro'
];
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}
gitextract_4aoyh11l/ ├── .gitignore ├── README.md ├── package.json ├── public/ │ └── index.html ├── src/ │ ├── App.tsx │ ├── components/ │ │ ├── Aside/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── BarChartBox/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Button/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Content/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── ContentHeader/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── HistoryBox/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── HistoryFinanceCard/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Input/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Layout/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── MainHeader/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── MessageBox/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── PieChartBox/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── SelectInput/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── Toggle/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ └── WalletBox/ │ │ ├── index.tsx │ │ └── styles.ts │ ├── hooks/ │ │ ├── auth.tsx │ │ └── theme.tsx │ ├── index.tsx │ ├── pages/ │ │ ├── Dashboard/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── List/ │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ └── SignIn/ │ │ ├── index.tsx │ │ └── styles.ts │ ├── react-app-env.d.ts │ ├── repositories/ │ │ ├── expenses.ts │ │ └── gains.ts │ ├── routes/ │ │ ├── app.routes.tsx │ │ ├── auth.routes.tsx │ │ └── index.tsx │ ├── styles/ │ │ ├── GlobalStyles.ts │ │ ├── styled.d.ts │ │ └── themes/ │ │ ├── dark.ts │ │ └── light.ts │ └── utils/ │ ├── emojis.ts │ ├── formatCurrency.ts │ ├── formatDate.ts │ └── months.ts └── tsconfig.json
SYMBOL INDEX (27 symbols across 22 files)
FILE: src/components/Aside/styles.ts
type IContainerProps (line 4) | interface IContainerProps {
type IThemeToggleFooterProps (line 9) | interface IThemeToggleFooterProps {
FILE: src/components/BarChartBox/index.tsx
type IBarChartProps (line 20) | interface IBarChartProps {
FILE: src/components/BarChartBox/styles.ts
type ILegendProps (line 3) | interface ILegendProps {
FILE: src/components/Button/index.tsx
type IButtonProps (line 5) | type IButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
FILE: src/components/ContentHeader/index.tsx
type IContentHeaderProps (line 9) | interface IContentHeaderProps {
FILE: src/components/ContentHeader/styles.ts
type ITitleContainerProps (line 3) | interface ITitleContainerProps {
FILE: src/components/HistoryBox/index.tsx
type IHistoryBoxProps (line 22) | interface IHistoryBoxProps {
FILE: src/components/HistoryBox/styles.ts
type ILegendProps (line 3) | interface ILegendProps {
FILE: src/components/HistoryFinanceCard/index.tsx
type IHistoryFinanceCardProps (line 5) | interface IHistoryFinanceCardProps {
FILE: src/components/HistoryFinanceCard/styles.ts
type ITagProps (line 3) | interface ITagProps {
FILE: src/components/Input/index.tsx
type IInputProps (line 5) | type IInputProps = InputHTMLAttributes<HTMLInputElement>;
FILE: src/components/MessageBox/index.tsx
type IMessageBoxProps (line 5) | interface IMessageBoxProps {
FILE: src/components/PieChartBox/index.tsx
type IPieChartProps (line 18) | interface IPieChartProps {
FILE: src/components/PieChartBox/styles.ts
type ILegendProps (line 3) | interface ILegendProps {
FILE: src/components/SelectInput/index.tsx
type ISelectInputProps (line 5) | interface ISelectInputProps {
FILE: src/components/Toggle/index.tsx
type IToggleProps (line 10) | interface IToggleProps {
FILE: src/components/WalletBox/index.tsx
type IWalletBoxProps (line 11) | interface IWalletBoxProps {
FILE: src/components/WalletBox/styles.ts
type IContainerProps (line 3) | interface IContainerProps {
FILE: src/hooks/auth.tsx
type IAuthContext (line 3) | interface IAuthContext {
function useAuth (line 39) | function useAuth(): IAuthContext {
FILE: src/hooks/theme.tsx
type IThemeContext (line 6) | interface IThemeContext {
type ITheme (line 11) | interface ITheme {
function useTheme (line 59) | function useTheme(): IThemeContext {
FILE: src/pages/List/index.tsx
type IRouteParams (line 20) | interface IRouteParams {
type IData (line 28) | interface IData {
FILE: src/styles/styled.d.ts
type DefaultTheme (line 4) | interface DefaultTheme {
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (80K chars).
[
{
"path": ".gitignore",
"chars": 310,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "README.md",
"chars": 983,
"preview": "<div align=\"center\" >\n <img src=\"./docs/assets/logo.png\" width=\"200\">\n</div>\n\n\nDashboard desenvolvido em **ReactJs** co"
},
{
"path": "package.json",
"chars": 1258,
"preview": "{\n \"name\": \"minha-carteira\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"@testing-library/jest-do"
},
{
"path": "public/index.html",
"chars": 487,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"./assets/logo.svg\" />\n"
},
{
"path": "src/App.tsx",
"chars": 428,
"preview": "import React from 'react';\nimport { ThemeProvider } from 'styled-components';\nimport GlobalStyles from './styles/GlobalS"
},
{
"path": "src/components/Aside/index.tsx",
"chars": 2395,
"preview": "import React, {useState} from 'react';\nimport Toggle from '../Toggle';\n\nimport {\n MdDashboard,\n MdArrowDownward,\n "
},
{
"path": "src/components/Aside/styles.ts",
"chars": 2742,
"preview": "import styled, { css } from 'styled-components';\n\n\ninterface IContainerProps {\n menuIsOpen: boolean;\n}\n\n\ninterface IT"
},
{
"path": "src/components/BarChartBox/index.tsx",
"chars": 2077,
"preview": "import React from 'react';\nimport {\n ResponsiveContainer,\n BarChart,\n Bar,\n Cell,\n Tooltip,\n} from 'recha"
},
{
"path": "src/components/BarChartBox/styles.ts",
"chars": 2216,
"preview": "import styled, { keyframes } from 'styled-components';\n\ninterface ILegendProps {\n color: string;\n}\n\nconst animate = k"
},
{
"path": "src/components/Button/index.tsx",
"chars": 310,
"preview": "import React, { ButtonHTMLAttributes } from 'react';\n\nimport { Container } from './styles'\n\ntype IButtonProps = ButtonH"
},
{
"path": "src/components/Button/styles.ts",
"chars": 370,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.button`\n width: 100%;\n\n margin: 7px 0;\n "
},
{
"path": "src/components/Content/index.tsx",
"chars": 193,
"preview": "import React from 'react';\n\nimport { Container } from './styles';\n\nconst Content: React.FC = ({ children }) => (\n <C"
},
{
"path": "src/components/Content/styles.ts",
"chars": 587,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div`\n grid-area: CT;\n color: ${props => p"
},
{
"path": "src/components/ContentHeader/index.tsx",
"chars": 567,
"preview": "import React from 'react';\n\nimport { \n Container,\n TitleContainer,\n Controllers \n} from './styles';\n\ninterface"
},
{
"path": "src/components/ContentHeader/styles.ts",
"chars": 1133,
"preview": "import styled from 'styled-components';\n\ninterface ITitleContainerProps {\n lineColor: string;\n}\n\nexport const Contain"
},
{
"path": "src/components/HistoryBox/index.tsx",
"chars": 2357,
"preview": "import React from 'react';\nimport {\n ResponsiveContainer,\n LineChart,\n Line,\n XAxis,\n CartesianGrid,\n "
},
{
"path": "src/components/HistoryBox/styles.ts",
"chars": 1658,
"preview": "import styled, {keyframes} from 'styled-components';\n\ninterface ILegendProps {\n color: string;\n}\n\nconst animate = key"
},
{
"path": "src/components/HistoryFinanceCard/index.tsx",
"chars": 559,
"preview": "import React from 'react';\n\nimport { Container, Tag } from './styles';\n\ninterface IHistoryFinanceCardProps {\n tagCol"
},
{
"path": "src/components/HistoryFinanceCard/styles.ts",
"chars": 1179,
"preview": "import styled, { keyframes } from 'styled-components';\n\ninterface ITagProps {\n color: string;\n}\n\nconst animate = keyfra"
},
{
"path": "src/components/Input/index.tsx",
"chars": 260,
"preview": "import React, { InputHTMLAttributes } from 'react';\n\nimport { Container } from './styles'\n\ntype IInputProps = InputHTML"
},
{
"path": "src/components/Input/styles.ts",
"chars": 163,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.input`\n width: 100%;\n\n margin: 7px 0;\n "
},
{
"path": "src/components/Layout/index.tsx",
"chars": 365,
"preview": "import React from 'react';\n\nimport { Grid } from './styles';\n\nimport MainHeader from '../MainHeader';\nimport Aside from "
},
{
"path": "src/components/Layout/styles.ts",
"chars": 500,
"preview": "import styled from 'styled-components';\n\n/**\n* Layout\n* MH = Main Header\n* AS = Aside\n* CT = Content \n*/\n\nexport cons"
},
{
"path": "src/components/MainHeader/index.tsx",
"chars": 1110,
"preview": "import React, { useMemo, useState } from 'react';\nimport Toggle from '../Toggle';\n\nimport emojis from '../../utils/emoji"
},
{
"path": "src/components/MainHeader/styles.ts",
"chars": 507,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div`\n grid-area: MH;\n \n background-col"
},
{
"path": "src/components/MessageBox/index.tsx",
"chars": 604,
"preview": "import React from 'react';\n\nimport { Container } from './styles';\n\ninterface IMessageBoxProps {\n title: string;\n "
},
{
"path": "src/components/MessageBox/styles.ts",
"chars": 1203,
"preview": "import styled, {keyframes} from 'styled-components';\n\nconst animate = keyframes`\n 0% {\n transform: translateX("
},
{
"path": "src/components/PieChartBox/index.tsx",
"chars": 1440,
"preview": "import React from 'react';\nimport {\n PieChart,\n Pie,\n Cell,\n ResponsiveContainer\n} from 'recharts';\n\nimport "
},
{
"path": "src/components/PieChartBox/styles.ts",
"chars": 2424,
"preview": "import styled, { keyframes } from 'styled-components';\n\ninterface ILegendProps {\n color: string;\n}\n\n\nconst animate = "
},
{
"path": "src/components/SelectInput/index.tsx",
"chars": 783,
"preview": "import React from 'react';\n\nimport { Container } from './styles';\n\ninterface ISelectInputProps {\n options: {\n "
},
{
"path": "src/components/SelectInput/styles.ts",
"chars": 184,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div`\n\n > select {\n padding: 7px 10px;"
},
{
"path": "src/components/Toggle/index.tsx",
"chars": 671,
"preview": "import React from 'react';\n\nimport {\n Container,\n ToggleLabel,\n ToggleSelector\n} from './styles';\n\n\ninterface I"
},
{
"path": "src/components/Toggle/styles.ts",
"chars": 492,
"preview": "import styled from 'styled-components';\nimport Switch, { ReactSwitchProps } from 'react-switch';\n\nexport const Container"
},
{
"path": "src/components/WalletBox/index.tsx",
"chars": 1398,
"preview": "import React, { useMemo } from 'react';\nimport CountUp from 'react-countup';\n\nimport dolarImg from '../../assets/dolar.s"
},
{
"path": "src/components/WalletBox/styles.ts",
"chars": 1710,
"preview": "import styled, {keyframes} from 'styled-components';\n\ninterface IContainerProps {\n color: string;\n}\n\nconst animate = "
},
{
"path": "src/hooks/auth.tsx",
"chars": 1181,
"preview": "import React, { createContext, useState, useContext } from 'react';\n\ninterface IAuthContext {\n logged: boolean;\n s"
},
{
"path": "src/hooks/theme.tsx",
"chars": 1499,
"preview": "import React, { createContext, useState, useContext } from 'react';\n\nimport dark from '../styles/themes/dark';\nimport li"
},
{
"path": "src/index.tsx",
"chars": 382,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { ThemeProvider } from './hooks/theme';\nimport { Au"
},
{
"path": "src/pages/Dashboard/index.tsx",
"chars": 12590,
"preview": "import React, { useState, useMemo, useCallback } from 'react';\n\n\nimport ContentHeader from '../../components/ContentHead"
},
{
"path": "src/pages/Dashboard/styles.ts",
"chars": 195,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div``;\n\nexport const Content = styled.div`\n "
},
{
"path": "src/pages/List/index.tsx",
"chars": 6317,
"preview": "import React, { useMemo, useState, useEffect } from 'react';\nimport { uuid } from 'uuidv4';\n \nimport ContentHeader from "
},
{
"path": "src/pages/List/styles.ts",
"chars": 1023,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div``;\n\nexport const Content = styled.main``;\n\n"
},
{
"path": "src/pages/SignIn/index.tsx",
"chars": 1314,
"preview": "import React, {useState} from 'react';\n\nimport logoImg from '../../assets/logo.svg';\n\nimport Input from '../../component"
},
{
"path": "src/pages/SignIn/styles.ts",
"chars": 1012,
"preview": "import styled from 'styled-components';\n\nexport const Container = styled.div`\n height: 100vh;\n \n display: flex;"
},
{
"path": "src/react-app-env.d.ts",
"chars": 40,
"preview": "/// <reference types=\"react-scripts\" />\n"
},
{
"path": "src/repositories/expenses.ts",
"chars": 6118,
"preview": "export default [\n {\"description\":\"Energia elétrica\", \"amount\":\"150.55\",\"type\":\"saída\",\"frequency\":\"recorrente\",\"da"
},
{
"path": "src/repositories/gains.ts",
"chars": 1282,
"preview": "export default [\n { \"description\": \"Salário\", \"amount\": \"1300.52\", \"type\": \"entrada\", \"frequency\": \"recorrente\", \"d"
},
{
"path": "src/routes/app.routes.tsx",
"chars": 450,
"preview": "import React from 'react';\nimport { Switch, Route } from 'react-router-dom';\n\nimport Layout from '../components/Layout';"
},
{
"path": "src/routes/auth.routes.tsx",
"chars": 258,
"preview": "import React from 'react';\nimport { Switch, Route } from 'react-router-dom'; \n\nimport SignIn from '../pages/SignIn';\n\nco"
},
{
"path": "src/routes/index.tsx",
"chars": 396,
"preview": "import React from 'react';\nimport { BrowserRouter } from 'react-router-dom';\n\nimport { useAuth } from '../hooks/auth';\n\n"
},
{
"path": "src/styles/GlobalStyles.ts",
"chars": 387,
"preview": "import { createGlobalStyle } from 'styled-components';\n\nexport default createGlobalStyle`\n * {\n margin: 0;\n "
},
{
"path": "src/styles/styled.d.ts",
"chars": 431,
"preview": "import 'styled-components';\n\ndeclare module 'styled-components' {\n export interface DefaultTheme {\n title: str"
},
{
"path": "src/styles/themes/dark.ts",
"chars": 301,
"preview": "export default {\n title: 'dark',\n\n colors: {\n primary: '#1B1F38',\n secondary: '#252A48',\n ter"
},
{
"path": "src/styles/themes/light.ts",
"chars": 302,
"preview": "export default {\n title: 'light',\n\n colors: {\n primary: '#DCDCDC',\n secondary: '#FFFFFF',\n te"
},
{
"path": "src/utils/emojis.ts",
"chars": 51,
"preview": "export default ['🤑', '🤩', '😍', '😎', '🤑', '😃', '😄'];"
},
{
"path": "src/utils/formatCurrency.ts",
"chars": 224,
"preview": "const formatCurrency = (current: number): string => {\n return current.toLocaleString( \n 'pt-br', \n {\n "
},
{
"path": "src/utils/formatDate.ts",
"chars": 438,
"preview": "const formatDate = (date: string): string => {\n const dateFormatted = new Date(date);\n const year = dateFormatted.ge"
},
{
"path": "src/utils/months.ts",
"chars": 191,
"preview": "export default [\n 'Janeiro',\n 'Fevereiro',\n 'Março',\n 'Abril',\n 'Maio',\n 'Junho',\n 'Julho',\n 'Ag"
},
{
"path": "tsconfig.json",
"chars": 491,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\n \"dom\",\n \"dom.iterable\",\n \"esnext\"\n ],\n "
}
]
About this extraction
This page contains the full source code of the rodrigorgtic/minha-carteira-dashboard GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (70.8 KB), approximately 19.0k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.