1
+ # Importa o módulo principal da biblioteca Dash para criar a aplicação web.
2
+ import dash
3
+
4
+ # Importa componentes específicos da biblioteca Dash para criar elementos
5
+ # HTML e dashboards interativos.
6
+ from dash import dcc , html , Input , Output , State , dash_table , ALL
7
+
8
+ # Importa a classe PreventUpdate do Dash, que é usada para evitar atualizações
9
+ # desnecessárias dos callbacks.
10
+ from dash .exceptions import PreventUpdate
11
+
12
+ # Importa a biblioteca pandas.
13
+ # A biblioteca pandas é extensivamente usada para manipulação e análise de dados.
14
+ # Ela oferece estruturas de dados como DataFrames e Series para lidar com dados
15
+ # tabulares de forma eficiente.
16
+ import pandas as pd
17
+
18
+ # Importa a biblioteca numpy.
19
+ # Numpy é uma biblioteca para a linguagem Python que suporta arrays e matrizes multidimensionais.
20
+ # Ela também oferece uma coleção de funções matemáticas para operar nesses arrays.
21
+ import numpy as np
22
+
23
+ # Importa a biblioteca requests para fazer requisições HTTP.
24
+ import requests
25
+
26
+ # Importa o módulo win32com.client para interagir com o Microsoft Outlook.
27
+ import win32com .client as win32
28
+
29
+ # Importa o módulo pythoncom para inicialização do COM.
30
+ import pythoncom
31
+
32
+ # Importa o módulo os para operações relacionadas ao sistema operacional,
33
+ # como manipulação de arquivos.
34
+ import os
35
+
36
+ # Define a função buscar_cep que recebe um código postal como argumento.
37
+ def buscar_cep (codigo_postal ):
38
+
39
+ # O bloco try tenta executar as instruções contidas nele.
40
+ try :
41
+
42
+ # Usa a biblioteca requests para fazer uma requisição HTTP GET ao serviço de CEP.
43
+ # A URL final será algo como "https://viacep.com.br/ws/01001000/json/".
44
+ resposta = requests .get (f"https://viacep.com.br/ws/{ codigo_postal } /json/" )
45
+
46
+ # Converte a resposta JSON para um dicionário Python usando o método .json() da resposta.
47
+ dados = resposta .json ()
48
+
49
+ # Verifica se a chave "erro" está presente no dicionário.
50
+ # Se estiver, significa que o CEP é inválido ou não foi encontrado.
51
+ if "erro" in dados :
52
+ return None # Retorna None para indicar que o CEP é inválido ou não foi encontrado.
53
+
54
+ # Monta uma string de endereço completo usando os dados recebidos.
55
+ # dados['logradouro'], dados['bairro'], etc., são campos no dicionário retornado pela API.
56
+ endereco_completo = f"{ dados ['logradouro' ]} , { dados ['bairro' ]} , { dados ['localidade' ]} , { dados ['uf' ]} "
57
+
58
+ # Retorna a string do endereço completo.
59
+ return endereco_completo
60
+
61
+ # O bloco except captura qualquer exceção que ocorra durante a execução do bloco try.
62
+ except Exception as e :
63
+
64
+ # Imprime a mensagem "Exceção: " seguida dos detalhes da exceção.
65
+ print ("Exceção: " , e )
66
+
67
+ # Retorna None para indicar que ocorreu um erro durante a busca do CEP.
68
+ return None
69
+
70
+
71
+ # Define a função enviar_email_com_outlook, que recebe um objeto
72
+ # linha (geralmente uma linha de um DataFrame).
73
+ def enviar_email_com_outlook (linha ):
74
+
75
+ # Inicializa o mecanismo COM (Component Object Model) do Windows.
76
+ # Isso é necessário para usar a automação COM, especialmente em ambientes multithread.
77
+ pythoncom .CoInitialize ()
78
+
79
+ # Cria um objeto Dispatch para a aplicação Microsoft Outlook.
80
+ # Isso inicia o Outlook se ele ainda não estiver em execução.
81
+ outlook = win32 .Dispatch ('outlook.application' )
82
+
83
+ # Cria um novo item de e-mail no Outlook.
84
+ # O argumento 0 indica que um e-mail será criado (outros números
85
+ # representam outros tipos de itens, como compromissos).
86
+ email = outlook .CreateItem (0 )
87
+
88
+ # Define o campo "Assunto" do e-mail usando a chave 'Nome' do objeto linha.
89
+ email .Subject = f"Assunto do E-mail para { linha ['Nome' ]} "
90
+
91
+ # Define o corpo do e-mail usando várias chaves do objeto linha.
92
+ # Está sendo usado um string multilinha para formatar o corpo do e-mail de forma mais legível.
93
+ email .Body = f"""Olá { linha ['Nome' ]} ,
94
+
95
+ Estamos muito felizes em ter você como nosso cliente. Abaixo seguem algumas informações úteis:
96
+
97
+ - Endereço: { linha ['Endereco' ]}
98
+ - Celular: { linha ['Celular' ]}
99
+ - Telefone: { linha ['Telefone' ]}
100
+
101
+ Atenciosamente,
102
+ Equipe da Empresa
103
+ """
104
+
105
+ # Define o campo "Para" do e-mail usando a chave 'Email' do objeto linha.
106
+ email .To = linha ['Email' ]
107
+
108
+ # Salva o e-mail no Outlook.
109
+ # Isso não envia o e-mail, apenas o salva no rascunho.
110
+ # email.Send()
111
+ # email.Display()
112
+ email .Save ()
113
+
114
+
115
+ # O bloco try tenta executar o código dentro dele.
116
+ try :
117
+
118
+ # Usa a biblioteca pandas para ler uma planilha Excel chamada 'Dados_Agenda.xlsx' e a aba 'Dados'.
119
+ # O DataFrame resultante é armazenado na variável df.
120
+ df = pd .read_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' )
121
+
122
+ # O bloco except captura uma exceção específica (FileNotFoundError) caso o
123
+ # arquivo 'Dados_Agenda.xlsx' não seja encontrado.
124
+ except FileNotFoundError :
125
+
126
+ # Se o arquivo não for encontrado, um novo DataFrame vazio com as colunas especificadas é criado.
127
+ df = pd .DataFrame (columns = ['CEP' , 'Nome' , 'Endereco' , 'Celular' , 'Telefone' , 'Email' ])
128
+
129
+ # Faz uma cópia profunda do DataFrame df e armazena na variável dataframe_original.
130
+ # Isso é feito para manter uma versão original dos dados, que pode ser útil para operações futuras.
131
+ dataframe_original = df .copy ()
132
+
133
+ # Inicializa uma nova aplicação Dash e armazena na variável app.
134
+ # O argumento __name__ é usado para indicar o nome do script em execução como ponto de entrada para a aplicação.
135
+ app = dash .Dash (__name__ )
136
+
137
+ # Define o layout da aplicação Dash. O layout é a estrutura visual da página web.
138
+ # O html.Div é um container que pode conter outros componentes HTML e Dash.
139
+ app .layout = html .Div ([
140
+
141
+ # html.H1 cria um cabeçalho de nível 1 na página web.
142
+ # O texto "Agenda de Contatos" será o conteúdo desse cabeçalho.
143
+ # O argumento style é um dicionário que contém estilos CSS. Aqui, ele centraliza o texto do cabeçalho.
144
+ html .H1 ("Agenda de Contatos" , style = {'textAlign' : 'center' , 'color' : 'blue' }),
145
+
146
+ # Contêiner principal com estilo flex para alinhar os itens horizontalmente.
147
+ html .Div (children = [
148
+
149
+ # Contêiner para os campos de entrada com estilo flex para alinhar os itens na mesma linha.
150
+ html .Div (children = [
151
+
152
+ # Início do contêiner para o campo "CEP".
153
+ # Este contêiner é um 'Div', que é um contêiner genérico para conteúdo de fluxo e não possui significado semântico específico.
154
+ # Dentro deste 'Div', temos dois elementos filhos: um rótulo e um campo de entrada.
155
+ html .Div (children = [
156
+
157
+ # O primeiro filho é um rótulo 'Label' para o campo de entrada.
158
+ # O rótulo é um elemento inline que representa uma legenda para um elemento de entrada de formulário.
159
+ # A propriedade 'children' do rótulo é simplesmente o texto que será exibido ao usuário, neste caso, "CEP:".
160
+ # Este texto ajuda o usuário a entender o que o campo de entrada subsequente espera como informação.
161
+ html .Label ("CEP:" ),
162
+
163
+ # O segundo filho é um campo de entrada 'Input'.
164
+ # Este é um componente do Dash que representa um campo de entrada HTML onde os usuários podem inserir dados.
165
+ # 'id' é um identificador único para o campo de entrada, que pode ser usado para referenciar o campo em callbacks no Dash.
166
+ # O atributo 'type' especifica o tipo de dados que o campo de entrada aceitará, neste caso, 'text' significa que ele aceita texto.
167
+ dcc .Input (id = 'cep' , type = 'text' )
168
+
169
+ # Fechamento do 'Div' para o campo "CEP".
170
+ # 'style' é um dicionário de estilos CSS aplicados a este 'Div'.
171
+ # 'marginRight' define uma margem à direita do 'Div'. Isso é usado para criar espaço entre este 'Div' e qualquer elemento vizinho à direita.
172
+ # Aqui, '10px' é a quantidade de espaço entre este contêiner de campo de entrada e o próximo.
173
+ ], style = {'marginRight' : '10px' }),
174
+
175
+ # Início do contêiner para o campo "Nome".
176
+ # A estrutura é idêntica ao contêiner "CEP", com uma 'Div' contendo um 'Label' e um 'Input'.
177
+ html .Div (children = [
178
+
179
+ # Rótulo para o campo de entrada "Nome". Funciona da mesma forma que o rótulo para "CEP".
180
+ html .Label ("Nome:" ),
181
+
182
+ # Campo de entrada para o "Nome". 'id' e 'type' funcionam da mesma forma que no campo "CEP".
183
+ dcc .Input (id = 'nome' , type = 'text' )
184
+
185
+ # Fechamento do 'Div' para o campo "Nome".
186
+ # A propriedade 'style' com 'marginRight' também é usada aqui para manter a consistência no layout,
187
+ # separando este contêiner do próximo contêiner de campo de entrada.
188
+ ], style = {'marginRight' : '10px' }),
189
+
190
+ # Contêiner para o campo "Endereço".
191
+ # A 'Div' é um bloco de contêiner genérico para outros elementos HTML. Aqui, serve como contêiner para o rótulo e o campo de entrada de "Endereço".
192
+ html .Div (children = [
193
+
194
+ # Cria um rótulo para o campo de entrada do endereço.
195
+ # 'Label' representa uma etiqueta para o campo de entrada seguinte, facilitando a identificação do usuário sobre o que deve ser preenchido.
196
+ html .Label ("Endereço:" ),
197
+
198
+ # Cria um campo de entrada de texto.
199
+ # 'Input' é um componente interativo onde os usuários podem digitar o endereço. 'id' é o identificador único para este campo no Dash.
200
+ dcc .Input (id = 'endereco' , type = 'text' )
201
+
202
+ # Define o estilo do contêiner Div.
203
+ # 'marginRight' adiciona uma margem à direita do contêiner para separá-lo de elementos adjacentes, garantindo espaço entre os campos de entrada.
204
+ ], style = {'marginRight' : '10px' }),
205
+
206
+ # Contêiner para o campo "Celular".
207
+ # Esta 'Div' atua como contêiner para o rótulo e campo de entrada do celular, seguindo o mesmo padrão do contêiner de "Endereço".
208
+ html .Div (children = [
209
+
210
+ # Rótulo para o campo de entrada de celular.
211
+ # Fornece uma indicação clara para o usuário de que o dado a ser inserido é o número de celular.
212
+ html .Label ("Celular:" ),
213
+
214
+ # Campo de entrada para o número de celular.
215
+ # Permite ao usuário inserir um número de celular. O 'id' associado a este campo permite que seja referenciado unicamente na aplicação.
216
+ dcc .Input (id = 'celular' , type = 'text' )
217
+
218
+ # Estilo para o contêiner 'Div' de "Celular".
219
+ # Mantém a margem à direita consistente com os outros campos de entrada para alinhamento e espaçamento adequado no layout geral.
220
+ ], style = {'marginRight' : '10px' }),
221
+
222
+ # Contêiner para o campo "Telefone".
223
+ # Este 'Div' atua como um contêiner para agrupar visualmente o rótulo e o campo de entrada de "Telefone", facilitando a organização do layout.
224
+ html .Div (children = [
225
+
226
+ # Cria um rótulo 'Label' para o campo de entrada de "Telefone".
227
+ # 'Label' serve como uma legenda textual para o campo de entrada, indicando ao usuário que informações são esperadas neste campo.
228
+ html .Label ("Telefone:" ),
229
+
230
+ # Define o campo de entrada 'Input' para o número de telefone.
231
+ # 'id' é um identificador único no DOM que permite ao Dash identificar e interagir com este campo de entrada em callbacks.
232
+ # 'type' é definido como 'text', que especifica que o usuário pode inserir texto neste campo.
233
+ dcc .Input (id = 'telefone' , type = 'text' )
234
+
235
+ # Aplica estilos CSS ao 'Div' do campo "Telefone".
236
+ # 'marginRight' define uma margem à direita, criando um espaçamento entre este campo de entrada e qualquer elemento vizinho à direita.
237
+ ], style = {'marginRight' : '10px' }),
238
+
239
+ # Contêiner para o campo "Email".
240
+ # Similarmente, este 'Div' é utilizado para encapsular o rótulo e o campo de entrada de "Email".
241
+ html .Div (children = [
242
+
243
+ # Rótulo 'Label' para o campo de entrada de "Email".
244
+ # O texto "Email:" serve como uma indicação clara do propósito do campo de entrada seguinte.
245
+ html .Label ("Email:" ),
246
+
247
+ # Campo de entrada 'Input' para o endereço de email.
248
+ # Com o 'id' 'email', esse campo é identificado unicamente na aplicação para referência em operações de interação ou estilização.
249
+ dcc .Input (id = 'email' , type = 'text' )
250
+
251
+ # Neste caso, não estamos aplicando uma margem à direita.
252
+ # Isso pode é o último campo de entrada na linha ou para manter um alinhamento específico dentro do layout.
253
+ ], style = {}),
254
+
255
+ ], style = {
256
+ 'display' : 'flex' , # Alinha os itens horizontalmente.
257
+ 'align-items' : 'center' , # Centraliza os itens verticalmente.
258
+ 'width' : '100%' , # Largura ajustada para 100% do contêiner pai.
259
+ 'padding' : '15px' , # Preenchimento interno.
260
+ 'flex-wrap' : 'wrap' # Permite que os itens se envolvam conforme necessário.
261
+ }),
262
+
263
+ ], style = {'width' : '100%' }),
264
+ # Alinhamento central com flexbox
265
+
266
+
267
+ # Este é um novo container Div que contém a tabela de dados e os botões de ação.
268
+ html .Div ([
269
+
270
+ # Contêiner Div filho que abriga exclusivamente a tabela de dados.
271
+ html .Div ([
272
+
273
+ # Criando a tabela usando o componente dash_table.DataTable.
274
+ dash_table .DataTable (
275
+
276
+ # ID único para a tabela, útil para referenciar em callbacks.
277
+ id = 'tabela' ,
278
+
279
+ # Definindo as colunas da tabela com base nas colunas do DataFrame df.
280
+ columns = [{"name" : i , "id" : i } for i in df .columns ],
281
+
282
+ # Preenchendo os dados na tabela usando os registros do DataFrame df.
283
+ data = df .to_dict ('records' ),
284
+
285
+ # Tornando as células da tabela editáveis.
286
+ editable = True ,
287
+
288
+ # Permitindo que apenas uma linha seja selecionável de cada vez.
289
+ row_selectable = "single" ,
290
+
291
+ # Nenhuma linha está selecionada inicialmente.
292
+ selected_rows = [],
293
+
294
+ # Permitindo que as linhas sejam deletáveis.
295
+ row_deletable = True ,
296
+
297
+ # Estilos para o contêiner da tabela.
298
+ style_table = {
299
+ 'overflowX' : 'scroll' , # Rolagem horizontal se o conteúdo exceder a largura.
300
+ 'border' : 'thin lightgrey solid' # Borda fina cinza claro ao redor da tabela.
301
+ },
302
+
303
+ # Estilos para o cabeçalho da tabela.
304
+ style_header = {
305
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
306
+ 'color' : 'white' , # Texto branco.
307
+ 'fontWeight' : 'bold' , # Texto em negrito.
308
+ 'border' : '1px solid white' # Borda branca ao redor das células do cabeçalho.
309
+ },
310
+
311
+ # Estilos para as células da tabela.
312
+ style_cell = {
313
+ 'textAlign' : 'left' , # Alinhamento do texto à esquerda.
314
+ 'padding' : '8px' , # Preenchimento interno de 8px.
315
+ 'border' : '1px solid lightgrey' # Borda cinza claro ao redor das células.
316
+ },
317
+
318
+ # Estilos condicionais para as células da tabela.
319
+ style_data_conditional = [
320
+ {
321
+ 'if' : {'row_index' : 'odd' }, # Condição para linhas ímpares.
322
+ 'backgroundColor' : 'lightgrey' # Cor de fundo cinza claro para linhas ímpares.
323
+ }
324
+ ],
325
+ )
326
+
327
+ # Estilos para o contêiner Div que contém a tabela.
328
+ ], style = {
329
+ 'width' : '75%' , # Ocupa 75% da largura total disponível.
330
+ 'display' : 'inline-block' , # Disposto como um bloco inline.
331
+ 'vertical-align' : 'top' , # Alinhamento vertical ao topo.
332
+ 'padding' : '15px' # Preenchimento de 15px em todos os lados.
333
+ }),
334
+
335
+
336
+ # Este trecho de código cria um novo container Div que conterá apenas botões de ação.
337
+ # html.Div é um componente que cria um novo bloco ou seção na página web.
338
+ html .Div ([
339
+
340
+ # Criando um botão com o texto "Adicionar".
341
+ html .Button (
342
+
343
+ "Adicionar" , # Texto exibido no botão.
344
+
345
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
346
+ id = 'botao_adicionar' ,
347
+
348
+ # Definindo o estilo CSS do botão.
349
+ style = {
350
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
351
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
352
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
353
+ 'color' : 'white' , # Cor do texto branco.
354
+ 'fontSize' : '20px'
355
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
356
+ }
357
+ ),
358
+
359
+ # Um novo botão é criado com o texto "Alterar".
360
+ # Assim como o botão anterior, este botão tem um id único e um estilo específico.
361
+ html .Button (
362
+
363
+ "Alterar" , # Texto exibido no botão.
364
+
365
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
366
+ id = 'botao_alterar' ,
367
+
368
+ # Definindo o estilo CSS do botão.
369
+ style = {
370
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
371
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
372
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
373
+ 'color' : 'white' , # Cor do texto branco.
374
+ 'fontSize' : '20px'
375
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
376
+ }
377
+ ),
378
+
379
+ # Mais um botão é criado com o texto "Pesquisar".
380
+ html .Button (
381
+
382
+ "Pesquisar" , # Texto exibido no botão.
383
+
384
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
385
+ id = 'botao_pesquisar' ,
386
+
387
+ # Definindo o estilo CSS do botão.
388
+ style = {
389
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
390
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
391
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
392
+ 'color' : 'white' , # Cor do texto branco.
393
+ 'fontSize' : '20px'
394
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
395
+ }
396
+ ),
397
+
398
+ # O botão "Excluir" é criado a seguir.
399
+ html .Button (
400
+
401
+ "Excluir" , # Texto exibido no botão.
402
+
403
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
404
+ id = 'botao_excluir' ,
405
+
406
+ # Definindo o estilo CSS do botão.
407
+ style = {
408
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
409
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
410
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
411
+ 'color' : 'white' , # Cor do texto branco.
412
+ 'fontSize' : '20px'
413
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
414
+ }
415
+ ),
416
+
417
+ # O botão "Limpar Filtro" é para redefinir quaisquer filtros aplicados.
418
+ html .Button (
419
+
420
+ "Limpar Filtro" , # Texto exibido no botão.
421
+
422
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
423
+ id = 'botao_limpar' ,
424
+
425
+ # Definindo o estilo CSS do botão.
426
+ style = {
427
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
428
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
429
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
430
+ 'color' : 'white' , # Cor do texto branco.
431
+ 'fontSize' : '20px'
432
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
433
+ }
434
+ ),
435
+
436
+ # O botão "Exportar para Excel" serve para iniciar um processo de exportação de dados.
437
+ html .Button (
438
+
439
+ "Exportar para Excel" , # Texto exibido no botão.
440
+
441
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
442
+ id = 'botao_exportar' ,
443
+
444
+ # Definindo o estilo CSS do botão.
445
+ style = {
446
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
447
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
448
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
449
+ 'color' : 'white' , # Cor do texto branco.
450
+ 'fontSize' : '20px'
451
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
452
+ }
453
+ ),
454
+
455
+ # O último botão, "Criar E-mail", servirá para enviar e-mails.
456
+ html .Button (
457
+
458
+ "Criar E-mail" , # Texto exibido no botão.
459
+
460
+ # ID único para o botão, que pode ser usado para referenciar este botão em futuros callbacks.
461
+ id = 'botao_email' ,
462
+
463
+ # Definindo o estilo CSS do botão.
464
+ style = {
465
+ 'width' : '100%' , # Ocupa 100% da largura do contêiner pai.
466
+ 'marginBottom' : '10px' , # Margem inferior de 10 pixels para separá-lo de outros elementos abaixo.
467
+ 'backgroundColor' : '#007BFF' , # Cor de fundo azul.
468
+ 'color' : 'white' , # Cor do texto branco.
469
+ 'fontSize' : '20px'
470
+ # Tamanho da fonte definido como 20 pixels. Pode ser ajustado conforme necessário.
471
+ }
472
+ ),
473
+
474
+ # O estilo desta Div é configurado para que ela ocupe 25% da largura total disponível da página.
475
+ # Ela é exibida como um bloco inline para que possa ficar ao lado de outros elementos.
476
+ # O alinhamento vertical é definido como 'top', o que significa que ela se alinhará ao topo do seu container pai.
477
+ # Um padding de 15 pixels é adicionado para algum espaço extra ao redor do conteúdo.
478
+ # Por fim, o texto dentro desta Div é centralizado usando 'textAlign': 'center'.
479
+ ], style = {'width' : '25%' , 'display' : 'inline-block' , 'vertical-align' : 'top' , 'padding' : '15px' ,
480
+ 'textAlign' : 'center' }),
481
+
482
+ # Este é o estilo da Div principal que contém a tabela e os botões.
483
+ # Ela é configurada para ocupar 100% da largura disponível e para dispor seus elementos filhos em uma linha (flex).
484
+ ], style = {'width' : '100%' , 'display' : 'flex' }),
485
+ ])
486
+
487
+ # O layout agora está completo. Todos os componentes HTML e Dash estão aninhados em Divs,
488
+ # o que permite um controle preciso sobre o layout e o estilo da aplicação.
489
+
490
+
491
+ # @app.callback é um decorador que define uma função de callback em uma aplicação Dash.
492
+ # A função decorada será chamada sempre que os componentes especificados nos argumentos
493
+ # Input, Output e State forem modificados.
494
+ @app .callback (
495
+
496
+ # A lista de Outputs define os componentes cujas propriedades serão atualizadas pelo callback.
497
+ # Cada Output é um objeto que leva dois argumentos: o id do componente e a propriedade que será atualizada.
498
+ [Output ('tabela' , 'data' ), # Atualiza os dados na tabela.
499
+ Output ('cep' , 'value' ), # Atualiza o valor do campo CEP.
500
+ Output ('nome' , 'value' ), # Atualiza o valor do campo Nome.
501
+ Output ('endereco' , 'value' ), # Atualiza o valor do campo Endereço.
502
+ Output ('celular' , 'value' ), # Atualiza o valor do campo Celular.
503
+ Output ('telefone' , 'value' ), # Atualiza o valor do campo Telefone.
504
+ Output ('email' , 'value' )], # Atualiza o valor do campo Email.
505
+
506
+ # A lista de Inputs define os componentes que, quando interagidos, irão disparar o callback.
507
+ # Semelhante ao Output, cada Input é um objeto que leva dois argumentos: o id do componente e a propriedade que irá disparar o callback.
508
+ [Input ('botao_adicionar' , 'n_clicks' ), # Número de cliques no botão Adicionar.
509
+ Input ('botao_alterar' , 'n_clicks' ), # Número de cliques no botão Alterar.
510
+ Input ('botao_pesquisar' , 'n_clicks' ), # Número de cliques no botão Pesquisar.
511
+ Input ('botao_excluir' , 'n_clicks' ), # Número de cliques no botão Excluir.
512
+ Input ('botao_limpar' , 'n_clicks' ), # Número de cliques no botão Limpar Filtro.
513
+ Input ('botao_exportar' , 'n_clicks' ), # Número de cliques no botão Exportar para Excel.
514
+ Input ('botao_email' , 'n_clicks' ), # Número de cliques no botão Criar E-mail.
515
+ Input ('cep' , 'value' ), # Valor atual do campo CEP.
516
+ Input ('tabela' , 'active_cell' ), # Informações sobre a célula ativa na tabela.
517
+ Input ('tabela' , 'data_previous' )], # Dados anteriores da tabela antes da última edição.
518
+
519
+ # A lista de States contém os componentes cujos estados atuais serão passados para o callback, mas que não disparam o callback.
520
+ # Assim como em Output e Input, cada State é um objeto que leva dois argumentos: o id do componente e a propriedade que será passada.
521
+ [State ('cep' , 'value' ), # Valor atual do campo CEP.
522
+ State ('nome' , 'value' ), # Valor atual do campo Nome.
523
+ State ('endereco' , 'value' ), # Valor atual do campo Endereço.
524
+ State ('celular' , 'value' ), # Valor atual do campo Celular.
525
+ State ('telefone' , 'value' ), # Valor atual do campo Telefone.
526
+ State ('email' , 'value' ), # Valor atual do campo Email.
527
+ State ('tabela' , 'data' ), # Dados atuais da tabela.
528
+ State ('tabela' , 'selected_rows' )] # Índices das linhas selecionadas na tabela.
529
+ )
530
+
531
+ # A função atualizar_tabela é definida como o callback que será disparado quando os Inputs e States especificados forem alterados.
532
+ def atualizar_tabela (cliques_adicionar , cliques_alterar , cliques_pesquisar , cliques_excluir , cliques_limpar ,
533
+ cliques_exportar , cliques_email , valor_cep , celula_ativa , dados_anteriores , cep , nome , endereco ,
534
+ celular , telefone , email , dados_tabela , linhas_selecionadas ):
535
+
536
+ # A palavra-chave global é usada para indicar que a variável dataframe_original é uma variável global.
537
+ # Isso permite que a função modifique a variável fora do seu escopo local.
538
+ global dataframe_original
539
+
540
+ # dash.callback_context fornece informações sobre o componente que disparou o callback.
541
+ # Isso é útil quando você tem múltiplos Inputs e precisa saber qual deles foi acionado.
542
+ contexto = dash .callback_context
543
+
544
+ # Se nenhum Input foi disparado (por exemplo, na inicialização da aplicação), a função raise PreventUpdate é chamada.
545
+ # Isso evita que o callback atualize qualquer Output, efetivamente "cancelando" a execução do callback.
546
+ if not contexto .triggered :
547
+ raise PreventUpdate
548
+
549
+ # O Input que disparou o callback é identificado e o seu id é armazenado na variável input_ativado.
550
+ # A propriedade 'prop_id' contém o id e a propriedade do Input que disparou o callback, separados por um ponto.
551
+ # O método split('.') é usado para separar o id do nome da propriedade, e somente o id é usado.
552
+ input_ativado = contexto .triggered [0 ]['prop_id' ].split ('.' )[0 ]
553
+
554
+ # Um novo DataFrame df é criado a partir dos dados atuais da tabela.
555
+ # Isso é feito para que qualquer modificação nos dados possa ser feita neste DataFrame local, sem afetar os dados originais.
556
+ df = pd .DataFrame (dados_tabela )
557
+
558
+ # Este bloco de código é executado se o Input que disparou o callback é o campo 'cep'.
559
+ # A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'cep'.
560
+ if input_ativado == 'cep' :
561
+
562
+ # https://www.consultarcep.com.br/rj/rio-de-janeiro/
563
+
564
+ # A função buscar_cep é chamada com o valor atual do campo 'cep' (valor_cep) como argumento.
565
+ # Esta função é responsável por buscar informações de endereço com base no CEP fornecido.
566
+ endereco = buscar_cep (valor_cep )
567
+
568
+ # Se a função buscar_cep retornar None (o que significa que o CEP não foi encontrado),
569
+ # a variável endereco é atualizada com a string "CEP não encontrado".
570
+ if endereco is None :
571
+ endereco = "CEP não encontrado"
572
+
573
+ # O callback então retorna as atualizações para os Outputs.
574
+ # Neste caso, apenas o valor do campo 'endereco' é atualizado com o valor da variável endereco.
575
+ # Todos os outros Outputs são mantidos como estão, o que é indicado por dash.no_update.
576
+ return dash .no_update , dash .no_update , dash .no_update , endereco , dash .no_update , dash .no_update , dash .no_update
577
+
578
+ # Este bloco de código é executado se o Input que disparou o callback é a 'tabela'.
579
+ # A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'tabela'.
580
+ elif input_ativado == 'tabela' :
581
+
582
+ # Verifica se a variável dados_anteriores não é None.
583
+ # dados_anteriores contém o estado anterior dos dados da tabela antes de qualquer edição.
584
+ if dados_anteriores is not None :
585
+
586
+ # Compara o tamanho dos dados anteriores e dos dados atuais da tabela.
587
+ # Se os dados anteriores são maiores em tamanho, isso indica que uma linha foi excluída.
588
+ if len (dados_anteriores ) > len (dados_tabela ):
589
+
590
+ # Salva o DataFrame df atualizado como um arquivo Excel.
591
+ df .to_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' , index = False )
592
+
593
+ # Retorna os dados atualizados da tabela e limpa todos os outros campos.
594
+ return df .to_dict ('records' ), '' , '' , '' , '' , '' , ''
595
+
596
+ # Verifica se a variável celula_ativa não é None.
597
+ # celula_ativa contém informações sobre a célula que está atualmente ativa (selecionada) na tabela.
598
+
599
+ if celula_ativa is not None :
600
+
601
+ # Extrai o índice da linha da célula ativa.
602
+ linha = celula_ativa ['row' ]
603
+
604
+ # Usa o índice da linha para obter os dados da linha correspondente no DataFrame df.
605
+ dados_selecionados = df .iloc [linha ]
606
+
607
+ # Retorna os valores dos campos CEP, Nome, Endereço, Celular, Telefone e Email dessa linha específica.
608
+ # dash.no_update é usado para os outros Outputs que não precisam ser atualizados.
609
+ return dash .no_update , dados_selecionados ['CEP' ], dados_selecionados ['Nome' ], dados_selecionados [
610
+ 'Endereco' ], dados_selecionados ['Celular' ], dados_selecionados ['Telefone' ], dados_selecionados ['Email' ]
611
+
612
+
613
+
614
+ # Este bloco de código é executado se o Input que disparou o callback é o botão 'botao_adicionar'.
615
+ # A variável input_ativado contém o id do componente que disparou o callback, que neste caso é 'botao_adicionar'.
616
+ elif input_ativado == 'botao_adicionar' :
617
+
618
+ # Um novo DataFrame chamado nova_linha é criado para armazenar os valores dos campos de entrada: cep, nome, endereco, celular, telefone e email.
619
+ # As colunas deste novo DataFrame são as mesmas que as do DataFrame df existente.
620
+ nova_linha = pd .DataFrame ([[cep , nome , endereco , celular , telefone , email ]], columns = df .columns )
621
+
622
+ # O novo DataFrame nova_linha é concatenado ao DataFrame df existente.
623
+ # O parâmetro ignore_index=True reindexa o DataFrame resultante para que os índices sejam contínuos.
624
+ df = pd .concat ([df , nova_linha ], ignore_index = True )
625
+
626
+ # O DataFrame df atualizado é salvo como um arquivo Excel com o nome 'Dados_Agenda.xlsx' e a aba 'Dados'.
627
+ # O parâmetro index=False evita que os índices do DataFrame sejam salvos no arquivo Excel.
628
+ df .to_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' , index = False )
629
+
630
+ # O callback retorna os dados atualizados da tabela e limpa todos os outros campos.
631
+ # O DataFrame df é convertido para um dicionário de registros para ser compatível com a tabela Dash.
632
+ # Todos os outros campos são limpos, retornando strings vazias.
633
+ return df .to_dict ('records' ), '' , '' , '' , '' , '' , ''
634
+
635
+
636
+ # Este bloco de código é ativado quando o Input que disparou o callback é o botão 'botao_alterar'.
637
+ # A variável input_ativado contém o id do componente que disparou o callback, neste caso, 'botao_alterar'.
638
+ elif input_ativado == 'botao_alterar' :
639
+
640
+ # Verifica se alguma linha foi selecionada na tabela.
641
+ # Se nenhuma linha foi selecionada (ou seja, linhas_selecionadas é None ou sua extensão é 0),
642
+ # a atualização é impedida usando o comando PreventUpdate.
643
+ if linhas_selecionadas is None or len (linhas_selecionadas ) == 0 :
644
+ raise PreventUpdate
645
+
646
+ # Itera sobre cada índice de linha selecionada na variável linhas_selecionadas.
647
+ for i in linhas_selecionadas :
648
+
649
+ # Atualiza o valor da coluna 'CEP' na linha i do DataFrame df se o valor de cep não for None.
650
+ if cep is not None :
651
+ df .loc [i , 'CEP' ] = cep
652
+
653
+ # Similarmente, atualiza os valores das outras colunas com base nas entradas fornecidas, se elas não forem None.
654
+ if nome is not None :
655
+ df .loc [i , 'Nome' ] = nome
656
+ if endereco is not None :
657
+ df .loc [i , 'Endereco' ] = endereco
658
+ if celular is not None :
659
+ df .loc [i , 'Celular' ] = celular
660
+ if telefone is not None :
661
+ df .loc [i , 'Telefone' ] = telefone
662
+ if email is not None :
663
+ df .loc [i , 'Email' ] = email
664
+
665
+ # Após realizar todas as alterações, o DataFrame df atualizado é salvo como um arquivo Excel.
666
+ df .to_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' , index = False )
667
+
668
+ # Finalmente, o callback retorna os dados atualizados da tabela e limpa todos os outros campos de entrada.
669
+ # O DataFrame df é convertido para um dicionário de registros para atualizar a tabela da interface do usuário.
670
+ return df .to_dict ('records' ), '' , '' , '' , '' , '' , ''
671
+
672
+
673
+ # Este bloco de código é ativado quando o botão 'Pesquisar' é clicado.
674
+ elif input_ativado == 'botao_pesquisar' :
675
+
676
+ # Cria uma máscara booleana inicial com todos os valores como True.
677
+ # Essa máscara terá o mesmo número de linhas que o DataFrame 'df'.
678
+ # A ideia é que, inicialmente, todos os registros são elegíveis para serem retornados pela pesquisa.
679
+ final_mask = np .array ([True ] * len (df ))
680
+
681
+ # Para cada campo de entrada, atualizamos a máscara booleana para refletir os registros que satisfazem a condição.
682
+ # Utilizamos a operação 'logical_and' para combinar a máscara atual com a nova condição.
683
+ # Assim, a máscara final só conterá 'True' para os registros que satisfazem todas as condições.
684
+
685
+ # Atualiza a máscara se o campo 'Nome' não estiver vazio.
686
+ if nome :
687
+ final_mask = np .logical_and (final_mask , df ['Nome' ].str .contains (nome , case = False ))
688
+
689
+ # Atualiza a máscara se o campo 'CEP' não estiver vazio.
690
+ if cep :
691
+ final_mask = np .logical_and (final_mask , df ['CEP' ].str .contains (cep , case = False ))
692
+
693
+ # Atualiza a máscara se o campo 'Endereco' não estiver vazio.
694
+ if endereco :
695
+ final_mask = np .logical_and (final_mask , df ['Endereco' ].str .contains (endereco , case = False ))
696
+
697
+ # Atualiza a máscara se o campo 'Celular' não estiver vazio.
698
+ if celular :
699
+ final_mask = np .logical_and (final_mask , df ['Celular' ].str .contains (celular , case = False ))
700
+
701
+ # Atualiza a máscara se o campo 'Telefone' não estiver vazio.
702
+ if telefone :
703
+ final_mask = np .logical_and (final_mask , df ['Telefone' ].str .contains (telefone , case = False ))
704
+
705
+ # Atualiza a máscara se o campo 'Email' não estiver vazio.
706
+ if email :
707
+ final_mask = np .logical_and (final_mask , df ['Email' ].str .contains (email , case = False ))
708
+
709
+ # Aplica a máscara booleana final ao DataFrame para filtrar os registros.
710
+ # Isso resulta em um novo DataFrame que contém apenas os registros que satisfazem todas as condições de pesquisa.
711
+ resultado_pesquisa = df [final_mask ]
712
+
713
+ # Retorna o resultado da pesquisa como um dicionário de registros.
714
+ # Os argumentos 'dash.no_update' indicam que os outros outputs do callback não devem ser atualizados.
715
+ return resultado_pesquisa .to_dict (
716
+ 'records' ), dash .no_update , dash .no_update , dash .no_update , dash .no_update , dash .no_update , dash .no_update
717
+
718
+
719
+ # Este bloco de código é ativado quando o botão 'botao_excluir' é pressionado.
720
+ # A variável 'input_ativado' conterá o valor 'botao_excluir', indicando que este botão disparou o callback.
721
+ elif input_ativado == 'botao_excluir' :
722
+
723
+ # Primeiramente, verifica se qualquer linha foi selecionada na tabela.
724
+ # 'linhas_selecionadas' é uma lista dos índices das linhas selecionadas.
725
+ # Se nenhuma linha for selecionada, o código impede qualquer atualização adicional usando 'raise PreventUpdate'.
726
+ if linhas_selecionadas is None or len (linhas_selecionadas ) == 0 :
727
+ raise PreventUpdate
728
+
729
+ # O método 'drop' do DataFrame é utilizado para excluir as linhas selecionadas.
730
+ # 'axis=0' especifica que queremos excluir linhas (e não colunas).
731
+ # 'inplace=True' modifica o DataFrame original.
732
+ df .drop (linhas_selecionadas , axis = 0 , inplace = True )
733
+
734
+ # O índice do DataFrame é redefinido e o antigo índice é descartado.
735
+ df .reset_index (drop = True , inplace = True )
736
+
737
+ # O DataFrame modificado é salvo em um arquivo Excel.
738
+ df .to_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' , index = False )
739
+
740
+ # Retorna os dados atualizados para preencher a tabela na interface do usuário.
741
+ # O DataFrame 'df' é convertido para um formato de dicionário de registros.
742
+ # Todos os outros campos de entrada são limpos e retornam strings vazias.
743
+ return df .to_dict ('records' ), '' , '' , '' , '' , '' , ''
744
+
745
+
746
+ # Este bloco é ativado quando o botão 'botao_limpar' é clicado.
747
+ # A variável input_ativado conterá o valor 'botao_limpar', indicando que este botão disparou o callback.
748
+ elif input_ativado == 'botao_limpar' :
749
+
750
+ # Um bloco try-except é utilizado para capturar qualquer erro que
751
+ # possa ocorrer durante a leitura do arquivo Excel.
752
+ try :
753
+
754
+ # Tenta ler o arquivo 'Dados_Agenda.xlsx' na planilha 'Dados' para um novo DataFrame chamado novo_df.
755
+ novo_df = pd .read_excel ('Dados_Agenda.xlsx' , sheet_name = 'Dados' )
756
+
757
+ # Cria uma cópia profunda do novo DataFrame para a variável global dataframe_original.
758
+ # Isso é feito para que qualquer operação futura não afete o DataFrame original.
759
+ dataframe_original = novo_df .copy ()
760
+
761
+ # Retorna os dados atualizados para preencher a tabela na interface do usuário.
762
+ # O DataFrame dataframe_original é convertido em um dicionário de registros para ser compatível com a tabela Dash.
763
+ # Todos os outros campos de entrada são limpos e retornam strings vazias.
764
+ return dataframe_original .to_dict ('records' ), '' , '' , '' , '' , '' , ''
765
+
766
+ # Se ocorrer um FileNotFoundError (por exemplo, se o arquivo Excel não for encontrado),
767
+ # o código entrará no bloco except.
768
+ except FileNotFoundError :
769
+
770
+ # Neste caso, retorna um dash.no_update para a tabela, indicando que os dados da tabela não devem ser atualizados.
771
+ # Todos os outros campos de entrada são limpos e retornam strings vazias.
772
+ return dash .no_update , '' , '' , '' , '' , '' , ''
773
+
774
+
775
+ # Este bloco de código é ativado quando o botão 'botao_exportar' é clicado.
776
+ # A variável 'input_ativado' conterá o valor 'botao_exportar', sinalizando que
777
+ # este botão disparou o callback.
778
+ elif input_ativado == 'botao_exportar' :
779
+
780
+ # Um bloco try-except é usado para capturar qualquer exceção que possa
781
+ # ocorrer durante a exportação dos dados para o Excel.
782
+ try :
783
+
784
+ # A função os.path.abspath é usada para obter o caminho absoluto onde o arquivo Excel exportado será salvo.
785
+ caminho_exportacao = os .path .abspath ('Dados_Agenda_Exportado.xlsx' )
786
+
787
+ # O método to_excel do DataFrame 'df' é usado para salvar os dados no arquivo Excel.
788
+ # O parâmetro 'sheet_name' especifica o nome da aba do Excel e 'index=False' evita que os índices do DataFrame sejam exportados.
789
+ df .to_excel (caminho_exportacao , sheet_name = 'Dados' , index = False )
790
+
791
+ # O bloco except captura qualquer tipo de exceção e imprime uma mensagem de erro.
792
+ except Exception as e :
793
+ print (f"Erro ao exportar: { e } " )
794
+
795
+ # Independentemente de a exportação ser bem-sucedida ou não, este
796
+ # retorno garante que a tabela e os campos de entrada não sejam atualizados.
797
+ return dash .no_update , '' , '' , '' , '' , '' , ''
798
+
799
+
800
+ # Este bloco de código é ativado quando o botão 'botao_email' é pressionado na interface do usuário.
801
+ # A variável 'input_ativado' terá o valor 'botao_email', indicando que este botão é o responsável por disparar o callback.
802
+ elif input_ativado == 'botao_email' :
803
+
804
+ # Um bloco try-except é usado para capturar qualquer exceção que possa ocorrer durante o envio de e-mails.
805
+ try :
806
+
807
+ # O método 'iterrows()' do DataFrame 'df' é usado para iterar sobre todas as linhas do DataFrame.
808
+ # Cada 'linha' é uma Series do Pandas que contém todos os dados de uma linha individual do DataFrame.
809
+ for _ , linha in df .iterrows ():
810
+
811
+ # A função 'enviar_email_com_outlook' é chamada para cada linha do DataFrame.
812
+ # Esta função foi definida anteriormente no script para enviar um e-mail usando a API do Outlook.
813
+ enviar_email_com_outlook (linha )
814
+
815
+ # O bloco 'except' captura qualquer tipo de exceção que possa ocorrer durante o envio de e-mails.
816
+ # A exceção é armazenada na variável 'e', e uma mensagem de erro é impressa no console.
817
+ except Exception as e :
818
+ print (f"Erro ao criar e-mails: { e } " )
819
+
820
+ # Independentemente do sucesso ou falha do envio de e-mails, o código retorna 'dash.no_update' para todos os Outputs.
821
+ # Isso significa que a interface do usuário não será atualizada.
822
+ return dash .no_update , '' , '' , '' , '' , '' , ''
823
+
824
+ # Este é o retorno padrão se nenhum dos blocos 'elif' anteriores for ativado.
825
+ # Ele evita qualquer atualização na interface do usuário.
826
+ return dash .no_update , dash .no_update , dash .no_update , dash .no_update , dash .no_update , dash .no_update , dash .no_update
827
+
828
+ # Este é o ponto de entrada principal do script.
829
+ # Ele inicia o servidor Dash na porta 8055 e com a depuração ativada.
830
+ if __name__ == '__main__' :
831
+ app .run_server (debug = True , port = 8055 )
0 commit comments