cargo fmt

This commit is contained in:
Nickiel12 2023-08-12 11:54:09 -07:00
parent f639d350b9
commit 871d6317ff
12 changed files with 362 additions and 179 deletions

View file

@ -1,15 +1,15 @@
use std::sync::Arc; use std::sync::Arc;
use crossterm::event::{Event, self, KeyCode}; use crossterm::event::{self, Event, KeyCode};
use tokio; use tokio;
use log::warn; use log::warn;
use crate::db::DB;
use crate::db::data_cache::DataCache; use crate::db::data_cache::DataCache;
use crate::db::DB;
use crate::uis::history::HistoryState; use crate::uis::history::HistoryState;
use crate::uis::new_transaction::NewTransactionTabState;
use crate::uis::navigation_frame::NavigationState; use crate::uis::navigation_frame::NavigationState;
use crate::uis::new_transaction::NewTransactionTabState;
pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
@ -80,21 +80,19 @@ impl<'a> App<'a> {
} }
} }
} }
return Ok(()) return Ok(());
} }
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
let fut = Arc::clone(&self.db); let fut = Arc::clone(&self.db);
tokio::spawn(async move { tokio::spawn(async move {
let res = fut.lock().await.get_all_records().await; let res = fut.lock().await.get_all_records().await;
match res { match res {
Ok(_) => {}, Ok(_) => {}
Err(e) => warn!("{}", e) Err(e) => warn!("{}", e),
} }
}); });
} }
} }

View file

@ -1,8 +1,7 @@
use futures::TryStreamExt; use futures::TryStreamExt;
use sqlx::postgres::PgPoolOptions;
use sqlx::Error; use sqlx::Error;
use sqlx::PgPool; use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use super::data_cache::DataCache; use super::data_cache::DataCache;
use super::tables::Transaction; use super::tables::Transaction;
@ -13,12 +12,12 @@ pub struct DB {
data_cache: DataCache, data_cache: DataCache,
} }
impl DB { impl DB {
pub async fn new(data_cache: DataCache) -> Result<DB, sqlx::Error> { pub async fn new(data_cache: DataCache) -> Result<DB, sqlx::Error> {
let connection_pool = PgPoolOptions::new() let connection_pool = PgPoolOptions::new()
.max_connections(3) .max_connections(3)
.connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis").await?; .connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis")
.await?;
Ok(DB { Ok(DB {
conn_pool: connection_pool, conn_pool: connection_pool,
data_cache, data_cache,
@ -35,7 +34,6 @@ impl DB {
temp_transactions.push(row); temp_transactions.push(row);
} }
{ {
let mut transactions = self.data_cache.transactions.lock().unwrap(); let mut transactions = self.data_cache.transactions.lock().unwrap();
transactions.clear(); transactions.clear();
@ -43,7 +41,5 @@ impl DB {
} }
return Ok(()); return Ok(());
} }
} }

View file

@ -1,11 +1,9 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use super::tables; use super::tables::{self, Account};
#[derive(Clone)] #[derive(Clone)]
pub struct DataCache { pub struct DataCache {
pub accounts: Arc<Mutex<Vec<tables::Account>>>, pub accounts: Arc<Mutex<Vec<tables::Account>>>,
pub buckets: Arc<Mutex<Vec<tables::Buckets>>>, pub buckets: Arc<Mutex<Vec<tables::Buckets>>>,
@ -13,15 +11,13 @@ pub struct DataCache {
pub transaction_catagories: Arc<Mutex<Vec<tables::TransactionCatagories>>>, pub transaction_catagories: Arc<Mutex<Vec<tables::TransactionCatagories>>>,
pub transaction_breakdowns: Arc<Mutex<Vec<tables::TransactionBreakdown>>>, pub transaction_breakdowns: Arc<Mutex<Vec<tables::TransactionBreakdown>>>,
pub transactions: Arc<Mutex<Vec<tables::Transaction>>>, pub transactions: Arc<Mutex<Vec<tables::Transaction>>>,
} }
impl DataCache { impl DataCache {
pub fn new() -> DataCache { pub fn new() -> DataCache {
DataCache { DataCache {
accounts: Arc::new(Mutex::new(Vec::new())), accounts: Arc::new(Mutex::new(Vec::new())),
buckets: Arc::new(Mutex::new(Vec::new())), buckets: Arc::new(Mutex::new(Vec::new())),
transaction_catagories: Arc::new(Mutex::new(Vec::new())), transaction_catagories: Arc::new(Mutex::new(Vec::new())),
@ -29,4 +25,16 @@ impl DataCache {
transactions: Arc::new(Mutex::new(Vec::new())), transactions: Arc::new(Mutex::new(Vec::new())),
} }
} }
pub fn find_account_by_id(&self, id: i32) -> Option<Account> {
{
let accounts = self.accounts.lock().unwrap();
for i in accounts.iter() {
if i.acnt_id == id {
return Some(i.clone());
}
}
}
return None;
}
} }

View file

@ -1,6 +1,5 @@
pub mod connection; pub mod connection;
pub use self::connection::DB; pub use self::connection::DB;
pub mod tables;
pub mod data_cache; pub mod data_cache;
pub mod tables;

View file

@ -1,9 +1,8 @@
use std::borrow::Cow; use std::borrow::Cow;
use sqlx::{FromRow, postgres::types::PgMoney}; use sqlx::{postgres::types::PgMoney, FromRow};
use time::Date; use time::Date;
#[derive(Clone, FromRow)] #[derive(Clone, FromRow)]
pub struct Account { pub struct Account {
pub acnt_id: i32, pub acnt_id: i32,
@ -11,7 +10,6 @@ pub struct Account {
pub acnt_description: String, pub acnt_description: String,
} }
#[derive(Clone, FromRow)] #[derive(Clone, FromRow)]
pub struct Buckets { pub struct Buckets {
pub bkt_id: i32, pub bkt_id: i32,
@ -20,7 +18,6 @@ pub struct Buckets {
pub bkt_description: String, pub bkt_description: String,
} }
#[derive(Clone, FromRow)] #[derive(Clone, FromRow)]
pub struct TransactionCatagories { pub struct TransactionCatagories {
pub trns_ctgry_id: i32, pub trns_ctgry_id: i32,
@ -29,7 +26,6 @@ pub struct TransactionCatagories {
pub trns_ctgry_description: String, pub trns_ctgry_description: String,
} }
#[derive(Clone, FromRow)] #[derive(Clone, FromRow)]
pub struct TransactionBreakdown { pub struct TransactionBreakdown {
pub trns_brkdwn_id: i32, pub trns_brkdwn_id: i32,
@ -39,27 +35,60 @@ pub struct TransactionBreakdown {
pub trns_brkdwn_bucket: i32, pub trns_brkdwn_bucket: i32,
} }
#[derive(Clone)]
pub struct PartialTransactionBreakdown {
pub trns_brkdwn_id: Option<i32>,
pub trns_brkdwn_amount: Option<PgMoney>,
pub trns_brkdwn_parent_transaction: Option<i32>,
pub trns_brkdwn_catagory: Option<i32>,
pub trns_brkdwn_bucket: Option<i32>,
}
impl PartialTransactionBreakdown {
pub fn new_empty() -> PartialTransactionBreakdown {
PartialTransactionBreakdown {
trns_brkdwn_id: None,
trns_brkdwn_amount: None,
trns_brkdwn_parent_transaction: None,
trns_brkdwn_catagory: None,
trns_brkdwn_bucket: None,
}
}
}
#[derive(Clone, FromRow)] #[derive(Clone, FromRow)]
pub struct Transaction{ pub struct Transaction {
pub trns_id: i32, pub trns_id: i32,
pub trns_amount: PgMoney, pub trns_amount: PgMoney,
pub trns_description: String, pub trns_description: String,
pub trns_bucket: i32, pub trns_bucket: i32,
pub trns_date: Date pub trns_date: Date,
}
#[derive(Clone)]
pub struct PartialTransaction {
pub trns_id: Option<i32>,
pub trns_amount: Option<PgMoney>,
pub trns_description: Option<String>,
pub trns_bucket: Option<i32>,
pub trns_date: Option<Date>,
} }
impl Transaction { impl Transaction {
pub fn get_header() -> String { pub fn get_header() -> String {
return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date") return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date");
} }
pub fn to_string(&self) -> String { 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()) 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<str> { pub fn as_str(&self) -> Cow<str> {
return self.to_string().into() return self.to_string().into();
} }
} }

View file

@ -1,9 +1,8 @@
pub mod app; pub mod app;
pub mod tui; pub mod tui;
pub mod ui; pub mod ui;
pub mod uis;
pub mod db; pub mod db;
pub mod uis;

View file

@ -1,22 +1,18 @@
use std::io;
use std::fs::File; use std::fs::File;
use std::io;
use std::time::Duration; use std::time::Duration;
use simplelog::*; use simplelog::*;
use ratatui::{ use ratatui::{backend::CrosstermBackend, Terminal};
backend::CrosstermBackend,
Terminal,
};
use recount::db::DB;
use recount::db::data_cache::DataCache;
use recount::app::{App, AppResult}; use recount::app::{App, AppResult};
use recount::db::data_cache::DataCache;
use recount::db::DB;
use recount::tui::Tui; use recount::tui::Tui;
#[tokio::main] #[tokio::main]
async fn main() -> AppResult<()> { async fn main() -> AppResult<()> {
let log_file = "testing_log.txt".to_string(); let log_file = "testing_log.txt".to_string();
init_logger(log_file); init_logger(log_file);
@ -49,18 +45,17 @@ async fn main() -> AppResult<()> {
tui.exit() tui.exit()
} }
pub fn init_logger(output_file: String) { pub fn init_logger(output_file: String) {
// TODO: configure the log levels to something appropriate // TODO: configure the log levels to something appropriate
CombinedLogger::init(vec![ CombinedLogger::init(vec![
// TermLogger::new( // TermLogger::new(
// LevelFilter::Info, // LevelFilter::Info,
// Config::default(), // Config::default(),
// TerminalMode::Mixed, // TerminalMode::Mixed,
// ColorChoice::Auto, // ColorChoice::Auto,
// ), // ),
WriteLogger::new( WriteLogger::new(
LevelFilter::Info, LevelFilter::Debug,
Config::default(), Config::default(),
File::create(output_file).unwrap(), File::create(output_file).unwrap(),
), ),

View file

@ -1,10 +1,10 @@
use crate::ui;
use crate::app::{App, AppResult}; use crate::app::{App, AppResult};
use crate::ui;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use std::io;
use ratatui::backend::Backend; use ratatui::backend::Backend;
use ratatui::Terminal; use ratatui::Terminal;
use std::io;
/// Representation of a terminal user interface. /// Representation of a terminal user interface.
/// ///

View file

@ -1,32 +1,39 @@
use crate::{app::{App, ActiveFrame}, uis::render_history_tab}; use crate::{
app::{ActiveFrame, App},
uis::render_history_tab,
};
use log;
use ratatui::{ use ratatui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::Style,
widgets::{Block, Borders}, widgets::{Block, Borders},
Frame, style::Style, Frame,
}; };
use log;
use crate::uis::{render_navigation_frame, render_new_transaction_tab}; use crate::uis::{render_navigation_frame, render_new_transaction_tab};
pub fn render<B: Backend>(f: &mut Frame<B>, app: &mut App) {
pub fn render<B: Backend> (f: &mut Frame<B>, app: &mut App) {
let size = f.size(); let size = f.size();
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(0), Constraint::Length(3)].as_ref()) .constraints(
[
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
]
.as_ref(),
)
.split(size); .split(size);
let bottom_block = Block::default() let bottom_block = Block::default().borders(Borders::ALL).border_style({
.borders(Borders::ALL) if let ActiveFrame::Navigation = app.states.active_frame {
.border_style({ Style::default()
if let ActiveFrame::Navigation = app.states.active_frame { } else {
Style::default() Style::default().fg(ratatui::style::Color::Green)
} else { }
Style::default().fg(ratatui::style::Color::Green) });
}
});
f.render_widget(bottom_block, chunks[1]); f.render_widget(bottom_block, chunks[1]);
render_navigation_frame(f, chunks[0], chunks[2], app); render_navigation_frame(f, chunks[0], chunks[2], app);

View file

@ -1,22 +1,26 @@
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}};
use crate::{app::App, db::tables::Transaction}; use crate::{app::App, db::tables::Transaction};
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::{Line, Span, Text},
widgets::{Block, Borders, List, ListItem, Paragraph},
Frame,
};
pub struct HistoryState { pub struct HistoryState {
pub selected_index: Option<usize>, pub selected_index: Option<usize>,
} }
impl HistoryState { impl HistoryState {
pub fn new() -> HistoryState { pub fn new() -> HistoryState {
HistoryState { HistoryState {
selected_index: None, selected_index: None,
} }
} }
pub fn handle_event(event: KeyEvent, app: &mut App) { pub fn handle_event(event: KeyEvent, app: &mut App) {
if app.states.history.selected_index.is_none() { if app.states.history.selected_index.is_none() {
app.states.history.selected_index = Some(0); app.states.history.selected_index = Some(0);
} }
@ -26,14 +30,13 @@ impl HistoryState {
if event.kind == KeyEventKind::Press { if event.kind == KeyEventKind::Press {
match event.code { match event.code {
KeyCode::Up | KeyCode::Tab => app.states.history.next(max_index), KeyCode::Up | KeyCode::Tab => app.states.history.next(max_index),
KeyCode::Down => app.states.history.previous(), KeyCode::Down => app.states.history.previous(),
_ => {} _ => {}
} }
} }
} }
pub fn next(&mut self, max_index: usize) { pub fn next(&mut self, max_index: usize) {
if self.selected_index.is_some() { if self.selected_index.is_some() {
let cur_index = self.selected_index.unwrap(); let cur_index = self.selected_index.unwrap();
if cur_index + 1 < max_index { if cur_index + 1 < max_index {
@ -43,19 +46,16 @@ impl HistoryState {
} }
pub fn previous(&mut self) { pub fn previous(&mut self) {
if self.selected_index.is_some() { if self.selected_index.is_some() {
let cur_index = self.selected_index.unwrap(); let cur_index = self.selected_index.unwrap();
if cur_index > 0 { if cur_index > 0 {
self.selected_index = Some(cur_index - 1); self.selected_index = Some(cur_index - 1);
} }
} }
} }
} }
pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &mut App) { pub fn render_history_tab<B: Backend>(f: &mut Frame<B>, body_rect: Rect, app: &mut App) {
let split_body = Layout::default() let split_body = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
@ -65,45 +65,51 @@ pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &
{ {
let transacts_list = app.data_cache.transactions.lock().unwrap(); let transacts_list = app.data_cache.transactions.lock().unwrap();
if app.states.history.selected_index.is_some() { if app.states.history.selected_index.is_some() {
let selected_item: &Transaction = &transacts_list[app.states.history.selected_index.unwrap()]; let selected_item: &Transaction =
&transacts_list[app.states.history.selected_index.unwrap()];
let ident_style: Style = Style::default().fg(Color::Yellow); let ident_style: Style = Style::default().fg(Color::Yellow);
let value_style: Style = Style::default().fg(Color::LightBlue); let value_style: Style = Style::default().fg(Color::LightBlue);
lines.push( lines.push(Line::from(vec![
Line::from(vec![ Span::styled("Transaction Id: ", ident_style),
Span::styled("Transaction Id: ", ident_style), Span::styled(format!("T{:0>6}", selected_item.trns_id), value_style),
Span::styled(format!("T{:0>6}", selected_item.trns_id), value_style), ]));
]) lines.push(Line::from(vec![
); Span::styled("Amount: ", ident_style),
lines.push( Span::styled(
Line::from(vec![ format!("{}", selected_item.trns_amount.to_decimal(2).to_string()),
Span::styled("Amount: ", ident_style), value_style,
Span::styled(format!("{}", selected_item.trns_amount.to_decimal(2).to_string()), value_style) ),
]) ]));
); lines.push(Line::from(vec![
lines.push( Span::styled("Transaction Date: ", ident_style),
Line::from(vec![ Span::styled(
Span::styled("Transaction Date: ", ident_style), format!("{}", selected_item.trns_date.to_string()),
Span::styled(format!("{}", selected_item.trns_date.to_string()), value_style) value_style,
]) ),
); ]));
} }
let paragraph = Paragraph::new(lines.clone()) let paragraph = Paragraph::new(lines.clone()).block(
.block(Block::default().borders(Borders::ALL).title("Details").title_alignment(ratatui::layout::Alignment::Left)); 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() let list_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(3)]) .constraints([Constraint::Length(1), Constraint::Min(3)])
.split(split_body[0]); .split(split_body[0]);
let list_header = Paragraph::new( let list_header = Paragraph::new(Text::styled(
Text::styled(Transaction::get_header(), Style::default().fg(Color::Black).bg(Color::Cyan)) Transaction::get_header(),
).block(Block::default().borders(Borders::NONE)); 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]);
@ -117,19 +123,17 @@ pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &
} else { } else {
Style::default().fg(Color::Black).bg(Color::White) Style::default().fg(Color::Black).bg(Color::White)
} }
}, }
None => Style::default().fg(Color::Black).bg(Color::White), None => Style::default().fg(Color::Black).bg(Color::White),
}; };
items.push(ListItem::new( items.push(
Text::from(transacts_list.get(i).unwrap().as_str()) ListItem::new(Text::from(transacts_list.get(i).unwrap().as_str())).style(style),
).style(style)); );
} }
let history_items = List::new(items).block(Block::default().borders(Borders::NONE));
let history_items = List::new(items) f.render_widget(history_items, list_chunks[1]);
.block(Block::default().borders(Borders::NONE));
f.render_widget(history_items, list_chunks[1]);
} }
} }

View file

@ -1,7 +1,14 @@
use crossterm::event::{KeyEvent, KeyEventKind, KeyCode}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ratatui::{widgets::{Borders, Paragraph, Block, Tabs}, backend::Backend, Frame, style::{Style, Color, Modifier}, text::{Text, Line}, layout::{Rect, Layout, Direction, Constraint}}; use ratatui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Text},
widgets::{Block, Borders, Paragraph, Tabs},
Frame,
};
use crate::app::{App, ActiveFrame}; use crate::app::{ActiveFrame, App};
pub struct NavigationState<'a> { pub struct NavigationState<'a> {
pub tabs: Vec<&'a str>, pub tabs: Vec<&'a str>,
@ -37,7 +44,7 @@ impl<'a> NavigationState<'a> {
match self.tab_index { match self.tab_index {
0 => Some(ActiveFrame::History), 0 => Some(ActiveFrame::History),
1 => Some(ActiveFrame::NewTransaction), 1 => Some(ActiveFrame::NewTransaction),
_ => todo!() _ => todo!(),
} }
} }
@ -45,34 +52,39 @@ impl<'a> NavigationState<'a> {
if event.kind == KeyEventKind::Press { if event.kind == KeyEventKind::Press {
match event.code { match event.code {
KeyCode::Tab => app.states.nav_state.next_tab(), KeyCode::Tab => app.states.nav_state.next_tab(),
KeyCode::Enter => app.states.active_frame = app.states.nav_state.get_active_tab_frametype().unwrap(), KeyCode::Enter => {
app.states.active_frame =
app.states.nav_state.get_active_tab_frametype().unwrap()
}
_ => {} _ => {}
} }
} }
} }
} }
pub fn render_navigation_frame<B: Backend> (f: &mut Frame<B>, status_rect: Rect, navbar_rect: Rect, app: &App) { pub fn render_navigation_frame<B: Backend>(
f: &mut Frame<B>,
status_rect: Rect,
navbar_rect: Rect,
app: &App,
) {
let status_bar_chunks = Layout::default() let status_bar_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(75), Constraint::Min(10)]) .constraints([Constraint::Percentage(75), Constraint::Min(10)])
.split(status_rect); .split(status_rect);
let left_block = Block::default() let left_block = Block::default().borders(Borders::ALL).border_style({
.borders(Borders::ALL) if let ActiveFrame::Navigation = app.states.active_frame {
.border_style({ Style::default().fg(ratatui::style::Color::Green)
if let ActiveFrame::Navigation = app.states.active_frame { } else {
Style::default().fg(ratatui::style::Color::Green) Style::default()
} else { }
Style::default() });
} let right_block = Block::default().borders(Borders::ALL);
});
let right_block = Block::default()
.borders(Borders::ALL);
let titles = app
let titles = app.states.nav_state .states
.nav_state
.tabs .tabs
.iter() .iter()
.cloned() .cloned()
@ -93,11 +105,9 @@ pub fn render_navigation_frame<B: Backend> (f: &mut Frame<B>, status_rect: Rect,
f.render_widget(tabs, status_bar_chunks[0]); f.render_widget(tabs, status_bar_chunks[0]);
let connection_paragraph = Paragraph::new( let connection_paragraph =
Text::styled("Aurora", Paragraph::new(Text::styled("Aurora", Style::default().fg(Color::Green)))
Style::default().fg(Color::Green) .block(right_block);
)
).block(right_block);
f.render_widget(connection_paragraph, status_bar_chunks[1]); f.render_widget(connection_paragraph, status_bar_chunks[1]);
@ -112,9 +122,7 @@ pub fn render_navigation_frame<B: Backend> (f: &mut Frame<B>, status_rect: Rect,
app.states.nav_state.message.clone().unwrap() app.states.nav_state.message.clone().unwrap()
} }
}; };
let bottom_navbar = Paragraph::new( let bottom_navbar = Paragraph::new(Text::styled(navbar, Style::default().fg(Color::White)))
Text::styled(navbar, Style::default().fg(Color::White))
)
.block(Block::default().borders(Borders::ALL)); .block(Block::default().borders(Borders::ALL));
f.render_widget(bottom_navbar, navbar_rect); f.render_widget(bottom_navbar, navbar_rect);
} }

View file

@ -1,17 +1,72 @@
use crate::app::App; use crate::{
use crossterm::event::{KeyEvent, KeyEventKind, KeyCode}; app::App,
use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Paragraph, Borders, Block}, text::Text, style::{Style, Color}}; db::tables::{Account, Buckets, PartialTransactionBreakdown},
};
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::Text,
widgets::{Block, Borders, Paragraph},
Frame,
};
use rust_decimal::Decimal;
use time::Date;
use log::debug;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ManualDataFocus {
Account,
Amount,
Date,
Bucket,
Description,
Breakdowns,
}
struct ManualData {
pub focus: Option<ManualDataFocus>,
pub account: Option<Account>,
pub amount: Option<Decimal>,
pub date: Option<Date>,
pub bucket: Option<Buckets>,
pub description: Option<String>,
pub editing_breakdown: PartialTransactionBreakdown,
pub breakdowns: Vec<PartialTransactionBreakdown>,
}
impl ManualData {
pub fn new() -> ManualData {
ManualData {
focus: None,
account: None,
amount: None,
date: None,
bucket: None,
description: None,
editing_breakdown: PartialTransactionBreakdown::new_empty(),
breakdowns: Vec::new(),
}
}
}
pub struct NewTransactionTabState<'a> { pub struct NewTransactionTabState<'a> {
pub cur_tab_index: usize, pub cur_tab_index: usize,
pub tabs: Vec<&'a str>, pub tabs: Vec<&'a str>,
manual_data: ManualData,
} }
impl<'a> NewTransactionTabState<'a> { impl<'a> NewTransactionTabState<'a> {
pub fn new() -> NewTransactionTabState<'a> { pub fn new() -> NewTransactionTabState<'a> {
NewTransactionTabState { NewTransactionTabState {
cur_tab_index: 0, cur_tab_index: 0,
tabs: vec!["Quick Entry", "Manual Entry"] tabs: vec!["Quick Entry", "Manual Entry"],
manual_data: ManualData::new(),
} }
} }
@ -29,8 +84,7 @@ impl<'a> NewTransactionTabState<'a> {
} }
} }
pub fn render_new_transaction_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &App) { pub fn render_new_transaction_tab<B: Backend>(f: &mut Frame<B>, body_rect: Rect, app: &App) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(0)]) .constraints([Constraint::Length(3), Constraint::Min(0)])
@ -39,7 +93,7 @@ pub fn render_new_transaction_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect
// Render the custom tab bar // Render the custom tab bar
let mut constraints: Vec<Constraint> = vec![]; let mut constraints: Vec<Constraint> = vec![];
let tab_percent: u16 = (100 / app.states.transactions.tabs.len()) as u16; let tab_percent: u16 = (100 / app.states.transactions.tabs.len()) as u16;
for _ in 0..app.states.transactions.tabs.len() { for _ in 0..app.states.transactions.tabs.len() {
constraints.push(Constraint::Percentage(tab_percent)); constraints.push(Constraint::Percentage(tab_percent));
} }
@ -48,41 +102,127 @@ pub fn render_new_transaction_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect
.constraints(constraints) .constraints(constraints)
.split(chunks[0]); .split(chunks[0]);
for i in 0..app.states.transactions.tabs.len() { for i in 0..app.states.transactions.tabs.len() {
let tab = Paragraph::new(Text::styled(
let tab = Paragraph::new( app.states.transactions.tabs[i],
Text::styled(app.states.transactions.tabs[i], Style::default().fg(Color::White),
Style::default().fg(Color::White) ))
) .alignment(ratatui::layout::Alignment::Center)
) .block(Block::default().borders(Borders::ALL).style({
.alignment(ratatui::layout::Alignment::Center) if app.states.transactions.cur_tab_index == i {
.block( Style::default().bg(Color::Blue)
Block::default() } else {
.borders(Borders::ALL) Style::default()
.style({ }
if app.states.transactions.cur_tab_index == i { }));
Style::default().bg(Color::Blue)
} else {
Style::default()
}
})
);
f.render_widget(tab, tab_chunks[i]) f.render_widget(tab, tab_chunks[i])
} }
match app.states.transactions.tabs[app.states.transactions.cur_tab_index] { match app.states.transactions.tabs[app.states.transactions.cur_tab_index] {
"Quick Entry" => render_quick_entry(f, chunks[1], app), "Quick Entry" => render_quick_entry(f, chunks[1], app),
"Manual Entry" => render_manual_entry(f, chunks[1], app), "Manual Entry" => render_manual_entry(f, chunks[1], app),
_ => return _ => return,
};
}
pub fn render_manual_entry<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
let num_can_show = (area.height / 3) as usize;
let mut constraints: Vec<Constraint> = Vec::new();
for _ in 0..num_can_show {
constraints.push(Constraint::Length(1));
}
debug!("Can show: {}", num_can_show);
debug!("saved {} constraints", constraints.len());
let split_body = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(area);
{
let mut first_found = false;
let mut num_rendered = 0;
let manual_state = &app.states.transactions.manual_data;
if manual_state.focus.is_none()
|| matches!(manual_state.focus.unwrap(), ManualDataFocus::Account)
{
let right_text = match manual_state.account.clone() {
Some(val) => val.acnt_dsply_name,
None => "".to_string(),
};
render_manual_row(
f,
split_body[num_rendered * 2],
"Account: ",
right_text,
manual_state.focus,
ManualDataFocus::Account
);
first_found = true;
num_rendered = 1;
}
if (first_found && num_rendered < num_can_show)
|| matches!(manual_state.focus.unwrap(), ManualDataFocus::Amount)
{
let right_text = match manual_state.amount {
Some(val) => val.to_string(),
None => "".to_string(),
};
render_manual_row(
f,
split_body[num_rendered * 2],
"Amount: ",
right_text,
manual_state.focus,
ManualDataFocus::Amount
);
first_found = true;
num_rendered += 1;
}
}
}
pub fn render_manual_row<B: Backend>(
f: &mut Frame<B>,
row_area: Rect,
left_text: &str,
right_text: String,
focus: Option<ManualDataFocus>,
matching: ManualDataFocus,
) {
let is_active = focus.map(|focus| focus == matching).unwrap_or(false);
let horizontal_pieces = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(row_area);
f.render_widget(
Paragraph::new(Text::styled(left_text, Style::default().fg(Color::Yellow))),
horizontal_pieces[0],
);
let right_bg_color = if is_active {
Color::Yellow
} else {
Color::White
}; };
f.render_widget(
Paragraph::new(Text::styled(right_text, Style::default().fg(Color::Black))).block(
Block::default()
.borders(Borders::NONE)
.style(Style::default().bg(right_bg_color)),
),
horizontal_pieces[1],
);
} }
pub fn render_manual_entry <B: Backend> (f: &mut Frame<B>, area: Rect, app: &App) { pub fn render_quick_entry<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {}
}
pub fn render_quick_entry<B: Backend> (f: &mut Frame<B>, area: Rect, app: &App) {
}