Spaces:
Runtime error
Runtime error
| """ | |
| Schedule & Note Management Web App | |
| A lightweight Gradio 6 application for managing tasks, events, reminders, and fees. | |
| Based on Product Requirements Document v1.0 | |
| """ | |
| import gradio as gr | |
| import json | |
| from datetime import datetime, timedelta | |
| from typing import Optional | |
| from enum import Enum | |
| # ============================================================================ | |
| # DATA MODELS AND STATE MANAGEMENT | |
| # ============================================================================ | |
| class TaskPriority(str, Enum): | |
| LOW = "Low" | |
| MEDIUM = "Medium" | |
| HIGH = "High" | |
| class TaskStatus(str, Enum): | |
| PENDING = "Pending" | |
| COMPLETED = "Completed" | |
| class ReminderType(str, Enum): | |
| NONE = "None" | |
| BEFORE_15_MIN = "15 minutes before" | |
| BEFORE_30_MIN = "30 minutes before" | |
| BEFORE_1_HOUR = "1 hour before" | |
| BEFORE_1_DAY = "1 day before" | |
| # Sample data for demonstration | |
| SAMPLE_TASKS = [ | |
| { | |
| "id": "1", | |
| "title": "Team Meeting", | |
| "description": "Weekly sync with the development team", | |
| "date": datetime.now().strftime("%Y-%m-%d"), | |
| "time": "09:00", | |
| "priority": TaskPriority.HIGH.value, | |
| "status": TaskStatus.PENDING.value, | |
| "fee": 0.0, | |
| "reminder": ReminderType.BEFORE_15_MIN.value, | |
| "notes": "Prepare agenda beforehand", | |
| "created_at": datetime.now().isoformat() | |
| }, | |
| { | |
| "id": "2", | |
| "title": "Client Presentation", | |
| "description": "Present the new product roadmap", | |
| "date": datetime.now().strftime("%Y-%m-%d"), | |
| "time": "14:00", | |
| "priority": TaskPriority.HIGH.value, | |
| "status": TaskStatus.PENDING.value, | |
| "fee": 150.0, | |
| "reminder": ReminderType.BEFORE_1_HOUR.value, | |
| "notes": "Review slides and prepare demo", | |
| "created_at": datetime.now().isoformat() | |
| }, | |
| { | |
| "id": "3", | |
| "title": "Code Review", | |
| "description": "Review PRs for the new feature branch", | |
| "date": (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d"), | |
| "time": "11:00", | |
| "priority": TaskPriority.MEDIUM.value, | |
| "status": TaskStatus.PENDING.value, | |
| "fee": 0.0, | |
| "reminder": ReminderType.BEFORE_30_MIN.value, | |
| "notes": "Check for security vulnerabilities", | |
| "created_at": datetime.now().isoformat() | |
| }, | |
| { | |
| "id": "4", | |
| "title": "Gym Session", | |
| "description": "Weekly workout routine", | |
| "date": datetime.now().strftime("%Y-%m-%d"), | |
| "time": "18:00", | |
| "priority": TaskPriority.LOW.value, | |
| "status": TaskStatus.COMPLETED.value, | |
| "fee": 50.0, | |
| "reminder": ReminderType.NONE.value, | |
| "notes": "Focus on cardio today", | |
| "created_at": datetime.now().isoformat() | |
| } | |
| ] | |
| def get_next_id(tasks: list) -> str: | |
| """Generate a new unique ID for tasks.""" | |
| if not tasks: | |
| return "1" | |
| max_id = max(int(task["id"]) for task in tasks) | |
| return str(max_id + 1) | |
| def calculate_weekly_stats(tasks: list) -> dict: | |
| """Calculate statistics for the current week.""" | |
| now = datetime.now() | |
| week_start = now - timedelta(days=now.weekday()) | |
| week_end = week_start + timedelta(days=6) | |
| weekly_tasks = [ | |
| t for t in tasks | |
| if week_start.strftime("%Y-%m-%d") <= t["date"] <= week_end.strftime("%Y-%m-%d") | |
| ] | |
| completed = sum(1 for t in weekly_tasks if t["status"] == TaskStatus.COMPLETED.value) | |
| total_fee = sum(float(t.get("fee", 0) or 0) for t in weekly_tasks) | |
| return { | |
| "total_tasks": len(weekly_tasks), | |
| "completed": completed, | |
| "pending": len(weekly_tasks) - completed, | |
| "total_fee": total_fee, | |
| "completion_rate": round((completed / len(weekly_tasks) * 100), 1) if weekly_tasks else 0 | |
| } | |
| def get_weekly_schedule(tasks: list) -> str: | |
| """Generate a weekly schedule display.""" | |
| now = datetime.now() | |
| days = [] | |
| for i in range(7): | |
| day = now + timedelta(days=i) | |
| day_str = day.strftime("%Y-%m-%d") | |
| day_name = day.strftime("%A") | |
| is_today = i == 0 | |
| day_tasks = [ | |
| f" • **{t['time']}** - {t['title']}" | |
| for t in sorted(tasks, key=lambda x: x.get("time", "")) | |
| if t["date"] == day_str | |
| ] | |
| day_header = f"**{day_name} {day.strftime('%b %d')}" + (' (Today)' if is_today else '') + "**" | |
| day_content = "\n".join(day_tasks) if day_tasks else " No tasks scheduled" | |
| days.append(f"{day_header}\n{day_content}") | |
| return "\n\n".join(days) | |
| # ============================================================================ | |
| # APPLICATION STATE | |
| # ============================================================================ | |
| class AppState: | |
| """Application state manager for Gradio 6.""" | |
| def __init__(self): | |
| self.tasks = SAMPLE_TASKS.copy() | |
| def get_tasks(self) -> list: | |
| """Get all tasks.""" | |
| return self.tasks | |
| def add_task(self, task: dict) -> None: | |
| """Add a new task.""" | |
| task["id"] = get_next_id(self.tasks) | |
| task["created_at"] = datetime.now().isoformat() | |
| self.tasks.append(task) | |
| def update_task(self, task_id: str, updates: dict) -> None: | |
| """Update an existing task.""" | |
| for task in self.tasks: | |
| if task["id"] == task_id: | |
| task.update(updates) | |
| break | |
| def delete_task(self, task_id: str) -> None: | |
| """Delete a task by ID.""" | |
| self.tasks = [t for t in self.tasks if t["id"] != task_id] | |
| def get_task_by_id(self, task_id: str) -> Optional[dict]: | |
| """Get a task by its ID.""" | |
| for task in self.tasks: | |
| if task["id"] == task_id: | |
| return task | |
| return None | |
| # Initialize global state | |
| app_state = AppState() | |
| # ============================================================================ | |
| # HELPER FUNCTIONS | |
| # ============================================================================ | |
| def refresh_task_list() -> dict: | |
| """Refresh the task list and return current state.""" | |
| tasks = app_state.get_tasks() | |
| return { | |
| task_list: create_task_cards(tasks), | |
| task_count: f"Total Tasks: {len(tasks)}", | |
| weekly_stats: create_weekly_stats_display(), | |
| weekly_schedule: get_weekly_schedule(tasks) | |
| } | |
| def create_task_cards(tasks: list) -> str: | |
| """Create HTML cards for task display.""" | |
| if not tasks: | |
| return """ | |
| <div style="text-align: center; padding: 40px; color: #666;"> | |
| <h3>📋 No tasks yet</h3> | |
| <p>Create your first task to get started!</p> | |
| </div> | |
| """ | |
| cards = [] | |
| sorted_tasks = sorted(tasks, key=lambda x: (x.get("date", ""), x.get("time", ""))) | |
| for task in sorted_tasks: | |
| priority_colors = { | |
| "Low": "#28a745", | |
| "Medium": "#ffc107", | |
| "High": "#dc3545" | |
| } | |
| status_colors = { | |
| "Pending": "#6c757d", | |
| "Completed": "#28a745" | |
| } | |
| priority_color = priority_colors.get(task.get("priority", "Medium"), "#6c757d") | |
| status_color = status_colors.get(task.get("status", "Pending"), "#6c757d") | |
| card = f""" | |
| <div style=" | |
| background: white; | |
| border-radius: 12px; | |
| padding: 16px; | |
| margin-bottom: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.08); | |
| border-left: 4px solid {priority_color}; | |
| "> | |
| <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;"> | |
| <div> | |
| <h4 style="margin: 0; color: #333;">{task['title']}</h4> | |
| <span style=" | |
| display: inline-block; | |
| background: {priority_color}20; | |
| color: {priority_color}; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| margin-top: 4px; | |
| ">{task.get('priority', 'Medium')}</span> | |
| <span style=" | |
| display: inline-block; | |
| background: {status_color}20; | |
| color: {status_color}; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| margin-left: 8px; | |
| ">{task.get('status', 'Pending')}</span> | |
| </div> | |
| <div style="text-align: right; color: #666; font-size: 14px;"> | |
| <div>📅 {task.get('date', 'No date')}</div> | |
| <div>🕐 {task.get('time', 'No time')}</div> | |
| </div> | |
| </div> | |
| <p style="color: #666; font-size: 14px; margin: 8px 0;"> | |
| {task.get('description', 'No description')} | |
| </p> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;"> | |
| <div style="font-size: 14px;"> | |
| {'💰 Fee: $' + str(task.get('fee', 0)) if float(task.get('fee', 0)) > 0 else ''} | |
| {' | ⏰ Reminder: ' + task.get('reminder', 'None') if task.get('reminder') != 'None' else ''} | |
| </div> | |
| <div style="display: flex; gap: 8px;"> | |
| <span style="font-size: 12px; color: #666;">ID: {task['id']}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| cards.append(card) | |
| return "\n".join(cards) | |
| def create_weekly_stats_display() -> str: | |
| """Create weekly statistics display.""" | |
| stats = calculate_weekly_stats(app_state.get_tasks()) | |
| return f""" | |
| <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px;"> | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 32px; font-weight: bold;">{stats['total_tasks']}</div> | |
| <div style="font-size: 14px; opacity: 0.9;">This Week's Tasks</div> | |
| </div> | |
| <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 32px; font-weight: bold;">{stats['completed']}</div> | |
| <div style="font-size: 14px; opacity: 0.9;">Completed</div> | |
| </div> | |
| <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 32px; font-weight: bold;">{stats['pending']}</div> | |
| <div style="font-size: 14px; opacity: 0.9;">Pending</div> | |
| </div> | |
| <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 32px; font-weight: bold;">${stats['total_fee']:.2f}</div> | |
| <div style="font-size: 14px; opacity: 0.9;">Total Fees</div> | |
| </div> | |
| </div> | |
| """ | |
| # ============================================================================ | |
| # TASK OPERATIONS | |
| # ============================================================================ | |
| def add_new_task( | |
| title: str, | |
| description: str, | |
| task_date: str, | |
| task_time: str, | |
| priority: str, | |
| fee: float, | |
| reminder: str, | |
| notes: str | |
| ) -> dict: | |
| """Add a new task to the system.""" | |
| if not title or not task_date or not task_time: | |
| return { | |
| task_list: create_task_cards(app_state.get_tasks()), | |
| status_msg: gr.update(value="❌ Please fill in all required fields!", visible=True), | |
| form_message: gr.update(value="❌ Error: Title, date, and time are required", visible=True) | |
| } | |
| new_task = { | |
| "title": title, | |
| "description": description, | |
| "date": task_date, | |
| "time": task_time, | |
| "priority": priority, | |
| "status": TaskStatus.PENDING.value, | |
| "fee": fee, | |
| "reminder": reminder, | |
| "notes": notes | |
| } | |
| app_state.add_task(new_task) | |
| return { | |
| task_list: create_task_cards(app_state.get_tasks()), | |
| status_msg: gr.update(value="✅ Task created successfully!", visible=True), | |
| form_message: gr.update(value="✅ Task added successfully!", visible=True), | |
| # Clear form | |
| title_input: gr.update(value=""), | |
| description_input: gr.update(value=""), | |
| fee_input: gr.update(value=0.0), | |
| notes_input: gr.update(value="") | |
| } | |
| def update_task_status(task_id: str, new_status: str) -> dict: | |
| """Update a task's status.""" | |
| if task_id and new_status: | |
| app_state.update_task(task_id, {"status": new_status}) | |
| return { | |
| task_list: create_task_cards(app_state.get_tasks()), | |
| status_msg: gr.update(value=f"✅ Task {task_id} marked as {new_status}", visible=True) | |
| } | |
| def delete_task_action(task_id: str) -> dict: | |
| """Delete a task.""" | |
| if task_id: | |
| app_state.delete_task(task_id) | |
| return { | |
| task_list: create_task_cards(app_state.get_tasks()), | |
| status_msg: gr.update(value=f"✅ Task deleted successfully", visible=True) | |
| } | |
| def load_task_for_edit(task_id: str) -> dict: | |
| """Load task data into the edit form.""" | |
| task = app_state.get_task_by_id(task_id) | |
| if task: | |
| return { | |
| edit_task_id: task_id, | |
| edit_title: gr.update(value=task["title"]), | |
| edit_description: gr.update(value=task.get("description", "")), | |
| edit_date: gr.update(value=task.get("date", "")), | |
| edit_time: gr.update(value=task.get("time", "")), | |
| edit_priority: gr.update(value=task.get("priority", "Medium")), | |
| edit_fee: gr.update(value=float(task.get("fee", 0) or 0)), | |
| edit_reminder: gr.update(value=task.get("reminder", "None")), | |
| edit_notes: gr.update(value=task.get("notes", "")), | |
| show_edit_form: gr.update(visible=True) | |
| } | |
| return {show_edit_form: gr.update(visible=False)} | |
| def save_edited_task( | |
| task_id: str, | |
| title: str, | |
| description: str, | |
| task_date: str, | |
| task_time: str, | |
| priority: str, | |
| fee: float, | |
| reminder: str, | |
| notes: str | |
| ) -> dict: | |
| """Save edited task.""" | |
| if task_id and title: | |
| updates = { | |
| "title": title, | |
| "description": description, | |
| "date": task_date, | |
| "time": task_time, | |
| "priority": priority, | |
| "fee": fee, | |
| "reminder": reminder, | |
| "notes": notes | |
| } | |
| app_state.update_task(task_id, updates) | |
| return { | |
| task_list: create_task_cards(app_state.get_tasks()), | |
| status_msg: gr.update(value="✅ Task updated successfully!", visible=True), | |
| show_edit_form: gr.update(visible=False) | |
| } | |
| def filter_tasks(status_filter: str, priority_filter: str) -> dict: | |
| """Filter tasks by status and priority.""" | |
| tasks = app_state.get_tasks() | |
| if status_filter != "All": | |
| tasks = [t for t in tasks if t.get("status") == status_filter] | |
| if priority_filter != "All": | |
| tasks = [t for t in tasks if t.get("priority") == priority_filter] | |
| return { | |
| task_list: create_task_cards(tasks) | |
| } | |
| def search_tasks(search_query: str) -> dict: | |
| """Search tasks by title or description.""" | |
| if not search_query.strip(): | |
| return {task_list: create_task_cards(app_state.get_tasks())} | |
| query = search_query.lower() | |
| tasks = [ | |
| t for t in app_state.get_tasks() | |
| if query in t.get("title", "").lower() or query in t.get("description", "").lower() | |
| ] | |
| return { | |
| task_list: create_task_cards(tasks) | |
| } | |
| # ============================================================================ | |
| # GRADIO 6 APPLICATION | |
| # ============================================================================ | |
| # Custom theme for the application | |
| custom_theme = gr.themes.Soft( | |
| primary_hue="indigo", | |
| secondary_hue="purple", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter"), | |
| text_size="lg", | |
| spacing_size="lg", | |
| radius_size="md" | |
| ).set( | |
| button_primary_background_fill="*primary_600", | |
| button_primary_background_fill_hover="*primary_700", | |
| button_secondary_background_fill="*secondary_100", | |
| button_secondary_background_fill_hover="*secondary_200", | |
| block_title_text_weight="600", | |
| block_background_fill="white", | |
| block_radius="12px", | |
| ) | |
| with gr.Blocks(theme=custom_theme) as demo: | |
| # Header | |
| gr.HTML(""" | |
| <div style="text-align: center; padding: 20px 0; margin-bottom: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;"> | |
| <h1 style="margin: 0; font-size: 28px;">📅 Schedule & Note Management</h1> | |
| <p style="margin: 8px 0 0 0; opacity: 0.9;">Plan tasks, manage events, and track fees efficiently</p> | |
| </div> | |
| """) | |
| gr.HTML(""" | |
| <div style="text-align: right; margin-bottom: 10px;"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #667eea; text-decoration: none; font-size: 14px;"> | |
| ✨ Built with anycoder | |
| </a> | |
| </div> | |
| """) | |
| # Main content with tabs | |
| with gr.Tabs() as tabs: | |
| # Tab 1: Dashboard | |
| with gr.TabItem("📊 Dashboard", id="dashboard"): | |
| gr.Markdown("### 📊 Weekly Overview") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| weekly_stats_html = gr.HTML( | |
| value=create_weekly_stats_display(), | |
| elem_id="weekly-stats" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📅 Quick Stats") | |
| task_count = gr.Markdown(f"**Total Tasks: {len(app_state.get_tasks())}**") | |
| gr.Markdown("### 📆 This Week's Schedule") | |
| weekly_schedule = gr.Markdown( | |
| value=get_weekly_schedule(app_state.get_tasks()), | |
| elem_id="weekly-schedule" | |
| ) | |
| status_msg = gr.Markdown(visible=False, value="") | |
| # Tab 2: Task Management | |
| with gr.TabItem("✅ Tasks", id="tasks"): | |
| gr.Markdown("### ✅ Task Management") | |
| # Search and filter row | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| search_input = gr.Textbox( | |
| label="🔍 Search Tasks", | |
| placeholder="Search by title or description...", | |
| elem_id="search-input" | |
| ) | |
| with gr.Column(scale=1): | |
| status_filter = gr.Dropdown( | |
| choices=["All", "Pending", "Completed"], | |
| value="All", | |
| label="Status" | |
| ) | |
| with gr.Column(scale=1): | |
| priority_filter = gr.Dropdown( | |
| choices=["All", "Low", "Medium", "High"], | |
| value="All", | |
| label="Priority" | |
| ) | |
| # Task list display | |
| task_list = gr.HTML( | |
| value=create_task_cards(app_state.get_tasks()), | |
| elem_id="task-list" | |
| ) | |
| status_msg = gr.Markdown(visible=False, value="") | |
| # Tab 3: Add New Task | |
| with gr.TabItem("➕ Add Task", id="add-task"): | |
| gr.Markdown("### ➕ Create New Task") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| title_input = gr.Textbox( | |
| label="Task Title *", | |
| placeholder="Enter task title", | |
| elem_id="title-input" | |
| ) | |
| description_input = gr.Textbox( | |
| label="Description", | |
| placeholder="Enter task description", | |
| lines=3, | |
| elem_id="description-input" | |
| ) | |
| with gr.Column(scale=1): | |
| task_date = gr.DateTime( | |
| label="Date & Time *", | |
| type="datetime", | |
| elem_id="task-date" | |
| ) | |
| task_time = gr.Textbox( | |
| label="Time (HH:MM) *", | |
| placeholder="09:00", | |
| value="09:00", | |
| elem_id="task-time" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| priority = gr.Radio( | |
| choices=["Low", "Medium", "High"], | |
| value="Medium", | |
| label="Priority" | |
| ) | |
| with gr.Column(): | |
| fee_input = gr.Number( | |
| label="Fee/Cost ($)", | |
| value=0.0, | |
| minimum=0.0, | |
| elem_id="fee-input" | |
| ) | |
| with gr.Column(): | |
| reminder = gr.Dropdown( | |
| choices=["None", "15 minutes before", "30 minutes before", | |
| "1 hour before", "1 day before"], | |
| value="None", | |
| label="Reminder" | |
| ) | |
| notes_input = gr.Textbox( | |
| label="Notes", | |
| placeholder="Additional notes or comments...", | |
| lines=2, | |
| elem_id="notes-input" | |
| ) | |
| form_message = gr.Markdown(visible=False, value="") | |
| with gr.Row(): | |
| add_btn = gr.Button("Create Task", variant="primary", size="lg") | |
| clear_btn = gr.Button("Clear Form", variant="secondary") | |
| # Tab 4: Edit Task | |
| with gr.TabItem("✏️ Edit Task", id="edit-task"): | |
| gr.Markdown("### ✏️ Edit Task") | |
| # Task selection dropdown | |
| task_selector = gr.Dropdown( | |
| choices=[(f"{t['title']} ({t.get('date', 'No date')}) - ID:{t['id']}", t['id']) | |
| for t in app_state.get_tasks()], | |
| label="Select Task to Edit", | |
| allow_custom_value=True, | |
| elem_id="task-selector" | |
| ) | |
| show_edit_form = gr.Column(visible=False, elem_id="edit-form-container") | |
| with show_edit_form: | |
| edit_task_id = gr.Textbox(visible=False, elem_id="edit-task-id") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| edit_title = gr.Textbox( | |
| label="Task Title *", | |
| placeholder="Enter task title", | |
| elem_id="edit-title" | |
| ) | |
| edit_description = gr.Textbox( | |
| label="Description", | |
| placeholder="Enter task description", | |
| lines=3, | |
| elem_id="edit-description" | |
| ) | |
| with gr.Column(scale=1): | |
| edit_date = gr.Textbox( | |
| label="Date (YYYY-MM-DD) *", | |
| placeholder="2024-01-15", | |
| elem_id="edit-date" | |
| ) | |
| edit_time = gr.Textbox( | |
| label="Time (HH:MM) *", | |
| placeholder="09:00", | |
| elem_id="edit-time" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| edit_priority = gr.Radio( | |
| choices=["Low", "Medium", "High"], | |
| value="Medium", | |
| label="Priority" | |
| ) | |
| with gr.Column(): | |
| edit_fee = gr.Number( | |
| label="Fee/Cost ($)", | |
| value=0.0, | |
| minimum=0.0, | |
| elem_id="edit-fee" | |
| ) | |
| with gr.Column(): | |
| edit_reminder = gr.Dropdown( | |
| choices=["None", "15 minutes before", "30 minutes before", | |
| "1 hour before", "1 day before"], | |
| value="None", | |
| label="Reminder" | |
| ) | |
| edit_notes = gr.Textbox( | |
| label="Notes", | |
| placeholder="Additional notes or comments...", | |
| lines=2, | |
| elem_id="edit-notes" | |
| ) | |
| with gr.Row(): | |
| save_btn = gr.Button("Save Changes", variant="primary", size="lg") | |
| cancel_btn = gr.Button("Cancel", variant="secondary") | |
| # Tab 5: Notes Manager | |
| with gr.TabItem("📝 Notes", id="notes"): | |
| gr.Markdown("### 📝 Quick Notes") | |
| gr.Markdown("*Notes are attached to individual tasks. View task notes in the Tasks tab.*") | |
| with gr.Row(): | |
| quick_note = gr.Textbox( | |
| label="Quick Note", | |
| placeholder="Write a quick note...", | |
| lines=4 | |
| ) | |
| note_display = gr.Markdown( | |
| value="**Recent Notes**\n\n• Review meeting notes from Monday\n• Update project timeline\n• Prepare budget report", | |
| elem_id="note-display" | |
| ) | |
| with gr.Row(): | |
| save_note_btn = gr.Button("Save Note", variant="primary") | |
| clear_note_btn = gr.Button("Clear", variant="secondary") | |
| # Tab 6: Fees Summary | |
| with gr.TabItem("💰 Fees", id="fees"): | |
| gr.Markdown("### 💰 Fee Tracking Summary") | |
| # Fee statistics | |
| tasks = app_state.get_tasks() | |
| total_fees = sum(float(t.get("fee", 0) or 0) for t in tasks) | |
| task_with_fees = sum(1 for t in tasks if float(t.get("fee", 0) or 0) > 0) | |
| avg_fee = total_fees / task_with_fees if task_with_fees > 0 else 0 | |
| gr.HTML(f""" | |
| <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;"> | |
| <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 36px; font-weight: bold;">${total_fees:.2f}</div> | |
| <div style="font-size: 16px; opacity: 0.9;">Total Fees</div> | |
| </div> | |
| <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 36px; font-weight: bold;">{task_with_fees}</div> | |
| <div style="font-size: 16px; opacity: 0.9;">Tasks with Fees</div> | |
| </div> | |
| <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;"> | |
| <div style="font-size: 36px; font-weight: bold;">${avg_fee:.2f}</div> | |
| <div style="font-size: 16px; opacity: 0.9;">Average Fee</div> | |
| </div> | |
| </div> | |
| """) | |
| gr.Markdown("### 💵 Tasks with Fees") | |
| fee_task_list = gr.HTML( | |
| value=create_task_cards([t for t in tasks if float(t.get("fee", 0) or 0) > 0]), | |
| elem_id="fee-task-list" | |
| ) | |
| # ============================================================================ | |
| # EVENT HANDLERS | |
| # ============================================================================ | |
| # Search and filter handlers | |
| search_input.submit(search_tasks, inputs=search_input, outputs=[task_list]) | |
| status_filter.change(filter_tasks, inputs=[status_filter, priority_filter], outputs=[task_list]) | |
| priority_filter.change(filter_tasks, inputs=[status_filter, priority_filter], outputs=[task_list]) | |
| # Add task handlers | |
| add_btn.click( | |
| add_new_task, | |
| inputs=[title_input, description_input, task_date, task_time, | |
| priority, fee_input, reminder, notes_input], | |
| outputs=[task_list, status_msg, form_message, title_input, | |
| description_input, fee_input, notes_input] | |
| ) | |
| clear_btn.click( | |
| lambda: { | |
| title_input: gr.update(value=""), | |
| description_input: gr.update(value=""), | |
| fee_input: gr.update(value=0.0), | |
| notes_input: gr.update(value=""), | |
| form_message: gr.update(visible=False) | |
| }, | |
| outputs=[title_input, description_input, fee_input, notes_input, form_message] | |
| ) | |
| # Task selector for editing | |
| task_selector.select( | |
| load_task_for_edit, | |
| inputs=task_selector, | |
| outputs=[edit_task_id, edit_title, edit_description, edit_date, edit_time, | |
| edit_priority, edit_fee, edit_reminder, edit_notes, show_edit_form] | |
| ) | |
| # Save edited task | |
| save_btn.click( | |
| save_edited_task, | |
| inputs=[edit_task_id, edit_title, edit_description, edit_date, edit_time, | |
| edit_priority, edit_fee, edit_reminder, edit_notes], | |
| outputs=[task_list, status_msg, show_edit_form] | |
| ) | |
| # Cancel edit | |
| cancel_btn.click( | |
| lambda: {show_edit_form: gr.update(visible=False)}, | |
| outputs=[show_edit_form] | |
| ) | |
| # Notes handlers | |
| save_note_btn.click( | |
| lambda note: { | |
| note_display: note_display.value + f"\n• {note}" if note else note_display.value | |
| }, | |
| inputs=quick_note, | |
| outputs=[note_display] | |
| ) | |
| clear_note_btn.click( | |
| lambda: {quick_note: gr.update(value="")}, | |
| outputs=[quick_note] | |
| ) | |
| # Refresh button (hidden, used for updates) | |
| refresh_btn = gr.Button("Refresh", visible=False) | |
| refresh_btn.click(refresh_task_list, outputs=[task_list, task_count, weekly_stats_html, weekly_schedule]) | |
| # ============================================================================ | |
| # LAUNCH APPLICATION | |
| # ============================================================================ | |
| demo.launch( | |
| theme=gr.themes.Soft( | |
| primary_hue="indigo", | |
| secondary_hue="purple", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter"), | |
| text_size="lg", | |
| spacing_size="lg", | |
| radius_size="md" | |
| ), | |
| title="Schedule & Note Management App", | |
| description="A lightweight application for managing tasks, events, reminders, and fees. Based on PRD v1.0", | |
| css=""" | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| } | |
| #weekly-stats, #fee-task-list { | |
| margin-bottom: 20px; | |
| } | |
| #search-input { | |
| border-radius: 8px; | |
| } | |
| """, | |
| footer_links=[ | |
| {"label": "Documentation", "url": "#"}, | |
| {"label": "Support", "url": "#"}, | |
| {"api": "api"} | |
| ] | |
| ) |