botserver/docs/src/chapter-04-gbui/apps/tasks.md
Rodrigo Rodriguez (Pragmatismo) 16a792b5cb Add Suite app documentation, templates, and Askama config
- Add askama.toml for template configuration (ui/ directory)
- Add Suite app documentation with flow diagrams (SVG)
  - App launcher, chat flow, drive flow, tasks flow
  - Individual app docs: chat, drive, tasks, mail, etc.
- Add HTML templates for Suite apps
  - Base template with header and app launcher
  - Auth login page
  - Chat, Drive, Mail, Meet, Tasks templates
  - Partial templates for messages, sessions, notifications
- Add Extensions type to AppState for type-erased storage
- Add mTLS module for service-to-service authentication
- Update web handlers to use new template paths (suite/)
- Fix auth module to avoid axum-extra TypedHeader dependency
2025-11-30 21:00:48 -03:00

15 KiB

Tasks - To-Do Management

Track what needs to be done

Tasks Flow Diagram

Overview

Tasks is your to-do list manager within General Bots Suite. Create tasks, set priorities, organize by category, and track your progress. Built with HTMX for instant updates without page reloads.

Interface Layout

┌─────────────────────────────────────────────────────────────────┐
│  ✓ Tasks                          Total: 12  Active: 5  Done: 7│
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ What needs to be done?         [Category ▼] [📅] [+ Add]│   │
│  └─────────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────────┤
│  [📋 All (12)] [⏳ Active (5)] [✓ Completed (7)] [⚡ Priority] │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ☐ Review quarterly report                    📅 Today    🔴   │
│  ☐ Call client about proposal                 📅 Today    🟡   │
│  ☐ Update project documentation               📅 Tomorrow 🟢   │
│  ☐ Schedule team meeting                      📅 Mar 20   ⚪   │
│  ────────────────────────────────────────────────────────────── │
│  ☑ Send meeting notes                         ✓ Done           │
│  ☑ Complete expense report                    ✓ Done           │
│  ☑ Review pull request                        ✓ Done           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Features

Adding Tasks

Quick Add:

  1. Type task description in the input box
  2. Press Enter or click + Add

With Details:

  1. Type task description
  2. Select a category (optional)
  3. Pick a due date (optional)
  4. Click + Add
<form class="add-task-form"
      hx-post="/api/tasks"
      hx-target="#task-list"
      hx-swap="afterbegin"
      hx-on::after-request="this.reset()">
    <input type="text" 
           name="text" 
           placeholder="What needs to be done?" 
           required>
    <select name="category">
        <option value="">No Category</option>
        <option value="work">Work</option>
        <option value="personal">Personal</option>
        <option value="shopping">Shopping</option>
        <option value="health">Health</option>
    </select>
    <input type="date" name="dueDate">
    <button type="submit">+ Add Task</button>
</form>

Completing Tasks

Click the checkbox to mark a task complete:

<div class="task-item" id="task-123">
    <input type="checkbox"
           hx-patch="/api/tasks/123"
           hx-vals='{"completed": true}'
           hx-target="#task-123"
           hx-swap="outerHTML">
    <span class="task-text">Review quarterly report</span>
    <span class="task-due">📅 Today</span>
    <span class="task-priority high">🔴</span>
</div>

Priority Levels

Priority Color Icon When to Use
High Red 🔴 Must do today
Medium Yellow 🟡 Important but not urgent
Low Green 🟢 Can wait
None Gray No deadline

Categories

Organize tasks by category:

Category Color Icon
Work Blue 💼
Personal Green 🏠
Shopping Orange 🛒
Health Red ❤️
Custom Purple 🏷️

Filter Tabs

Tab Shows HTMX Trigger
All All tasks hx-get="/api/tasks?filter=all"
Active Uncompleted tasks hx-get="/api/tasks?filter=active"
Completed Done tasks hx-get="/api/tasks?filter=completed"
Priority High priority only hx-get="/api/tasks?filter=priority"
<div class="filter-tabs">
    <button class="filter-tab active"
            hx-get="/api/tasks?filter=all"
            hx-target="#task-list"
            hx-swap="innerHTML">
        📋 All
        <span class="tab-count" id="count-all">12</span>
    </button>
    <button class="filter-tab"
            hx-get="/api/tasks?filter=active"
            hx-target="#task-list"
            hx-swap="innerHTML">
        ⏳ Active
        <span class="tab-count" id="count-active">5</span>
    </button>
    <button class="filter-tab"
            hx-get="/api/tasks?filter=completed"
            hx-target="#task-list"
            hx-swap="innerHTML">
        ✓ Completed
        <span class="tab-count" id="count-completed">7</span>
    </button>
    <button class="filter-tab"
            hx-get="/api/tasks?filter=priority"
            hx-target="#task-list"
            hx-swap="innerHTML">
        ⚡ Priority
    </button>
</div>

Stats Dashboard

Real-time statistics shown in the header:

<div class="header-stats" id="task-stats"
     hx-get="/api/tasks/stats"
     hx-trigger="load, taskUpdated from:body"
     hx-swap="innerHTML">
    <span class="stat-item">
        <span class="stat-value">12</span>
        <span class="stat-label">Total</span>
    </span>
    <span class="stat-item">
        <span class="stat-value">5</span>
        <span class="stat-label">Active</span>
    </span>
    <span class="stat-item">
        <span class="stat-value">7</span>
        <span class="stat-label">Completed</span>
    </span>
</div>

Keyboard Shortcuts

Shortcut Action
Enter Add task (when in input)
Space Toggle task completion
Delete Remove selected task
Tab Move to next field
Escape Cancel editing
/ Navigate tasks

API Endpoints

Endpoint Method Description
/api/tasks GET List all tasks
/api/tasks POST Create new task
/api/tasks/:id GET Get single task
/api/tasks/:id PATCH Update task
/api/tasks/:id DELETE Delete task
/api/tasks/stats GET Get task statistics

Query Parameters

GET /api/tasks?filter=active&category=work&sort=dueDate&order=asc
Parameter Values Default
filter all, active, completed, priority all
category work, personal, shopping, health none
sort created, dueDate, priority, text created
order asc, desc desc

Request Body (Create/Update)

{
    "text": "Review quarterly report",
    "category": "work",
    "dueDate": "2024-03-20",
    "priority": "high",
    "completed": false
}

Response Format

{
    "id": 123,
    "text": "Review quarterly report",
    "category": "work",
    "dueDate": "2024-03-20",
    "priority": "high",
    "completed": false,
    "createdAt": "2024-03-18T10:30:00Z",
    "updatedAt": "2024-03-18T10:30:00Z"
}

HTMX Integration

Task Creation

<form hx-post="/api/tasks"
      hx-target="#task-list"
      hx-swap="afterbegin"
      hx-on::after-request="this.reset(); htmx.trigger('body', 'taskUpdated')">
    <input type="text" name="text" required>
    <button type="submit">Add</button>
</form>

Task Toggle

<input type="checkbox"
       hx-patch="/api/tasks/123"
       hx-vals='{"completed": true}'
       hx-target="closest .task-item"
       hx-swap="outerHTML"
       hx-on::after-request="htmx.trigger('body', 'taskUpdated')">

Task Deletion

<button hx-delete="/api/tasks/123"
        hx-target="closest .task-item"
        hx-swap="outerHTML swap:0.3s"
        hx-confirm="Delete this task?">
    🗑
</button>

Inline Editing

<span class="task-text"
      hx-get="/api/tasks/123/edit"
      hx-trigger="dblclick"
      hx-target="this"
      hx-swap="outerHTML">
    Review quarterly report
</span>

CSS Classes

.tasks-container {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 1rem;
}

.tasks-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}

.header-stats {
    display: flex;
    gap: 1.5rem;
}

.stat-item {
    text-align: center;
}

.stat-value {
    display: block;
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--primary);
}

.stat-label {
    font-size: 0.75rem;
    color: var(--text-secondary);
}

.add-task-form {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.task-input {
    flex: 1;
    padding: 0.75rem 1rem;
    border: 2px solid var(--border);
    border-radius: 8px;
    font-size: 1rem;
}

.task-input:focus {
    border-color: var(--primary);
    outline: none;
}

.filter-tabs {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.5rem;
}

.filter-tab {
    padding: 0.5rem 1rem;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 6px;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.filter-tab:hover {
    background: var(--surface-hover);
}

.filter-tab.active {
    background: var(--primary);
    color: white;
}

.tab-count {
    background: rgba(255,255,255,0.2);
    padding: 0.125rem 0.5rem;
    border-radius: 10px;
    font-size: 0.75rem;
}

.task-list {
    flex: 1;
    overflow-y: auto;
}

.task-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    border-bottom: 1px solid var(--border);
    transition: all 0.2s;
}

.task-item:hover {
    background: var(--surface-hover);
}

.task-item.completed {
    opacity: 0.6;
}

.task-item.completed .task-text {
    text-decoration: line-through;
    color: var(--text-secondary);
}

.task-checkbox {
    width: 20px;
    height: 20px;
    border: 2px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    appearance: none;
}

.task-checkbox:checked {
    background: var(--success);
    border-color: var(--success);
}

.task-checkbox:checked::after {
    content: '✓';
    display: block;
    text-align: center;
    color: white;
    font-size: 14px;
    line-height: 16px;
}

.task-text {
    flex: 1;
}

.task-due {
    font-size: 0.875rem;
    color: var(--text-secondary);
}

.task-due.overdue {
    color: var(--error);
}

.task-priority {
    font-size: 0.75rem;
}

.task-priority.high {
    color: var(--error);
}

.task-priority.medium {
    color: var(--warning);
}

.task-priority.low {
    color: var(--success);
}

.task-delete {
    opacity: 0;
    padding: 0.25rem;
    border: none;
    background: transparent;
    cursor: pointer;
    color: var(--error);
}

.task-item:hover .task-delete {
    opacity: 1;
}

/* Animation for task completion */
.task-item.completing {
    animation: complete 0.3s ease-out;
}

@keyframes complete {
    0% { transform: scale(1); }
    50% { transform: scale(1.02); background: var(--success-light); }
    100% { transform: scale(1); }
}

JavaScript Handlers

// Tab switching
function setActiveTab(button) {
    document.querySelectorAll('.filter-tab').forEach(tab => {
        tab.classList.remove('active');
    });
    button.classList.add('active');
}

// Task completion animation
document.body.addEventListener('htmx:beforeSwap', (e) => {
    if (e.detail.target.classList.contains('task-item')) {
        e.detail.target.classList.add('completing');
    }
});

// Update counts after any task change
document.body.addEventListener('taskUpdated', () => {
    htmx.ajax('GET', '/api/tasks/stats', {
        target: '#task-stats',
        swap: 'innerHTML'
    });
});

// Keyboard navigation
document.addEventListener('keydown', (e) => {
    const taskItems = document.querySelectorAll('.task-item');
    const focused = document.activeElement.closest('.task-item');
    
    if (e.key === 'ArrowDown' && focused) {
        const index = [...taskItems].indexOf(focused);
        if (index < taskItems.length - 1) {
            taskItems[index + 1].querySelector('input').focus();
        }
    }
    
    if (e.key === 'ArrowUp' && focused) {
        const index = [...taskItems].indexOf(focused);
        if (index > 0) {
            taskItems[index - 1].querySelector('input').focus();
        }
    }
    
    if (e.key === ' ' && focused) {
        e.preventDefault();
        focused.querySelector('.task-checkbox').click();
    }
});

// Due date formatting
function formatDueDate(dateString) {
    const date = new Date(dateString);
    const today = new Date();
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    
    if (date.toDateString() === today.toDateString()) {
        return 'Today';
    } else if (date.toDateString() === tomorrow.toDateString()) {
        return 'Tomorrow';
    } else {
        return date.toLocaleDateString('en-US', { 
            month: 'short', 
            day: 'numeric' 
        });
    }
}

Creating Tasks from Chat

In the Chat app, you can create tasks naturally:

You: Create a task to review the budget by Friday
Bot: ✅ Task created:
     📋 Review the budget
     📅 Due: Friday, March 22
     🏷️ Category: Work
     
     [View Tasks]

Integration with Calendar

Tasks with due dates appear in your Calendar view:

<div class="calendar-task"
     hx-get="/api/tasks/123"
     hx-target="#task-detail"
     hx-trigger="click">
    📋 Review quarterly report
</div>

Troubleshooting

Tasks Not Saving

  1. Check network connection
  2. Verify API endpoint is accessible
  3. Check browser console for errors
  4. Try refreshing the page

Filters Not Working

  1. Click the filter tab again
  2. Check if tasks exist for that filter
  3. Clear browser cache
  4. Verify HTMX is loaded

Stats Not Updating

  1. Check for JavaScript errors
  2. Verify taskUpdated event is firing
  3. Inspect network requests
  4. Reload the page

See Also