started using ratatui

This commit is contained in:
thatscringebro 2025-12-10 20:54:25 -05:00
parent 24ac0a0fbe
commit 55dfd16cd2
4 changed files with 282 additions and 137 deletions

View File

@ -5,4 +5,5 @@ edition = "2024"
[dependencies]
chrono = "0.4.42"
ratatui = "0.29.0"
sqlite = "0.37.0"

29
src/app.rs Normal file
View File

@ -0,0 +1,29 @@
use sqlite::Connection;
pub enum CurrentScreen {
Main,
Exiting,
}
pub struct App {
pub current_screen: CurrentScreen,
connection: Connection,
exit: bool,
}
impl App {
pub fn new() -> App {
let connection = match Connection::open("ft_rs.db") {
Ok(con) => con,
Err(e) => {
eprintln!("Error opening database: {}", e);
panic!("stopping");
}
};
return App {
current_screen: CurrentScreen::Main,
connection: connection,
exit: false,
};
}
}

View File

@ -1,150 +1,214 @@
mod app;
mod data_layer;
mod entities;
use chrono::{Local, TimeZone};
use sqlite::Connection;
use std::io;
mod ui;
use crate::{
app::{App, CurrentScreen},
ui::ui,
};
use ratatui::{
Terminal,
crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
},
prelude::Backend,
};
use std::{error::Error, io};
use crate::entities::{Account, Transaction, TransactionType};
fn main() -> Result<(), Box<dyn Error>> {
enable_raw_mode()?;
let mut stderr = io::stderr();
execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?;
fn main() {
let connection = match Connection::open("ft_rs.db") {
Ok(con) => con,
Err(e) => {
eprintln!("Error opening database: {}", e);
return;
}
};
let mut terminal = ratatui::init();
let mut app = App::new();
let res = run_app(&mut terminal, &mut app);
data_layer::setup(&connection);
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("{err:?}");
}
return Ok(());
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<bool> {
loop {
println!("Please choose an option:");
println!("1. List accounts");
println!("2. Add an account");
println!("3. List transaction types");
println!("4. Add a transaction type");
println!("5. List transactions");
println!("6. Add a transaction");
println!("0. Exit");
terminal.draw(|f| ui(f, app))?;
let mut choice = String::new();
io::stdin()
.read_line(&mut choice)
.expect("Failed to read line");
// S'occupe des keyevents
if let Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Release {
continue;
}
let choice = choice.trim();
match choice {
"1" => {
let accounts = data_layer::get_accounts(&connection);
let mut total = 0.0;
for ac in accounts.iter() {
let ac_total = ac.get_total(&connection);
println!(
"Id: {}, Name: {}, Total: {}",
ac.get_id(),
ac.get_name(),
ac_total
);
total += ac_total;
}
println!("Total: {}", total);
}
"2" => {
println!("Please enter the account name:");
let mut ac_name = String::new();
io::stdin()
.read_line(&mut ac_name)
.expect("Failed to read line");
let ac_name = ac_name.trim();
let new_ac = Account::new(0, ac_name.to_string(), entities::AccountType::Cash);
data_layer::upsert_account(&connection, new_ac);
}
"3" => {
let types = data_layer::get_transaction_types(&connection);
for t in types.iter() {
println!("Name: {}", t.get_description());
}
}
"4" => {
println!("Please enter the transaction type:");
let mut desc = String::new();
io::stdin()
.read_line(&mut desc)
.expect("Failed to read line");
let desc = desc.trim();
let tr_type = TransactionType::new(0, desc.to_string());
data_layer::upsert_transaction_type(&connection, tr_type);
}
"5" => {
println!("Please enter the account id:");
let mut ac_id_str = String::new();
io::stdin()
.read_line(&mut ac_id_str)
.expect("Failed to read line");
ac_id_str = ac_id_str.trim().to_string();
let ac_id = ac_id_str.parse::<i64>().unwrap();
let trx: Vec<entities::Transaction> =
data_layer::get_account_transactions(&connection, ac_id);
// .sort_by(|a, b| b.get_date().cmp(&a.get_date()))
// .try_into()
// .unwrap();
for t in trx.iter() {
println!(
"Date: {}, Type: {}, Description: {}, Amount: {}$",
Local.from_utc_datetime(&t.get_date().naive_local()),
t.get_type().get_description(),
t.get_desc(),
t.get_amount()
);
}
}
"6" => {
println!("Please enter the account id:");
let mut ac_id_str = String::new();
io::stdin()
.read_line(&mut ac_id_str)
.expect("Failed to read line");
ac_id_str = ac_id_str.trim().to_string();
let ac_id = ac_id_str.parse::<i64>().unwrap();
println!("Please enter the transaction type id:");
let mut tr_type_id_str = String::new();
io::stdin()
.read_line(&mut tr_type_id_str)
.expect("Failed to read line");
tr_type_id_str = tr_type_id_str.trim().to_string();
let type_id = tr_type_id_str.parse::<i64>().unwrap();
println!("Please enter the amount:");
let mut amnt_str = String::new();
io::stdin()
.read_line(&mut amnt_str)
.expect("Failed to read line");
amnt_str = amnt_str.trim().to_string();
let amount = amnt_str.parse::<f64>().unwrap();
println!("Please enter the description:");
let mut desc = String::new();
io::stdin()
.read_line(&mut desc)
.expect("Failed to read line");
desc = desc.trim().to_string();
let tr = Transaction::new(
0,
ac_id,
amount,
chrono::offset::Utc::now(),
desc,
type_id,
&connection,
);
data_layer::upsert_transaction(&connection, tr);
}
"0" => {
println!("Exiting...");
break; // Exit the loop
}
_ => {
println!("Invalid choice.");
match app.current_screen {
CurrentScreen::Main => match key.code {
KeyCode::Char('q') => {
app.current_screen = CurrentScreen::Exiting;
}
_ => {}
},
CurrentScreen::Exiting => match key.code {
KeyCode::Char('y') => {
return Ok(true);
}
_ => {
app.current_screen = CurrentScreen::Main;
}
},
}
}
}
}
// fn main() {
// let connection = match Connection::open("ft_rs.db") {
// Ok(con) => con,
// Err(e) => {
// eprintln!("Error opening database: {}", e);
// return;
// }
// };
// data_layer::setup(&connection);
// loop {
// println!("Please choose an option:");
// println!("1. List accounts");
// println!("2. Add an account");
// println!("3. List transaction types");
// println!("4. Add a transaction type");
// println!("5. List transactions");
// println!("6. Add a transaction");
// println!("0. Exit");
// let mut choice = String::new();
// io::stdin()
// .read_line(&mut choice)
// .expect("Failed to read line");
// let choice = choice.trim();
// match choice {
// "1" => {
// let accounts = data_layer::get_accounts(&connection);
// let mut total = 0.0;
// for ac in accounts.iter() {
// let ac_total = ac.get_total(&connection);
// println!(
// "Id: {}, Name: {}, Total: {}",
// ac.get_id(),
// ac.get_name(),
// ac_total
// );
// total += ac_total;
// }
// println!("Total: {}", total);
// }
// "2" => {
// println!("Please enter the account name:");
// let mut ac_name = String::new();
// io::stdin()
// .read_line(&mut ac_name)
// .expect("Failed to read line");
// let ac_name = ac_name.trim();
// let new_ac = Account::new(0, ac_name.to_string(), entities::AccountType::Cash);
// data_layer::upsert_account(&connection, new_ac);
// }
// "3" => {
// let types = data_layer::get_transaction_types(&connection);
// for t in types.iter() {
// println!("Name: {}", t.get_description());
// }
// }
// "4" => {
// println!("Please enter the transaction type:");
// let mut desc = String::new();
// io::stdin()
// .read_line(&mut desc)
// .expect("Failed to read line");
// let desc = desc.trim();
// let tr_type = TransactionType::new(0, desc.to_string());
// data_layer::upsert_transaction_type(&connection, tr_type);
// }
// "5" => {
// println!("Please enter the account id:");
// let mut ac_id_str = String::new();
// io::stdin()
// .read_line(&mut ac_id_str)
// .expect("Failed to read line");
// ac_id_str = ac_id_str.trim().to_string();
// let ac_id = ac_id_str.parse::<i64>().unwrap();
// let trx: Vec<entities::Transaction> =
// data_layer::get_account_transactions(&connection, ac_id);
// // .sort_by(|a, b| b.get_date().cmp(&a.get_date()))
// // .try_into()
// // .unwrap();
// for t in trx.iter() {
// println!(
// "Date: {}, Type: {}, Description: {}, Amount: {}$",
// Local.from_utc_datetime(&t.get_date().naive_local()),
// t.get_type().get_description(),
// t.get_desc(),
// t.get_amount()
// );
// }
// }
// "6" => {
// println!("Please enter the account id:");
// let mut ac_id_str = String::new();
// io::stdin()
// .read_line(&mut ac_id_str)
// .expect("Failed to read line");
// ac_id_str = ac_id_str.trim().to_string();
// let ac_id = ac_id_str.parse::<i64>().unwrap();
// println!("Please enter the transaction type id:");
// let mut tr_type_id_str = String::new();
// io::stdin()
// .read_line(&mut tr_type_id_str)
// .expect("Failed to read line");
// tr_type_id_str = tr_type_id_str.trim().to_string();
// let type_id = tr_type_id_str.parse::<i64>().unwrap();
// println!("Please enter the amount:");
// let mut amnt_str = String::new();
// io::stdin()
// .read_line(&mut amnt_str)
// .expect("Failed to read line");
// amnt_str = amnt_str.trim().to_string();
// let amount = amnt_str.parse::<f64>().unwrap();
// println!("Please enter the description:");
// let mut desc = String::new();
// io::stdin()
// .read_line(&mut desc)
// .expect("Failed to read line");
// desc = desc.trim().to_string();
// let tr = Transaction::new(
// 0,
// ac_id,
// amount,
// chrono::offset::Utc::now(),
// desc,
// type_id,
// &connection,
// );
// data_layer::upsert_transaction(&connection, tr);
// }
// "0" => {
// println!("Exiting...");
// break; // Exit the loop
// }
// _ => {
// println!("Invalid choice.");
// }
// }
// }
// }

51
src/ui.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::app::{App, CurrentScreen};
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::Text,
widgets::{Block, Borders, Clear, Paragraph, Wrap},
};
pub fn ui(frame: &mut Frame, app: &App) {
if let CurrentScreen::Exiting = app.current_screen {
frame.render_widget(Clear, frame.area());
let popup = Block::default()
.title("Exiting program")
.borders(Borders::all())
.style(Style::default());
let exit_text = Text::styled(
"Are you sure you want to close the program? (y/n)",
Style::default().fg(Color::Red),
);
let exit_paragraph = Paragraph::new(exit_text)
.block(popup)
.wrap(Wrap { trim: false });
let area = centered_rect(60, 20, frame.area());
frame.render_widget(exit_paragraph, area);
}
}
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
return Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1];
}