cargo fmt
This commit is contained in:
parent
f639d350b9
commit
871d6317ff
12 changed files with 362 additions and 179 deletions
14
src/app.rs
14
src/app.rs
|
@ -1,15 +1,15 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crossterm::event::{Event, self, KeyCode};
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
use tokio;
|
||||
|
||||
use log::warn;
|
||||
|
||||
use crate::db::DB;
|
||||
use crate::db::data_cache::DataCache;
|
||||
use crate::db::DB;
|
||||
use crate::uis::history::HistoryState;
|
||||
use crate::uis::new_transaction::NewTransactionTabState;
|
||||
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>>;
|
||||
|
||||
|
@ -80,21 +80,19 @@ impl<'a> App<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
|
||||
let fut = Arc::clone(&self.db);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let res = fut.lock().await.get_all_records().await;
|
||||
match res {
|
||||
Ok(_) => {},
|
||||
Err(e) => warn!("{}", e)
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("{}", e),
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::Error;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
|
||||
use super::data_cache::DataCache;
|
||||
use super::tables::Transaction;
|
||||
|
@ -13,12 +12,12 @@ pub struct DB {
|
|||
data_cache: DataCache,
|
||||
}
|
||||
|
||||
|
||||
impl DB {
|
||||
pub async fn new(data_cache: DataCache) -> Result<DB, sqlx::Error> {
|
||||
let connection_pool = PgPoolOptions::new()
|
||||
.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 {
|
||||
conn_pool: connection_pool,
|
||||
data_cache,
|
||||
|
@ -35,7 +34,6 @@ impl DB {
|
|||
temp_transactions.push(row);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
let mut transactions = self.data_cache.transactions.lock().unwrap();
|
||||
transactions.clear();
|
||||
|
@ -43,7 +41,5 @@ impl DB {
|
|||
}
|
||||
|
||||
return Ok(());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::tables;
|
||||
|
||||
use super::tables::{self, Account};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataCache {
|
||||
|
||||
pub accounts: Arc<Mutex<Vec<tables::Account>>>,
|
||||
|
||||
pub buckets: Arc<Mutex<Vec<tables::Buckets>>>,
|
||||
|
@ -15,10 +13,8 @@ pub struct DataCache {
|
|||
pub transaction_breakdowns: Arc<Mutex<Vec<tables::TransactionBreakdown>>>,
|
||||
|
||||
pub transactions: Arc<Mutex<Vec<tables::Transaction>>>,
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl DataCache {
|
||||
pub fn new() -> DataCache {
|
||||
DataCache {
|
||||
|
@ -29,4 +25,16 @@ impl DataCache {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
pub mod connection;
|
||||
pub use self::connection::DB;
|
||||
|
||||
pub mod tables;
|
||||
pub mod data_cache;
|
||||
pub mod tables;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use sqlx::{FromRow, postgres::types::PgMoney};
|
||||
use sqlx::{postgres::types::PgMoney, FromRow};
|
||||
use time::Date;
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Account {
|
||||
pub acnt_id: i32,
|
||||
|
@ -11,7 +10,6 @@ pub struct Account {
|
|||
pub acnt_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Buckets {
|
||||
pub bkt_id: i32,
|
||||
|
@ -20,7 +18,6 @@ pub struct Buckets {
|
|||
pub bkt_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct TransactionCatagories {
|
||||
pub trns_ctgry_id: i32,
|
||||
|
@ -29,7 +26,6 @@ pub struct TransactionCatagories {
|
|||
pub trns_ctgry_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct TransactionBreakdown {
|
||||
pub trns_brkdwn_id: i32,
|
||||
|
@ -39,6 +35,26 @@ pub struct TransactionBreakdown {
|
|||
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)]
|
||||
pub struct Transaction {
|
||||
|
@ -46,20 +62,33 @@ pub struct Transaction{
|
|||
pub trns_amount: PgMoney,
|
||||
pub trns_description: String,
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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> {
|
||||
return self.to_string().into()
|
||||
return self.to_string().into();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
pub mod app;
|
||||
|
||||
pub mod tui;
|
||||
|
||||
pub mod ui;
|
||||
|
||||
pub mod uis;
|
||||
pub mod db;
|
||||
pub mod uis;
|
||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -1,22 +1,18 @@
|
|||
use std::io;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
use simplelog::*;
|
||||
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
Terminal,
|
||||
};
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
use recount::db::DB;
|
||||
use recount::db::data_cache::DataCache;
|
||||
use recount::app::{App, AppResult};
|
||||
use recount::db::data_cache::DataCache;
|
||||
use recount::db::DB;
|
||||
use recount::tui::Tui;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> AppResult<()> {
|
||||
|
||||
let log_file = "testing_log.txt".to_string();
|
||||
init_logger(log_file);
|
||||
|
||||
|
@ -49,7 +45,6 @@ async fn main() -> AppResult<()> {
|
|||
tui.exit()
|
||||
}
|
||||
|
||||
|
||||
pub fn init_logger(output_file: String) {
|
||||
// TODO: configure the log levels to something appropriate
|
||||
CombinedLogger::init(vec![
|
||||
|
@ -60,7 +55,7 @@ pub fn init_logger(output_file: String) {
|
|||
// ColorChoice::Auto,
|
||||
// ),
|
||||
WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
LevelFilter::Debug,
|
||||
Config::default(),
|
||||
File::create(output_file).unwrap(),
|
||||
),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::ui;
|
||||
use crate::app::{App, AppResult};
|
||||
use crate::ui;
|
||||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use std::io;
|
||||
use ratatui::backend::Backend;
|
||||
use ratatui::Terminal;
|
||||
use std::io;
|
||||
|
||||
/// Representation of a terminal user interface.
|
||||
///
|
||||
|
|
25
src/ui.rs
25
src/ui.rs
|
@ -1,26 +1,33 @@
|
|||
use crate::{app::{App, ActiveFrame}, uis::render_history_tab};
|
||||
use crate::{
|
||||
app::{ActiveFrame, App},
|
||||
uis::render_history_tab,
|
||||
};
|
||||
use log;
|
||||
use ratatui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::Style,
|
||||
widgets::{Block, Borders},
|
||||
Frame, style::Style,
|
||||
Frame,
|
||||
};
|
||||
use log;
|
||||
|
||||
use crate::uis::{render_navigation_frame, render_new_transaction_tab};
|
||||
|
||||
|
||||
pub fn render<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||
|
||||
let size = f.size();
|
||||
let chunks = Layout::default()
|
||||
.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);
|
||||
|
||||
let bottom_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style({
|
||||
let bottom_block = Block::default().borders(Borders::ALL).border_style({
|
||||
if let ActiveFrame::Navigation = app.states.active_frame {
|
||||
Style::default()
|
||||
} else {
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
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 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 selected_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl HistoryState {
|
||||
|
||||
pub fn new() -> HistoryState {
|
||||
HistoryState {
|
||||
selected_index: None,
|
||||
|
@ -16,7 +21,6 @@ impl HistoryState {
|
|||
}
|
||||
|
||||
pub fn handle_event(event: KeyEvent, app: &mut App) {
|
||||
|
||||
if app.states.history.selected_index.is_none() {
|
||||
app.states.history.selected_index = Some(0);
|
||||
}
|
||||
|
@ -33,7 +37,6 @@ impl HistoryState {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -43,19 +46,16 @@ impl HistoryState {
|
|||
}
|
||||
|
||||
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<B: Backend>(f: &mut Frame<B>, body_rect: Rect, app: &mut App) {
|
||||
|
||||
let split_body = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.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();
|
||||
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 value_style: Style = Style::default().fg(Color::LightBlue);
|
||||
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
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![
|
||||
]));
|
||||
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(
|
||||
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)
|
||||
])
|
||||
);
|
||||
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]);
|
||||
|
||||
|
||||
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(Transaction::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]);
|
||||
|
||||
|
@ -117,18 +123,16 @@ pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &
|
|||
} 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));
|
||||
items.push(
|
||||
ListItem::new(Text::from(transacts_list.get(i).unwrap().as_str())).style(style),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
let history_items = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE));
|
||||
let history_items = List::new(items).block(Block::default().borders(Borders::NONE));
|
||||
|
||||
f.render_widget(history_items, list_chunks[1]);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
use crossterm::event::{KeyEvent, KeyEventKind, KeyCode};
|
||||
use ratatui::{widgets::{Borders, Paragraph, Block, Tabs}, backend::Backend, Frame, style::{Style, Color, Modifier}, text::{Text, Line}, layout::{Rect, Layout, Direction, Constraint}};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
||||
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 tabs: Vec<&'a str>,
|
||||
|
@ -37,7 +44,7 @@ impl<'a> NavigationState<'a> {
|
|||
match self.tab_index {
|
||||
0 => Some(ActiveFrame::History),
|
||||
1 => Some(ActiveFrame::NewTransaction),
|
||||
_ => todo!()
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,34 +52,39 @@ impl<'a> NavigationState<'a> {
|
|||
if event.kind == KeyEventKind::Press {
|
||||
match event.code {
|
||||
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()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(75), Constraint::Min(10)])
|
||||
.split(status_rect);
|
||||
|
||||
let left_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style({
|
||||
let left_block = Block::default().borders(Borders::ALL).border_style({
|
||||
if let ActiveFrame::Navigation = app.states.active_frame {
|
||||
Style::default().fg(ratatui::style::Color::Green)
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
});
|
||||
let right_block = Block::default()
|
||||
.borders(Borders::ALL);
|
||||
let right_block = Block::default().borders(Borders::ALL);
|
||||
|
||||
|
||||
let titles = app.states.nav_state
|
||||
let titles = app
|
||||
.states
|
||||
.nav_state
|
||||
.tabs
|
||||
.iter()
|
||||
.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]);
|
||||
|
||||
let connection_paragraph = Paragraph::new(
|
||||
Text::styled("Aurora",
|
||||
Style::default().fg(Color::Green)
|
||||
)
|
||||
).block(right_block);
|
||||
let connection_paragraph =
|
||||
Paragraph::new(Text::styled("Aurora", Style::default().fg(Color::Green)))
|
||||
.block(right_block);
|
||||
|
||||
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()
|
||||
}
|
||||
};
|
||||
let bottom_navbar = Paragraph::new(
|
||||
Text::styled(navbar, Style::default().fg(Color::White))
|
||||
)
|
||||
let bottom_navbar = Paragraph::new(Text::styled(navbar, Style::default().fg(Color::White)))
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(bottom_navbar, navbar_rect);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,72 @@
|
|||
use crate::app::App;
|
||||
use crossterm::event::{KeyEvent, KeyEventKind, KeyCode};
|
||||
use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Paragraph, Borders, Block}, text::Text, style::{Style, Color}};
|
||||
use crate::{
|
||||
app::App,
|
||||
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 cur_tab_index: usize,
|
||||
pub tabs: Vec<&'a str>,
|
||||
|
||||
manual_data: ManualData,
|
||||
}
|
||||
|
||||
impl<'a> NewTransactionTabState<'a> {
|
||||
pub fn new() -> NewTransactionTabState<'a> {
|
||||
NewTransactionTabState {
|
||||
cur_tab_index: 0,
|
||||
tabs: vec!["Quick Entry", "Manual Entry"]
|
||||
tabs: vec!["Quick Entry", "Manual Entry"],
|
||||
manual_data: ManualData::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +85,6 @@ impl<'a> NewTransactionTabState<'a> {
|
|||
}
|
||||
|
||||
pub fn render_new_transaction_tab<B: Backend>(f: &mut Frame<B>, body_rect: Rect, app: &App) {
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||
|
@ -49,40 +103,126 @@ pub fn render_new_transaction_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect
|
|||
.split(chunks[0]);
|
||||
|
||||
for i in 0..app.states.transactions.tabs.len() {
|
||||
|
||||
let tab = Paragraph::new(
|
||||
Text::styled(app.states.transactions.tabs[i],
|
||||
Style::default().fg(Color::White)
|
||||
)
|
||||
)
|
||||
let tab = Paragraph::new(Text::styled(
|
||||
app.states.transactions.tabs[i],
|
||||
Style::default().fg(Color::White),
|
||||
))
|
||||
.alignment(ratatui::layout::Alignment::Center)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style({
|
||||
.block(Block::default().borders(Borders::ALL).style({
|
||||
if app.states.transactions.cur_tab_index == i {
|
||||
Style::default().bg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
})
|
||||
);
|
||||
}));
|
||||
f.render_widget(tab, tab_chunks[i])
|
||||
}
|
||||
|
||||
match app.states.transactions.tabs[app.states.transactions.cur_tab_index] {
|
||||
"Quick Entry" => render_quick_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());
|
||||
|
||||
pub fn render_quick_entry<B: Backend> (f: &mut Frame<B>, area: Rect, app: &App) {
|
||||
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_quick_entry<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {}
|
||||
|
|
Loading…
Reference in a new issue