Geoeasy commited on
Commit
4be3d38
·
verified ·
1 Parent(s): 1074760

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -843
app.py DELETED
@@ -1,843 +0,0 @@
1
- import gradio as gr
2
- import sqlite3
3
- import bcrypt
4
- import pandas as pd
5
- from pathlib import Path
6
- from datetime import datetime
7
- import plotly.figure_factory as ff
8
- import plotly.graph_objects as go
9
-
10
- # ======================================================
11
- # Configuração
12
- # ======================================================
13
- BASE_DIR = Path(__file__).parent
14
- DB_PATH = BASE_DIR / "db" / "app.db"
15
- DB_PATH.parent.mkdir(parents=True, exist_ok=True)
16
-
17
- # Inicializar base de dados automaticamente
18
- try:
19
- from init_db import init_database
20
- init_database()
21
- except Exception as e:
22
- print(f"Aviso ao inicializar BD: {e}")
23
-
24
- # ======================================================
25
- # Funções de Base de Dados
26
- # ======================================================
27
- def get_conn():
28
- """Retorna conexão com BD"""
29
- conn = sqlite3.connect(DB_PATH, timeout=30)
30
- conn.execute("PRAGMA foreign_keys = ON")
31
- return conn
32
-
33
- # ======================================================
34
- # Utilitários
35
- # ======================================================
36
- def slots_30_min():
37
- """Gera slots de 30 em 30 minutos"""
38
- return [f"{h:02d}:{m:02d}" for h in range(24) for m in (0, 30)]
39
-
40
- def calcular_horas(start_date, start_time, end_date, end_time):
41
- """Calcula horas entre duas datas/horas"""
42
- if not all([start_date, start_time, end_date, end_time]):
43
- raise ValueError("Preencha todos os campos")
44
-
45
- try:
46
- # Aceitar formato de gr.DateTime (pode vir como "2026-02-03" ou "2026-02-03 00:00:00")
47
- if isinstance(start_date, str) and " " in start_date:
48
- start_date = start_date.split()[0]
49
- if isinstance(end_date, str) and " " in end_date:
50
- end_date = end_date.split()[0]
51
-
52
- inicio = datetime.strptime(f"{start_date} {start_time}", "%Y-%m-%d %H:%M")
53
- fim = datetime.strptime(f"{end_date} {end_time}", "%Y-%m-%d %H:%M")
54
- except ValueError as e:
55
- raise ValueError(f"Formato inválido: {e}")
56
-
57
- if fim <= inicio:
58
- raise ValueError("Data/hora fim deve ser posterior ao início")
59
-
60
- return round((fim - inicio).total_seconds() / 3600, 2)
61
-
62
- def calcular_horas_preview(start_date, start_time, end_date, end_time):
63
- """Preview de horas calculadas"""
64
- try:
65
- if not all([start_date, start_time, end_date, end_time]):
66
- return ""
67
- horas = calcular_horas(start_date, start_time, end_date, end_time)
68
- return f"{horas} h"
69
- except:
70
- return ""
71
-
72
- # ======================================================
73
- # Autenticação
74
- # ======================================================
75
- def login_user(username, password):
76
- """Faz login do utilizador - retorna username"""
77
- if not username or not password:
78
- return None, "❌ Preencha todos os campos", "⚠️ **Não autenticado**"
79
-
80
- conn = get_conn()
81
- cur = conn.cursor()
82
- cur.execute("SELECT username, password_hash FROM users WHERE username = ?", (username,))
83
- row = cur.fetchone()
84
-
85
- if not row:
86
- conn.close()
87
- return None, "❌ Utilizador não encontrado", "⚠️ **Não autenticado**"
88
-
89
- db_username, pw_hash = row
90
-
91
- if bcrypt.checkpw(password.encode(), pw_hash.encode()):
92
- conn.close()
93
- indicator = f"## ✅ Logado: **{db_username}**"
94
- return db_username, f"✅ Bem-vindo, {db_username}!", indicator
95
-
96
- conn.close()
97
- return None, "❌ Password incorreta", "⚠️ **Não autenticado**"
98
-
99
- def create_user(username, password):
100
- """Cria novo utilizador"""
101
- if not username or not password:
102
- return "❌ Preencha todos os campos"
103
-
104
- if len(password) < 6:
105
- return "❌ Password deve ter pelo menos 6 caracteres"
106
-
107
- conn = get_conn()
108
- cur = conn.cursor()
109
- pw_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
110
-
111
- try:
112
- cur.execute("INSERT INTO users (username, password_hash) VALUES (?, ?)", (username, pw_hash))
113
- conn.commit()
114
- return f"✅ Utilizador '{username}' criado! Faça login agora."
115
- except sqlite3.IntegrityError:
116
- return f"⚠️ Utilizador '{username}' já existe"
117
- finally:
118
- conn.close()
119
-
120
- # ======================================================
121
- # CRUD Tarefas
122
- # ======================================================
123
- def get_task_codes(username):
124
- """Lista códigos de tarefas do utilizador"""
125
- if username is None:
126
- return []
127
- conn = get_conn()
128
- cur = conn.cursor()
129
- cur.execute("SELECT task_code FROM tasks WHERE username = ? ORDER BY id DESC", (username,))
130
- codes = [row[0] for row in cur.fetchall()]
131
- conn.close()
132
- return codes
133
-
134
- def add_task(username, task_name, start_date, start_time, end_date, end_time):
135
- """Adiciona nova tarefa"""
136
- if username is None:
137
- return "", "❌ Faça login primeiro", gr.Dropdown(choices=[])
138
-
139
- if not task_name or not task_name.strip():
140
- return "", "❌ Preencha a descrição da tarefa", gr.Dropdown(choices=[])
141
-
142
- try:
143
- planned_hours = calcular_horas(start_date, start_time, end_date, end_time)
144
- except ValueError as e:
145
- return "", f"❌ {str(e)}", gr.Dropdown(choices=[])
146
-
147
- conn = get_conn()
148
- cur = conn.cursor()
149
-
150
- try:
151
- # Gerar código com username
152
- cur.execute("SELECT COUNT(*) FROM tasks WHERE username = ?", (username,))
153
- seq = cur.fetchone()[0] + 1
154
- task_code = f"TAR_{username}_{seq:04d}"
155
-
156
- # Limpar formato de data
157
- if isinstance(start_date, str) and " " in start_date:
158
- start_date = start_date.split()[0]
159
- if isinstance(end_date, str) and " " in end_date:
160
- end_date = end_date.split()[0]
161
-
162
- cur.execute("""
163
- INSERT INTO tasks (username, task_code, task_name, task_date, start_time, end_time, planned_hours)
164
- VALUES (?, ?, ?, ?, ?, ?, ?)
165
- """, (username, task_code, task_name, start_date,
166
- f"{start_date} {start_time}:00",
167
- f"{end_date} {end_time}:00",
168
- planned_hours))
169
-
170
- conn.commit()
171
-
172
- # Atualizar lista
173
- task_codes = get_task_codes(username)
174
-
175
- return f"{planned_hours} h", f"✅ Tarefa {task_code} criada!", gr.Dropdown(choices=task_codes, value=task_code)
176
-
177
- except Exception as e:
178
- conn.rollback()
179
- return "", f"❌ Erro: {e}", gr.Dropdown(choices=[])
180
- finally:
181
- conn.close()
182
-
183
- def get_task_by_code(username, task_code):
184
- """Busca tarefa por código"""
185
- if not task_code or username is None:
186
- return "", "", "09:00", "", "17:00", ""
187
-
188
- conn = get_conn()
189
- cur = conn.cursor()
190
- cur.execute("""
191
- SELECT task_name, start_time, end_time, planned_hours
192
- FROM tasks
193
- WHERE username = ? AND task_code = ?
194
- """, (username, task_code))
195
- row = cur.fetchone()
196
- conn.close()
197
-
198
- if not row:
199
- return "", "", "09:00", "", "17:00", ""
200
-
201
- task_name, start_time, end_time, planned_hours = row
202
-
203
- # Parse timestamps
204
- start_dt = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
205
- end_dt = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
206
-
207
- return (
208
- task_name,
209
- start_dt.strftime("%Y-%m-%d"),
210
- start_dt.strftime("%H:%M"),
211
- end_dt.strftime("%Y-%m-%d"),
212
- end_dt.strftime("%H:%M"),
213
- f"{planned_hours} h"
214
- )
215
-
216
- def update_task(username, task_code, task_name, start_date, start_time, end_date, end_time):
217
- """Atualiza tarefa existente"""
218
- if username is None:
219
- return "❌ Faça login primeiro"
220
-
221
- if not task_code:
222
- return "❌ Selecione uma tarefa"
223
-
224
- if not task_name or not task_name.strip():
225
- return "❌ Preencha a descrição"
226
-
227
- try:
228
- planned_hours = calcular_horas(start_date, start_time, end_date, end_time)
229
- except ValueError as e:
230
- return f"❌ {str(e)}"
231
-
232
- conn = get_conn()
233
- cur = conn.cursor()
234
-
235
- try:
236
- # Limpar formato de data
237
- if isinstance(start_date, str) and " " in start_date:
238
- start_date = start_date.split()[0]
239
- if isinstance(end_date, str) and " " in end_date:
240
- end_date = end_date.split()[0]
241
-
242
- cur.execute("""
243
- UPDATE tasks
244
- SET task_name = ?, task_date = ?, start_time = ?, end_time = ?, planned_hours = ?
245
- WHERE username = ? AND task_code = ?
246
- """, (task_name, start_date,
247
- f"{start_date} {start_time}:00",
248
- f"{end_date} {end_time}:00",
249
- planned_hours, username, task_code))
250
-
251
- if cur.rowcount == 0:
252
- return "❌ Tarefa não encontrada"
253
-
254
- conn.commit()
255
- return f"✅ Tarefa {task_code} atualizada!"
256
-
257
- except Exception as e:
258
- conn.rollback()
259
- return f"❌ Erro: {e}"
260
- finally:
261
- conn.close()
262
-
263
- def delete_task(username, task_code):
264
- """Elimina tarefa"""
265
- if username is None:
266
- return "❌ Faça login primeiro"
267
-
268
- if not task_code:
269
- return "❌ Selecione uma tarefa"
270
-
271
- conn = get_conn()
272
- cur = conn.cursor()
273
-
274
- try:
275
- cur.execute("DELETE FROM tasks WHERE username = ? AND task_code = ?", (username, task_code))
276
-
277
- if cur.rowcount == 0:
278
- return "❌ Tarefa não encontrada"
279
-
280
- conn.commit()
281
- return f"✅ Tarefa {task_code} eliminada!"
282
-
283
- except Exception as e:
284
- conn.rollback()
285
- return f"❌ Erro: {e}"
286
- finally:
287
- conn.close()
288
-
289
- def list_tasks(username):
290
- """Lista tarefas do utilizador"""
291
- if username is None:
292
- return pd.DataFrame()
293
-
294
- conn = get_conn()
295
-
296
- df = pd.read_sql_query("""
297
- SELECT
298
- t.task_code AS 'Código',
299
- t.task_name AS 'Tarefa',
300
- t.start_time AS 'Início',
301
- t.end_time AS 'Fim',
302
- t.planned_hours AS 'Horas Previstas',
303
- COALESCE(SUM(s.planned_hours), 0) AS 'Horas Subtarefas',
304
- COUNT(s.id) AS 'Nº Subtarefas'
305
- FROM tasks t
306
- LEFT JOIN subtasks s ON t.id = s.task_id
307
- WHERE t.username = ?
308
- GROUP BY t.id
309
- ORDER BY t.id DESC
310
- """, conn, params=(username,))
311
-
312
- conn.close()
313
-
314
- if not df.empty:
315
- df['Status'] = df.apply(
316
- lambda row: '🔴 EXCEDIDO' if row['Horas Subtarefas'] > row['Horas Previstas'] else '🟢 OK',
317
- axis=1
318
- )
319
-
320
- return df
321
-
322
- # ======================================================
323
- # CRUD Subtarefas
324
- # ======================================================
325
- def get_subtask_codes(username, task_code):
326
- """Lista códigos de subtarefas"""
327
- if username is None or not task_code:
328
- return []
329
-
330
- conn = get_conn()
331
- cur = conn.cursor()
332
- cur.execute("""
333
- SELECT s.subtask_code
334
- FROM subtasks s
335
- JOIN tasks t ON s.task_id = t.id
336
- WHERE t.username = ? AND t.task_code = ?
337
- ORDER BY s.id DESC
338
- """, (username, task_code))
339
- codes = [row[0] for row in cur.fetchall()]
340
- conn.close()
341
- return codes
342
-
343
- def add_subtask(username, task_code, sub_name, start_date, start_time, end_date, end_time):
344
- """Adiciona subtarefa"""
345
- if username is None:
346
- return "", "❌ Faça login primeiro"
347
-
348
- if not task_code:
349
- return "", "❌ Selecione uma tarefa"
350
-
351
- if not sub_name or not sub_name.strip():
352
- return "", "❌ Preencha a descrição"
353
-
354
- try:
355
- planned_hours = calcular_horas(start_date, start_time, end_date, end_time)
356
- except ValueError as e:
357
- return "", f"❌ {str(e)}"
358
-
359
- conn = get_conn()
360
- cur = conn.cursor()
361
-
362
- try:
363
- # Buscar task_id e horas
364
- cur.execute("SELECT id, planned_hours FROM tasks WHERE username = ? AND task_code = ?",
365
- (username, task_code))
366
- row = cur.fetchone()
367
- if not row:
368
- return "", "❌ Tarefa não encontrada"
369
-
370
- task_id, task_hours = row
371
-
372
- # Verificar limite de 10
373
- cur.execute("SELECT COUNT(*) FROM subtasks WHERE task_id = ?", (task_id,))
374
- count = cur.fetchone()[0]
375
- if count >= 10:
376
- return "", "❌ Limite de 10 subtarefas atingido"
377
-
378
- # Gerar código
379
- subtask_code = f"SUB_{task_id}_{count+1:02d}"
380
-
381
- # Limpar formato de data
382
- if isinstance(start_date, str) and " " in start_date:
383
- start_date = start_date.split()[0]
384
- if isinstance(end_date, str) and " " in end_date:
385
- end_date = end_date.split()[0]
386
-
387
- cur.execute("""
388
- INSERT INTO subtasks (task_id, subtask_code, subtask_name, start_time, end_time, planned_hours)
389
- VALUES (?, ?, ?, ?, ?, ?)
390
- """, (task_id, subtask_code, sub_name,
391
- f"{start_date} {start_time}:00",
392
- f"{end_date} {end_time}:00",
393
- planned_hours))
394
-
395
- conn.commit()
396
-
397
- # Verificar total
398
- cur.execute("SELECT COALESCE(SUM(planned_hours), 0) FROM subtasks WHERE task_id = ?", (task_id,))
399
- total_sub = cur.fetchone()[0]
400
-
401
- if total_sub > task_hours:
402
- msg = f"⚠️ Subtarefa {subtask_code} criada, mas TOTAL EXCEDE ({total_sub}h / {task_hours}h)"
403
- else:
404
- msg = f"✅ Subtarefa {subtask_code} criada! ({total_sub}h / {task_hours}h)"
405
-
406
- return f"{planned_hours} h", msg
407
-
408
- except Exception as e:
409
- conn.rollback()
410
- return "", f"❌ Erro: {e}"
411
- finally:
412
- conn.close()
413
-
414
- def delete_subtask(username, task_code, subtask_code):
415
- """Elimina subtarefa"""
416
- if username is None:
417
- return "❌ Faça login primeiro"
418
-
419
- if not task_code or not subtask_code:
420
- return "❌ Selecione tarefa e subtarefa"
421
-
422
- conn = get_conn()
423
- cur = conn.cursor()
424
-
425
- try:
426
- # Buscar task_id
427
- cur.execute("SELECT id FROM tasks WHERE username = ? AND task_code = ?", (username, task_code))
428
- row = cur.fetchone()
429
- if not row:
430
- return "❌ Tarefa não encontrada"
431
-
432
- task_id = row[0]
433
-
434
- cur.execute("DELETE FROM subtasks WHERE task_id = ? AND subtask_code = ?", (task_id, subtask_code))
435
-
436
- if cur.rowcount == 0:
437
- return "❌ Subtarefa não encontrada"
438
-
439
- conn.commit()
440
- return f"✅ Subtarefa {subtask_code} eliminada!"
441
-
442
- except Exception as e:
443
- conn.rollback()
444
- return f"❌ Erro: {e}"
445
- finally:
446
- conn.close()
447
-
448
- def list_subtasks(username, task_code):
449
- """Lista subtarefas de uma tarefa"""
450
- if username is None or not task_code:
451
- return pd.DataFrame()
452
-
453
- conn = get_conn()
454
- cur = conn.cursor()
455
-
456
- # Buscar task_id e horas
457
- cur.execute("SELECT id, planned_hours FROM tasks WHERE username = ? AND task_code = ?",
458
- (username, task_code))
459
- row = cur.fetchone()
460
- if not row:
461
- conn.close()
462
- return pd.DataFrame()
463
-
464
- task_id, task_hours = row
465
-
466
- df = pd.read_sql_query("""
467
- SELECT
468
- subtask_code AS 'Código',
469
- subtask_name AS 'Subtarefa',
470
- start_time AS 'Início',
471
- end_time AS 'Fim',
472
- planned_hours AS 'Horas'
473
- FROM subtasks
474
- WHERE task_id = ?
475
- ORDER BY id DESC
476
- """, conn, params=(task_id,))
477
-
478
- conn.close()
479
-
480
- if not df.empty:
481
- total_sub = df['Horas'].sum()
482
- exceeded = total_sub > task_hours
483
- df['Status'] = '🔴 EXCEDIDO' if exceeded else '🟢 OK'
484
-
485
- return df
486
-
487
- # ======================================================
488
- # Gráfico Gantt
489
- # ======================================================
490
- def generate_gantt(username, selected_task_code=None):
491
- """Gera gráfico Gantt - mostra tarefas ou subtarefas"""
492
- if username is None:
493
- return go.Figure().add_annotation(
494
- text="⚠️ Faça login primeiro",
495
- xref="paper", yref="paper",
496
- x=0.5, y=0.5, showarrow=False,
497
- font=dict(size=20)
498
- )
499
-
500
- conn = get_conn()
501
-
502
- # Se nenhuma tarefa selecionada, mostrar todas as tarefas
503
- if not selected_task_code:
504
- df = pd.read_sql_query("""
505
- SELECT
506
- task_code,
507
- task_name,
508
- start_time,
509
- end_time
510
- FROM tasks
511
- WHERE username = ?
512
- ORDER BY start_time
513
- """, conn, params=(username,))
514
-
515
- conn.close()
516
-
517
- if df.empty:
518
- return go.Figure().add_annotation(
519
- text="Nenhuma tarefa encontrada",
520
- xref="paper", yref="paper",
521
- x=0.5, y=0.5, showarrow=False,
522
- font=dict(size=16)
523
- )
524
-
525
- # Preparar dados para Gantt
526
- gantt_data = []
527
- for _, row in df.iterrows():
528
- start = datetime.strptime(row['start_time'], "%Y-%m-%d %H:%M:%S")
529
- end = datetime.strptime(row['end_time'], "%Y-%m-%d %H:%M:%S")
530
-
531
- gantt_data.append(dict(
532
- Task=row['task_name'][:30], # Limitar tamanho
533
- Start=start.strftime("%Y-%m-%d"),
534
- Finish=end.strftime("%Y-%m-%d"),
535
- Resource=row['task_code']
536
- ))
537
-
538
- fig = ff.create_gantt(
539
- gantt_data,
540
- index_col='Resource',
541
- show_colorbar=True,
542
- group_tasks=True,
543
- title=f"📊 Gantt - Tarefas de {username}"
544
- )
545
-
546
- fig.update_layout(
547
- xaxis_title="Data",
548
- height=max(500, 200 + len(gantt_data) * 50),
549
- margin=dict(l=400, r=50, t=80, b=50),
550
- hovermode='closest',
551
- yaxis=dict(automargin=True)
552
- )
553
-
554
- return fig
555
-
556
- # Se tarefa selecionada, mostrar tarefa + subtarefas
557
- else:
558
- # Buscar tarefa
559
- cur = conn.cursor()
560
- cur.execute("""
561
- SELECT id, task_name, start_time, end_time
562
- FROM tasks
563
- WHERE username = ? AND task_code = ?
564
- """, (username, selected_task_code))
565
- task_row = cur.fetchone()
566
-
567
- if not task_row:
568
- conn.close()
569
- return go.Figure().add_annotation(
570
- text="Tarefa não encontrada",
571
- xref="paper", yref="paper",
572
- x=0.5, y=0.5, showarrow=False
573
- )
574
-
575
- task_id, task_name, task_start, task_end = task_row
576
-
577
- # Buscar subtarefas
578
- df_sub = pd.read_sql_query("""
579
- SELECT
580
- subtask_code,
581
- subtask_name,
582
- start_time,
583
- end_time
584
- FROM subtasks
585
- WHERE task_id = ?
586
- ORDER BY start_time
587
- """, conn, params=(task_id,))
588
-
589
- conn.close()
590
-
591
- # Preparar dados
592
- gantt_data = []
593
-
594
- # Adicionar tarefa principal
595
- task_start_dt = datetime.strptime(task_start, "%Y-%m-%d %H:%M:%S")
596
- task_end_dt = datetime.strptime(task_end, "%Y-%m-%d %H:%M:%S")
597
-
598
- gantt_data.append(dict(
599
- Task=f"📌 {task_name[:30]}",
600
- Start=task_start_dt.strftime("%Y-%m-%d"),
601
- Finish=task_end_dt.strftime("%Y-%m-%d"),
602
- Resource=selected_task_code
603
- ))
604
-
605
- # Adicionar subtarefas
606
- for _, row in df_sub.iterrows():
607
- start = datetime.strptime(row['start_time'], "%Y-%m-%d %H:%M:%S")
608
- end = datetime.strptime(row['end_time'], "%Y-%m-%d %H:%M:%S")
609
-
610
- gantt_data.append(dict(
611
- Task=f" └─ {row['subtask_name'][:25]}",
612
- Start=start.strftime("%Y-%m-%d"),
613
- Finish=end.strftime("%Y-%m-%d"),
614
- Resource=row['subtask_code']
615
- ))
616
-
617
- if len(gantt_data) == 1:
618
- # Só tarefa, sem subtarefas
619
- fig = ff.create_gantt(
620
- gantt_data,
621
- index_col='Resource',
622
- show_colorbar=False,
623
- title=f"📊 Gantt - {selected_task_code} (sem subtarefas)"
624
- )
625
- else:
626
- fig = ff.create_gantt(
627
- gantt_data,
628
- index_col='Resource',
629
- show_colorbar=True,
630
- group_tasks=True,
631
- title=f"📊 Gantt - {selected_task_code} com {len(gantt_data)-1} subtarefas"
632
- )
633
-
634
- fig.update_layout(
635
- xaxis_title="Data",
636
- height=max(500, 200 + len(gantt_data) * 50),
637
- margin=dict(l=600, r=50, t=80, b=50),
638
- hovermode='closest',
639
- yaxis=dict(
640
- automargin=False,
641
- tickfont=dict(size=10),
642
- tickmode='linear',
643
- side='left'
644
- ),
645
- xaxis=dict(domain=[0, 1])
646
- )
647
-
648
- return fig
649
-
650
-
651
-
652
- # ======================================================
653
- # Interface Gradio
654
- # ======================================================
655
- with gr.Blocks(title="Time Tracking") as demo:
656
-
657
- gr.Markdown("# ⛏️ Time Tracking")
658
-
659
- user_state = gr.State(None) # Armazena username
660
- login_indicator = gr.Markdown("## ⚠️ **Não autenticado**")
661
-
662
- # ===== TAB: Login =====
663
- with gr.Tab("🔐 Login"):
664
- with gr.Row():
665
- with gr.Column():
666
- gr.Markdown("### Entrar")
667
- login_user_input = gr.Textbox(label="Utilizador")
668
- login_pass_input = gr.Textbox(label="Password", type="password")
669
- login_msg = gr.Markdown()
670
- login_btn = gr.Button("Entrar", variant="primary")
671
-
672
- with gr.Column():
673
- gr.Markdown("### Criar Conta")
674
- new_user_input = gr.Textbox(label="Novo utilizador")
675
- new_pass_input = gr.Textbox(label="Password", type="password")
676
- create_msg = gr.Markdown()
677
- create_btn = gr.Button("Criar", variant="secondary")
678
-
679
- login_btn.click(
680
- login_user,
681
- [login_user_input, login_pass_input],
682
- [user_state, login_msg, login_indicator]
683
- )
684
-
685
- create_btn.click(
686
- create_user,
687
- [new_user_input, new_pass_input],
688
- create_msg
689
- )
690
-
691
- # ===== TAB: Gerir Tarefas =====
692
- with gr.Tab("📝 Gerir Tarefas"):
693
- with gr.Row():
694
- edit_task_code = gr.Dropdown(label="Selecionar tarefa (vazio = nova)", choices=[])
695
- refresh_tasks_btn = gr.Button("🔄", size="sm")
696
-
697
- task_name = gr.Textbox(label="Descrição", lines=2)
698
-
699
- with gr.Row():
700
- with gr.Column():
701
- task_start_date = gr.DateTime(label="📅 Data início", value=datetime.now().strftime("%Y-%m-%d"), include_time=False, type="string")
702
- task_start_time = gr.Dropdown(label="Hora início", choices=slots_30_min(), value="09:00")
703
- with gr.Column():
704
- task_end_date = gr.DateTime(label="📅 Data fim", value=datetime.now().strftime("%Y-%m-%d"), include_time=False, type="string")
705
- task_end_time = gr.Dropdown(label="Hora fim", choices=slots_30_min(), value="17:00")
706
-
707
- task_hours_box = gr.Textbox(label="Horas previstas", interactive=False)
708
- task_msg = gr.Markdown()
709
-
710
- with gr.Row():
711
- save_task_btn = gr.Button("💾 Guardar Nova", variant="primary")
712
- update_task_btn = gr.Button("✏️ Atualizar", variant="secondary")
713
- delete_task_btn = gr.Button("🗑️ Eliminar", variant="stop")
714
-
715
- # Eventos
716
- for comp in [task_start_date, task_start_time, task_end_date, task_end_time]:
717
- comp.change(calcular_horas_preview, [task_start_date, task_start_time, task_end_date, task_end_time], task_hours_box)
718
-
719
- edit_task_code.change(
720
- get_task_by_code,
721
- [user_state, edit_task_code],
722
- [task_name, task_start_date, task_start_time, task_end_date, task_end_time, task_hours_box]
723
- )
724
-
725
- refresh_tasks_btn.click(lambda u: gr.Dropdown(choices=get_task_codes(u)), user_state, edit_task_code)
726
-
727
- save_task_btn.click(
728
- add_task,
729
- [user_state, task_name, task_start_date, task_start_time, task_end_date, task_end_time],
730
- [task_hours_box, task_msg, edit_task_code]
731
- )
732
-
733
- update_task_btn.click(
734
- update_task,
735
- [user_state, edit_task_code, task_name, task_start_date, task_start_time, task_end_date, task_end_time],
736
- task_msg
737
- )
738
-
739
- delete_task_btn.click(delete_task, [user_state, edit_task_code], task_msg)
740
-
741
- # ===== TAB: Gerir Subtarefas =====
742
- with gr.Tab("📋 Gerir Subtarefas"):
743
- with gr.Row():
744
- sub_task_code = gr.Dropdown(label="Tarefa", choices=[])
745
- refresh_sub_btn = gr.Button("🔄", size="sm")
746
-
747
- sub_name = gr.Textbox(label="Descrição", lines=2)
748
-
749
- with gr.Row():
750
- with gr.Column():
751
- sub_start_date = gr.DateTime(label="📅 Data início", value=datetime.now().strftime("%Y-%m-%d"), include_time=False, type="string")
752
- sub_start_time = gr.Dropdown(label="Hora início", choices=slots_30_min(), value="09:00")
753
- with gr.Column():
754
- sub_end_date = gr.DateTime(label="📅 Data fim", value=datetime.now().strftime("%Y-%m-%d"), include_time=False, type="string")
755
- sub_end_time = gr.Dropdown(label="Hora fim", choices=slots_30_min(), value="10:00")
756
-
757
- sub_hours_box = gr.Textbox(label="Horas", interactive=False)
758
- sub_msg = gr.Markdown()
759
- save_sub_btn = gr.Button("💾 Guardar Subtarefa", variant="primary")
760
-
761
- gr.Markdown("---\n### Eliminar Subtarefa")
762
- with gr.Row():
763
- del_task_code = gr.Dropdown(label="Tarefa", choices=[])
764
- del_sub_code = gr.Dropdown(label="Subtarefa", choices=[])
765
- refresh_del_btn = gr.Button("🔄", size="sm")
766
-
767
- del_msg = gr.Markdown()
768
- del_sub_btn = gr.Button("🗑️ Eliminar", variant="stop")
769
-
770
- # Eventos
771
- for comp in [sub_start_date, sub_start_time, sub_end_date, sub_end_time]:
772
- comp.change(calcular_horas_preview, [sub_start_date, sub_start_time, sub_end_date, sub_end_time], sub_hours_box)
773
-
774
- refresh_sub_btn.click(lambda u: gr.Dropdown(choices=get_task_codes(u)), user_state, sub_task_code)
775
-
776
- save_sub_btn.click(
777
- add_subtask,
778
- [user_state, sub_task_code, sub_name, sub_start_date, sub_start_time, sub_end_date, sub_end_time],
779
- [sub_hours_box, sub_msg]
780
- )
781
-
782
- refresh_del_btn.click(lambda u: gr.Dropdown(choices=get_task_codes(u)), user_state, del_task_code)
783
- del_task_code.change(lambda u, tc: gr.Dropdown(choices=get_subtask_codes(u, tc)), [user_state, del_task_code], del_sub_code)
784
-
785
- del_sub_btn.click(delete_subtask, [user_state, del_task_code, del_sub_code], del_msg)
786
-
787
- # ===== TAB: Visualizar Tarefas =====
788
- with gr.Tab("📊 Visualizar Tarefas"):
789
- tasks_table = gr.Dataframe()
790
- refresh_view_btn = gr.Button("🔄 Atualizar")
791
- refresh_view_btn.click(list_tasks, user_state, tasks_table)
792
-
793
- # ===== TAB: Visualizar Subtarefas =====
794
- with gr.Tab("📋 Visualizar Subtarefas"):
795
- view_task_code = gr.Dropdown(label="Tarefa", choices=[])
796
- refresh_view_sub_btn = gr.Button("🔄 Atualizar lista")
797
- subtasks_table = gr.Dataframe()
798
-
799
- refresh_view_sub_btn.click(lambda u: gr.Dropdown(choices=get_task_codes(u)), user_state, view_task_code)
800
- view_task_code.change(list_subtasks, [user_state, view_task_code], subtasks_table)
801
-
802
- # ===== TAB: Gráfico Gantt =====
803
- with gr.Tab("📊 Gráfico Gantt"):
804
- gr.Markdown("""
805
- ### 📊 Gráfico de Gantt
806
-
807
- **Modo 1**: Deixe vazio para ver **todas as tarefas**
808
- **Modo 2**: Selecione uma tarefa para ver **tarefa + subtarefas**
809
- """)
810
-
811
- gantt_task_select = gr.Dropdown(label="🔍 Selecionar tarefa (opcional - deixe vazio para ver todas)", choices=[], allow_custom_value=False)
812
-
813
- with gr.Row():
814
- gantt_refresh_btn = gr.Button("🔄 Atualizar lista")
815
- gantt_clear_btn = gr.Button("❌ Limpar seleção")
816
- gantt_generate_btn = gr.Button("📊 Gerar Gantt", variant="primary")
817
-
818
- gantt_plot = gr.Plot(label="Gráfico")
819
-
820
- # Atualizar dropdown de tarefas
821
- gantt_refresh_btn.click(
822
- lambda u: gr.Dropdown(choices=get_task_codes(u)),
823
- user_state,
824
- gantt_task_select
825
- )
826
-
827
- # Limpar seleção (volta para modo "todas as tarefas")
828
- gantt_clear_btn.click(
829
- lambda: gr.Dropdown(value=None),
830
- None,
831
- gantt_task_select
832
- )
833
-
834
- # Gerar Gantt (todas as tarefas ou tarefa específica)
835
- gantt_generate_btn.click(
836
- generate_gantt,
837
- [user_state, gantt_task_select],
838
- gantt_plot
839
- )
840
-
841
- if __name__ == "__main__":
842
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
843
-