mrradix commited on
Commit
a24b87e
·
verified ·
1 Parent(s): 972a3bd

Update pages/tasks.py

Browse files
Files changed (1) hide show
  1. pages/tasks.py +423 -523
pages/tasks.py CHANGED
@@ -1,550 +1,450 @@
1
- import gradio as gr
2
- import datetime
3
- from typing import Dict, List, Any, Union, Optional
4
- import random
 
 
5
 
6
  # Import utilities
7
  from utils.storage import load_data, save_data
8
- from utils.state import generate_id, get_timestamp, record_activity
9
- from utils.ai_models import break_down_task, estimate_task_time
10
- from utils.ui_components import create_kanban_board, create_priority_matrix
11
- from utils.config import FILE_PATHS
12
  from utils.logging import get_logger
13
- from utils.error_handling import handle_exceptions, ValidationError, safe_get
14
 
15
- # Initialize logger
16
  logger = get_logger(__name__)
17
 
18
- @handle_exceptions
19
- def create_tasks_page(state: Dict[str, Any]) -> None:
 
 
 
 
20
  """
21
- Create the tasks and projects page with multiple views
22
-
23
- Args:
24
- state: Application state
25
  """
26
- logger.info("Creating tasks page")
27
 
28
- # Create the tasks page layout
29
- with gr.Column(elem_id="tasks-page"):
30
- gr.Markdown("# 📋 Tasks & Projects")
 
 
 
 
31
 
32
- # Task views and actions
33
- with gr.Row():
34
- # View selector
35
- view_selector = gr.Radio(
36
- choices=["Kanban", "List", "Calendar", "Timeline", "Priority Matrix"],
37
- value="Kanban",
38
- label="View",
39
- elem_id="task-view-selector"
40
- )
41
-
42
- # Add task button
43
- add_task_btn = gr.Button("➕ Add Task", elem_classes=["action-button"])
44
 
45
- # Task views container
46
- with gr.Group(elem_id="task-views-container"):
47
- # Kanban View
48
- with gr.Group(elem_id="kanban-view", visible=True) as kanban_view:
49
- create_kanban_board(safe_get(state, "tasks", []))
50
-
51
- # List View
52
- with gr.Group(elem_id="list-view", visible=False) as list_view:
53
- with gr.Column():
54
- # Filter and sort options
55
- with gr.Row():
56
- status_filter = gr.Dropdown(
57
- choices=["All", "To Do", "In Progress", "Done"],
58
- value="All",
59
- label="Status"
60
- )
61
- priority_filter = gr.Dropdown(
62
- choices=["All", "High", "Medium", "Low"],
63
- value="All",
64
- label="Priority"
65
- )
66
- sort_by = gr.Dropdown(
67
- choices=["Created (Newest)", "Created (Oldest)", "Due Date", "Priority"],
68
- value="Created (Newest)",
69
- label="Sort By"
70
- )
71
-
72
- # Task list
73
- task_list = gr.Dataframe(
74
- headers=["Title", "Status", "Priority", "Due Date", "Created"],
75
- datatype=["str", "str", "str", "str", "str"],
76
- col_count=(5, "fixed"),
77
- elem_id="task-list-table"
78
- )
79
-
80
- # Function to update task list based on filters
81
- @handle_exceptions
82
- def update_task_list(status, priority, sort):
83
- """Update the task list based on filters and sort options"""
84
- logger.debug(f"Updating task list with filters: status={status}, priority={priority}, sort={sort}")
85
- tasks = safe_get(state, "tasks", [])
86
-
87
- # Apply status filter
88
- if status != "All":
89
- status_map = {"To Do": "todo", "In Progress": "in_progress", "Done": "done"}
90
- tasks = [t for t in tasks if safe_get(t, "status", "") == status_map.get(status, "")]
91
-
92
- # Apply priority filter
93
- if priority != "All":
94
- tasks = [t for t in tasks if safe_get(t, "priority", "").lower() == priority.lower()]
95
-
96
- # Apply sorting
97
- if sort == "Created (Newest)":
98
- tasks.sort(key=lambda x: safe_get(x, "created_at", ""), reverse=True)
99
- elif sort == "Created (Oldest)":
100
- tasks.sort(key=lambda x: safe_get(x, "created_at", ""))
101
- elif sort == "Due Date":
102
- # Sort by due date, putting tasks without due dates at the end
103
- tasks.sort(
104
- key=lambda x: safe_get(x, "deadline", "9999-12-31T23:59:59")
105
- )
106
- elif sort == "Priority":
107
- # Sort by priority (high, medium, low)
108
- priority_order = {"high": 0, "medium": 1, "low": 2}
109
- tasks.sort(
110
- key=lambda x: priority_order.get(safe_get(x, "priority", "").lower(), 3)
111
- )
112
-
113
- # Format data for the table
114
- table_data = []
115
- for task in tasks:
116
- # Format status
117
- status_map = {"todo": "To Do", "in_progress": "In Progress", "done": "Done"}
118
- status_str = status_map.get(safe_get(task, "status", ""), "To Do")
119
-
120
- # Format priority
121
- priority_str = safe_get(task, "priority", "").capitalize()
122
-
123
- # Format due date
124
- due_date = "None"
125
- if "deadline" in task:
126
- try:
127
- deadline = datetime.datetime.fromisoformat(task["deadline"])
128
- due_date = deadline.strftime("%Y-%m-%d")
129
- except Exception as e:
130
- logger.warning(f"Error formatting deadline: {str(e)}")
131
-
132
- # Format created date
133
- created = "Unknown"
134
- if "created_at" in task:
135
- try:
136
- created_at = datetime.datetime.fromisoformat(task["created_at"])
137
- created = created_at.strftime("%Y-%m-%d")
138
- except Exception as e:
139
- logger.warning(f"Error formatting created_at: {str(e)}")
140
-
141
- table_data.append([
142
- safe_get(task, "title", "Untitled Task"),
143
- status_str,
144
- priority_str,
145
- due_date,
146
- created
147
- ])
148
-
149
- return table_data
150
-
151
- # Update task list when filters change
152
- status_filter.change(
153
- update_task_list,
154
- inputs=[status_filter, priority_filter, sort_by],
155
- outputs=[task_list]
156
- )
157
- priority_filter.change(
158
- update_task_list,
159
- inputs=[status_filter, priority_filter, sort_by],
160
- outputs=[task_list]
161
- )
162
- sort_by.change(
163
- update_task_list,
164
- inputs=[status_filter, priority_filter, sort_by],
165
- outputs=[task_list]
166
- )
167
-
168
- # Initialize task list
169
- task_list.value = update_task_list("All", "All", "Created (Newest)")
170
-
171
- # Calendar View
172
- with gr.Group(elem_id="calendar-view", visible=False) as calendar_view:
173
- gr.Markdown("### 📅 Calendar View")
174
- gr.Markdown("*Calendar view will display tasks with due dates*")
175
-
176
- # Simple calendar implementation (placeholder)
177
- current_month = datetime.datetime.now().strftime("%B %Y")
178
- gr.Markdown(f"## {current_month}")
179
-
180
- # Create a simple calendar grid
181
- days_of_week = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
182
- with gr.Row():
183
- for day in days_of_week:
184
- gr.Markdown(f"**{day}**")
185
-
186
- # Get the current month's calendar
187
- now = datetime.datetime.now()
188
- month_start = datetime.datetime(now.year, now.month, 1)
189
- month_end = (month_start.replace(month=month_start.month+1, day=1) -
190
- datetime.timedelta(days=1))
191
- start_weekday = month_start.weekday()
192
- days_in_month = month_end.day
193
-
194
- # Adjust for Sunday as first day of week
195
- start_weekday = (start_weekday + 1) % 7
196
-
197
- # Create calendar grid
198
- day_counter = 1
199
- for week in range(6): # Maximum 6 weeks in a month
200
- with gr.Row():
201
- for weekday in range(7):
202
- if (week == 0 and weekday < start_weekday) or day_counter > days_in_month:
203
- # Empty cell
204
- gr.Markdown("")
205
- else:
206
- # Get tasks for this day
207
- day_tasks = []
208
- for task in safe_get(state, "tasks", []):
209
- if "deadline" in task:
210
- try:
211
- deadline = datetime.datetime.fromisoformat(task["deadline"])
212
- if (deadline.year == now.year and
213
- deadline.month == now.month and
214
- deadline.day == day_counter):
215
- day_tasks.append(task)
216
- except Exception as e:
217
- logger.warning(f"Error parsing deadline: {str(e)}")
218
-
219
- # Create day cell
220
- with gr.Group(elem_classes=["calendar-day"]):
221
- gr.Markdown(f"**{day_counter}**")
222
-
223
- # Add tasks for this day
224
- for task in day_tasks:
225
- priority_colors = {"high": "red", "medium": "orange", "low": "green"}
226
- priority = safe_get(task, "priority", "").lower()
227
- color = priority_colors.get(priority, "gray")
228
-
229
- gr.Markdown(
230
- f"<span style='color: {color};'>●</span> {safe_get(task, 'title', 'Untitled')}"
231
- )
232
-
233
- day_counter += 1
234
-
235
- if day_counter > days_in_month:
236
- break
237
-
238
- if day_counter > days_in_month:
239
- break
240
-
241
- # Timeline View
242
- with gr.Group(elem_id="timeline-view", visible=False) as timeline_view:
243
- gr.Markdown("### ⏱️ Timeline View")
244
- gr.Markdown("*Timeline view will display tasks in chronological order*")
245
-
246
- # Get tasks with dates
247
- dated_tasks = []
248
- for task in safe_get(state, "tasks", []):
249
- if "deadline" in task or "created_at" in task:
250
- dated_tasks.append(task)
251
-
252
- # Sort by date
253
- dated_tasks.sort(key=lambda x: safe_get(x, "deadline", safe_get(x, "created_at", "")))
254
-
255
- # Group tasks by month
256
- tasks_by_month = {}
257
- for task in dated_tasks:
258
- date_str = safe_get(task, "deadline", safe_get(task, "created_at", ""))
259
- try:
260
- date = datetime.datetime.fromisoformat(date_str)
261
- month_key = date.strftime("%Y-%m")
262
- if month_key not in tasks_by_month:
263
- tasks_by_month[month_key] = []
264
- tasks_by_month[month_key].append((date, task))
265
- except Exception as e:
266
- logger.warning(f"Error parsing date: {str(e)}")
267
-
268
- # Display timeline
269
- for month_key in sorted(tasks_by_month.keys()):
270
- try:
271
- month_date = datetime.datetime.strptime(month_key, "%Y-%m")
272
- month_name = month_date.strftime("%B %Y")
273
- gr.Markdown(f"## {month_name}")
274
-
275
- # Display tasks for this month
276
- for date, task in sorted(tasks_by_month[month_key]):
277
- day_str = date.strftime("%d %b")
278
- status_emoji = "🔴" if safe_get(task, "status") == "todo" else \
279
- "🟡" if safe_get(task, "status") == "in_progress" else "🟢"
280
-
281
- with gr.Group(elem_classes=["timeline-item"]):
282
- with gr.Row():
283
- gr.Markdown(f"**{day_str}**")
284
- gr.Markdown(f"{status_emoji} {safe_get(task, 'title', 'Untitled Task')}")
285
-
286
- if safe_get(task, "description"):
287
- gr.Markdown(task["description"])
288
- except Exception as e:
289
- logger.warning(f"Error displaying timeline month: {str(e)}")
290
-
291
- # Priority Matrix View
292
- with gr.Group(elem_id="priority-matrix-view", visible=False) as priority_matrix_view:
293
- create_priority_matrix(safe_get(state, "tasks", []))
294
 
295
- # Task detail modal (shown when a task is selected)
296
- with gr.Group(elem_id="task-detail-modal", visible=False) as task_detail_modal:
297
- gr.Markdown("## Task Details")
298
-
299
- # Task information
300
- task_title_display = gr.Textbox(label="Title", interactive=True)
301
- task_description_display = gr.Textbox(label="Description", lines=3, interactive=True)
302
- task_status_display = gr.Dropdown(
303
- choices=["To Do", "In Progress", "Done"],
304
- label="Status",
305
- interactive=True
306
- )
307
- task_priority_display = gr.Dropdown(
308
- choices=["High", "Medium", "Low"],
309
- label="Priority",
310
- interactive=True
311
- )
312
- task_deadline_display = gr.Textbox(
313
- label="Deadline (YYYY-MM-DD)",
314
- interactive=True
315
- )
316
-
317
- # Task actions
318
- with gr.Row():
319
- save_task_btn = gr.Button("Save Changes")
320
- delete_task_btn = gr.Button("Delete Task")
321
- close_detail_btn = gr.Button("Close")
322
-
323
- # AI features
324
- with gr.Accordion("AI Features", open=False):
325
- # AI Task Breakdown
326
- gr.Markdown("### 🤖 AI Task Breakdown")
327
- generate_subtasks_btn = gr.Button("Generate Subtasks")
328
- subtasks_display = gr.Markdown("*Click the button to generate subtasks*")
329
-
330
- # AI Time Estimation
331
- gr.Markdown("### ⏱️ AI Time Estimation")
332
- estimate_time_btn = gr.Button("Estimate Time")
333
- time_estimate_display = gr.Markdown("*Click the button to estimate time*")
334
-
335
- # Subtasks
336
- with gr.Group():
337
- gr.Markdown("### Subtasks")
338
- subtask_list = gr.Dataframe(
339
- headers=["Subtask", "Status"],
340
- datatype=["str", "str"],
341
- col_count=(2, "fixed"),
342
- row_count=(0, "dynamic"),
343
- elem_id="subtask-list"
344
- )
345
- add_subtask_btn = gr.Button("Add Subtask")
346
 
347
- # Add task modal
348
- with gr.Group(elem_id="add-task-modal", visible=False) as add_task_modal:
349
- gr.Markdown("## Add New Task")
350
-
351
- # Task information inputs
352
- new_task_title = gr.Textbox(label="Title", placeholder="Enter task title")
353
- new_task_description = gr.Textbox(
354
- label="Description",
355
- placeholder="Enter task description",
356
- lines=3
357
- )
358
- new_task_status = gr.Dropdown(
359
- choices=["To Do", "In Progress", "Done"],
360
- label="Status",
361
- value="To Do"
362
- )
363
- new_task_priority = gr.Dropdown(
364
- choices=["High", "Medium", "Low"],
365
- label="Priority",
366
- value="Medium"
367
- )
368
- new_task_deadline = gr.Textbox(
369
- label="Deadline (YYYY-MM-DD)",
370
- placeholder="YYYY-MM-DD"
371
- )
372
-
373
- # Task categorization
374
- with gr.Accordion("Smart Categorization", open=False):
375
- gr.Markdown("### 🏷️ Tags")
376
- new_task_tags = gr.Textbox(
377
- label="Tags (comma separated)",
378
- placeholder="work, project, urgent"
379
- )
380
-
381
- gr.Markdown("### 📂 Project")
382
- new_task_project = gr.Dropdown(
383
- choices=["None", "Work", "Personal", "Study", "Health"],
384
- label="Project",
385
- value="None"
386
- )
387
-
388
- # Task actions
389
- with gr.Row():
390
- create_task_btn = gr.Button("Create Task")
391
- cancel_add_btn = gr.Button("Cancel")
 
 
 
 
 
392
 
393
- # Function to switch between views
394
- @handle_exceptions
395
- def switch_view(view):
396
- """Switch between different task views"""
397
- logger.info(f"Switching to {view} view")
398
- views = {
399
- "Kanban": kanban_view,
400
- "List": list_view,
401
- "Calendar": calendar_view,
402
- "Timeline": timeline_view,
403
- "Priority Matrix": priority_matrix_view
404
- }
405
-
406
- return [gr.update(visible=(view_name == view)) for view_name, view_component in views.items()]
407
 
408
- # Set up view switching
409
- view_outputs = [kanban_view, list_view, calendar_view, timeline_view, priority_matrix_view]
410
- view_selector.change(switch_view, inputs=[view_selector], outputs=view_outputs)
 
411
 
412
- # Function to show add task modal
413
- @handle_exceptions
414
- def show_add_task_modal():
415
- """Show the add task modal"""
416
- logger.debug("Showing add task modal")
417
- return gr.update(visible=True)
418
 
419
- # Function to hide add task modal
420
- @handle_exceptions
421
- def hide_add_task_modal():
422
- """Hide the add task modal"""
423
- logger.debug("Hiding add task modal")
424
- return gr.update(visible=False)
425
 
426
- # Set up add task modal
427
- add_task_btn.click(show_add_task_modal, inputs=[], outputs=[add_task_modal])
428
- cancel_add_btn.click(hide_add_task_modal, inputs=[], outputs=[add_task_modal])
429
 
430
- # Function to create a new task
431
- @handle_exceptions
432
- def create_task(title, description, status, priority, deadline, tags, project):
433
- """Create a new task"""
434
- if not title.strip():
435
- logger.warning("Attempted to create task with empty title")
436
- return "Please enter a task title", gr.update(visible=True)
437
-
438
- logger.info(f"Creating new task: {title}")
439
-
440
- # Validate deadline format if provided
441
- deadline_iso = None
442
- if deadline.strip():
443
- try:
444
- deadline_date = datetime.datetime.strptime(deadline.strip(), "%Y-%m-%d")
445
- deadline_iso = deadline_date.isoformat()
446
- except ValueError as e:
447
- logger.warning(f"Invalid date format: {deadline}, error: {str(e)}")
448
- return "Invalid date format. Use YYYY-MM-DD", gr.update(visible=True)
449
-
450
- # Map status to internal representation
451
- status_map = {"To Do": "todo", "In Progress": "in_progress", "Done": "done"}
452
-
453
- # Create new task
454
- new_task = {
455
- "id": generate_id(),
456
- "title": title.strip(),
457
- "description": description.strip(),
458
- "status": status_map.get(status, "todo"),
459
- "priority": priority.lower(),
460
- "completed": status == "Done",
461
- "created_at": get_timestamp()
462
- }
463
-
464
- if deadline_iso:
465
- new_task["deadline"] = deadline_iso
466
-
467
- # Add tags if provided
468
- if tags.strip():
469
- new_task["tags"] = [tag.strip() for tag in tags.split(",") if tag.strip()]
470
-
471
- # Add project if selected
472
- if project != "None":
473
- new_task["project"] = project
474
-
475
- # Add to state
476
- state["tasks"].append(new_task)
477
- state["stats"]["tasks_total"] += 1
478
- if status == "Done":
479
- state["stats"]["tasks_completed"] += 1
480
- new_task["completed_at"] = get_timestamp()
481
-
482
- # Record activity
483
- record_activity({
484
- "type": "task_created",
485
- "title": title,
486
- "timestamp": datetime.datetime.now().isoformat()
487
- })
488
-
489
- # Save to file
490
- save_data(FILE_PATHS["tasks"], state["tasks"])
491
-
492
- # Reset form and hide modal
493
- return "Task created successfully!", gr.update(visible=False)
494
 
495
- # Set up create task action
496
- create_task_btn.click(
497
- create_task,
498
- inputs=[
499
- new_task_title, new_task_description, new_task_status,
500
- new_task_priority, new_task_deadline, new_task_tags, new_task_project
501
- ],
502
- outputs=[gr.Markdown(visible=False), add_task_modal]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
 
505
- # Function to generate subtasks using AI
506
- @handle_exceptions
507
- def generate_subtasks(title, description):
508
- """Generate subtasks using AI"""
509
- logger.info(f"Generating subtasks for task: {title}")
510
- subtasks = break_down_task(title, description)
511
-
512
- # Format subtasks as markdown list
513
- subtasks_md = "**Suggested Subtasks:**\n\n"
514
- for subtask in subtasks:
515
- subtasks_md += f"- {subtask}\n"
516
-
517
- return subtasks_md
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
519
- # Set up generate subtasks action
520
- generate_subtasks_btn.click(
521
- generate_subtasks,
522
- inputs=[task_title_display, task_description_display],
523
- outputs=[subtasks_display]
524
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
526
- # Function to estimate task time using AI
527
- @handle_exceptions
528
- def estimate_task_time_ai(title, description):
529
- """Estimate task time using AI"""
530
- logger.info(f"Estimating time for task: {title}")
531
- minutes = estimate_task_time(title, description)
532
-
533
- # Format time estimate
534
- if minutes < 60:
535
- time_str = f"{minutes} minutes"
536
- else:
537
- hours = minutes // 60
538
- remaining_minutes = minutes % 60
539
- time_str = f"{hours} hour{'s' if hours > 1 else ''}"
540
- if remaining_minutes > 0:
541
- time_str += f" {remaining_minutes} minute{'s' if remaining_minutes > 1 else ''}"
542
-
543
- return f"**Estimated Time:** {time_str}"
544
 
545
- # Set up estimate time action
546
- estimate_time_btn.click(
547
- estimate_task_time_ai,
548
- inputs=[task_title_display, task_description_display],
549
- outputs=[time_estimate_display]
550
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from datetime import datetime, timedelta
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from typing import Dict, List, Any, Optional
7
 
8
  # Import utilities
9
  from utils.storage import load_data, save_data
10
+ from utils.error_handling import show_error, show_success, show_warning, handle_data_exceptions
 
 
 
11
  from utils.logging import get_logger
12
+ from utils.state import generate_id, get_timestamp, record_activity, get_session_state, set_session_state
13
 
 
14
  logger = get_logger(__name__)
15
 
16
+ # Task status options
17
+ TASK_STATUSES = ["Todo", "In Progress", "Review", "Done", "Cancelled"]
18
+ TASK_PRIORITIES = ["Low", "Medium", "High", "Critical"]
19
+ TASK_CATEGORIES = ["Development", "Testing", "Documentation", "Bug Fix", "Feature", "Maintenance"]
20
+
21
+ def create_tasks_page():
22
  """
23
+ Create the tasks management page
 
 
 
24
  """
25
+ st.title("📋 Task Management")
26
 
27
+ # Initialize session state
28
+ if 'tasks' not in st.session_state:
29
+ st.session_state.tasks = load_tasks_data()
30
+
31
+ # Sidebar for task controls
32
+ with st.sidebar:
33
+ st.header("🔧 Task Controls")
34
 
35
+ # Add new task button
36
+ if st.button("➕ Add New Task", use_container_width=True):
37
+ set_session_state('show_add_task', True)
 
 
 
 
 
 
 
 
 
38
 
39
+ # Filter options
40
+ st.subheader("🔍 Filters")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ status_filter = st.multiselect(
43
+ "Filter by Status",
44
+ TASK_STATUSES,
45
+ default=TASK_STATUSES
46
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ priority_filter = st.multiselect(
49
+ "Filter by Priority",
50
+ TASK_PRIORITIES,
51
+ default=TASK_PRIORITIES
52
+ )
53
+
54
+ category_filter = st.multiselect(
55
+ "Filter by Category",
56
+ TASK_CATEGORIES,
57
+ default=TASK_CATEGORIES
58
+ )
59
+
60
+ # Date range filter
61
+ st.subheader("📅 Date Range")
62
+ date_range = st.date_input(
63
+ "Select Date Range",
64
+ value=(datetime.now() - timedelta(days=30), datetime.now()),
65
+ max_value=datetime.now()
66
+ )
67
+
68
+ # Bulk actions
69
+ st.subheader("🔄 Bulk Actions")
70
+ if st.button("Save All Tasks"):
71
+ save_tasks_data(st.session_state.tasks)
72
+ show_success("Tasks saved successfully!")
73
+
74
+ if st.button("Reset Filters"):
75
+ st.experimental_rerun()
76
+
77
+ # Main content area
78
+ if get_session_state('show_add_task', False):
79
+ show_add_task_form()
80
+
81
+ # Task statistics
82
+ show_task_statistics()
83
+
84
+ # Tasks table
85
+ show_tasks_table(status_filter, priority_filter, category_filter, date_range)
86
+
87
+ # Task analytics
88
+ show_task_analytics()
89
+
90
+ def show_add_task_form():
91
+ """
92
+ Display the add task form
93
+ """
94
+ st.subheader("➕ Add New Task")
95
+
96
+ with st.form("add_task_form"):
97
+ col1, col2 = st.columns(2)
98
 
99
+ with col1:
100
+ title = st.text_input("Task Title*", placeholder="Enter task title")
101
+ status = st.selectbox("Status", TASK_STATUSES, index=0)
102
+ priority = st.selectbox("Priority", TASK_PRIORITIES, index=1)
 
 
 
 
 
 
 
 
 
 
103
 
104
+ with col2:
105
+ category = st.selectbox("Category", TASK_CATEGORIES, index=0)
106
+ assignee = st.text_input("Assignee", placeholder="Assign to...")
107
+ due_date = st.date_input("Due Date", value=datetime.now() + timedelta(days=7))
108
 
109
+ description = st.text_area("Description", placeholder="Task description...")
110
+ tags = st.text_input("Tags", placeholder="Enter tags separated by commas")
 
 
 
 
111
 
112
+ col1, col2, col3 = st.columns(3)
 
 
 
 
 
113
 
114
+ with col1:
115
+ submitted = st.form_submit_button("Create Task", type="primary")
 
116
 
117
+ with col2:
118
+ if st.form_submit_button("Cancel"):
119
+ set_session_state('show_add_task', False)
120
+ st.experimental_rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ if submitted:
123
+ if title.strip():
124
+ new_task = create_new_task(
125
+ title=title.strip(),
126
+ description=description.strip(),
127
+ status=status,
128
+ priority=priority,
129
+ category=category,
130
+ assignee=assignee.strip(),
131
+ due_date=due_date,
132
+ tags=tags.strip()
133
+ )
134
+
135
+ st.session_state.tasks.append(new_task)
136
+ save_tasks_data(st.session_state.tasks)
137
+ record_activity("task_created", {"task_id": new_task["id"], "title": title})
138
+
139
+ show_success(f"Task '{title}' created successfully!")
140
+ set_session_state('show_add_task', False)
141
+ st.experimental_rerun()
142
+ else:
143
+ show_error("Task title is required!")
144
+
145
+ def create_new_task(title: str, description: str, status: str, priority: str,
146
+ category: str, assignee: str, due_date, tags: str) -> Dict[str, Any]:
147
+ """
148
+ Create a new task dictionary
149
+ """
150
+ return {
151
+ "id": generate_id("task"),
152
+ "title": title,
153
+ "description": description,
154
+ "status": status,
155
+ "priority": priority,
156
+ "category": category,
157
+ "assignee": assignee,
158
+ "due_date": due_date.isoformat() if due_date else None,
159
+ "created_at": get_timestamp(),
160
+ "updated_at": get_timestamp(),
161
+ "tags": [tag.strip() for tag in tags.split(",") if tag.strip()],
162
+ "completed": status == "Done"
163
+ }
164
+
165
+ def show_task_statistics():
166
+ """
167
+ Display task statistics
168
+ """
169
+ st.subheader("📊 Task Statistics")
170
+
171
+ tasks = st.session_state.tasks
172
+
173
+ if not tasks:
174
+ st.info("No tasks available. Create your first task!")
175
+ return
176
+
177
+ # Calculate statistics
178
+ total_tasks = len(tasks)
179
+ completed_tasks = len([t for t in tasks if t["status"] == "Done"])
180
+ in_progress_tasks = len([t for t in tasks if t["status"] == "In Progress"])
181
+ overdue_tasks = len(get_overdue_tasks(tasks))
182
+
183
+ # Display metrics
184
+ col1, col2, col3, col4 = st.columns(4)
185
+
186
+ with col1:
187
+ st.metric(
188
+ "Total Tasks",
189
+ total_tasks,
190
+ delta=None
191
+ )
192
+
193
+ with col2:
194
+ completion_rate = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0
195
+ st.metric(
196
+ "Completed",
197
+ completed_tasks,
198
+ delta=f"{completion_rate:.1f}%"
199
+ )
200
+
201
+ with col3:
202
+ st.metric(
203
+ "In Progress",
204
+ in_progress_tasks,
205
+ delta=None
206
  )
207
+
208
+ with col4:
209
+ st.metric(
210
+ "Overdue",
211
+ overdue_tasks,
212
+ delta="⚠️" if overdue_tasks > 0 else "✅"
213
+ )
214
+
215
+ def show_tasks_table(status_filter: List[str], priority_filter: List[str],
216
+ category_filter: List[str], date_range):
217
+ """
218
+ Display the tasks table with filtering
219
+ """
220
+ st.subheader("📋 Tasks List")
221
+
222
+ tasks = st.session_state.tasks
223
+
224
+ if not tasks:
225
+ st.info("No tasks to display.")
226
+ return
227
+
228
+ # Apply filters
229
+ filtered_tasks = filter_tasks(tasks, status_filter, priority_filter, category_filter, date_range)
230
+
231
+ if not filtered_tasks:
232
+ st.warning("No tasks match the current filters.")
233
+ return
234
+
235
+ # Convert to DataFrame for display
236
+ df = pd.DataFrame(filtered_tasks)
237
+
238
+ # Select columns to display
239
+ display_columns = ["title", "status", "priority", "category", "assignee", "due_date", "created_at"]
240
+ available_columns = [col for col in display_columns if col in df.columns]
241
+
242
+ # Display the table
243
+ edited_df = st.data_editor(
244
+ df[available_columns],
245
+ use_container_width=True,
246
+ hide_index=True,
247
+ column_config={
248
+ "title": st.column_config.TextColumn("Title", max_chars=50),
249
+ "status": st.column_config.SelectboxColumn(
250
+ "Status",
251
+ options=TASK_STATUSES,
252
+ required=True
253
+ ),
254
+ "priority": st.column_config.SelectboxColumn(
255
+ "Priority",
256
+ options=TASK_PRIORITIES,
257
+ required=True
258
+ ),
259
+ "category": st.column_config.SelectboxColumn(
260
+ "Category",
261
+ options=TASK_CATEGORIES,
262
+ required=True
263
+ ),
264
+ "due_date": st.column_config.DateColumn("Due Date"),
265
+ "created_at": st.column_config.DatetimeColumn("Created")
266
+ },
267
+ num_rows="dynamic"
268
+ )
269
+
270
+ # Handle updates
271
+ if not edited_df.equals(df[available_columns]):
272
+ # Update the original tasks
273
+ for idx, row in edited_df.iterrows():
274
+ if idx < len(filtered_tasks):
275
+ task_id = filtered_tasks[idx]["id"]
276
+ update_task_from_row(task_id, row)
277
 
278
+ save_tasks_data(st.session_state.tasks)
279
+ show_success("Tasks updated successfully!")
280
+
281
+ def show_task_analytics():
282
+ """
283
+ Display task analytics charts
284
+ """
285
+ st.subheader("📈 Task Analytics")
286
+
287
+ tasks = st.session_state.tasks
288
+
289
+ if not tasks:
290
+ return
291
+
292
+ col1, col2 = st.columns(2)
293
+
294
+ with col1:
295
+ # Status distribution
296
+ status_counts = pd.Series([t["status"] for t in tasks]).value_counts()
297
+ fig_status = px.pie(
298
+ values=status_counts.values,
299
+ names=status_counts.index,
300
+ title="Tasks by Status"
301
+ )
302
+ st.plotly_chart(fig_status, use_container_width=True)
303
+
304
+ with col2:
305
+ # Priority distribution
306
+ priority_counts = pd.Series([t["priority"] for t in tasks]).value_counts()
307
+ fig_priority = px.bar(
308
+ x=priority_counts.index,
309
+ y=priority_counts.values,
310
+ title="Tasks by Priority"
311
+ )
312
+ st.plotly_chart(fig_priority, use_container_width=True)
313
+
314
+ # Tasks over time
315
+ tasks_df = pd.DataFrame(tasks)
316
+ if "created_at" in tasks_df.columns:
317
+ tasks_df["created_date"] = pd.to_datetime(tasks_df["created_at"]).dt.date
318
+ daily_counts = tasks_df.groupby("created_date").size().reset_index(name="count")
319
 
320
+ fig_timeline = px.line(
321
+ daily_counts,
322
+ x="created_date",
323
+ y="count",
324
+ title="Tasks Created Over Time"
325
  )
326
+ st.plotly_chart(fig_timeline, use_container_width=True)
327
+
328
+ @handle_data_exceptions
329
+ def load_tasks_data() -> List[Dict[str, Any]]:
330
+ """
331
+ Load tasks from storage
332
+ """
333
+ tasks = load_data("tasks.json", "json")
334
+ if tasks is None:
335
+ # Create sample tasks
336
+ sample_tasks = create_sample_tasks()
337
+ save_data(sample_tasks, "tasks.json", "json")
338
+ return sample_tasks
339
+ return tasks
340
+
341
+ @handle_data_exceptions
342
+ def save_tasks_data(tasks: List[Dict[str, Any]]) -> bool:
343
+ """
344
+ Save tasks to storage
345
+ """
346
+ return save_data(tasks, "tasks.json", "json")
347
+
348
+ def create_sample_tasks() -> List[Dict[str, Any]]:
349
+ """
350
+ Create sample tasks for demonstration
351
+ """
352
+ sample_tasks = [
353
+ create_new_task(
354
+ "Setup project structure",
355
+ "Initialize the project with proper folder structure and configuration files",
356
+ "Done",
357
+ "High",
358
+ "Development",
359
+ "Developer 1",
360
+ datetime.now() - timedelta(days=2),
361
+ "setup, initialization"
362
+ ),
363
+ create_new_task(
364
+ "Implement user authentication",
365
+ "Add login/logout functionality with session management",
366
+ "In Progress",
367
+ "High",
368
+ "Development",
369
+ "Developer 2",
370
+ datetime.now() + timedelta(days=5),
371
+ "auth, security"
372
+ ),
373
+ create_new_task(
374
+ "Write unit tests",
375
+ "Create comprehensive unit tests for core functionality",
376
+ "Todo",
377
+ "Medium",
378
+ "Testing",
379
+ "QA Team",
380
+ datetime.now() + timedelta(days=10),
381
+ "testing, quality"
382
+ )
383
+ ]
384
+
385
+ return sample_tasks
386
+
387
+ def filter_tasks(tasks: List[Dict], status_filter: List[str], priority_filter: List[str],
388
+ category_filter: List[str], date_range) -> List[Dict]:
389
+ """
390
+ Filter tasks based on criteria
391
+ """
392
+ filtered = []
393
+
394
+ for task in tasks:
395
+ # Status filter
396
+ if task["status"] not in status_filter:
397
+ continue
398
 
399
+ # Priority filter
400
+ if task["priority"] not in priority_filter:
401
+ continue
402
+
403
+ # Category filter
404
+ if task["category"] not in category_filter:
405
+ continue
406
+
407
+ # Date filter
408
+ if "created_at" in task and task["created_at"]:
409
+ try:
410
+ task_date = datetime.fromisoformat(task["created_at"]).date()
411
+ if len(date_range) == 2:
412
+ start_date, end_date = date_range
413
+ if not (start_date <= task_date <= end_date):
414
+ continue
415
+ except (ValueError, TypeError):
416
+ continue
417
 
418
+ filtered.append(task)
419
+
420
+ return filtered
421
+
422
+ def get_overdue_tasks(tasks: List[Dict]) -> List[Dict]:
423
+ """
424
+ Get overdue tasks
425
+ """
426
+ overdue = []
427
+ today = datetime.now().date()
428
+
429
+ for task in tasks:
430
+ if task["status"] != "Done" and task.get("due_date"):
431
+ try:
432
+ due_date = datetime.fromisoformat(task["due_date"]).date()
433
+ if due_date < today:
434
+ overdue.append(task)
435
+ except (ValueError, TypeError):
436
+ continue
437
+
438
+ return overdue
439
+
440
+ def update_task_from_row(task_id: str, row: pd.Series):
441
+ """
442
+ Update task from DataFrame row
443
+ """
444
+ for task in st.session_state.tasks:
445
+ if task["id"] == task_id:
446
+ for key, value in row.items():
447
+ if key in task and pd.notna(value):
448
+ task[key] = value
449
+ task["updated_at"] = get_timestamp()
450
+ break