use crate::user_input::{get_user_confirmation, get_user_selection};
use chrono::{TimeZone, Utc};
use colored::*;
use home::home_dir;
use rusqlite::{Connection, Result};
use tabled::Tabled;
pub mod dao;
pub mod user_input;
pub const TABLE_TASKS: &str = "tasks";
pub const TABLE_BOARDS: &str = "boards";
pub const TABLE_COMMENTS: &str = "comments";
pub const MAIN_MENU_OPTIONS: [&str; 7] = [
"Create Task",
"View Tasks [Pending]",
"View Tasks [Done]",
"Create Board",
"View Boards",
"other",
"Exit",
];
const TASK_ACTIONS: [&str; 6] = [
"Delete",
"Change",
"Add comment",
"View comments",
"Set reminder",
"Cancel",
];
pub const BOARD_ACTIONS: [&str; 3] = ["Delete", "Change title", "Cancel"];
pub const SAMPLE_TITLE: &str = "sample";
pub const DATETIME_FORMAT: &str = "%a, %b %e %Y %T";
pub const DATE_FORMAT: &str = "%Y%m%d";
pub const TIME_FORMAT: &str = "%H:%M:%S";
pub const ALTERNATIVE_DATETIME_FORMAT: &str = "%Y%m%d %H:%M:%S";
#[derive(Debug, Tabled)]
pub struct Task {
pub id: u16,
pub title: String,
pub done: u8,
pub board_id: u16,
pub created_at: String,
pub reminder: String,
}
#[derive(Debug)]
pub struct Board {
pub id: u16,
pub title: String,
}
#[derive(Debug)]
pub struct Comment {
pub id: u16,
pub title: String,
pub created_at: String,
}
#[derive(Debug)]
pub enum Color {
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
#[derive(Debug)]
pub struct Record {
pub qtd: u16,
}
pub fn get_connection() -> Connection {
let conn = Connection::open(&get_database_path()).unwrap();
conn
}
pub fn get_database_path() -> String {
format!(
"{}/{}.db3",
home_dir().unwrap().display(),
env!("CARGO_PKG_NAME")
)
}
fn select_comment(comments_raw: &Vec<Comment>, task_title: &str) -> Option<(String, u16)> {
let mut comments: Vec<String> = comments_raw
.iter()
.map(|x| format!("{} - {} [{}]", &x.id, &x.title, &x.created_at))
.collect();
comments.sort();
let selected_comment = user_input::get_user_selection(
&comments,
format!("Comments for task {}", task_title).as_str(),
);
let selected_comment_id: u16 = selected_comment
.0
.split_whitespace()
.next()
.unwrap()
.parse()
.unwrap();
let selected_comment: &Comment = comments_raw
.iter()
.find(|x| x.id == selected_comment_id)
.unwrap();
let selected_comment = (selected_comment.title.to_string(), selected_comment.id);
Some(selected_comment)
}
pub fn select_board() -> Option<(String, u16)> {
let records_qtd = dao::get_records_qtd(TABLE_BOARDS).unwrap();
if records_qtd == 0 {
display_message("info", "No Boards found in database", Color::Blue);
return None;
}
let boards_raw = dao::get_boards().unwrap();
let mut boards: Vec<String> = boards_raw
.iter()
.map(|x| format!("{} - {}", &x.id, &x.title))
.collect();
boards.sort();
let selected_board = user_input::get_user_selection(&boards, "Board");
let selected_board_id: u16 = selected_board
.0
.split_whitespace()
.next()
.unwrap()
.parse()
.unwrap();
let selected_board: &Board = boards_raw
.iter()
.find(|x| x.id == selected_board_id)
.unwrap();
let selected_board = (selected_board.title.to_string(), selected_board.id);
Some(selected_board)
}
pub fn list_boards() -> Result<()> {
let selected_board = select_board();
if selected_board.is_none() {
return Ok(());
}
let (board_title, board_id) = selected_board.unwrap();
let (_, action_index) = get_user_selection(
&BOARD_ACTIONS.to_vec(),
format!("Action on Board {}", board_title).as_str(),
);
match action_index {
0 => delete_board(&board_title, board_id)?,
1 => dao::edit_board(&board_title, board_id)?,
_ => return Ok(()),
};
Ok(())
}
fn delete_board(board_title: &str, board_id: u16) -> Result<()> {
let deletion_confirmation =
get_user_confirmation(format!("Are you sure you want to delete {}", &board_title).as_str());
if deletion_confirmation {
let deletion_successful = dao::delete_record_by_id(TABLE_BOARDS, board_id);
match deletion_successful {
Ok(_) => display_message(
"ok",
format!("Board {} has been deleted", &board_title).as_str(),
Color::Green,
),
Err(_) => display_message(
"error",
format!("Could not delete {} ", &board_title).as_str(),
Color::Red,
),
}
}
Ok(())
}
pub fn datetime_str_is_past(reminder: &str) -> bool {
let datetime = Utc.datetime_from_str(reminder, DATETIME_FORMAT);
if datetime.is_err() {
return false;
}
datetime.unwrap() < Utc::now()
}
fn delete_task(task_title: &str, task_id: u16) -> Result<()> {
let deletion_confirmation =
get_user_confirmation(format!("Are you sure you want to delete {}", &task_title).as_str());
if deletion_confirmation {
let deletion_successful = dao::delete_record_by_id(TABLE_TASKS, task_id);
match deletion_successful {
Ok(_) => display_message(
"ok",
format!("task {} has been deleted", &task_title).as_str(),
Color::Green,
),
Err(_) => display_message(
"error",
format!("Could not delete {} ", &task_title).as_str(),
Color::Red,
),
}
}
Ok(())
}
pub fn list_tasks(done: u8) -> Result<()> {
let selected_task = dao::select_task(done);
if selected_task.is_none() {
return Ok(());
}
let (task_title, task_id) = selected_task.unwrap();
let (_, action_index) = get_user_selection(
&TASK_ACTIONS.to_vec(),
format!("Action on Task {}", task_title).as_str(),
);
match action_index {
0 => delete_task(&task_title, task_id)?,
1 => dao::switch_task_status(task_id)?,
2 => dao::create_comment(task_id)?,
3 => list_comments(&task_title, task_id)?,
4 => dao::set_reminder(task_id)?,
_ => return Ok(()),
};
Ok(())
}
pub fn list_comments(task_title: &str, task_id: u16) -> Result<()> {
let comments = dao::get_comments_by_task_id(task_id)?;
if comments.len() == 0 {
display_message("info", "No comments for this Task", Color::Cyan);
return Ok(());
}
let selected_comment = select_comment(&comments, &task_title).unwrap();
println!("{:?}", selected_comment);
Ok(())
}
pub fn other() -> Result<()> {
Ok(())
}
pub fn display_message(message_type: &str, message: &str, color: Color) {
let color = match color {
Color::Red => "red",
Color::Green => "green",
Color::Yellow => "yellow",
Color::Blue => "blue",
Color::Magenta => "magenta",
Color::Cyan => "cyan",
Color::White => "white",
};
let msg = format!("[{}] {}", message_type.to_uppercase(), message).color(color);
println!("{msg}");
}
pub fn get_tasks(query: &str) -> Result<Vec<Task>> {
let conn = get_connection();
let mut records: Vec<Task> = Vec::new();
let mut stmt = conn.prepare(&query)?;
let result_iter = stmt.query_map([], |row| {
Ok(Task {
id: row.get(0)?,
title: row.get(1)?,
done: row.get(2)?,
board_id: row.get(3)?,
created_at: row.get(4)?,
reminder: row.get(5)?,
})
})?;
for i in result_iter {
records.push(i?);
}
Ok(records)
}
pub fn display_app_intro() {
let title = format!(
"\n{} - {} \nAuthors: {}\nVersion: {}\nLicense: {}\nCrafted with ❤️ using Rust language\nDatabase: {}\n",
env!("CARGO_PKG_NAME").to_uppercase(),
env!("CARGO_PKG_DESCRIPTION"),
env!("CARGO_PKG_AUTHORS"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_LICENSE"),
get_database_path()
);
println!("{title}");
}