import gradio as gr import datetime from typing import Dict, List, Any, Union, Optional import random # Import utilities from utils.storage import load_data, save_data from utils.state import generate_id, get_timestamp, record_activity from utils.ai_models import break_down_task, estimate_task_time from utils.ui_components import create_kanban_board, create_priority_matrix from utils.config import FILE_PATHS from utils.logging import get_logger from utils.error_handling import handle_exceptions, ValidationError, safe_get # Initialize logger logger = get_logger(__name__) @handle_exceptions def create_tasks_page(state: Dict[str, Any]) -> None: """ Create the tasks and projects page with multiple views Args: state: Application state """ logger.info("Creating tasks page") # Create the tasks page layout with gr.Column(elem_id="tasks-page"): gr.Markdown("# 📋 Tasks & Projects") # Task views and actions with gr.Row(): # View selector view_selector = gr.Radio( choices=["Kanban", "List", "Calendar", "Timeline", "Priority Matrix"], value="Kanban", label="View", elem_id="task-view-selector" ) # Add task button add_task_btn = gr.Button("➕ Add Task", elem_classes=["action-button"]) # Task views container with gr.Group(elem_id="task-views-container"): # Kanban View with gr.Group(elem_id="kanban-view", visible=True) as kanban_view: create_kanban_board(safe_get(state, "tasks", [])) # List View with gr.Group(elem_id="list-view", visible=False) as list_view: with gr.Column(): # Filter and sort options with gr.Row(): status_filter = gr.Dropdown( choices=["All", "To Do", "In Progress", "Done"], value="All", label="Status" ) priority_filter = gr.Dropdown( choices=["All", "High", "Medium", "Low"], value="All", label="Priority" ) sort_by = gr.Dropdown( choices=["Created (Newest)", "Created (Oldest)", "Due Date", "Priority"], value="Created (Newest)", label="Sort By" ) # Task list task_list = gr.Dataframe( headers=["Title", "Status", "Priority", "Due Date", "Created"], datatype=["str", "str", "str", "str", "str"], col_count=(5, "fixed"), elem_id="task-list-table" ) # Function to update task list based on filters @handle_exceptions def update_task_list(status, priority, sort): """Update the task list based on filters and sort options""" logger.debug(f"Updating task list with filters: status={status}, priority={priority}, sort={sort}") tasks = safe_get(state, "tasks", []) # Apply status filter if status != "All": status_map = {"To Do": "todo", "In Progress": "in_progress", "Done": "done"} tasks = [t for t in tasks if safe_get(t, "status", "") == status_map.get(status, "")] # Apply priority filter if priority != "All": tasks = [t for t in tasks if safe_get(t, "priority", "").lower() == priority.lower()] # Apply sorting if sort == "Created (Newest)": tasks.sort(key=lambda x: safe_get(x, "created_at", ""), reverse=True) elif sort == "Created (Oldest)": tasks.sort(key=lambda x: safe_get(x, "created_at", "")) elif sort == "Due Date": # Sort by due date, putting tasks without due dates at the end tasks.sort( key=lambda x: safe_get(x, "deadline", "9999-12-31T23:59:59") ) elif sort == "Priority": # Sort by priority (high, medium, low) priority_order = {"high": 0, "medium": 1, "low": 2} tasks.sort( key=lambda x: priority_order.get(safe_get(x, "priority", "").lower(), 3) ) # Format data for the table table_data = [] for task in tasks: # Format status status_map = {"todo": "To Do", "in_progress": "In Progress", "done": "Done"} status_str = status_map.get(safe_get(task, "status", ""), "To Do") # Format priority priority_str = safe_get(task, "priority", "").capitalize() # Format due date due_date = "None" if "deadline" in task: try: deadline = datetime.datetime.fromisoformat(task["deadline"]) due_date = deadline.strftime("%Y-%m-%d") except Exception as e: logger.warning(f"Error formatting deadline: {str(e)}") # Format created date created = "Unknown" if "created_at" in task: try: created_at = datetime.datetime.fromisoformat(task["created_at"]) created = created_at.strftime("%Y-%m-%d") except Exception as e: logger.warning(f"Error formatting created_at: {str(e)}") table_data.append([ safe_get(task, "title", "Untitled Task"), status_str, priority_str, due_date, created ]) return table_data # Update task list when filters change status_filter.change( update_task_list, inputs=[status_filter, priority_filter, sort_by], outputs=[task_list] ) priority_filter.change( update_task_list, inputs=[status_filter, priority_filter, sort_by], outputs=[task_list] ) sort_by.change( update_task_list, inputs=[status_filter, priority_filter, sort_by], outputs=[task_list] ) # Initialize task list task_list.value = update_task_list("All", "All", "Created (Newest)") # Calendar View with gr.Group(elem_id="calendar-view", visible=False) as calendar_view: gr.Markdown("### 📅 Calendar View") gr.Markdown("*Calendar view will display tasks with due dates*") # Simple calendar implementation (placeholder) current_month = datetime.datetime.now().strftime("%B %Y") gr.Markdown(f"## {current_month}") # Create a simple calendar grid days_of_week = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] with gr.Row(): for day in days_of_week: gr.Markdown(f"**{day}**") # Get the current month's calendar now = datetime.datetime.now() month_start = datetime.datetime(now.year, now.month, 1) month_end = (month_start.replace(month=month_start.month+1, day=1) - datetime.timedelta(days=1)) start_weekday = month_start.weekday() days_in_month = month_end.day # Adjust for Sunday as first day of week start_weekday = (start_weekday + 1) % 7 # Create calendar grid day_counter = 1 for week in range(6): # Maximum 6 weeks in a month with gr.Row(): for weekday in range(7): if (week == 0 and weekday < start_weekday) or day_counter > days_in_month: # Empty cell gr.Markdown("") else: # Get tasks for this day day_tasks = [] for task in safe_get(state, "tasks", []): if "deadline" in task: try: deadline = datetime.datetime.fromisoformat(task["deadline"]) if (deadline.year == now.year and deadline.month == now.month and deadline.day == day_counter): day_tasks.append(task) except Exception as e: logger.warning(f"Error parsing deadline: {str(e)}") # Create day cell with gr.Group(elem_classes=["calendar-day"]): gr.Markdown(f"**{day_counter}**") # Add tasks for this day for task in day_tasks: priority_colors = {"high": "red", "medium": "orange", "low": "green"} priority = safe_get(task, "priority", "").lower() color = priority_colors.get(priority, "gray") gr.Markdown( f" {safe_get(task, 'title', 'Untitled')}" ) day_counter += 1 if day_counter > days_in_month: break if day_counter > days_in_month: break # Timeline View with gr.Group(elem_id="timeline-view", visible=False) as timeline_view: gr.Markdown("### ⏱️ Timeline View") gr.Markdown("*Timeline view will display tasks in chronological order*") # Get tasks with dates dated_tasks = [] for task in safe_get(state, "tasks", []): if "deadline" in task or "created_at" in task: dated_tasks.append(task) # Sort by date dated_tasks.sort(key=lambda x: safe_get(x, "deadline", safe_get(x, "created_at", ""))) # Group tasks by month tasks_by_month = {} for task in dated_tasks: date_str = safe_get(task, "deadline", safe_get(task, "created_at", "")) try: date = datetime.datetime.fromisoformat(date_str) month_key = date.strftime("%Y-%m") if month_key not in tasks_by_month: tasks_by_month[month_key] = [] tasks_by_month[month_key].append((date, task)) except Exception as e: logger.warning(f"Error parsing date: {str(e)}") # Display timeline for month_key in sorted(tasks_by_month.keys()): try: month_date = datetime.datetime.strptime(month_key, "%Y-%m") month_name = month_date.strftime("%B %Y") gr.Markdown(f"## {month_name}") # Display tasks for this month for date, task in sorted(tasks_by_month[month_key]): day_str = date.strftime("%d %b") status_emoji = "🔴" if safe_get(task, "status") == "todo" else \ "🟡" if safe_get(task, "status") == "in_progress" else "🟢" with gr.Group(elem_classes=["timeline-item"]): with gr.Row(): gr.Markdown(f"**{day_str}**") gr.Markdown(f"{status_emoji} {safe_get(task, 'title', 'Untitled Task')}") if safe_get(task, "description"): gr.Markdown(task["description"]) except Exception as e: logger.warning(f"Error displaying timeline month: {str(e)}") # Priority Matrix View with gr.Group(elem_id="priority-matrix-view", visible=False) as priority_matrix_view: create_priority_matrix(safe_get(state, "tasks", [])) # Task detail modal (shown when a task is selected) with gr.Group(elem_id="task-detail-modal", visible=False) as task_detail_modal: gr.Markdown("## Task Details") # Task information task_title_display = gr.Textbox(label="Title", interactive=True) task_description_display = gr.Textbox(label="Description", lines=3, interactive=True) task_status_display = gr.Dropdown( choices=["To Do", "In Progress", "Done"], label="Status", interactive=True ) task_priority_display = gr.Dropdown( choices=["High", "Medium", "Low"], label="Priority", interactive=True ) task_deadline_display = gr.Textbox( label="Deadline (YYYY-MM-DD)", interactive=True ) # Task actions with gr.Row(): save_task_btn = gr.Button("Save Changes") delete_task_btn = gr.Button("Delete Task") close_detail_btn = gr.Button("Close") # AI features with gr.Accordion("AI Features", open=False): # AI Task Breakdown gr.Markdown("### 🤖 AI Task Breakdown") generate_subtasks_btn = gr.Button("Generate Subtasks") subtasks_display = gr.Markdown("*Click the button to generate subtasks*") # AI Time Estimation gr.Markdown("### ⏱️ AI Time Estimation") estimate_time_btn = gr.Button("Estimate Time") time_estimate_display = gr.Markdown("*Click the button to estimate time*") # Subtasks with gr.Group(): gr.Markdown("### Subtasks") subtask_list = gr.Dataframe( headers=["Subtask", "Status"], datatype=["str", "str"], col_count=(2, "fixed"), row_count=(0, "dynamic"), elem_id="subtask-list" ) add_subtask_btn = gr.Button("Add Subtask") # Add task modal with gr.Group(elem_id="add-task-modal", visible=False) as add_task_modal: gr.Markdown("## Add New Task") # Task information inputs new_task_title = gr.Textbox(label="Title", placeholder="Enter task title") new_task_description = gr.Textbox( label="Description", placeholder="Enter task description", lines=3 ) new_task_status = gr.Dropdown( choices=["To Do", "In Progress", "Done"], label="Status", value="To Do" ) new_task_priority = gr.Dropdown( choices=["High", "Medium", "Low"], label="Priority", value="Medium" ) new_task_deadline = gr.Textbox( label="Deadline (YYYY-MM-DD)", placeholder="YYYY-MM-DD" ) # Task categorization with gr.Accordion("Smart Categorization", open=False): gr.Markdown("### 🏷️ Tags") new_task_tags = gr.Textbox( label="Tags (comma separated)", placeholder="work, project, urgent" ) gr.Markdown("### 📂 Project") new_task_project = gr.Dropdown( choices=["None", "Work", "Personal", "Study", "Health"], label="Project", value="None" ) # Task actions with gr.Row(): create_task_btn = gr.Button("Create Task") cancel_add_btn = gr.Button("Cancel") # Function to switch between views @handle_exceptions def switch_view(view): """Switch between different task views""" logger.info(f"Switching to {view} view") views = { "Kanban": kanban_view, "List": list_view, "Calendar": calendar_view, "Timeline": timeline_view, "Priority Matrix": priority_matrix_view } return [gr.update(visible=(view_name == view)) for view_name, view_component in views.items()] # Set up view switching view_outputs = [kanban_view, list_view, calendar_view, timeline_view, priority_matrix_view] view_selector.change(switch_view, inputs=[view_selector], outputs=view_outputs) # Function to show add task modal @handle_exceptions def show_add_task_modal(): """Show the add task modal""" logger.debug("Showing add task modal") return gr.update(visible=True) # Function to hide add task modal @handle_exceptions def hide_add_task_modal(): """Hide the add task modal""" logger.debug("Hiding add task modal") return gr.update(visible=False) # Set up add task modal add_task_btn.click(show_add_task_modal, inputs=[], outputs=[add_task_modal]) cancel_add_btn.click(hide_add_task_modal, inputs=[], outputs=[add_task_modal]) # Function to create a new task @handle_exceptions def create_task(title, description, status, priority, deadline, tags, project): """Create a new task""" if not title.strip(): logger.warning("Attempted to create task with empty title") return "Please enter a task title", gr.update(visible=True) logger.info(f"Creating new task: {title}") # Validate deadline format if provided deadline_iso = None if deadline.strip(): try: deadline_date = datetime.datetime.strptime(deadline.strip(), "%Y-%m-%d") deadline_iso = deadline_date.isoformat() except ValueError as e: logger.warning(f"Invalid date format: {deadline}, error: {str(e)}") return "Invalid date format. Use YYYY-MM-DD", gr.update(visible=True) # Map status to internal representation status_map = {"To Do": "todo", "In Progress": "in_progress", "Done": "done"} # Create new task new_task = { "id": generate_id(), "title": title.strip(), "description": description.strip(), "status": status_map.get(status, "todo"), "priority": priority.lower(), "completed": status == "Done", "created_at": get_timestamp() } if deadline_iso: new_task["deadline"] = deadline_iso # Add tags if provided if tags.strip(): new_task["tags"] = [tag.strip() for tag in tags.split(",") if tag.strip()] # Add project if selected if project != "None": new_task["project"] = project # Add to state state["tasks"].append(new_task) state["stats"]["tasks_total"] += 1 if status == "Done": state["stats"]["tasks_completed"] += 1 new_task["completed_at"] = get_timestamp() # Record activity record_activity({ "type": "task_created", "title": title, "timestamp": datetime.datetime.now().isoformat() }) # Save to file save_data(FILE_PATHS["tasks"], state["tasks"]) # Reset form and hide modal return "Task created successfully!", gr.update(visible=False) # Set up create task action create_task_btn.click( create_task, inputs=[ new_task_title, new_task_description, new_task_status, new_task_priority, new_task_deadline, new_task_tags, new_task_project ], outputs=[gr.Markdown(visible=False), add_task_modal] ) # Function to generate subtasks using AI @handle_exceptions def generate_subtasks(title, description): """Generate subtasks using AI""" logger.info(f"Generating subtasks for task: {title}") subtasks = break_down_task(title, description) # Format subtasks as markdown list subtasks_md = "**Suggested Subtasks:**\n\n" for subtask in subtasks: subtasks_md += f"- {subtask}\n" return subtasks_md # Set up generate subtasks action generate_subtasks_btn.click( generate_subtasks, inputs=[task_title_display, task_description_display], outputs=[subtasks_display] ) # Function to estimate task time using AI @handle_exceptions def estimate_task_time_ai(title, description): """Estimate task time using AI""" logger.info(f"Estimating time for task: {title}") minutes = estimate_task_time(title, description) # Format time estimate if minutes < 60: time_str = f"{minutes} minutes" else: hours = minutes // 60 remaining_minutes = minutes % 60 time_str = f"{hours} hour{'s' if hours > 1 else ''}" if remaining_minutes > 0: time_str += f" {remaining_minutes} minute{'s' if remaining_minutes > 1 else ''}" return f"**Estimated Time:** {time_str}" # Set up estimate time action estimate_time_btn.click( estimate_task_time_ai, inputs=[task_title_display, task_description_display], outputs=[time_estimate_display] )