From 20700e8a38d8b0efd5da2e5493d70bf9cc6b74af Mon Sep 17 00:00:00 2001 From: Nickiel12 Date: Fri, 11 Aug 2023 22:46:43 -0700 Subject: [PATCH] got working history tab --- src/app.rs | 67 ++--------------- src/db/connection.rs | 44 ++++------- src/db/data_cache.rs | 32 ++++++++ src/db/mod.rs | 4 +- src/db/tables.rs | 65 ++++++++++++++++ src/db/tables/mod.rs | 9 --- src/db/transaction.rs | 34 --------- src/main.rs | 17 ++--- src/uis/history.rs | 168 ++++++++++++++++++++++-------------------- 9 files changed, 215 insertions(+), 225 deletions(-) create mode 100644 src/db/data_cache.rs create mode 100644 src/db/tables.rs delete mode 100644 src/db/tables/mod.rs delete mode 100644 src/db/transaction.rs diff --git a/src/app.rs b/src/app.rs index 01b0977..bba371c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,69 +1,18 @@ - -use std::sync::Mutex; use std::sync::Arc; use crossterm::event::{Event, self, KeyCode}; use tokio; -use ratatui::widgets::ListState; - -use crossbeam_channel::Receiver; use log::warn; use crate::db::DB; +use crate::db::data_cache::DataCache; use crate::uis::history::HistoryState; use crate::uis::new_transaction::NewTransactionTabState; use crate::uis::navigation_frame::NavigationState; -use crate::db::transaction::TransactionRecord; pub type AppResult = std::result::Result>; -pub struct StatefulList { - pub state: ListState, - pub items: Vec, -} - -impl StatefulList { - pub fn with_items(items: Vec) -> StatefulList { - StatefulList { - state: ListState::default(), - items, - } - } - - pub fn next(&mut self) { - let i = match self.state.selected() { - Some(i) => { - if i >= self.items.len() - 1 { - 0 - } else { - i + 1 - } - } - None => 0, - }; - self.state.select(Some(i)); - } - - pub fn previous(&mut self) { - let i = match self.state.selected() { - Some(i) => { - if i == 0 { - self.items.len() - 1 - } else { - i - 1 - } - } - None => 0, - }; - self.state.select(Some(i)); - } - - pub fn unselect(&mut self) { - self.state.select(None); - } -} - pub enum InputMode { Insert, Normal, @@ -77,17 +26,17 @@ pub struct App<'a> { pub input_mode: InputMode, pub db: Arc>, - pub records: Arc>>, + pub data_cache: DataCache, } impl<'a> App<'a> { - pub fn new(db: DB, records: Arc>>, r: Receiver) -> App<'a> { + pub fn new(db: DB, data_cache: DataCache) -> App<'a> { App { running: true, - states: States::new(r), + states: States::new(), input_mode: InputMode::Normal, db: Arc::new(tokio::sync::Mutex::new(db)), - records, + data_cache, } } @@ -160,20 +109,16 @@ pub struct States<'a> { pub transactions: NewTransactionTabState<'a>, pub history: HistoryState, - pub new_data: Receiver, - pub active_frame: ActiveFrame, } impl<'a> States<'a> { - pub fn new(r: Receiver) -> States<'a> { + pub fn new() -> States<'a> { States { nav_state: NavigationState::new(), transactions: NewTransactionTabState::new(), history: HistoryState::new(), - new_data: r, - active_frame: ActiveFrame::Navigation, } } diff --git a/src/db/connection.rs b/src/db/connection.rs index 09ea734..6888f32 100644 --- a/src/db/connection.rs +++ b/src/db/connection.rs @@ -1,60 +1,46 @@ -use std::sync::Mutex; -use std::sync::Arc; - -use crossbeam_channel::Sender; - use futures::TryStreamExt; - use sqlx::Error; use sqlx::PgPool; -use sqlx::Row; use sqlx::postgres::PgPoolOptions; -use sqlx::postgres::types::PgMoney; -use time::Date; -use crate::db::transaction::TransactionRecord; +use super::data_cache::DataCache; +use super::tables::Transaction; pub struct DB { conn_pool: PgPool, - new_data_notify: Sender, - records: Arc>>, + data_cache: DataCache, } impl DB { - pub async fn new(records: Arc>>, s: Sender) -> Result { + pub async fn new(data_cache: DataCache) -> Result { let connection_pool = PgPoolOptions::new() .max_connections(3) .connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis").await?; Ok(DB { conn_pool: connection_pool, - new_data_notify: s, - records, + data_cache, }) } pub async fn get_all_records(&mut self) -> Result<(), Error> { - let mut rows = sqlx::query("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date FROM rcnt.transactions") + let mut rows = sqlx::query_as::<_, Transaction>("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date FROM rcnt.transactions ORDER BY trns_id DESC") .fetch(&self.conn_pool); - while let Some(row) = rows.try_next().await? { - let id: i32 = row.try_get("trns_id")?; - let amount: PgMoney = row.try_get("trns_amount")?; - let date: Date = row.try_get("trns_date")?; - self.records.lock().unwrap().clear(); + let mut temp_transactions: Vec = Vec::new(); - self.records.lock().unwrap().push( - TransactionRecord { - id: id.into(), - amount: amount.to_decimal(2).to_string(), - date: date.to_string() - } - ); + while let Some(row) = rows.try_next().await? { + temp_transactions.push(row); } - self.new_data_notify.try_send(true).unwrap(); + + { + let mut transactions = self.data_cache.transactions.lock().unwrap(); + transactions.clear(); + transactions.append(&mut temp_transactions); + } return Ok(()); diff --git a/src/db/data_cache.rs b/src/db/data_cache.rs new file mode 100644 index 0000000..59a769f --- /dev/null +++ b/src/db/data_cache.rs @@ -0,0 +1,32 @@ +use std::sync::{Arc, Mutex}; + +use super::tables; + + +#[derive(Clone)] +pub struct DataCache { + + pub accounts: Arc>>, + + pub buckets: Arc>>, + + pub transaction_catagories: Arc>>, + + pub transaction_breakdowns: Arc>>, + + pub transactions: Arc>>, + +} + + +impl DataCache { + pub fn new() -> DataCache { + DataCache { + accounts: Arc::new(Mutex::new(Vec::new())), + buckets: Arc::new(Mutex::new(Vec::new())), + transaction_catagories: Arc::new(Mutex::new(Vec::new())), + transaction_breakdowns: Arc::new(Mutex::new(Vec::new())), + transactions: Arc::new(Mutex::new(Vec::new())), + } + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 71db374..52701cd 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,8 +1,6 @@ -pub mod transaction; -pub use self::transaction::TransactionRecord; - pub mod connection; pub use self::connection::DB; pub mod tables; +pub mod data_cache; diff --git a/src/db/tables.rs b/src/db/tables.rs new file mode 100644 index 0000000..ba4bab6 --- /dev/null +++ b/src/db/tables.rs @@ -0,0 +1,65 @@ +use std::borrow::Cow; + +use sqlx::{FromRow, postgres::types::PgMoney}; +use time::Date; + + +#[derive(Clone, FromRow)] +pub struct Account { + pub acnt_id: i32, + pub acnt_dsply_name: String, + pub acnt_description: String, +} + + +#[derive(Clone, FromRow)] +pub struct Buckets { + pub bkt_id: i32, + pub bkt_dsply_code: String, + pub bkt_dsply_name: String, + pub bkt_description: String, +} + + +#[derive(Clone, FromRow)] +pub struct TransactionCatagories { + pub trns_ctgry_id: i32, + pub trns_ctgry_dsply_code: String, + pub trns_ctgry_dsply_name: String, + pub trns_ctgry_description: String, +} + + +#[derive(Clone, FromRow)] +pub struct TransactionBreakdown { + pub trns_brkdwn_id: i32, + pub trns_brkdwn_amount: PgMoney, + pub trns_brkdwn_parent_transaction: i32, + pub trns_brkdwn_catagory: i32, + pub trns_brkdwn_bucket: i32, +} + + +#[derive(Clone, FromRow)] +pub struct Transaction{ + pub trns_id: i32, + pub trns_amount: PgMoney, + pub trns_description: String, + pub trns_bucket: i32, + pub trns_date: Date +} + +impl Transaction { + + pub fn get_header() -> String { + return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date") + } + + pub fn to_string(&self) -> String { + return format!(" T{:0>6} | {:>9} | {:>10}", self.trns_id, self.trns_amount.to_decimal(2).to_string(), self.trns_date.to_string()) + } + + pub fn as_str(&self) -> Cow { + return self.to_string().into() + } +} diff --git a/src/db/tables/mod.rs b/src/db/tables/mod.rs deleted file mode 100644 index c3b84e1..0000000 --- a/src/db/tables/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use sqlx::FromRow; - - -#[derive(Clone, FromRow)] -pub struct Account { - pub acnt_id: i32, - pub acnt_dsply_name: String, - pub acnt_description: String, -} diff --git a/src/db/transaction.rs b/src/db/transaction.rs deleted file mode 100644 index f4619b6..0000000 --- a/src/db/transaction.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::borrow::Cow; - - -// cargo add crust_decimal -#[derive(Clone)] -pub struct TransactionRecord { - pub id: i64, - // pub amount: Decimal, - pub amount: String, - //pub record_date: Date, - pub date: String, -} - -impl TransactionRecord { - pub fn new(id: i64, amount: String, record_date: String) -> TransactionRecord { - TransactionRecord { - id, - amount, - date: record_date - } - } - - pub fn get_header() -> String { - return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date") - } - - pub fn to_string(&self) -> String { - return format!(" T{:0>6} | {:>9} | {:>10}", self.id, self.amount, self.date) - } - - pub fn as_str(&self) -> Cow { - return self.to_string().into() - } -} diff --git a/src/main.rs b/src/main.rs index b407ba0..d1ec030 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,6 @@ use std::io; use std::fs::File; use std::time::Duration; -use std::sync::Mutex; -use std::sync::Arc; - -use crossbeam_channel::bounded; use simplelog::*; @@ -13,10 +9,10 @@ use ratatui::{ Terminal, }; +use recount::db::DB; +use recount::db::data_cache::DataCache; use recount::app::{App, AppResult}; use recount::tui::Tui; -use recount::db::DB; -use recount::db::tables; #[tokio::main] async fn main() -> AppResult<()> { @@ -24,13 +20,12 @@ async fn main() -> AppResult<()> { let log_file = "testing_log.txt".to_string(); init_logger(log_file); - let records = Arc::new(Mutex::new(Vec::new())); - let (s, r) = bounded::(2); - let db = DB::new(Arc::clone(&records), s).await?; - + let data_cache = DataCache::new(); + let db = DB::new(data_cache.clone()).await?; // Create an application. - let mut app = App::new(db, records, r); + let mut app = App::new(db, data_cache); + app.refresh(); // Initialize the terminal user interface. let backend = CrosstermBackend::new(io::stderr()); diff --git a/src/uis/history.rs b/src/uis/history.rs index 55e8cf9..a9e73f9 100644 --- a/src/uis/history.rs +++ b/src/uis/history.rs @@ -1,123 +1,135 @@ use crossterm::event::{KeyEvent, KeyEventKind, KeyCode}; -use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Block, Borders, List, ListItem, Paragraph}, text::{Text, Line, Span}, style::{Style, Color, Modifier}}; -use crate::{app::{App, StatefulList}, db::TransactionRecord}; +use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Block, Borders, List, ListItem, Paragraph}, text::{Text, Line, Span}, style::{Style, Color}}; +use crate::{app::App, db::tables::Transaction}; pub struct HistoryState { - pub transacts_list: StatefulList + pub selected_index: Option, } impl HistoryState { + pub fn new() -> HistoryState { - HistoryState { - transacts_list: StatefulList::with_items(vec![ - TransactionRecord::new(1, "$10.00".to_string(), "05-01-2020".to_string()), - TransactionRecord::new(2, "$10.00".to_string(), "05-01-2020".to_string()), - TransactionRecord::new(3, "$10.00".to_string(), "05-01-2020".to_string()), - TransactionRecord::new(4, "$10.00".to_string(), "05-01-2020".to_string()), - TransactionRecord::new(5, "$10.00".to_string(), "05-01-2020".to_string()), - ]) + HistoryState { + selected_index: None, } } pub fn handle_event(event: KeyEvent, app: &mut App) { + if app.states.history.selected_index.is_none() { + app.states.history.selected_index = Some(0); + } + + let max_index = app.data_cache.transactions.lock().unwrap().len(); + if event.kind == KeyEventKind::Press { match event.code { - KeyCode::Tab => app.states.transactions.next_tab(), - KeyCode::Up => app.states.history.transacts_list.previous(), - KeyCode::Down => app.states.history.transacts_list.next(), + KeyCode::Up | KeyCode::Tab => app.states.history.next(max_index), + KeyCode::Down => app.states.history.previous(), _ => {} } } } - pub fn update_transactions(app: &mut App) { - app.states.history.transacts_list.items.clear(); - for i in app.records.lock().unwrap().iter() { - app.states.history.transacts_list.items.push(i.clone()); + pub fn next(&mut self, max_index: usize) { + + if self.selected_index.is_some() { + let cur_index = self.selected_index.unwrap(); + if cur_index + 1 < max_index { + self.selected_index = Some(cur_index + 1); + } } } + + pub fn previous(&mut self) { + + if self.selected_index.is_some() { + let cur_index = self.selected_index.unwrap(); + if cur_index > 0 { + self.selected_index = Some(cur_index - 1); + } + } + + } } pub fn render_history_tab (f: &mut Frame, body_rect: Rect, app: &mut App) { - let do_update = match app.states.new_data.try_recv() { - Ok(val) => val, - Err(_) => false, - }; - - if do_update { - HistoryState::update_transactions(app); - } - - let split_body = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(body_rect); let mut lines: Vec = vec![]; - if app.states.history.transacts_list.state.selected().is_some(){ - let selected_item: &TransactionRecord = &app.states.history.transacts_list.items[app.states.history.transacts_list.state.selected().unwrap()]; + { + let transacts_list = app.data_cache.transactions.lock().unwrap(); + if app.states.history.selected_index.is_some() { + let selected_item: &Transaction = &transacts_list[app.states.history.selected_index.unwrap()]; - let ident_style: Style = Style::default().fg(Color::Yellow); - let value_style: Style = Style::default().fg(Color::LightBlue); + let ident_style: Style = Style::default().fg(Color::Yellow); + let value_style: Style = Style::default().fg(Color::LightBlue); - lines.push( - Line::from(vec![ - Span::styled("Transaction Id: ", ident_style), - Span::styled(format!("T{:0>6}", selected_item.id), value_style), - ]) - ); - lines.push( - Line::from(vec![ - Span::styled("Amount: ", ident_style), - Span::styled(format!("{}", selected_item.amount), value_style) - ]) - ); - lines.push( - Line::from(vec![ - Span::styled("Transaction Date: ", ident_style), - Span::styled(format!("{}", selected_item.date), value_style) - ]) - ); - } + lines.push( + Line::from(vec![ + Span::styled("Transaction Id: ", ident_style), + Span::styled(format!("T{:0>6}", selected_item.trns_id), value_style), + ]) + ); + lines.push( + Line::from(vec![ + Span::styled("Amount: ", ident_style), + Span::styled(format!("{}", selected_item.trns_amount.to_decimal(2).to_string()), value_style) + ]) + ); + lines.push( + Line::from(vec![ + Span::styled("Transaction Date: ", ident_style), + Span::styled(format!("{}", selected_item.trns_date.to_string()), value_style) + ]) + ); + } - let paragraph = Paragraph::new(lines.clone()) - .block(Block::default().borders(Borders::ALL).title("Details").title_alignment(ratatui::layout::Alignment::Left)); + let paragraph = Paragraph::new(lines.clone()) + .block(Block::default().borders(Borders::ALL).title("Details").title_alignment(ratatui::layout::Alignment::Left)); - f.render_widget(paragraph, split_body[1]); + f.render_widget(paragraph, split_body[1]); - let list_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Length(1), Constraint::Min(3)]) - .split(split_body[0]); + let list_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(3)]) + .split(split_body[0]); - let list_header = Paragraph::new( - Text::styled(TransactionRecord::get_header(), Style::default().fg(Color::Black).bg(Color::Cyan)) - ).block(Block::default().borders(Borders::NONE)); + let list_header = Paragraph::new( + Text::styled(Transaction::get_header(), Style::default().fg(Color::Black).bg(Color::Cyan)) + ).block(Block::default().borders(Borders::NONE)); - f.render_widget(list_header, list_chunks[0]); + f.render_widget(list_header, list_chunks[0]); + + let mut items: Vec = Vec::new(); + + for i in 0..transacts_list.len() { + let style = match app.states.history.selected_index { + Some(val) => { + if val == i { + Style::default().fg(Color::Black).bg(Color::Yellow) + } else { + Style::default().fg(Color::Black).bg(Color::White) + } + }, + None => Style::default().fg(Color::Black).bg(Color::White), + }; + + items.push(ListItem::new( + Text::from(transacts_list.get(i).unwrap().as_str()) + ).style(style)); + } - // Iterate through all elements in the `items` app and append some debug text to it. - let items: Vec = app - .states.history.transacts_list - .items - .iter() - .map(|i| { - ListItem::new(Text::from(i.as_str())).style(Style::default().fg(Color::Black).bg(Color::White)) - }) - .collect(); let history_items = List::new(items) - .block(Block::default().borders(Borders::NONE)) - .highlight_style( - Style::default() - .bg(Color::Gray) - .add_modifier(Modifier::BOLD) - ); + .block(Block::default().borders(Borders::NONE)); - f.render_stateful_widget(history_items, list_chunks[1], &mut app.states.history.transacts_list.state) + f.render_widget(history_items, list_chunks[1]); + } }