Skip to main content

dial/task/
mod.rs

1pub mod models;
2
3use crate::db::get_db;
4use crate::errors::{DialError, Result};
5use crate::output::{bold, dim, green, print_success, red, yellow};
6use chrono::Local;
7use models::Task;
8
9pub fn task_add(description: &str, priority: i32, spec_section_id: Option<i64>) -> Result<i64> {
10    let conn = get_db(None)?;
11    conn.execute(
12        "INSERT INTO tasks (description, priority, spec_section_id) VALUES (?1, ?2, ?3)",
13        rusqlite::params![description, priority, spec_section_id],
14    )?;
15    let task_id = conn.last_insert_rowid();
16    print_success(&format!("Added task #{}: {}", task_id, description));
17    Ok(task_id)
18}
19
20pub fn task_list(show_all: bool) -> Result<()> {
21    let conn = get_db(None)?;
22
23    let sql = if show_all {
24        "SELECT id, description, status, priority, blocked_by, created_at
25         FROM tasks ORDER BY priority, id"
26    } else {
27        "SELECT id, description, status, priority, blocked_by, created_at
28         FROM tasks WHERE status NOT IN ('completed', 'cancelled')
29         ORDER BY priority, id"
30    };
31
32    let mut stmt = conn.prepare(sql)?;
33    let rows: Vec<(i64, String, String, i32, Option<String>, String)> = stmt
34        .query_map([], |row| {
35            Ok((
36                row.get(0)?,
37                row.get(1)?,
38                row.get(2)?,
39                row.get(3)?,
40                row.get(4)?,
41                row.get(5)?,
42            ))
43        })?
44        .collect::<std::result::Result<Vec<_>, _>>()?;
45
46    if rows.is_empty() {
47        println!("{}", dim("No tasks found."));
48        return Ok(());
49    }
50
51    println!("{}", bold("Tasks"));
52    println!("{}", "=".repeat(60));
53
54    for (id, description, status, priority, blocked_by, _created_at) in rows {
55        let status_str = match status.as_str() {
56            "pending" => dim(&format!("[{}]", status)),
57            "in_progress" => yellow(&format!("[{}]", status)),
58            "completed" => green(&format!("[{}]", status)),
59            "blocked" => red(&format!("[{}]", status)),
60            "cancelled" => dim(&format!("[{}]", status)),
61            _ => format!("[{}]", status),
62        };
63
64        let priority_str = if priority != 5 {
65            format!("P{}", priority)
66        } else {
67            String::new()
68        };
69
70        let blocked_str = if let Some(reason) = blocked_by {
71            red(&format!(" (blocked: {})", reason))
72        } else {
73            String::new()
74        };
75
76        println!(
77            "  #{:3} {:20} {:4} {}{}",
78            id, status_str, priority_str, description, blocked_str
79        );
80    }
81
82    Ok(())
83}
84
85pub fn task_next() -> Result<Option<Task>> {
86    let conn = get_db(None)?;
87
88    let mut stmt = conn.prepare(
89        "SELECT id, description, status, priority, blocked_by, spec_section_id, created_at, started_at, completed_at
90         FROM tasks WHERE status = 'pending'
91         ORDER BY priority, id LIMIT 1",
92    )?;
93
94    let task = stmt
95        .query_row([], |row| Task::from_row(row))
96        .ok();
97
98    match &task {
99        Some(t) => {
100            println!("{}", bold("Next task:"));
101            println!("  #{}: {}", t.id, t.description);
102            if let Some(spec_id) = t.spec_section_id {
103                println!("{}", dim(&format!("  Spec section: {}", spec_id)));
104            }
105        }
106        None => {
107            println!("{}", dim("No pending tasks."));
108        }
109    }
110
111    Ok(task)
112}
113
114pub fn task_done(task_id: i64) -> Result<()> {
115    let conn = get_db(None)?;
116    let now = Local::now().to_rfc3339();
117
118    let changed = conn.execute(
119        "UPDATE tasks SET status = 'completed', completed_at = ?1 WHERE id = ?2",
120        rusqlite::params![now, task_id],
121    )?;
122
123    if changed == 0 {
124        return Err(DialError::TaskNotFound(task_id));
125    }
126
127    print_success(&format!("Task #{} marked as completed.", task_id));
128    Ok(())
129}
130
131pub fn task_block(task_id: i64, reason: &str) -> Result<()> {
132    let conn = get_db(None)?;
133
134    let changed = conn.execute(
135        "UPDATE tasks SET status = 'blocked', blocked_by = ?1 WHERE id = ?2",
136        rusqlite::params![reason, task_id],
137    )?;
138
139    if changed == 0 {
140        return Err(DialError::TaskNotFound(task_id));
141    }
142
143    println!("{}", yellow(&format!("Task #{} marked as blocked: {}", task_id, reason)));
144    Ok(())
145}
146
147pub fn task_cancel(task_id: i64) -> Result<()> {
148    let conn = get_db(None)?;
149
150    let changed = conn.execute(
151        "UPDATE tasks SET status = 'cancelled' WHERE id = ?1",
152        [task_id],
153    )?;
154
155    if changed == 0 {
156        return Err(DialError::TaskNotFound(task_id));
157    }
158
159    println!("{}", dim(&format!("Task #{} cancelled.", task_id)));
160    Ok(())
161}
162
163pub fn task_search(query: &str) -> Result<()> {
164    let conn = get_db(None)?;
165
166    let mut stmt = conn.prepare(
167        "SELECT t.id, t.description, t.status, t.priority
168         FROM tasks t
169         INNER JOIN tasks_fts fts ON t.id = fts.rowid
170         WHERE tasks_fts MATCH ?1
171         ORDER BY rank",
172    )?;
173
174    let rows: Vec<(i64, String, String, i32)> = stmt
175        .query_map([query], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)))?
176        .collect::<std::result::Result<Vec<_>, _>>()?;
177
178    if rows.is_empty() {
179        println!("{}", dim(&format!("No tasks matching '{}'.", query)));
180        return Ok(());
181    }
182
183    println!("{}", bold(&format!("Tasks matching '{}':", query)));
184    for (id, description, status, _priority) in rows {
185        println!("  #{} [{}] {}", id, status, description);
186    }
187
188    Ok(())
189}
190
191pub fn get_task_by_id(task_id: i64) -> Result<Task> {
192    let conn = get_db(None)?;
193
194    let mut stmt = conn.prepare(
195        "SELECT id, description, status, priority, blocked_by, spec_section_id, created_at, started_at, completed_at
196         FROM tasks WHERE id = ?1",
197    )?;
198
199    stmt.query_row([task_id], |row| Task::from_row(row))
200        .map_err(|_| DialError::TaskNotFound(task_id))
201}