{
36 | const query = await this.colecao().get()
37 | return query.docs.map(doc => doc.data()) ?? []
38 | }
39 |
40 | private colecao() {
41 | return firebase
42 | .firestore().collection('clientes')
43 | .withConverter(this.#conversor)
44 | }
45 | }
--------------------------------------------------------------------------------
/src/components/Botao.tsx:
--------------------------------------------------------------------------------
1 | interface BotaoProps {
2 | cor?: 'green' | 'blue' | 'gray'
3 | className?: string
4 | children: any
5 | onClick?: () => void
6 | }
7 |
8 | export default function Botao(props: BotaoProps) {
9 | const cor = props.cor ?? 'gray'
10 | return (
11 |
18 | )
19 | }
--------------------------------------------------------------------------------
/src/components/Entrada.tsx:
--------------------------------------------------------------------------------
1 | interface EntradaProps {
2 | tipo?: 'text' | 'number'
3 | texto: string
4 | valor: any
5 | somenteLeitura?: boolean
6 | className?: string
7 | valorMudou?: (valor: any) => void
8 | }
9 |
10 | export default function Entrada(props: EntradaProps) {
11 | return (
12 |
13 |
16 | props.valorMudou?.(e.target.value)}
21 | className={`
22 | border border-purple-500 rounded-lg
23 | focus:outline-none bg-gray-100 px-4 py-2
24 | ${props.somenteLeitura ? '' : 'focus:bg-white'}
25 | `}
26 | />
27 |
28 | )
29 | }
--------------------------------------------------------------------------------
/src/components/Formulario.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import Cliente from "../core/Cliente";
3 | import Botao from "./Botao";
4 | import Entrada from "./Entrada";
5 |
6 | interface FormularioProps {
7 | cliente: Cliente
8 | clienteMudou?: (cliente: Cliente) => void
9 | cancelado?: () => void
10 | }
11 |
12 | export default function Formulario(props: FormularioProps) {
13 | const id = props.cliente?.id
14 | const [nome, setNome] = useState(props.cliente?.nome ?? '')
15 | const [idade, setIdade] = useState(props.cliente?.idade ?? 0)
16 |
17 | return (
18 |
19 | {id ? (
20 |
26 | ) : false}
27 |
33 |
39 |
40 | props.clienteMudou?.(new Cliente(nome, +idade, id))}>
42 | {id ? 'Alterar' : 'Salvar'}
43 |
44 |
45 | Cancelar
46 |
47 |
48 |
49 | )
50 | }
--------------------------------------------------------------------------------
/src/components/Icones.tsx:
--------------------------------------------------------------------------------
1 | export const IconeEdicao = (
2 |
5 | )
6 |
7 | export const IconeLixo = (
8 |
11 | )
--------------------------------------------------------------------------------
/src/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import Titulo from "./Titulo";
2 |
3 | interface LayoutProps {
4 | titulo: string
5 | children: any
6 | }
7 |
8 | export default function Layout(props: LayoutProps) {
9 | return (
10 |
14 |
{props.titulo}
15 |
16 | {props.children}
17 |
18 |
19 | )
20 | }
--------------------------------------------------------------------------------
/src/components/Tabela.tsx:
--------------------------------------------------------------------------------
1 | import Cliente from "../core/Cliente";
2 | import { IconeEdicao, IconeLixo } from "./Icones";
3 |
4 | interface TabelaProps {
5 | clientes: Cliente[]
6 | clienteSelecionado?: (cliente: Cliente) => void
7 | clienteExcluido?: (cliente: Cliente) => void
8 | }
9 |
10 | export default function Tabela(props: TabelaProps) {
11 |
12 | const exibirAcoes = props.clienteExcluido || props.clienteSelecionado
13 |
14 | function renderizarCabecalho() {
15 | return (
16 |
17 | Código |
18 | Nome |
19 | Idade |
20 | {exibirAcoes ? Ações | : false}
21 |
22 | )
23 | }
24 |
25 | function renderizarDados() {
26 | return props.clientes?.map((cliente, i) => {
27 | return (
28 |
30 | {cliente.id} |
31 | {cliente.nome} |
32 | {cliente.idade} |
33 | {exibirAcoes ? renderizarAcoes(cliente) : false}
34 |
35 | )
36 | })
37 | }
38 |
39 | function renderizarAcoes(cliente: Cliente) {
40 | return (
41 |
42 | {props.clienteSelecionado ? (
43 |
50 | ) : false}
51 | {props.clienteExcluido ? (
52 |
59 | ) : false}
60 | |
61 | )
62 | }
63 |
64 | return (
65 |
66 |
70 | {renderizarCabecalho()}
71 |
72 |
73 | {renderizarDados()}
74 |
75 |
76 | )
77 | }
--------------------------------------------------------------------------------
/src/components/Titulo.tsx:
--------------------------------------------------------------------------------
1 | export default function Titulo(props) {
2 | return (
3 |
4 |
5 | {props.children}
6 |
7 |
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/core/Cliente.ts:
--------------------------------------------------------------------------------
1 | export default class Cliente {
2 | #id: string
3 | #nome: string
4 | #idade: number
5 |
6 | constructor(nome: string, idade: number, id: string = null) {
7 | this.#nome = nome
8 | this.#idade = idade
9 | this.#id = id
10 | }
11 |
12 | static vazio() {
13 | return new Cliente('', 0)
14 | }
15 |
16 | get id() {
17 | return this.#id
18 | }
19 |
20 | get nome() {
21 | return this.#nome
22 | }
23 |
24 | get idade() {
25 | return this.#idade
26 | }
27 | }
--------------------------------------------------------------------------------
/src/core/ClienteRepositorio.ts:
--------------------------------------------------------------------------------
1 | import Cliente from "./Cliente";
2 |
3 | export default interface ClienteRepositorio {
4 | salvar(cliente: Cliente): Promise
5 | excluir(cliente: Cliente): Promise
6 | obterTodos(): Promise
7 | }
--------------------------------------------------------------------------------
/src/hooks/useClientes.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import ColecaoCliente from "../backend/db/ColecaoCliente"
3 | import Cliente from "../core/Cliente"
4 | import ClienteRepositorio from "../core/ClienteRepositorio"
5 | import useTabelaOuForm from "./useTabelaOuForm"
6 |
7 | export default function useClientes() {
8 | const repo: ClienteRepositorio = new ColecaoCliente()
9 |
10 | const { tabelaVisivel, exibirTabela, exibirFormulario } = useTabelaOuForm()
11 |
12 | const [cliente, setCliente] = useState(Cliente.vazio())
13 | const [clientes, setClientes] = useState([])
14 |
15 | useEffect(obterTodos, [])
16 |
17 | function obterTodos() {
18 | repo.obterTodos().then(clientes => {
19 | setClientes(clientes)
20 | exibirTabela()
21 | })
22 | }
23 |
24 | function selecionarCliente(cliente: Cliente) {
25 | setCliente(cliente)
26 | exibirFormulario()
27 | }
28 |
29 | async function excluirCliente(cliente: Cliente) {
30 | await repo.excluir(cliente)
31 | obterTodos()
32 | }
33 |
34 | function novoCliente() {
35 | setCliente(Cliente.vazio())
36 | exibirFormulario()
37 | }
38 |
39 | async function salvarCliente(cliente: Cliente) {
40 | await repo.salvar(cliente)
41 | obterTodos()
42 | }
43 |
44 | return {
45 | cliente,
46 | clientes,
47 | novoCliente,
48 | salvarCliente,
49 | excluirCliente,
50 | selecionarCliente,
51 | obterTodos,
52 | tabelaVisivel,
53 | exibirTabela
54 | }
55 | }
--------------------------------------------------------------------------------
/src/hooks/useTabelaOuForm.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | export default function useTabelaOuForm() {
4 | const [visivel, setVisivel] = useState<'tabela' | 'form'>('tabela')
5 |
6 | const exibirTabela = () => setVisivel('tabela')
7 | const exibirFormulario = () => setVisivel('form')
8 |
9 | return {
10 | formularioVisivel: visivel === 'form',
11 | tabelaVisivel: visivel === 'tabela',
12 | exibirTabela,
13 | exibirFormulario,
14 | }
15 | }
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 | import 'tailwindcss/tailwind.css'
3 |
4 | function MyApp({ Component, pageProps }) {
5 | return
6 | }
7 |
8 | export default MyApp
9 |
--------------------------------------------------------------------------------
/src/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Botao from "../components/Botao";
2 | import Formulario from "../components/Formulario";
3 | import Layout from "../components/Layout";
4 | import Tabela from "../components/Tabela";
5 |
6 | import useClientes from "../hooks/useClientes";
7 |
8 | export default function Home() {
9 |
10 | const {
11 | cliente,
12 | clientes,
13 | novoCliente,
14 | salvarCliente,
15 | selecionarCliente,
16 | excluirCliente,
17 | tabelaVisivel,
18 | exibirTabela
19 | } = useClientes()
20 |
21 | return (
22 |
27 |
28 | {tabelaVisivel ? (
29 | <>
30 |
31 |
33 | Novo Cliente
34 |
35 |
36 |
40 | >
41 | ) : (
42 |
47 | )}
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: {
3 | content: [
4 | './src/pages/**/*.{js,ts,jsx,tsx}',
5 | './src/components/**/*.{js,ts,jsx,tsx}'
6 | ],
7 | safelist: [
8 | /^bg-/,
9 | /^to-/,
10 | /^from-/,
11 | ]
12 | },
13 | darkMode: false, // or 'media' or 'class'
14 | theme: {
15 | extend: {},
16 | },
17 | variants: {
18 | extend: {},
19 | },
20 | plugins: [],
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2015",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve"
20 | },
21 | "include": [
22 | "next-env.d.ts",
23 | "**/*.ts",
24 | "**/*.tsx"
25 | ],
26 | "exclude": [
27 | "node_modules"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------