diff --git a/.DS_Store b/.DS_Store
index 9f24cd0..7b548ba 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 74b7586..cb034b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,4 @@ yarn-error.log*
.env.production.local
# vercel
-.vercel
+.vercel
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..3662b37
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "typescript.tsdk": "node_modules/typescript/lib"
+}
\ No newline at end of file
diff --git a/components/Box/index.tsx b/components/Box/index.tsx
new file mode 100644
index 0000000..622e382
--- /dev/null
+++ b/components/Box/index.tsx
@@ -0,0 +1,31 @@
+import { Box as ChakraBox, BoxProps as ChakraBoxProps, Text, Flex } from '@chakra-ui/react';
+import { ReactNode } from 'react';
+
+type BoxProps = {
+ title?: string;
+ description?: string;
+ children: ReactNode;
+} & ChakraBoxProps;
+
+export const Box = ({ title, description, children, ...rest }: BoxProps) => {
+ return (
+
+ {(title || description) && (
+
+ {title && (
+
+ {title}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+ )}
+
+ {children}
+
+ );
+};
diff --git a/components/DatePicker/index.tsx b/components/DatePicker/date-picker.component.tsx
similarity index 74%
rename from components/DatePicker/index.tsx
rename to components/DatePicker/date-picker.component.tsx
index aa8e024..a823e99 100644
--- a/components/DatePicker/index.tsx
+++ b/components/DatePicker/date-picker.component.tsx
@@ -1,6 +1,8 @@
import React, { HTMLAttributes } from 'react';
-import { FormControl, FormLabel, FormErrorMessage, useColorMode } from '@chakra-ui/react';
import ReactDatePicker from 'react-datepicker';
+import { FormControl, FormLabel, FormErrorMessage, useColorMode } from '@chakra-ui/react';
+
+import { useIsMobile } from '@Modules/BaseModule/hooks/useIsMobile';
interface Props {
isClearable?: boolean;
@@ -11,6 +13,8 @@ interface Props {
label?: string;
error?: string;
value?: string;
+ maxDate?: Date;
+ minDate?: Date;
}
export const DatePicker = ({
@@ -22,20 +26,27 @@ export const DatePicker = ({
label,
error,
value,
+ maxDate,
+ minDate,
...rest
}: Props & HTMLAttributes) => {
const isLight = useColorMode().colorMode === 'light';
+ const isMobile = useIsMobile();
+
return (
{label && {label}}
-
+
{error &&
{error}}
diff --git a/components/If/if.component.tsx b/components/If/if.component.tsx
new file mode 100644
index 0000000..1e461c8
--- /dev/null
+++ b/components/If/if.component.tsx
@@ -0,0 +1,6 @@
+import { ReactElement, ReactNode } from 'react';
+
+export const If = ({ condition, children }: { condition: boolean; children: ReactElement }) => {
+ if (condition) return children;
+ return null;
+};
diff --git a/components/LateralMenu/index.tsx b/components/LateralMenu/index.tsx
new file mode 100644
index 0000000..1bbc53e
--- /dev/null
+++ b/components/LateralMenu/index.tsx
@@ -0,0 +1,166 @@
+import React, { ReactNode, ReactText } from 'react';
+import {
+ IconButton,
+ Box,
+ CloseButton,
+ Flex,
+ Icon,
+ useColorModeValue,
+ Button,
+ Link,
+ Drawer,
+ DrawerContent,
+ Text,
+ useDisclosure,
+ BoxProps,
+ FlexProps,
+ Badge,
+} from '@chakra-ui/react';
+import { FiHome, FiPower, FiTrendingUp, FiTrendingDown, FiMenu } from 'react-icons/fi';
+import { IconType } from 'react-icons';
+import Image from 'next/image';
+import { BiSolidCategoryAlt } from 'react-icons/bi';
+
+interface LinkItemProps {
+ name: string;
+ icon: IconType;
+ link: string;
+}
+const LinkItems: Array
= [
+ { name: 'Início', icon: FiHome, link: '/' },
+ { name: 'Criar receita', icon: FiTrendingUp, link: '/bill?type=INCOME' },
+ { name: 'Criar despesa', icon: FiTrendingDown, link: '/bill?type=EXPENSE' },
+ { name: 'Categorias', icon: BiSolidCategoryAlt, link: '/category' },
+ {
+ name: 'Sair',
+ icon: FiPower,
+ link: '/logout',
+ },
+];
+
+export const LateralMenu = ({ children }: { children: ReactNode }) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ return (
+
+ onClose} display={{ base: 'none', md: 'block' }} />
+
+
+
+
+
+ {/* mobilenav */}
+
+
+ {children}
+
+
+ );
+};
+
+interface SidebarProps extends BoxProps {
+ onClose: () => void;
+}
+
+const SidebarContent = ({ onClose, ...rest }: SidebarProps) => {
+ return (
+
+
+
+
+
+ Beta
+
+
+
+
+ {LinkItems.map(({ name, icon, link }) => (
+
+ {name}
+
+ ))}
+
+ );
+};
+
+interface NavItemProps extends FlexProps {
+ icon: IconType;
+ children: ReactText;
+ link: string;
+}
+const NavItem = ({ icon, link, children, ...rest }: NavItemProps) => {
+ return (
+
+
+ {icon && (
+
+ )}
+ {children}
+
+
+ );
+};
+
+interface MobileProps extends FlexProps {
+ onOpen: () => void;
+}
+const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
+ return (
+
+ } />
+
+
+
+
+
+ Beta
+
+
+
+
+ );
+};
diff --git a/components/Modal/index.tsx b/components/Modal/index.tsx
new file mode 100644
index 0000000..eb07131
--- /dev/null
+++ b/components/Modal/index.tsx
@@ -0,0 +1,31 @@
+import {
+ Modal as ModalChakra,
+ ModalOverlay,
+ ModalContent,
+ ModalBody,
+ ModalCloseButton,
+ ModalHeader,
+ ModalFooter,
+} from '@chakra-ui/react';
+
+interface ModalInterface {
+ onClose: () => void;
+ isOpen: boolean;
+ title?: string;
+ footer?: React.ReactElement;
+ children: React.ReactElement;
+}
+
+export const Modal = ({ title, footer, onClose, isOpen, children }: ModalInterface) => {
+ return (
+
+
+
+
+ {title && {title}}
+ {children}
+ {footer && {footer}}
+
+
+ );
+};
diff --git a/components/MoneyInput/index.tsx b/components/MoneyInput/index.tsx
index cf40d71..106183b 100644
--- a/components/MoneyInput/index.tsx
+++ b/components/MoneyInput/index.tsx
@@ -2,13 +2,11 @@ import {
FormControl,
FormLabel,
FormErrorMessage,
- NumberInputProps,
NumberInput,
NumberInputField,
- NumberInputStepper,
- NumberIncrementStepper,
- NumberDecrementStepper,
FormControlProps,
+ InputLeftAddon,
+ InputGroup,
} from '@chakra-ui/react';
import { forwardRef, LegacyRef } from 'react';
@@ -19,18 +17,17 @@ type MoneyInputProps = FormControlProps & {
};
export const MoneyInput = forwardRef(
- ({ name, label, error, ...rest }: MoneyInputProps, ref) => {
+ ({ name, label, error, isRequired, ...rest }: MoneyInputProps, ref) => {
return (
{label && {label}}
- }>
-
-
-
-
-
-
+
+ R$
+
+ } borderLeftRadius={0} />
+
+
{error && {error}}
);
diff --git a/components/RadioCard/radio-card.component.tsx b/components/RadioCard/radio-card.component.tsx
new file mode 100644
index 0000000..7c9f6c4
--- /dev/null
+++ b/components/RadioCard/radio-card.component.tsx
@@ -0,0 +1,34 @@
+import { Box, useRadio } from '@chakra-ui/react';
+
+export const RadioCard = props => {
+ const { getInputProps, getCheckboxProps } = useRadio(props);
+
+ const input = getInputProps();
+ const checkbox = getCheckboxProps();
+
+ return (
+
+
+
+ {props.children}
+
+
+ );
+};
diff --git a/components/index.ts b/components/index.ts
index 0653a6d..16b44ae 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -1,4 +1,9 @@
export * from './MoneyInput';
export * from './Input';
export * from './Select';
-export * from './DatePicker';
+export * from './DatePicker/date-picker.component';
+export * from './Modal';
+export * from './LateralMenu';
+export * from './Box';
+export * from './RadioCard/radio-card.component';
+export * from './If/if.component';
diff --git a/configs/Firebase.ts b/configs/Firebase.ts
index 9829e38..8c5d3a9 100644
--- a/configs/Firebase.ts
+++ b/configs/Firebase.ts
@@ -16,6 +16,9 @@ if (!firebase.apps.length) {
} else {
firebase.app();
}
+
export default firebase;
export const firestore = firebase.firestore();
export const auth = firebase.auth();
+
+export const googleProvider = new firebase.auth.GoogleAuthProvider();
diff --git a/configs/theme.tsx b/configs/theme.tsx
index 4a365e4..3a4bc4a 100644
--- a/configs/theme.tsx
+++ b/configs/theme.tsx
@@ -5,6 +5,8 @@ const config: ThemeConfig = {
useSystemColorMode: false,
};
-const theme = extendTheme({ config });
+const theme = extendTheme({
+ config,
+});
export default theme;
diff --git a/hoc/withLateralMenu.tsx b/hoc/withLateralMenu.tsx
new file mode 100644
index 0000000..7bdc016
--- /dev/null
+++ b/hoc/withLateralMenu.tsx
@@ -0,0 +1,11 @@
+import { LateralMenu } from '@Components/LateralMenu';
+
+const withLateralMenu = Children => {
+ return props => (
+
+
+
+ );
+};
+
+export default withLateralMenu;
diff --git a/modules/Authentication/container/FormForgotPassword/index.tsx b/modules/Authentication/container/FormForgotPassword/index.tsx
deleted file mode 100644
index 28dcdeb..0000000
--- a/modules/Authentication/container/FormForgotPassword/index.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { useForm } from 'react-hook-form';
-import { Button } from '@chakra-ui/button';
-import { Flex, Box, useToast } from '@chakra-ui/react';
-import { useRouter } from 'next/router';
-
-import AuthenticationService from '@Authentication/services/AuthenticationService';
-import { Input } from '@Components';
-
-const FormForgotPassword = () => {
- const toast = useToast();
-
- const {
- handleSubmit,
- register,
- formState: { errors, isSubmitting },
- } = useForm();
- const Router = useRouter();
-
- const onSubmit = async values => {
- try {
- const haveSentMail = await AuthenticationService.forgotPassword(values?.email);
- if (haveSentMail) {
- toast({
- description: 'An e-mail with password recovery link has been sent!',
- status: 'success',
- });
-
- Router.replace('/');
- }
- } catch (err) {
- toast({
- description: err.message,
- status: 'error',
- });
- }
- };
-
- return (
-
-
-
- );
-};
-
-export default FormForgotPassword;
diff --git a/modules/Authentication/container/FormLogin/form-login.container.tsx b/modules/Authentication/container/FormLogin/form-login.container.tsx
new file mode 100644
index 0000000..daee7e1
--- /dev/null
+++ b/modules/Authentication/container/FormLogin/form-login.container.tsx
@@ -0,0 +1,53 @@
+import { Button } from '@chakra-ui/button';
+import { Flex, Box, useToast, Text, Spacer, Image, AbsoluteCenter } from '@chakra-ui/react';
+import { useRouter } from 'next/router';
+import { useEffect } from 'react';
+import { FcGoogle } from 'react-icons/fc';
+
+import { signInWithGoogle } from '@Authentication/services/authentication.service';
+import { useUser } from '@Modules/Authentication/context/UserContext';
+
+export const FormLoginContainer = () => {
+ const toast = useToast();
+ const router = useRouter();
+ const { userId, setUserId } = useUser();
+
+ useEffect(() => {
+ if (userId) {
+ router.push('/');
+ }
+ }, [router, userId]);
+
+ const onLoginWithGoogle = async () => {
+ try {
+ const uid = await signInWithGoogle();
+
+ if (uid) {
+ setUserId(uid);
+ router.push('/');
+ }
+ } catch (err) {
+ toast({
+ description: err.message,
+ status: 'error',
+ });
+ }
+ };
+
+ return (
+
+
+
+
+ Desbrave o caminho para a estabilidade financeira e saia do vermelho.
+
+
+
+
+ );
+};
diff --git a/modules/Authentication/container/FormLogin/index.tsx b/modules/Authentication/container/FormLogin/index.tsx
deleted file mode 100644
index ce5ed91..0000000
--- a/modules/Authentication/container/FormLogin/index.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { useForm } from 'react-hook-form';
-import { Button } from '@chakra-ui/button';
-import { Flex, Box, useToast } from '@chakra-ui/react';
-import { useRouter } from 'next/router';
-import Link from 'next/link';
-import { useEffect } from 'react';
-
-import AuthenticationService from '@Authentication/services/AuthenticationService';
-import User from '@Authentication/interfaces/User.interface';
-import { useUser } from '@Modules/Authentication/context/UserContext';
-import { Input } from '@Components';
-
-const FormLogin = () => {
- const toast = useToast();
- const {
- handleSubmit,
- register,
- formState: { errors, isSubmitting },
- } = useForm();
- const Router = useRouter();
- const { userId, setUserId } = useUser();
-
- useEffect(() => {
- if (userId) {
- Router.replace('/');
- }
- }, [userId]);
-
- const onSubmit = async values => {
- try {
- const { uid } = (await AuthenticationService.signIn(values?.email, values?.password)) as User;
- if (uid) {
- setUserId(uid);
- Router.replace('/');
- }
- } catch (err) {
- toast({
- description: err.message,
- status: 'error',
- });
- }
- };
-
- return (
-
-
-
- );
-};
-
-export default FormLogin;
diff --git a/modules/Authentication/container/FormSignUp/index.tsx b/modules/Authentication/container/FormSignUp/index.tsx
deleted file mode 100644
index 8e47736..0000000
--- a/modules/Authentication/container/FormSignUp/index.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useForm } from 'react-hook-form';
-import { Button } from '@chakra-ui/button';
-import { useToast, Flex, Box } from '@chakra-ui/react';
-import { useRouter } from 'next/router';
-
-import { Input } from '@Components';
-import { auth } from '@Configs/Firebase';
-import { useUser } from '@Modules/Authentication/context/UserContext';
-
-const FormSignUp = () => {
- const {
- handleSubmit,
- register,
- formState: { errors, isSubmitting },
- } = useForm();
- const Router = useRouter();
- const toast = useToast();
- const { setUserId } = useUser();
-
- const onSubmit = async values => {
- try {
- const { user } = await auth.createUserWithEmailAndPassword(values.email, values.password);
-
- localStorage.setItem('USER_UID', user.uid);
- setUserId(user.uid);
- Router.replace('/');
-
- toast({
- description: 'User successful created!',
- status: 'success',
- });
- } catch (error) {
- console.error(error);
- }
- };
-
- return (
-
-
-
- );
-};
-
-export default FormSignUp;
diff --git a/modules/Authentication/context/UserContext.tsx b/modules/Authentication/context/UserContext.tsx
index 77f2142..df6a3a6 100644
--- a/modules/Authentication/context/UserContext.tsx
+++ b/modules/Authentication/context/UserContext.tsx
@@ -15,11 +15,6 @@ interface UserProviderProps {
children: React.ReactNode;
}
-const formatAuthUser = user => ({
- uid: user.uid,
- email: user.email,
-});
-
export const UserProvider = ({ children }: UserProviderProps) => {
const [userId, setUserId] = useState(null);
const [user, setUser] = useState(null);
@@ -31,9 +26,7 @@ export const UserProvider = ({ children }: UserProviderProps) => {
return;
}
- // var formattedUser = formatAuthUser(authState);
setUserId(authState.uid);
- // setUser(formatAuthUser);
};
useEffect(() => {
diff --git a/modules/Authentication/services/AuthenticationService.ts b/modules/Authentication/services/AuthenticationService.ts
deleted file mode 100644
index 9de8d30..0000000
--- a/modules/Authentication/services/AuthenticationService.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { auth } from '@Configs/Firebase';
-
-import User from '../interfaces/User.interface';
-
-export default class AuthenticationService {
- constructor() {}
-
- static signIn(email: string, password: string): Promise {
- return auth.signInWithEmailAndPassword(email, password).then(
- ({ user }) =>
- ({
- uid: user.uid,
- } as User)
- );
- }
-
- static signUp(email: string, password: string): Promise {
- return auth.createUserWithEmailAndPassword(email, password).then(
- ({ user }) =>
- ({
- uid: user.uid,
- } as User)
- );
- }
-
- static forgotPassword(email: string): Promise {
- return auth.sendPasswordResetEmail(email).then(_ => true);
- }
-}
diff --git a/modules/Authentication/services/authentication.service.ts b/modules/Authentication/services/authentication.service.ts
new file mode 100644
index 0000000..5f96907
--- /dev/null
+++ b/modules/Authentication/services/authentication.service.ts
@@ -0,0 +1,33 @@
+import { auth, firestore, googleProvider } from '@Configs/Firebase';
+import { createDefaultCategories } from '@Modules/Category/helpers/createDefaultCategories';
+
+interface IGoogleProviderProfile {
+ email?: string;
+ family_name?: string;
+ given_name?: string;
+ granted_scopes?: string;
+ id?: string;
+ locale?: string;
+ name?: string;
+ picture?: string;
+}
+
+export const signInWithGoogle = () => {
+ return auth.signInWithPopup(googleProvider).then(async ({ additionalUserInfo, user }) => {
+ if (additionalUserInfo.isNewUser) {
+ const profile = additionalUserInfo.profile as IGoogleProviderProfile;
+
+ await firestore
+ .collection('user')
+ .doc(user.uid)
+ .set({
+ email: profile?.email,
+ name: `${profile?.given_name} ${profile?.family_name}`,
+ locale: profile?.locale,
+ image: profile?.picture,
+ })
+ .then(() => createDefaultCategories(user.uid));
+ }
+ return user.uid;
+ });
+};
diff --git a/modules/BaseModule/components/Bubble/index.tsx b/modules/BaseModule/components/Bubble/index.tsx
new file mode 100644
index 0000000..d9e57b1
--- /dev/null
+++ b/modules/BaseModule/components/Bubble/index.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
+
+import { Input, Select } from '@Components';
+import SelectOption from '@Modules/BaseModule/interfaces/SelectOption';
+
+import { BubbleEnum, BUBBLE_TYPES } from '../../constants/Bubble';
+
+interface BubbleInterface {
+ type: BubbleEnum;
+ name: string;
+ label: string;
+ register: UseFormRegister;
+ errors: FieldErrors;
+ required?: boolean;
+ props?: object;
+ options?: SelectOption[];
+ value?: (string | number | readonly string[]) & string;
+}
+
+const Bubble = ({
+ type,
+ name,
+ label,
+ register,
+ errors,
+ required = false,
+ options = [],
+ value,
+ props = {},
+}: BubbleInterface) => {
+ switch (type) {
+ case BUBBLE_TYPES.INPUT_TEXT:
+ return (
+
+ );
+
+ case BUBBLE_TYPES.INPUT_COLOR:
+ return (
+
+ );
+
+ case BUBBLE_TYPES.SELECT:
+ return (
+
+ );
+
+ default:
+ return <>>;
+ }
+};
+
+export default Bubble;
diff --git a/modules/BaseModule/constants/Bubble.ts b/modules/BaseModule/constants/Bubble.ts
new file mode 100644
index 0000000..05dfa3e
--- /dev/null
+++ b/modules/BaseModule/constants/Bubble.ts
@@ -0,0 +1,7 @@
+export const BUBBLE_TYPES = {
+ INPUT_TEXT: 'INPUT_TEXT',
+ INPUT_COLOR: 'INPUT_COLOR',
+ SELECT: 'SELECT',
+};
+
+export type BubbleEnum = keyof typeof BUBBLE_TYPES;
diff --git a/modules/BaseModule/container/BaseForm/index.tsx b/modules/BaseModule/container/BaseForm/index.tsx
new file mode 100644
index 0000000..3111a4e
--- /dev/null
+++ b/modules/BaseModule/container/BaseForm/index.tsx
@@ -0,0 +1,82 @@
+import React, { useCallback, useEffect } from 'react';
+import { useForm } from 'react-hook-form';
+import { Button } from '@chakra-ui/button';
+import { Flex } from '@chakra-ui/react';
+import { yupResolver } from '@hookform/resolvers/yup';
+import * as Yup from 'yup';
+
+import BaseField from '@Modules/BaseModule/interfaces/BaseField';
+import renderFields from '@Modules/BaseModule/shared/renderFields';
+
+interface BaseFormInterface {
+ id?: string | number;
+ fetch?: (id: string | number) => Promise;
+ fields: BaseField[];
+ onSubmit: (element: T) => T | Promise | Promise;
+ submitLabel?: string;
+ schema: Yup.AnyObjectSchema;
+}
+
+const BaseForm = ({
+ id,
+ fetch,
+ onSubmit,
+ fields,
+ submitLabel = 'Submit',
+ schema,
+}: BaseFormInterface) => {
+ const {
+ handleSubmit,
+ register,
+ setValue,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: yupResolver(schema),
+ });
+
+ const wrapperOnSubmit = values => {
+ const element = values as T;
+
+ onSubmit(element);
+ };
+
+ const fetchData = useCallback(
+ async (id): Promise => {
+ if (id && !!fetch) {
+ const model = await fetch(id);
+
+ if (model) {
+ fields.forEach(({ name }) => {
+ if (model[name]) {
+ setValue(name, model[name]);
+ }
+ });
+ }
+
+ return model;
+ }
+ },
+ [fetch, fields, setValue]
+ );
+ //
+ useEffect(() => {
+ if (id) {
+ fetchData(id);
+ }
+ }, [fetchData, id]);
+
+ return (
+
+ );
+};
+
+export { BaseForm };
+export default BaseForm;
diff --git a/modules/BaseModule/container/BaseList/index.tsx b/modules/BaseModule/container/BaseList/index.tsx
new file mode 100644
index 0000000..adb5270
--- /dev/null
+++ b/modules/BaseModule/container/BaseList/index.tsx
@@ -0,0 +1,196 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import {
+ Box,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ Spinner,
+ Button,
+ Text,
+ Popover,
+ PopoverTrigger,
+ PopoverContent,
+ PopoverBody,
+ PopoverArrow,
+ IconButton,
+ Stack,
+} from '@chakra-ui/react';
+import { BsThreeDotsVertical } from 'react-icons/bs';
+import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
+
+import { Modal } from '@Components';
+
+const MODAL_TYPES = {
+ EDIT_MODAL: 'EDIT_MODAL',
+ DELETE_MODAL: 'DELETE_MODAL',
+};
+
+interface BaseHeader {
+ label: string;
+ field: string;
+ render?: (item) => React.ReactElement;
+}
+
+interface BaseListInterface {
+ headers: BaseHeader[];
+ items: T[];
+ isLoading: boolean;
+ optionsEnabled?: boolean;
+ onDeleteItem?: (id: string | number) => void;
+ onCloseOption?: () => void;
+ editComponent?: (id: string | number) => React.ReactElement;
+}
+
+const BaseList = ({
+ headers,
+ items,
+ isLoading,
+ optionsEnabled = false,
+ editComponent,
+ onCloseOption,
+ onDeleteItem,
+}: BaseListInterface) => {
+ const [modalType, setModalType] = useState(null);
+ const [id, setId] = useState();
+
+ useEffect(() => {
+ if (!modalType && !!onCloseOption) {
+ onCloseOption();
+ }
+ }, [modalType]);
+
+ const handleOpenModalWithId = (id, type) => {
+ setId(id);
+ setModalType(type);
+ };
+
+ const tableItems = useMemo(() => {
+ const fields = headers.map(({ field }) => field);
+
+ return items.map((item: T, index) => (
+
+ {headers.map(({ field, render }) => {
+ if (!item[field]) return;
+ if (!!render) return | {render(item)} | ;
+ return {item[field]} | ;
+ })}
+ {optionsEnabled && (
+
+
+
+ }
+ variant="outline"
+ size="sm"
+ w="fit-content"
+ />
+
+
+
+
+
+ }
+ justifyContent="flex-start"
+ fontWeight="normal"
+ fontSize="sm"
+ onClick={() => handleOpenModalWithId(item.id, MODAL_TYPES.EDIT_MODAL)}
+ >
+ Editar categoria
+
+
+ }
+ justifyContent="flex-start"
+ fontWeight="normal"
+ colorScheme="red"
+ fontSize="sm"
+ onClick={() => handleOpenModalWithId(item.id, MODAL_TYPES.DELETE_MODAL)}
+ >
+ Deletar categoria
+
+
+
+
+
+ |
+ )}
+
+ ));
+ }, [headers, items, optionsEnabled]);
+
+ const modalContent = useMemo(() => {
+ switch (modalType) {
+ case MODAL_TYPES.DELETE_MODAL:
+ return (
+ <>
+
+ Deletar categoria
+
+ Você tem certeza que deseja apagar esta categoria?
+ >
+ );
+
+ case MODAL_TYPES.EDIT_MODAL:
+ return <>{editComponent(id)}>;
+
+ default:
+ return <>>;
+ }
+ }, [editComponent, id, modalType]);
+
+ const modalFooter = useMemo(() => {
+ switch (modalType) {
+ case MODAL_TYPES.DELETE_MODAL:
+ return (
+ <>
+
+
+ >
+ );
+ }
+ }, [editComponent, id, modalType]);
+
+ return (
+ <>
+ setModalType(null)}
+ footer={modalFooter}
+ >
+ {modalContent}
+
+
+
+
+
+ {headers.map(({ label }) => (
+ | {label} |
+ ))}
+ {optionsEnabled && | }
+
+
+
+ <>
+ {isLoading && }
+ {!isLoading && tableItems}
+ >
+
+
+
+ >
+ );
+};
+
+export { BaseList };
+export default BaseList;
diff --git a/modules/BaseModule/hooks/useIsMobile.tsx b/modules/BaseModule/hooks/useIsMobile.tsx
new file mode 100644
index 0000000..70a9c3f
--- /dev/null
+++ b/modules/BaseModule/hooks/useIsMobile.tsx
@@ -0,0 +1,14 @@
+import { useState, useEffect } from 'react';
+
+export const useIsMobile = () => {
+ const [width, setWidth] = useState(0);
+
+ useEffect(() => {
+ const handleWindowSizeChange = () => setWidth(window.innerWidth);
+ handleWindowSizeChange();
+ window.addEventListener('resize', handleWindowSizeChange);
+ return () => window.removeEventListener('resize', handleWindowSizeChange);
+ }, []);
+
+ return width <= 576;
+};
diff --git a/modules/BaseModule/interfaces/BaseField.ts b/modules/BaseModule/interfaces/BaseField.ts
new file mode 100644
index 0000000..a2cb787
--- /dev/null
+++ b/modules/BaseModule/interfaces/BaseField.ts
@@ -0,0 +1,11 @@
+import { BubbleEnum } from '@Modules/BaseModule/constants/Bubble';
+
+import SelectOption from './SelectOption';
+
+export default interface BaseField {
+ name: string;
+ type: BubbleEnum;
+ label: string;
+ options?: SelectOption[];
+ value?: (string | number | readonly string[]) & string;
+}
diff --git a/modules/BaseModule/interfaces/SelectOption.ts b/modules/BaseModule/interfaces/SelectOption.ts
new file mode 100644
index 0000000..3a585d5
--- /dev/null
+++ b/modules/BaseModule/interfaces/SelectOption.ts
@@ -0,0 +1,4 @@
+export default interface SelectOption {
+ label: string;
+ value: string | number;
+}
diff --git a/modules/BaseModule/shared/renderFields.tsx b/modules/BaseModule/shared/renderFields.tsx
new file mode 100644
index 0000000..e9b168e
--- /dev/null
+++ b/modules/BaseModule/shared/renderFields.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import Bubble from '../components/Bubble';
+import BaseField from '../interfaces/BaseField';
+
+const renderFields = (fields: BaseField[], errors, register) =>
+ fields.map(({ name, type, label, options, value }) => (
+
+ ));
+
+export default renderFields;
diff --git a/modules/Bill/components/BillFilters/index.tsx b/modules/Bill/components/BillFilters/index.tsx
index 461409c..a43ce2c 100644
--- a/modules/Bill/components/BillFilters/index.tsx
+++ b/modules/Bill/components/BillFilters/index.tsx
@@ -3,6 +3,7 @@ import { useForm } from 'react-hook-form';
import { Flex, Spacer, Box } from '@chakra-ui/react';
import { Select } from '@Components/Select';
+import { MONTHS_DICT } from '@Modules/Bill/constants/BillConsts';
type BillFiltersType = {
year?: number;
@@ -16,21 +17,6 @@ interface BillFiltersProps {
to: number;
}
-const MONTHS_DICT = {
- 0: 'Janeiro',
- 1: 'Fevereiro',
- 2: 'Março',
- 3: 'Abril',
- 4: 'Maio',
- 5: 'Junho',
- 6: 'Julho',
- 7: 'Agosto',
- 8: 'Setembro',
- 9: 'Outubro',
- 10: 'Novembro',
- 11: 'Dezembro',
-};
-
const BillFilters = ({ filters, onChange, from, to }: BillFiltersProps) => {
const {
handleSubmit,
@@ -77,7 +63,7 @@ const BillFilters = ({ filters, onChange, from, to }: BillFiltersProps) => {
}
return finalMonths;
- }, [filters.year]);
+ }, []);
return (
@@ -85,7 +71,7 @@ const BillFilters = ({ filters, onChange, from, to }: BillFiltersProps) => {