From 8c5628187fc96ea805521d787dfb896558d3b480 Mon Sep 17 00:00:00 2001
From: Mark Pitblado <mark@pitblado.me>
Date: Sat, 26 Oct 2024 05:58:08 -0700
Subject: [PATCH 1/5] feat: show completed tasks with DONE

---
 src/main.rs   |  6 ++----
 src/models.rs |  2 +-
 src/ui.rs     | 17 +++++++++++++++--
 3 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index f3e8eba..797fae4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -48,9 +48,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     let api_key = config.vikunja.api_key;
 
     let all_tasks = api::fetch_tasks(&instance_url, &api_key, 1).await?;
-
-    let incomplete_tasks: Vec<models::Task> =
-        all_tasks.into_iter().filter(|task| !task.done).collect();
+    let tasks_for_app = all_tasks.clone();
 
     enable_raw_mode()?;
     let mut stdout = io::stdout();
@@ -60,7 +58,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     terminal.hide_cursor()?;
 
-    let app = App::new(incomplete_tasks);
+    let app = App::new(tasks_for_app);
 
     let res = run_app(&mut terminal, app, &instance_url, &api_key).await;
 
diff --git a/src/models.rs b/src/models.rs
index 8b85d12..c9f242d 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -1,7 +1,7 @@
 use serde::Deserialize;
 
 // Task struct
-#[derive(Deserialize, Debug)]
+#[derive(Clone, Deserialize, Debug)]
 pub struct Task {
     pub id: u64,
     pub title: String,
diff --git a/src/ui.rs b/src/ui.rs
index 046d516..479ccbf 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -124,7 +124,20 @@ pub async fn run_app<B: Backend>(
                         let tasks: Vec<ListItem> = app
                             .tasks
                             .iter()
-                            .map(|task| ListItem::new(Line::from(task.title.clone())))
+                            .map(|task| {
+                                // Check if the task is done
+                                let content = if task.done {
+                                    // Prepend "DONE" for completed tasks
+                                    vec![
+                                        Span::styled("DONE ", Style::default().fg(Color::Green)),
+                                        Span::raw(&task.title),
+                                    ]
+                                } else {
+                                    vec![Span::raw(&task.title)]
+                                };
+
+                                ListItem::new(Line::from(content))
+                            })
                             .collect();
 
                         List::new(tasks)
@@ -136,7 +149,7 @@ pub async fn run_app<B: Backend>(
                             )
                             .highlight_symbol(">> ")
                     } else {
-                        List::new(vec![ListItem::new("No incomplete tasks available")])
+                        List::new(vec![ListItem::new("No tasks available")])
                             .block(Block::default().borders(Borders::ALL).title("Tasks"))
                     };
 

From 2dacd1b0bc522ae1472bf2f8af4e3f04738fae43 Mon Sep 17 00:00:00 2001
From: Mark Pitblado <mark@pitblado.me>
Date: Sat, 26 Oct 2024 06:26:56 -0700
Subject: [PATCH 2/5] feat: add ability to toggle done tasks

---
 src/app.rs | 33 +++++++++++++++++++++++++++------
 src/ui.rs  | 15 ++++++++++-----
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index c470bdb..efa4bc3 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -11,6 +11,7 @@ pub struct App {
     pub input_mode: InputMode,
     pub new_task_title: String,
     pub page: usize,
+    pub show_done_tasks: bool,
 }
 
 pub enum InputMode {
@@ -33,9 +34,25 @@ impl App {
             input_mode: InputMode::Normal,
             new_task_title: String::new(),
             page: 1,
+            show_done_tasks: false,
         }
     }
 
+    pub async fn refresh_tasks(
+        &mut self,
+        instance_url: &str,
+        api_key: &str,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        let new_tasks = fetch_tasks(instance_url, api_key, self.page).await?;
+        if self.show_done_tasks {
+            self.tasks = new_tasks;
+        } else {
+            self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect();
+        }
+        self.state.select(Some(0));
+        Ok(())
+    }
+
     pub fn next_page(&mut self) {
         self.page += 1;
     }
@@ -101,17 +118,21 @@ impl App {
                 KeyCode::Char('n') => {
                     // Next page
                     self.next_page();
-                    if let Ok(new_tasks) = fetch_tasks(instance_url, api_key, self.page).await {
-                        self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect();
-                        self.state.select(Some(0));
+                    if let Err(err) = self.refresh_tasks(instance_url, api_key).await {
+                        eprintln!("Error fetching tasks: {}", err);
                     }
                 }
                 KeyCode::Char('p') => {
                     // Previous page
                     self.previous_page();
-                    if let Ok(new_tasks) = fetch_tasks(instance_url, api_key, self.page).await {
-                        self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect();
-                        self.state.select(Some(0));
+                    if let Err(err) = self.refresh_tasks(instance_url, api_key).await {
+                        eprintln!("Error fetching tasks: {}", err);
+                    }
+                }
+                KeyCode::Char('t') => {
+                    self.show_done_tasks = !self.show_done_tasks;
+                    if let Err(err) = self.refresh_tasks(instance_url, api_key).await {
+                        eprintln!("Error fetching tasks: {}", err);
                     }
                 }
                 KeyCode::Char('a') => {
diff --git a/src/ui.rs b/src/ui.rs
index 479ccbf..496f025 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -70,6 +70,8 @@ fn get_legend(input_mode: &InputMode) -> Text<'static> {
             Span::raw(": Next Page "),
             Span::styled(" p ", Style::default().fg(Color::Red)),
             Span::raw(": Previous Page "),
+            Span::styled(" t ", Style::default().fg(Color::Red)),
+            Span::raw(": Toggle Done "),
             Span::styled(" Enter ", Style::default().fg(Color::Red)),
             Span::raw(": View Details "),
             Span::styled(" a ", Style::default().fg(Color::Red)),
@@ -119,15 +121,19 @@ pub async fn run_app<B: Backend>(
                         )
                         .split(body_chunk);
 
+                    let task_title = if app.show_done_tasks {
+                        "Tasks (All)"
+                    } else {
+                        "Tasks (Undone)"
+                    };
+
                     // Left panel: Task list
                     let tasks_widget = if !app.tasks.is_empty() {
                         let tasks: Vec<ListItem> = app
                             .tasks
                             .iter()
                             .map(|task| {
-                                // Check if the task is done
                                 let content = if task.done {
-                                    // Prepend "DONE" for completed tasks
                                     vec![
                                         Span::styled("DONE ", Style::default().fg(Color::Green)),
                                         Span::raw(&task.title),
@@ -135,13 +141,12 @@ pub async fn run_app<B: Backend>(
                                 } else {
                                     vec![Span::raw(&task.title)]
                                 };
-
                                 ListItem::new(Line::from(content))
                             })
                             .collect();
 
                         List::new(tasks)
-                            .block(Block::default().borders(Borders::ALL).title("Tasks"))
+                            .block(Block::default().borders(Borders::ALL).title(task_title))
                             .highlight_style(
                                 Style::default()
                                     .fg(Color::Green)
@@ -150,7 +155,7 @@ pub async fn run_app<B: Backend>(
                             .highlight_symbol(">> ")
                     } else {
                         List::new(vec![ListItem::new("No tasks available")])
-                            .block(Block::default().borders(Borders::ALL).title("Tasks"))
+                            .block(Block::default().borders(Borders::ALL).title(task_title))
                     };
 
                     f.render_stateful_widget(tasks_widget, chunks[0], &mut app.state);

From 4bfbcb77bda368f4e6c1c8f96fac4c422a36a62f Mon Sep 17 00:00:00 2001
From: Mark Pitblado <mark@pitblado.me>
Date: Sat, 26 Oct 2024 06:29:02 -0700
Subject: [PATCH 3/5] docs: add conventional commits badge

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 2b1da34..e16b8af 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # vikunja-tui
 
+[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
+
 This is a simple terminal user interface for [vikunja](https://vikunja.io). The purpose is to allow users to manage tasks from the terminal, using their own API key. This project is not managed or affiliated with the Vikunja team.
 
 ## Setup

From 1354f4def1407f3d81f7c55fddd511b93c20dc6a Mon Sep 17 00:00:00 2001
From: Mark Pitblado <mark@pitblado.me>
Date: Sat, 26 Oct 2024 06:48:29 -0700
Subject: [PATCH 4/5] fix: show undone tasks before first toggle

---
 src/main.rs | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 797fae4..224ef47 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,8 @@ mod app;
 mod models;
 mod ui;
 
+use crate::api::fetch_tasks;
+
 use app::App;
 use crossterm::{
     execute,
@@ -47,8 +49,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     let instance_url = config.vikunja.instance_url;
     let api_key = config.vikunja.api_key;
 
-    let all_tasks = api::fetch_tasks(&instance_url, &api_key, 1).await?;
-    let tasks_for_app = all_tasks.clone();
+    let show_done_tasks = false;
+
+    let tasks = fetch_tasks(&instance_url, &api_key, 1).await?;
+    let tasks = if show_done_tasks {
+        tasks
+    } else {
+        tasks.into_iter().filter(|task| !task.done).collect()
+    };
 
     enable_raw_mode()?;
     let mut stdout = io::stdout();
@@ -58,7 +66,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     terminal.hide_cursor()?;
 
-    let app = App::new(tasks_for_app);
+    let mut app = App::new(tasks);
 
     let res = run_app(&mut terminal, app, &instance_url, &api_key).await;
 

From 42f17199e6596990c91e821044f8aad3aa452d32 Mon Sep 17 00:00:00 2001
From: Mark Pitblado <mark@pitblado.me>
Date: Sat, 26 Oct 2024 06:49:29 -0700
Subject: [PATCH 5/5] fix: automatic cargo recommended fixes

---
 src/api.rs  | 2 +-
 src/main.rs | 2 +-
 src/ui.rs   | 3 +--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/api.rs b/src/api.rs
index 42b9795..1eab872 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -1,4 +1,4 @@
-use crate::models::{Label, Task, TaskDetail};
+use crate::models::{Task, TaskDetail};
 use reqwest::Client;
 use std::collections::HashMap;
 
diff --git a/src/main.rs b/src/main.rs
index 224ef47..9f79278 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -66,7 +66,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     terminal.hide_cursor()?;
 
-    let mut app = App::new(tasks);
+    let app = App::new(tasks);
 
     let res = run_app(&mut terminal, app, &instance_url, &api_key).await;
 
diff --git a/src/ui.rs b/src/ui.rs
index 496f025..d6137a5 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,7 +1,6 @@
 use crate::app::{App, InputMode};
-use crate::models::{Label, Task};
 use ansi_parser::{AnsiParser, Output};
-use crossterm::event::{self, Event as CEvent, KeyCode};
+use crossterm::event::{self, Event as CEvent};
 use ratatui::{
     backend::Backend,
     layout::{Alignment, Constraint, Direction, Layout, Rect},