mona / pages /tasks.py
mrradix's picture
Upload 48 files
8e4018d verified
raw
history blame
25.3 kB
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"<span style='color: {color};'>●</span> {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]
)