diff --git a/.gitignore b/.gitignore
index cb034b6..992029d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,8 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
+.env.prod
# vercel
-.vercel
\ No newline at end of file
+.vercel
+.vscode
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 3662b37..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "typescript.tsdk": "node_modules/typescript/lib"
-}
\ No newline at end of file
diff --git a/components/LateralMenu/index.tsx b/components/LateralMenu/index.tsx
index 1bbc53e..047666a 100644
--- a/components/LateralMenu/index.tsx
+++ b/components/LateralMenu/index.tsx
@@ -20,6 +20,7 @@ import { FiHome, FiPower, FiTrendingUp, FiTrendingDown, FiMenu } from 'react-ico
import { IconType } from 'react-icons';
import Image from 'next/image';
import { BiSolidCategoryAlt } from 'react-icons/bi';
+import { TbBrandGoogleAnalytics, TbPigMoney } from 'react-icons/tb';
interface LinkItemProps {
name: string;
@@ -28,8 +29,10 @@ interface LinkItemProps {
}
const LinkItems: Array = [
{ name: 'Início', icon: FiHome, link: '/' },
+ { name: 'Análises', icon: TbBrandGoogleAnalytics, link: '/analytics' },
{ name: 'Criar receita', icon: FiTrendingUp, link: '/bill?type=INCOME' },
{ name: 'Criar despesa', icon: FiTrendingDown, link: '/bill?type=EXPENSE' },
+ { name: 'Orçamentos', icon: TbPigMoney, link: '/budget' },
{ name: 'Categorias', icon: BiSolidCategoryAlt, link: '/category' },
{
name: 'Sair',
diff --git a/configs/collections.config.ts b/configs/collections.config.ts
new file mode 100644
index 0000000..821b747
--- /dev/null
+++ b/configs/collections.config.ts
@@ -0,0 +1,4 @@
+import { firestore } from '@Configs/Firebase';
+
+export const billsCollection = firestore.collection('bills');
+export const budgetsCollection = firestore.collection('budgets');
diff --git a/hooks/useBills.tsx b/hooks/useBills.tsx
new file mode 100644
index 0000000..84b9321
--- /dev/null
+++ b/hooks/useBills.tsx
@@ -0,0 +1,48 @@
+import { useState, useEffect, useCallback } from 'react';
+
+import { firestore } from '@Configs/Firebase';
+import { useUser } from '@Authentication/context/UserContext';
+import { BillTypes } from '@Modules/Bill/constants/Types';
+import { billsCollection } from '@Configs/collections.config';
+
+export interface IBill {
+ id?: string;
+ name: string;
+ description?: string;
+ value: number;
+ type: 'INCOME' | 'EXPENSE';
+ status: string;
+ dueDate: number;
+ createdAt: number;
+ userId: string;
+ category?: string;
+}
+interface UseBillsOptions {
+ type?: string;
+}
+export const useBills = (options?: UseBillsOptions) => {
+ const [bills, setBills] = useState([]);
+ const { userId } = useUser();
+ const fetchBills = useCallback(async () => {
+ if (!userId) return;
+
+ const data = await billsCollection.where('userId', '==', userId).get();
+ const docs = data.docs.map(
+ category =>
+ ({
+ id: category.id,
+ ...category.data(),
+ } as IBill)
+ );
+
+ const processedDocs = docs.filter(item => (options?.type ? item.type === options.type : true));
+
+ setBills(processedDocs);
+ }, [setBills, userId]);
+
+ useEffect(() => {
+ fetchBills();
+ }, [fetchBills]);
+
+ return bills;
+};
diff --git a/hooks/useCategories.tsx b/hooks/useCategories.tsx
new file mode 100644
index 0000000..8be7488
--- /dev/null
+++ b/hooks/useCategories.tsx
@@ -0,0 +1,41 @@
+import { useState, useEffect, useCallback } from 'react';
+
+import { firestore } from '@Configs/Firebase';
+import { useUser } from '@Authentication/context/UserContext';
+import { BillTypes } from '@Modules/Bill/constants/Types';
+
+export interface ICategory {
+ id?: string;
+ name: string;
+ color: string;
+ type: string;
+}
+interface UseCategoriesOptions {
+ type?: string;
+}
+export const useCategories = (options?: UseCategoriesOptions) => {
+ const [categories, setCategories] = useState([]);
+ const { userId } = useUser();
+ const fetchUserCategories = useCallback(async () => {
+ if (!userId) return;
+
+ const data = await firestore.collection('categories').where('userId', '==', userId).get();
+ const docs = data.docs.map(
+ category =>
+ ({
+ id: category.id,
+ ...category.data(),
+ } as ICategory)
+ );
+
+ const processedDocs = docs.filter(item => (options?.type ? item.type === options.type : true));
+
+ setCategories(processedDocs);
+ }, [setCategories, userId]);
+
+ useEffect(() => {
+ fetchUserCategories();
+ }, [fetchUserCategories]);
+
+ return categories;
+};
diff --git a/modules/Analytics/components/categories-current-month.component.tsx b/modules/Analytics/components/categories-current-month.component.tsx
new file mode 100644
index 0000000..e69e484
--- /dev/null
+++ b/modules/Analytics/components/categories-current-month.component.tsx
@@ -0,0 +1,83 @@
+import dynamic from 'next/dynamic';
+
+// @ts-ignore
+const ResponsivePieCanvas = dynamic(() => import('@nivo/pie').then(m => m.ResponsivePieCanvas), { ssr: false });
+
+import { Text, Box as ChackraBox, Tag } from '@chakra-ui/react';
+import { useMemo } from 'react';
+
+import { Box } from '@Components';
+import { ICategory } from '@/hooks/useCategories';
+import { IBill } from '@/hooks/useBills';
+import { BillTypes } from '@Modules/Bill/constants/Types';
+
+interface CategoriesCurrentMonthProps {
+ bills: IBill[];
+ categories: ICategory[];
+}
+
+const keyToText = {
+ rest: 'Retante',
+ income: 'Receita',
+ expense: 'Despesa',
+};
+
+export const CategoriesCurrentMonth = ({ bills, categories }: CategoriesCurrentMonthProps) => {
+ const graphData = useMemo(() => {
+ return bills?.map(bill => {
+ return {
+ value: bill.value,
+ id: categories?.find(({ id }) => id === bill.category)?.name || 'Outro',
+ };
+ });
+ }, [bills, categories]);
+
+ const data = [
+ {
+ id: 'Cartão de crédito',
+ value: 440,
+ },
+ {
+ id: 'Investimento',
+ value: 251,
+ },
+ {
+ id: 'Outros',
+ value: 464,
+ },
+ ];
+ return (
+
+
+ Gastos por categoria
+
+
+ {/* @ts-ignore */}
+ <>>}
+ />
+
+
+ );
+};
diff --git a/modules/Analytics/components/month-by-month.component.tsx b/modules/Analytics/components/month-by-month.component.tsx
new file mode 100644
index 0000000..9241a34
--- /dev/null
+++ b/modules/Analytics/components/month-by-month.component.tsx
@@ -0,0 +1,101 @@
+import dynamic from 'next/dynamic';
+
+// @ts-ignore
+const ResponsiveBarCanvas = dynamic(() => import('@nivo/bar').then(m => m.ResponsiveBarCanvas), { ssr: false });
+
+import { Text, Box as ChackraBox, Tag } from '@chakra-ui/react';
+import { useMemo } from 'react';
+
+import { Box } from '@Components';
+import { IBill } from '@/hooks/useBills';
+import { BillTypes } from '@Modules/Bill/constants/Types';
+
+interface MonthByMonthProps {
+ bills: IBill[];
+}
+
+const keyToText = {
+ rest: 'Retante',
+ income: 'Receita',
+ expense: 'Despesa',
+};
+
+export const MonthByMonth = ({ bills }: MonthByMonthProps) => {
+ const data = useMemo(() => {
+ const billsReduce = bills.reduce((prev, curr) => {
+ const dueDate = new Date(curr.dueDate);
+ const month = `${dueDate.getMonth() + 1}/${dueDate.getFullYear()}`;
+
+ if (!prev[month]) {
+ prev[month] = {};
+
+ if (curr.type === BillTypes.EXPENSE) {
+ prev[month] = {
+ expense: curr.value,
+ income: 0,
+ month,
+ };
+ } else {
+ prev[month] = {
+ expense: 0,
+ income: curr.value,
+ month,
+ };
+ }
+
+ prev[month].rest = prev[month].income - prev[month].expense;
+ }
+
+ if (prev?.[month]) {
+ if (curr.type === BillTypes.EXPENSE) {
+ prev[month].expense += curr.value;
+ } else {
+ prev[month].income += curr.value;
+ }
+
+ prev[month].rest = prev[month].income - prev[month].expense;
+ }
+
+ return prev;
+ }, {});
+
+ return Object.values(billsReduce);
+ }, [bills]);
+
+ return (
+
+
+ Últimos meses
+
+
+ {/* @ts-ignore */}
+ {
+ return (
+
+ {keyToText[item.id]} {item.value}
+
+ );
+ }}
+ />
+
+
+ );
+};
diff --git a/modules/Analytics/container/list-analytics.container.tsx b/modules/Analytics/container/list-analytics.container.tsx
new file mode 100644
index 0000000..ea273e0
--- /dev/null
+++ b/modules/Analytics/container/list-analytics.container.tsx
@@ -0,0 +1,18 @@
+import { MonthByMonth } from '@Modules/Analytics/components/month-by-month.component';
+import { CategoriesCurrentMonth } from '@Modules/Analytics/components/categories-current-month.component';
+import { useCategories } from '@/hooks/useCategories';
+import { useBills } from '@/hooks/useBills';
+import { BillTypes } from '@Modules/Bill/constants/Types';
+
+export const ListAnalyticsContainer = () => {
+ const categories = useCategories();
+ const bills = useBills();
+ const billsExpesne = bills.filter(bill => bill.type === BillTypes.EXPENSE);
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/modules/BaseModule/components/Bubble/index.tsx b/modules/BaseModule/components/Bubble/index.tsx
index d9e57b1..8115f61 100644
--- a/modules/BaseModule/components/Bubble/index.tsx
+++ b/modules/BaseModule/components/Bubble/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
-import { Input, Select } from '@Components';
+import { Input, Select, MoneyInput } from '@Components';
import SelectOption from '@Modules/BaseModule/interfaces/SelectOption';
import { BubbleEnum, BUBBLE_TYPES } from '../../constants/Bubble';
@@ -55,6 +55,17 @@ const Bubble = ({
/>
);
+ case BUBBLE_TYPES.MONEY:
+ return (
+
+ );
+
case BUBBLE_TYPES.SELECT:
return (