From 62c24a9d19e9185b11a9ade0f88db9d98600bcaa Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sat, 16 Aug 2025 20:06:54 +0600 Subject: [PATCH 1/7] Clippy fix --- src/activity_page/activity_ui.rs | 5 ++--- src/home_page/home_ui.rs | 5 ++--- src/key_checker/key_handler.rs | 10 ++++------ src/page_handler/initializer.rs | 5 ++--- src/search_page/search_ui.rs | 5 ++--- src/summary_page/summary_ui.rs | 5 ++--- 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/activity_page/activity_ui.rs b/src/activity_page/activity_ui.rs index a9195b5..7c9e04c 100644 --- a/src/activity_page/activity_ui.rs +++ b/src/activity_page/activity_ui.rs @@ -180,11 +180,10 @@ pub fn activity_ui( } } - if let Some(index) = table_data.state.selected() { - if index > 10 { + if let Some(index) = table_data.state.selected() + && index > 10 { *table_data.state.offset_mut() = index - 10; } - } f.render_widget(year_tab, chunks[0]); f.render_widget(month_tab, chunks[1]); diff --git a/src/home_page/home_ui.rs b/src/home_page/home_ui.rs index 9697307..48dcd06 100644 --- a/src/home_page/home_ui.rs +++ b/src/home_page/home_ui.rs @@ -223,11 +223,10 @@ pub fn home_ui( } // Always keep some items rendered on the upper side of the table - if let Some(index) = table.state.selected() { - if index > 10 { + if let Some(index) = table.state.selected() + && index > 10 { *table.state.offset_mut() = index - 10; } - } // After all data is in place, render the widgets one by one // the chunks are selected based on the format I want the widgets to render diff --git a/src/key_checker/key_handler.rs b/src/key_checker/key_handler.rs index cdda55b..efb4aab 100644 --- a/src/key_checker/key_handler.rs +++ b/src/key_checker/key_handler.rs @@ -808,15 +808,14 @@ impl<'a> InputKeyHandler<'a> { /// go to search page and search for it #[cfg(not(tarpaulin_include))] pub fn search_tag(&mut self) { - if let SummaryTab::Table = self.summary_tab { - if let Some(index) = self.summary_table.state.selected() { + if let SummaryTab::Table = self.summary_tab + && let Some(index) = self.summary_table.state.selected() { let tag_name = &self.summary_table.items[index][0]; let search_param = TxData::custom("", "", "", "", "", "", tag_name, 0); *self.search_data = search_param; self.go_search(); self.search_tx(); } - } } /// Handle key press when deletion popup is turned on @@ -1012,8 +1011,8 @@ impl<'a> InputKeyHandler<'a> { #[cfg(not(tarpaulin_include))] pub fn switch_chart_tx_method_activation(&mut self) { - if !*self.chart_hidden_mode { - if let ChartTab::TxMethods = self.chart_tab { + if !*self.chart_hidden_mode + && let ChartTab::TxMethods = self.chart_tab { let selected_index = self.chart_tx_methods.index; let all_tx_methods = get_all_tx_methods_cumulative(self.conn); @@ -1025,7 +1024,6 @@ impl<'a> InputKeyHandler<'a> { *activation_status = !*activation_status; self.lerp_state.clear(); } - } } #[cfg(not(tarpaulin_include))] diff --git a/src/page_handler/initializer.rs b/src/page_handler/initializer.rs index a32b849..d35bbae 100644 --- a/src/page_handler/initializer.rs +++ b/src/page_handler/initializer.rs @@ -27,8 +27,8 @@ pub fn initialize_app( let new_version_available = new_version.unwrap_or_default(); // If is not terminal, try to start a terminal otherwise create an error.txt file with the error message - if !atty::is(Stream::Stdout) { - if let Err(err) = start_terminal(original_dir.to_str().unwrap()) { + if !atty::is(Stream::Stdout) + && let Err(err) = start_terminal(original_dir.to_str().unwrap()) { let mut error_location = PathBuf::from(&original_dir); error_location.push("Error.txt"); @@ -37,7 +37,6 @@ pub fn initialize_app( open.write_all(to_write.as_bytes())?; process::exit(1); } - } // If the location was changed/json file found, change the db directory. let db_path = if let Some(mut location) = is_location_changed(original_db_path) { diff --git a/src/search_page/search_ui.rs b/src/search_page/search_ui.rs index 809a808..b6688b1 100644 --- a/src/search_page/search_ui.rs +++ b/src/search_page/search_ui.rs @@ -318,11 +318,10 @@ pub fn search_ui( } } - if let Some(index) = search_table.state.selected() { - if index > 10 { + if let Some(index) = search_table.state.selected() + && index > 10 { *search_table.state.offset_mut() = index - 10; } - } // Render the previously generated data into an interface f.render_widget(details_sec, chunks[1]); diff --git a/src/summary_page/summary_ui.rs b/src/summary_page/summary_ui.rs index 7b2b133..131301b 100644 --- a/src/summary_page/summary_ui.rs +++ b/src/summary_page/summary_ui.rs @@ -432,11 +432,10 @@ pub fn summary_ui( } // Always keep some items rendered on the upper side of the table - if let Some(index) = table_data.state.selected() { - if index > 7 { + if let Some(index) = table_data.state.selected() + && index > 7 { *table_data.state.offset_mut() = index - 7; } - } if summary_hidden_mode { f.render_stateful_widget(summary_area_1, left_summary[0], &mut summary_table_1.state); From 420ad06413cfc72824f5524896c65fff7da20571 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 15:55:35 +0600 Subject: [PATCH 2/7] Initial summary mom/yoy --- src/summary_page/summary_data.rs | 113 ++++---- src/summary_page/summary_ui.rs | 457 ++++++++++++++++--------------- src/utility/utils.rs | 26 +- 3 files changed, 322 insertions(+), 274 deletions(-) diff --git a/src/summary_page/summary_data.rs b/src/summary_page/summary_data.rs index c8d8256..526be0f 100644 --- a/src/summary_page/summary_data.rs +++ b/src/summary_page/summary_data.rs @@ -222,8 +222,9 @@ impl SummaryData { mode: &IndexedData, month: usize, year: usize, + comparison: Option, conn: &Connection, - ) -> (MyVec, MyVec, MyVec, MyVec, MyVec) { + ) -> (MyVec, MyVec, MyVec, MyVec) { let all_methods = get_all_tx_methods(conn); let mut total_income: f64 = 0.0; let mut total_expense: f64 = 0.0; @@ -345,7 +346,7 @@ impl SummaryData { let mut method_data = Vec::new(); - for method in &all_methods { + for (index, method) in all_methods.iter().enumerate() { let earning_percentage = if method_earning[method] == 0.0 { format!("{:.2}", 0.0) } else { @@ -369,49 +370,71 @@ impl SummaryData { } else { format!("{:.2}", method_expense[method] / total_month_checked) }; - method_data.push(vec![ + let mut to_push = vec![ method.to_string(), format!("{:.2}", method_earning[method]), format!("{:.2}", method_expense[method]), earning_percentage, expense_percentage, - average_earning, - average_expense, - ]); + ]; + + if mode.index != 0 { + to_push.push(average_earning); + to_push.push(average_expense); + } + + if let Some(comparison) = comparison.as_ref() { + let last_earning = comparison[index][1].parse::().unwrap(); + let last_expense = comparison[index][2].parse::().unwrap(); + + let current_earning = method_earning[method]; + let current_expense = method_expense[method]; + + if last_earning > current_earning { + let earning_decreased_percentage = + ((last_earning - current_earning) / last_earning) * 100.0; + + to_push.push(format!("↓{:.2}", earning_decreased_percentage)); + } else if current_earning > last_earning { + let earning_increased_percentage = + ((current_earning - last_earning) / current_earning) * 100.0; + + to_push.push(format!("↑{:.2}", earning_increased_percentage)); + } else { + to_push.push(format!("{:.2}", 0.0)); + } + + if last_expense > current_expense { + let expense_decreased_percentage = + ((last_expense - current_expense) / last_expense) * 100.0; + + to_push.push(format!("↓{:.2}", expense_decreased_percentage)); + } else if current_expense > last_expense { + let expense_increased_percentage = + ((current_expense - last_expense) / current_expense) * 100.0; + + to_push.push(format!("↑{:.2}", expense_increased_percentage)); + } else { + to_push.push(format!("{:.2}", 0.0)); + } + } + method_data.push(to_push); } - let summary_data_1 = vec![ - vec![ - String::from("Total Income"), - format!("{:.2}", total_income), - income_percentage, - ], - vec![ - String::from("Total Expense"), - format!("{:.2}", total_expense), - expense_percentage, - ], - vec![ - String::from("Net"), - format!("{:.2}", total_income - total_expense), - String::from("-"), - ], - ]; + let mut summary_data_1 = vec![vec![ + String::from("Net"), + format!("{:.2}", total_income), + format!("{:.2}", total_expense), + income_percentage, + expense_percentage, + ]]; + + if mode.index != 0 { + summary_data_1[0].push(format!("{:.2}", average_income)); + summary_data_1[0].push(format!("{:.2}", average_expense)); + } let summary_data_2 = vec![ - vec![ - String::from("Average Income"), - format!("{:.2}", average_income), - String::from("-"), - ], - vec![ - String::from("Average Expense"), - format!("{:.2}", average_expense), - String::from("-"), - ], - ]; - - let summary_data_3 = vec![ vec![ String::from("Largest Income"), biggest_earning.2, @@ -424,36 +447,22 @@ impl SummaryData { format!("{:.2}", biggest_expense.0), biggest_expense.1, ], - vec![ - String::from("Months Checked"), - total_month_checked.to_string(), - String::from("-"), - String::from("-"), - ], ]; - let summary_data_4 = vec![ + let summary_data_3 = vec![ vec![ String::from("Peak Earning"), peak_earning.1, format!("{:.2}", peak_earning.0), - String::from("-"), ], vec![ String::from("Peak Expense"), peak_expense.1, format!("{:.2}", peak_expense.0), - String::from("-"), ], ]; - ( - summary_data_1, - summary_data_2, - summary_data_3, - summary_data_4, - method_data, - ) + (summary_data_1, summary_data_2, summary_data_3, method_data) } /// Updates values based on the gathered data diff --git a/src/summary_page/summary_ui.rs b/src/summary_page/summary_ui.rs index 131301b..dba067c 100644 --- a/src/summary_page/summary_ui.rs +++ b/src/summary_page/summary_ui.rs @@ -5,11 +5,15 @@ use ratatui::widgets::{Cell, Row, Table}; use rusqlite::Connection; use thousands::Separable; +use crate::db::MONTHS; use crate::page_handler::{ BACKGROUND, BOX, HEADER, IndexedData, SELECTED, SortingType, SummaryTab, TEXT, TableData, }; use crate::summary_page::SummaryData; -use crate::utility::{LerpState, create_tab, get_all_tx_methods, main_block, styled_block}; +use crate::utility::{ + LerpState, create_tab, get_all_tx_methods, main_block, styled_block, styled_block_no_bottom, + styled_block_no_top, +}; /// The function draws the Summary page of the interface. #[cfg(not(tarpaulin_include))] @@ -26,14 +30,48 @@ pub fn summary_ui( lerp_state: &mut LerpState, conn: &Connection, ) { - let (summary_data_1, summary_data_2, summary_data_3, summary_data_4, method_data) = - summary_data.get_tx_data(mode_selection, months.index, years.index, conn); + let previous_indexes = match mode_selection.index { + 0 => { + if months.index > 0 { + Some((months.index - 1, years.index)) + } else if months.index == 0 && years.index > 0 { + Some((years.index - 1, MONTHS.len() - 1)) + } else { + None + } + } + 1 => { + if years.index > 0 { + Some((months.index, years.index - 1)) + } else { + None + } + } + _ => None, + }; + + let mut previous_data = None; + let mut previous_tags_data = None; + + if let Some((prev_month, prev_year)) = previous_indexes + && !table_data.items.is_empty() + { + let (_, _, _, method_data) = + summary_data.get_tx_data(mode_selection, prev_month, prev_year, None, conn); + + previous_data = Some(method_data); + + previous_tags_data = + Some(summary_data.get_table_data(mode_selection, prev_month, prev_year)); + } - let mut summary_table_1 = TableData::new(summary_data_1); - let mut summary_table_2 = TableData::new(summary_data_2); - let mut summary_table_3 = TableData::new(summary_data_3); - let mut summary_table_4 = TableData::new(summary_data_4); - let mut method_table = TableData::new(method_data); + let (summary_data_1, summary_data_2, summary_data_3, method_data) = summary_data.get_tx_data( + mode_selection, + months.index, + years.index, + previous_data, + conn, + ); let size = f.area(); @@ -65,17 +103,30 @@ pub fn summary_ui( .into_iter() .map(|h| Cell::from(h).style(Style::default().fg(BACKGROUND))); - let method_header_cells = [ + let mut method_headers = vec![ "Method", "Total Income", "Total Expense", "Income %", "Expense %", - "Average Income", - "Average Expense", - ] - .iter() - .map(|h| Cell::from(*h).style(Style::default().fg(BACKGROUND))); + ]; + + if mode_selection.index != 0 { + method_headers.push("Average Income"); + method_headers.push("Average Expense"); + } + + if mode_selection.index == 0 { + method_headers.push("MoM Income %"); + method_headers.push("MoM Expense %"); + } else if mode_selection.index == 1 { + method_headers.push("YoY Income"); + method_headers.push("YoY Expense"); + } + + let method_header_cells = method_headers + .iter() + .map(|h| Cell::from(*h).style(Style::default().fg(BACKGROUND))); let header = Row::new(header_cells) .style(Style::default().bg(HEADER)) @@ -94,8 +145,9 @@ pub fn summary_ui( if summary_hidden_mode { main_layout = main_layout.constraints([ - Constraint::Length(method_len + 3), - Constraint::Length(9), + Constraint::Length(method_len + 2), + Constraint::Length(3), + Constraint::Length(4), Constraint::Min(0), ]); summary_layout = @@ -107,8 +159,9 @@ pub fn summary_ui( Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Length(method_len + 3), - Constraint::Length(9), + Constraint::Length(method_len + 2), + Constraint::Length(3), + Constraint::Length(4), Constraint::Min(0), ]); summary_layout = summary_layout @@ -118,8 +171,9 @@ pub fn summary_ui( main_layout = main_layout.constraints([ Constraint::Length(3), Constraint::Length(3), - Constraint::Length(method_len + 3), - Constraint::Length(9), + Constraint::Length(method_len + 2), + Constraint::Length(3), + Constraint::Length(4), Constraint::Min(0), ]); summary_layout = summary_layout @@ -128,12 +182,13 @@ pub fn summary_ui( 2 => { main_layout = main_layout.constraints([ Constraint::Length(3), - Constraint::Length(method_len + 3), - Constraint::Length(9), + Constraint::Length(method_len + 2), + Constraint::Length(3), + Constraint::Length(4), Constraint::Min(0), ]); summary_layout = summary_layout - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]); + .constraints([Constraint::Percentage(100), Constraint::Percentage(50)]); } _ => {} } @@ -141,21 +196,11 @@ pub fn summary_ui( let chunks = main_layout.split(size); let summary_chunk = if summary_hidden_mode { - summary_layout.split(chunks[1]) + summary_layout.split(chunks[2]) } else { - summary_layout.split(chunks[4 - mode_selection.index]) + summary_layout.split(chunks[5 - mode_selection.index]) }; - let left_summary = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) - .split(summary_chunk[0]); - - let right_summary = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) - .split(summary_chunk[1]); - f.render_widget(main_block(), size); let mut month_tab = create_tab(months, "Months"); @@ -200,124 +245,30 @@ pub fn summary_ui( .block(styled_block("Tags")) .style(Style::default().fg(BOX)); - let summary_rows_1 = summary_table_1 - .items - .iter() - .enumerate() - .map(|(row_index, item)| { - let cells = item.iter().enumerate().map(|(index, c)| { - let mut cell = if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("summary_table_1:{index}:{row_index}"); - let new_c = lerp_state.lerp(&lerp_id, parsed_num); - - let text = if index == 2 { - // Total income/expense % column. Add % char manually - format!("{new_c:.2}%").separate_with_commas() - } else { - format!("{new_c:.2}").separate_with_commas() - }; - - Cell::from(text) - } else { - Cell::from(c.separate_with_commas()) - }; - - if index == 0 { - cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); - } - cell - }); - Row::new(cells) - .height(1) - .bottom_margin(0) - .style(Style::default().fg(TEXT)) - }); - - let summary_area_1 = Table::new( - summary_rows_1, - [ - Constraint::Percentage(33), - Constraint::Percentage(33), - Constraint::Percentage(33), - ], - ) - .block(styled_block("")) - .style(Style::default().fg(BOX)); - - let summary_rows_2 = summary_table_2 - .items - .iter() - .enumerate() - .map(|(row_index, item)| { - let cells = item.iter().enumerate().map(|(index, c)| { - let mut cell = if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("summary_table_2:{index}:{row_index}"); - let new_c = lerp_state.lerp(&lerp_id, parsed_num); + let summary_rows_2 = summary_data_2.iter().enumerate().map(|(row_index, item)| { + let cells = item.iter().enumerate().map(|(index, c)| { + let mut cell = if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("summary_table_2:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); - Cell::from(format!("{new_c:.2}").separate_with_commas()) - } else { - Cell::from(c.separate_with_commas()) - }; + Cell::from(format!("{new_c:.2}").separate_with_commas()) + } else { + Cell::from(c.separate_with_commas()) + }; - if index == 0 { - cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); - } - cell - }); - Row::new(cells) - .height(1) - .bottom_margin(0) - .style(Style::default().fg(TEXT)) + if index == 0 { + cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); + } + cell }); + Row::new(cells) + .height(1) + .bottom_margin(0) + .style(Style::default().fg(TEXT)) + }); let summary_area_2 = Table::new( summary_rows_2, - [ - Constraint::Percentage(33), - Constraint::Percentage(33), - Constraint::Percentage(33), - ], - ) - .block(styled_block("")) - .style(Style::default().fg(BOX)); - - let summary_rows_3 = summary_table_3 - .items - .iter() - .enumerate() - .map(|(row_index, item)| { - let height = 1; - let cells = item.iter().enumerate().map(|(index, c)| { - let mut cell = if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("summary_table_3:{index}:{row_index}"); - let new_c = lerp_state.lerp(&lerp_id, parsed_num); - - let text = if index == 1 && row_index == 2 { - // Month checked value. No need float for this - let new_c = new_c as i64; - format!("{new_c}").separate_with_commas() - } else { - format!("{new_c:.2}").separate_with_commas() - }; - - Cell::from(text) - } else { - Cell::from(c.separate_with_commas()) - }; - - if index == 0 { - cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); - } - cell - }); - Row::new(cells) - .height(height as u16) - .bottom_margin(0) - .style(Style::default().fg(TEXT)) - }); - - let summary_area_3 = Table::new( - summary_rows_3, [ Constraint::Percentage(25), Constraint::Percentage(25), @@ -328,73 +279,87 @@ pub fn summary_ui( .block(styled_block("")) .style(Style::default().fg(BOX)); - let summary_rows_4 = summary_table_4 - .items - .iter() - .enumerate() - .map(|(row_index, item)| { - let cells = item.iter().enumerate().map(|(index, c)| { - let mut cell = if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("summary_table_4:{index}:{row_index}"); - let new_c = lerp_state.lerp(&lerp_id, parsed_num); + let summary_rows_3 = summary_data_3.iter().enumerate().map(|(row_index, item)| { + let height = 1; + let cells = item.iter().enumerate().map(|(index, c)| { + let mut cell = if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("summary_table_3:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); - Cell::from(format!("{new_c:.2}").separate_with_commas()) + let text = if index == 1 && row_index == 2 { + // Month checked value. No need float for this + let new_c = new_c as i64; + format!("{new_c}").separate_with_commas() } else { - Cell::from(c.separate_with_commas()) + format!("{new_c:.2}").separate_with_commas() }; - if index == 0 { - cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); - } - cell - }); - Row::new(cells) - .height(1) - .bottom_margin(0) - .style(Style::default().fg(TEXT)) + Cell::from(text) + } else { + Cell::from(c.separate_with_commas()) + }; + + if index == 0 { + cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); + } + cell }); + Row::new(cells) + .height(height as u16) + .bottom_margin(0) + .style(Style::default().fg(TEXT)) + }); - let summary_area_4 = Table::new( - summary_rows_4, + let summary_area_3 = Table::new( + summary_rows_3, [ - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), + Constraint::Percentage(33), + Constraint::Percentage(33), + Constraint::Percentage(33), ], ) .block(styled_block("")) .style(Style::default().fg(BOX)); - let method_rows = method_table - .items - .iter() - .enumerate() - .map(|(row_index, item)| { - let cells = item.iter().enumerate().map(|(index, c)| { - let mut cell = if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("method_table:{index}:{row_index}"); - let new_c = lerp_state.lerp(&lerp_id, parsed_num); + let method_rows = method_data.iter().enumerate().map(|(row_index, item)| { + let cells = item.iter().enumerate().map(|(index, c)| { + let mut cell = if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("method_table:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); - Cell::from(format!("{new_c:.2}").separate_with_commas()) + Cell::from(format!("{new_c:.2}").separate_with_commas()) + } else { + let symbol = if c.contains('↑') || c.contains('↓') { + c.chars().next() } else { - Cell::from(c.separate_with_commas()) + None }; - if index == 0 { - cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); + if let Some(sym) = symbol { + let c = c.replace(sym, ""); + if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("summary_rows_1:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); + + return Cell::from(format!("{sym}{new_c:.2}").separate_with_commas()); + } } - cell - }); - Row::new(cells) - .height(1) - .bottom_margin(0) - .style(Style::default().fg(TEXT)) - }); + Cell::from(c.separate_with_commas()) + }; - let method_area = Table::new( - method_rows, - [ + if index == 0 { + cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); + } + cell + }); + Row::new(cells) + .height(1) + .bottom_margin(0) + .style(Style::default().fg(TEXT)) + }); + + let method_widths = if mode_selection.index == 2 { + vec![ Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), @@ -402,11 +367,62 @@ pub fn summary_ui( Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), - ], - ) - .header(method_header) - .block(styled_block("")) - .style(Style::default().fg(BOX)); + ] + } else if mode_selection.index == 1 { + vec![ + Constraint::Percentage(10), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(10), + Constraint::Percentage(10), + Constraint::Percentage(12), + Constraint::Percentage(12), + Constraint::Percentage(8), + Constraint::Percentage(8), + ] + } else { + vec![ + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(15), + ] + }; + + let method_area = Table::new(method_rows, &method_widths) + .header(method_header) + .block(styled_block_no_bottom("")) + .style(Style::default().fg(BOX)); + + let net_row = summary_data_1.iter().enumerate().map(|(row_index, item)| { + let cells = item.iter().enumerate().map(|(index, c)| { + log::info!("{c}"); + let mut cell = if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("summary_rows_1:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); + + Cell::from(format!("{new_c:.2}").separate_with_commas()) + } else { + Cell::from(c.separate_with_commas()) + }; + + if index == 0 { + cell = cell.style(Style::default().fg(TEXT).add_modifier(Modifier::BOLD)); + } + cell + }); + Row::new(cells) + .height(1) + .bottom_margin(0) + .style(Style::default().fg(TEXT)) + }); + + let net_area = Table::new(net_row, &method_widths) + .block(styled_block_no_top("")) + .style(Style::default().fg(BOX)); match current_page { // Previously added a black block to year and month widget if a value is not selected @@ -433,39 +449,40 @@ pub fn summary_ui( // Always keep some items rendered on the upper side of the table if let Some(index) = table_data.state.selected() - && index > 7 { - *table_data.state.offset_mut() = index - 7; - } + && index > 7 + { + *table_data.state.offset_mut() = index - 7; + } if summary_hidden_mode { - f.render_stateful_widget(summary_area_1, left_summary[0], &mut summary_table_1.state); - f.render_stateful_widget(summary_area_2, left_summary[1], &mut summary_table_2.state); - f.render_stateful_widget(summary_area_3, right_summary[0], &mut summary_table_3.state); - f.render_stateful_widget(summary_area_4, right_summary[1], &mut summary_table_4.state); - f.render_stateful_widget(table_area, chunks[2], &mut table_data.state); - f.render_stateful_widget(method_area, chunks[0], &mut method_table.state); + f.render_widget(summary_area_2, summary_chunk[1]); + f.render_widget(summary_area_3, summary_chunk[0]); + f.render_widget(table_area, chunks[3]); + f.render_widget(net_area, chunks[1]); + f.render_widget(method_area, chunks[0]); } else { f.render_widget(mode_selection_tab, chunks[0]); - f.render_stateful_widget(summary_area_1, left_summary[0], &mut summary_table_1.state); - f.render_stateful_widget(summary_area_2, left_summary[1], &mut summary_table_2.state); - f.render_stateful_widget(summary_area_3, right_summary[0], &mut summary_table_3.state); - f.render_stateful_widget(summary_area_4, right_summary[1], &mut summary_table_4.state); + f.render_widget(summary_area_2, summary_chunk[1]); + f.render_widget(summary_area_3, summary_chunk[0]); match mode_selection.index { 0 => { f.render_widget(year_tab, chunks[1]); f.render_widget(month_tab, chunks[2]); - f.render_stateful_widget(table_area, chunks[5], &mut table_data.state); - f.render_stateful_widget(method_area, chunks[3], &mut method_table.state); + f.render_stateful_widget(table_area, chunks[6], &mut table_data.state); + f.render_widget(net_area, chunks[4]); + f.render_widget(method_area, chunks[3]); } 1 => { f.render_widget(year_tab, chunks[1]); - f.render_stateful_widget(table_area, chunks[4], &mut table_data.state); - f.render_stateful_widget(method_area, chunks[2], &mut method_table.state); + f.render_stateful_widget(table_area, chunks[5], &mut table_data.state); + f.render_widget(net_area, chunks[3]); + f.render_widget(method_area, chunks[2]); } 2 => { - f.render_stateful_widget(table_area, chunks[3], &mut table_data.state); - f.render_stateful_widget(method_area, chunks[1], &mut method_table.state); + f.render_stateful_widget(table_area, chunks[4], &mut table_data.state); + f.render_widget(net_area, chunks[2]); + f.render_widget(method_area, chunks[1]); } _ => {} } diff --git a/src/utility/utils.rs b/src/utility/utils.rs index b192322..26b3781 100644 --- a/src/utility/utils.rs +++ b/src/utility/utils.rs @@ -323,7 +323,7 @@ pub fn check_n_create_db(verifying_path: &PathBuf) -> Result<(), Box> /// Returns a styled block for UI to use #[cfg(not(tarpaulin_include))] #[must_use] -pub fn styled_block(title: &str) -> Block { +pub fn styled_block(title: &str) -> Block<'_> { Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) @@ -334,6 +334,28 @@ pub fn styled_block(title: &str) -> Block { )) } +pub fn styled_block_no_top(title: &str) -> Block<'_> { + Block::default() + .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) + .border_type(BorderType::Rounded) + .style(Style::default().bg(BACKGROUND).fg(BOX)) + .title(Span::styled( + title, + Style::default().add_modifier(Modifier::BOLD), + )) +} + +pub fn styled_block_no_bottom(title: &str) -> Block<'_> { + Block::default() + .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) + .border_type(BorderType::Rounded) + .style(Style::default().bg(BACKGROUND).fg(BOX)) + .title(Span::styled( + title, + Style::default().add_modifier(Modifier::BOLD), + )) +} + #[cfg(not(tarpaulin_include))] #[must_use] pub fn main_block<'a>() -> Block<'a> { @@ -344,7 +366,7 @@ pub fn main_block<'a>() -> Block<'a> { /// Used for rendering #[cfg(not(tarpaulin_include))] #[must_use] -pub fn create_bolded_text(text: &str) -> Vec { +pub fn create_bolded_text(text: &str) -> Vec> { let mut text_data = Vec::new(); for line in text.split('\n') { From 821a2a1e5af6cfdd99ea2d7a31a9fe92b5b65c94 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 18:18:14 +0600 Subject: [PATCH 3/7] Add mom/yoy for summary --- src/key_checker/key_handler.rs | 209 +++++++++++++++++++++++++++---- src/page_handler/ui_handler.rs | 4 + src/summary_page/summary_data.rs | 36 +++--- src/summary_page/summary_ui.rs | 134 +++++++++++--------- 4 files changed, 277 insertions(+), 106 deletions(-) diff --git a/src/key_checker/key_handler.rs b/src/key_checker/key_handler.rs index efb4aab..d2fe7dd 100644 --- a/src/key_checker/key_handler.rs +++ b/src/key_checker/key_handler.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use crate::activity_page::ActivityData; use crate::chart_page::ChartData; +use crate::db::MONTHS; use crate::home_page::TransactionData; use crate::outputs::TxType; use crate::outputs::{HandlingOutput, TxUpdateError, VerifyingOutput}; @@ -65,6 +66,7 @@ pub struct InputKeyHandler<'a> { popup_scroll_position: &'a mut usize, max_popup_scroll: &'a mut usize, lerp_state: &'a mut LerpState, + last_summary_data: &'a mut Option>>, conn: &'a mut Connection, } @@ -113,6 +115,7 @@ impl<'a> InputKeyHandler<'a> { popup_scroll_position: &'a mut usize, max_popup_scroll: &'a mut usize, lerp_state: &'a mut LerpState, + last_summary_data: &'a mut Option>>, conn: &'a mut Connection, ) -> InputKeyHandler<'a> { let total_tags = summary_data @@ -162,6 +165,7 @@ impl<'a> InputKeyHandler<'a> { popup_scroll_position, max_popup_scroll, lerp_state, + last_summary_data, conn, } } @@ -798,10 +802,89 @@ impl<'a> InputKeyHandler<'a> { pub fn change_summary_sort(&mut self) { *self.summary_sort = self.summary_sort.next_type(); let summary_data = self.summary_table.items.clone(); - let sorted_data = sort_table_data(summary_data, self.summary_sort); + let mut sorted_data = sort_table_data(summary_data, self.summary_sort); let selection_status = self.summary_table.state.selected(); - *self.summary_table = TableData::new(sorted_data); self.summary_table.state.select(selection_status); + + let mut summary_tags_map = HashMap::new(); + + let previous_indexes = match self.summary_modes.index { + 0 => { + if self.summary_months.index > 0 { + Some((self.summary_months.index - 1, self.summary_years.index)) + } else if self.summary_months.index == 0 && self.summary_years.index > 0 { + Some((self.summary_years.index - 1, MONTHS.len() - 1)) + } else { + None + } + } + 1 => { + if self.summary_years.index > 0 { + Some((self.summary_months.index, self.summary_years.index - 1)) + } else { + None + } + } + _ => None, + }; + + if let Some((month, year)) = previous_indexes { + let (_, _, _, method_data) = + self.summary_data + .get_tx_data(self.summary_modes, month, year, &None, self.conn); + + *self.last_summary_data = Some(method_data); + + let tag_data = self + .summary_data + .get_table_data(self.summary_modes, month, year); + + for tag in tag_data { + let tag_name = tag[0].clone(); + let tag_income = tag[1].parse::().unwrap(); + let tag_expense = tag[2].parse::().unwrap(); + + summary_tags_map.insert(tag_name, (tag_income, tag_expense)); + } + } + + for data in &mut sorted_data { + let tag_name = &data[0]; + let mut to_push = Vec::new(); + + if let Some((last_earning, last_expense)) = summary_tags_map.get(tag_name) { + let current_earning = data[1].parse::().unwrap(); + let current_expense = data[2].parse::().unwrap(); + + let earning_increased_percentage = + ((current_earning - last_earning) / last_earning) * 100.0; + + if last_earning == &0.0 { + to_push.push("∞".to_string()); + } else if earning_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); + } else { + to_push.push(format!("↑{:.2}", earning_increased_percentage)); + } + + let expense_increased_percentage = + ((current_expense - last_expense) / last_expense) * 100.0; + + if last_expense == &0.0 { + to_push.push("∞".to_string()); + } else if expense_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); + } else { + to_push.push(format!("↑{:.2}", expense_increased_percentage)); + } + } else { + to_push.push("∞".to_string()); + to_push.push("∞".to_string()); + } + data.extend(to_push); + } + + *self.summary_table = TableData::new(sorted_data); } /// If Enter is pressed on Summary page while a tag is selected @@ -809,13 +892,14 @@ impl<'a> InputKeyHandler<'a> { #[cfg(not(tarpaulin_include))] pub fn search_tag(&mut self) { if let SummaryTab::Table = self.summary_tab - && let Some(index) = self.summary_table.state.selected() { - let tag_name = &self.summary_table.items[index][0]; - let search_param = TxData::custom("", "", "", "", "", "", tag_name, 0); - *self.search_data = search_param; - self.go_search(); - self.search_tx(); - } + && let Some(index) = self.summary_table.state.selected() + { + let tag_name = &self.summary_table.items[index][0]; + let search_param = TxData::custom("", "", "", "", "", "", tag_name, 0); + *self.search_data = search_param; + self.go_search(); + self.search_tx(); + } } /// Handle key press when deletion popup is turned on @@ -1012,18 +1096,19 @@ impl<'a> InputKeyHandler<'a> { #[cfg(not(tarpaulin_include))] pub fn switch_chart_tx_method_activation(&mut self) { if !*self.chart_hidden_mode - && let ChartTab::TxMethods = self.chart_tab { - let selected_index = self.chart_tx_methods.index; - let all_tx_methods = get_all_tx_methods_cumulative(self.conn); - - let selected_method = &all_tx_methods[selected_index]; - let activation_status = self - .chart_activated_methods - .get_mut(selected_method) - .unwrap(); - *activation_status = !*activation_status; - self.lerp_state.clear(); - } + && let ChartTab::TxMethods = self.chart_tab + { + let selected_index = self.chart_tx_methods.index; + let all_tx_methods = get_all_tx_methods_cumulative(self.conn); + + let selected_method = &all_tx_methods[selected_index]; + let activation_status = self + .chart_activated_methods + .get_mut(selected_method) + .unwrap(); + *activation_status = !*activation_status; + self.lerp_state.clear(); + } } #[cfg(not(tarpaulin_include))] @@ -1690,14 +1775,92 @@ impl InputKeyHandler<'_> { /// Reset summary table data by recreating it from gathered Summary Data #[cfg(not(tarpaulin_include))] fn reload_summary(&mut self) { - let summary_table = self.summary_data.get_table_data( + let mut summary_table = self.summary_data.get_table_data( self.summary_modes, self.summary_months.index, self.summary_years.index, ); self.total_tags = summary_table.len(); - *self.summary_table = TableData::new(summary_table); *self.summary_sort = SortingType::ByTags; + let mut summary_tags_map = HashMap::new(); + + let previous_indexes = match self.summary_modes.index { + 0 => { + if self.summary_months.index > 0 { + Some((self.summary_months.index - 1, self.summary_years.index)) + } else if self.summary_months.index == 0 && self.summary_years.index > 0 { + Some((self.summary_years.index - 1, MONTHS.len() - 1)) + } else { + None + } + } + 1 => { + if self.summary_years.index > 0 { + Some((self.summary_months.index, self.summary_years.index - 1)) + } else { + None + } + } + _ => None, + }; + + if let Some((month, year)) = previous_indexes { + let (_, _, _, method_data) = + self.summary_data + .get_tx_data(self.summary_modes, month, year, &None, self.conn); + + *self.last_summary_data = Some(method_data); + + let tag_data = self + .summary_data + .get_table_data(self.summary_modes, month, year); + + for tag in tag_data { + let tag_name = tag[0].clone(); + let tag_income = tag[1].parse::().unwrap(); + let tag_expense = tag[2].parse::().unwrap(); + + summary_tags_map.insert(tag_name, (tag_income, tag_expense)); + } + } + + for data in &mut summary_table { + let tag_name = &data[0]; + let mut to_push = Vec::new(); + + if let Some((last_earning, last_expense)) = summary_tags_map.get(tag_name) { + let current_earning = data[1].parse::().unwrap(); + let current_expense = data[2].parse::().unwrap(); + + let earning_increased_percentage = + ((current_earning - last_earning) / last_earning) * 100.0; + + if last_earning == &0.0 { + to_push.push("∞".to_string()); + } else if earning_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); + } else { + to_push.push(format!("↑{:.2}", earning_increased_percentage)); + } + + let expense_increased_percentage = + ((current_expense - last_expense) / last_expense) * 100.0; + + if last_expense == &0.0 { + to_push.push("∞".to_string()); + } else if expense_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); + } else { + to_push.push(format!("↑{:.2}", expense_increased_percentage)); + } + } else { + to_push.push("∞".to_string()); + to_push.push("∞".to_string()); + } + data.extend(to_push); + } + + *self.summary_table = TableData::new(summary_table); } /// Reload summary data by fetching from the DB diff --git a/src/page_handler/ui_handler.rs b/src/page_handler/ui_handler.rs index a11f8b0..4cc4c7f 100644 --- a/src/page_handler/ui_handler.rs +++ b/src/page_handler/ui_handler.rs @@ -131,6 +131,8 @@ pub fn start_app( summary_years.index, )); + let mut last_summary_data = None; + // Data for the Search Page's table let mut search_table = TableData::new(Vec::new()); @@ -239,6 +241,7 @@ pub fn start_app( summary_hidden_mode, &summary_sort, &mut lerp_state, + &last_summary_data, conn, ), CurrentUi::Search => search_ui( @@ -342,6 +345,7 @@ pub fn start_app( &mut popup_scroll_position, &mut max_popup_scroll, &mut lerp_state, + &mut last_summary_data, conn, ); diff --git a/src/summary_page/summary_data.rs b/src/summary_page/summary_data.rs index 526be0f..8cedade 100644 --- a/src/summary_page/summary_data.rs +++ b/src/summary_page/summary_data.rs @@ -222,7 +222,7 @@ impl SummaryData { mode: &IndexedData, month: usize, year: usize, - comparison: Option, + comparison: &Option, conn: &Connection, ) -> (MyVec, MyVec, MyVec, MyVec) { let all_methods = get_all_tx_methods(conn); @@ -390,32 +390,26 @@ impl SummaryData { let current_earning = method_earning[method]; let current_expense = method_expense[method]; - if last_earning > current_earning { - let earning_decreased_percentage = - ((last_earning - current_earning) / last_earning) * 100.0; + let earning_increased_percentage = + ((current_earning - last_earning) / last_earning) * 100.0; - to_push.push(format!("↓{:.2}", earning_decreased_percentage)); - } else if current_earning > last_earning { - let earning_increased_percentage = - ((current_earning - last_earning) / current_earning) * 100.0; - - to_push.push(format!("↑{:.2}", earning_increased_percentage)); + if last_earning == 0.0 { + to_push.push("∞".to_string()); + } else if earning_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); } else { - to_push.push(format!("{:.2}", 0.0)); + to_push.push(format!("↑{:.2}", earning_increased_percentage)); } - if last_expense > current_expense { - let expense_decreased_percentage = - ((last_expense - current_expense) / last_expense) * 100.0; + let expense_increased_percentage = + ((current_expense - last_expense) / last_expense) * 100.0; - to_push.push(format!("↓{:.2}", expense_decreased_percentage)); - } else if current_expense > last_expense { - let expense_increased_percentage = - ((current_expense - last_expense) / current_expense) * 100.0; - - to_push.push(format!("↑{:.2}", expense_increased_percentage)); + if last_expense == 0.0 { + to_push.push("∞".to_string()); + } else if expense_increased_percentage < 0.0 { + to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); } else { - to_push.push(format!("{:.2}", 0.0)); + to_push.push(format!("↑{:.2}", expense_increased_percentage)); } } method_data.push(to_push); diff --git a/src/summary_page/summary_ui.rs b/src/summary_page/summary_ui.rs index dba067c..8966a04 100644 --- a/src/summary_page/summary_ui.rs +++ b/src/summary_page/summary_ui.rs @@ -5,7 +5,6 @@ use ratatui::widgets::{Cell, Row, Table}; use rusqlite::Connection; use thousands::Separable; -use crate::db::MONTHS; use crate::page_handler::{ BACKGROUND, BOX, HEADER, IndexedData, SELECTED, SortingType, SummaryTab, TEXT, TableData, }; @@ -28,43 +27,9 @@ pub fn summary_ui( summary_hidden_mode: bool, summary_sort: &SortingType, lerp_state: &mut LerpState, + previous_data: &Option>>, conn: &Connection, ) { - let previous_indexes = match mode_selection.index { - 0 => { - if months.index > 0 { - Some((months.index - 1, years.index)) - } else if months.index == 0 && years.index > 0 { - Some((years.index - 1, MONTHS.len() - 1)) - } else { - None - } - } - 1 => { - if years.index > 0 { - Some((months.index, years.index - 1)) - } else { - None - } - } - _ => None, - }; - - let mut previous_data = None; - let mut previous_tags_data = None; - - if let Some((prev_month, prev_year)) = previous_indexes - && !table_data.items.is_empty() - { - let (_, _, _, method_data) = - summary_data.get_tx_data(mode_selection, prev_month, prev_year, None, conn); - - previous_data = Some(method_data); - - previous_tags_data = - Some(summary_data.get_table_data(mode_selection, prev_month, prev_year)); - } - let (summary_data_1, summary_data_2, summary_data_3, method_data) = summary_data.get_tx_data( mode_selection, months.index, @@ -93,15 +58,25 @@ pub fn summary_ui( "Total Expense" }; - let header_cells = [ + let mut table_headers = vec![ tag_header, total_income_header, total_expense_header, "Income %", "Expense %", - ] - .into_iter() - .map(|h| Cell::from(h).style(Style::default().fg(BACKGROUND))); + ]; + + if mode_selection.index == 0 { + table_headers.push("MoM Income %"); + table_headers.push("MoM Expense %"); + } else if mode_selection.index == 1 { + table_headers.push("YoY Income %"); + table_headers.push("YoY Expense %"); + } + + let header_cells = table_headers + .into_iter() + .map(|h| Cell::from(h).style(Style::default().fg(BACKGROUND))); let mut method_headers = vec![ "Method", @@ -120,8 +95,8 @@ pub fn summary_ui( method_headers.push("MoM Income %"); method_headers.push("MoM Expense %"); } else if mode_selection.index == 1 { - method_headers.push("YoY Income"); - method_headers.push("YoY Expense"); + method_headers.push("YoY Income %"); + method_headers.push("YoY Expense %"); } let method_header_cells = method_headers @@ -217,6 +192,26 @@ pub fn summary_ui( .map(|(row_index, item)| { let cells = item.iter().enumerate().map(|(index, c)| { let Ok(parsed_num) = c.parse::() else { + if c == "∞" { + let lerp_id = format!("summary_table_main:{index}:{row_index}"); + lerp_state.lerp(&lerp_id, 0.0); + } + + let symbol = if c.contains('↑') || c.contains('↓') { + c.chars().next() + } else { + None + }; + + if let Some(sym) = symbol { + let c = c.replace(sym, ""); + if let Ok(parsed_num) = c.parse::() { + let lerp_id = format!("summary_table_main:{index}:{row_index}"); + let new_c = lerp_state.lerp(&lerp_id, parsed_num); + + return Cell::from(format!("{sym}{new_c:.2}").separate_with_commas()); + } + } return Cell::from(c.separate_with_commas()); }; @@ -231,19 +226,30 @@ pub fn summary_ui( .style(Style::default().fg(TEXT)) }); - let mut table_area = Table::new( - rows, - [ + let table_width = if mode_selection.index == 2 { + vec![ Constraint::Percentage(20), Constraint::Percentage(20), Constraint::Percentage(20), Constraint::Percentage(20), Constraint::Percentage(20), - ], - ) - .header(header) - .block(styled_block("Tags")) - .style(Style::default().fg(BOX)); + ] + } else { + vec![ + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(14), + Constraint::Percentage(15), + ] + }; + + let mut table_area = Table::new(rows, table_width) + .header(header) + .block(styled_block("Tags")) + .style(Style::default().fg(BOX)); let summary_rows_2 = summary_data_2.iter().enumerate().map(|(row_index, item)| { let cells = item.iter().enumerate().map(|(index, c)| { @@ -338,12 +344,17 @@ pub fn summary_ui( if let Some(sym) = symbol { let c = c.replace(sym, ""); if let Ok(parsed_num) = c.parse::() { - let lerp_id = format!("summary_rows_1:{index}:{row_index}"); + let lerp_id = format!("method_table:{index}:{row_index}"); let new_c = lerp_state.lerp(&lerp_id, parsed_num); return Cell::from(format!("{sym}{new_c:.2}").separate_with_commas()); } } + + if c == "∞" { + let lerp_id = format!("method_table:{index}:{row_index}"); + lerp_state.lerp(&lerp_id, 0.0); + } Cell::from(c.separate_with_commas()) }; @@ -360,35 +371,35 @@ pub fn summary_ui( let method_widths = if mode_selection.index == 2 { vec![ + Constraint::Percentage(10), Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), - Constraint::Percentage(14), - Constraint::Percentage(14), - Constraint::Percentage(14), + Constraint::Percentage(16), + Constraint::Percentage(16), ] } else if mode_selection.index == 1 { vec![ Constraint::Percentage(10), - Constraint::Percentage(14), - Constraint::Percentage(14), + Constraint::Percentage(12), + Constraint::Percentage(12), Constraint::Percentage(10), Constraint::Percentage(10), Constraint::Percentage(12), Constraint::Percentage(12), - Constraint::Percentage(8), - Constraint::Percentage(8), + Constraint::Percentage(10), + Constraint::Percentage(10), ] } else { vec![ + Constraint::Percentage(10), Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), Constraint::Percentage(14), - Constraint::Percentage(14), - Constraint::Percentage(14), - Constraint::Percentage(15), + Constraint::Percentage(16), + Constraint::Percentage(16), ] }; @@ -399,7 +410,6 @@ pub fn summary_ui( let net_row = summary_data_1.iter().enumerate().map(|(row_index, item)| { let cells = item.iter().enumerate().map(|(index, c)| { - log::info!("{c}"); let mut cell = if let Ok(parsed_num) = c.parse::() { let lerp_id = format!("summary_rows_1:{index}:{row_index}"); let new_c = lerp_state.lerp(&lerp_id, parsed_num); From d24be8f0d40c38322f7cd83526fb8adfb4c9155b Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 18:18:23 +0600 Subject: [PATCH 4/7] Update --- src/activity_page/activity_ui.rs | 7 ++++--- src/home_page/home_ui.rs | 7 ++++--- src/page_handler/initializer.rs | 19 ++++++++++--------- src/search_page/search_ui.rs | 7 ++++--- src/tx_handler/add_tx.rs | 1 + src/utility/traits/autofiller.rs | 3 ++- src/utility/traits/stepper.rs | 5 +++-- src/utility/traits/verifier.rs | 7 ++++--- 8 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/activity_page/activity_ui.rs b/src/activity_page/activity_ui.rs index 7c9e04c..111ef7b 100644 --- a/src/activity_page/activity_ui.rs +++ b/src/activity_page/activity_ui.rs @@ -181,9 +181,10 @@ pub fn activity_ui( } if let Some(index) = table_data.state.selected() - && index > 10 { - *table_data.state.offset_mut() = index - 10; - } + && index > 10 + { + *table_data.state.offset_mut() = index - 10; + } f.render_widget(year_tab, chunks[0]); f.render_widget(month_tab, chunks[1]); diff --git a/src/home_page/home_ui.rs b/src/home_page/home_ui.rs index 48dcd06..bd655b9 100644 --- a/src/home_page/home_ui.rs +++ b/src/home_page/home_ui.rs @@ -224,9 +224,10 @@ pub fn home_ui( // Always keep some items rendered on the upper side of the table if let Some(index) = table.state.selected() - && index > 10 { - *table.state.offset_mut() = index - 10; - } + && index > 10 + { + *table.state.offset_mut() = index - 10; + } // After all data is in place, render the widgets one by one // the chunks are selected based on the format I want the widgets to render diff --git a/src/page_handler/initializer.rs b/src/page_handler/initializer.rs index d35bbae..fe65a10 100644 --- a/src/page_handler/initializer.rs +++ b/src/page_handler/initializer.rs @@ -28,15 +28,16 @@ pub fn initialize_app( let new_version_available = new_version.unwrap_or_default(); // If is not terminal, try to start a terminal otherwise create an error.txt file with the error message if !atty::is(Stream::Stdout) - && let Err(err) = start_terminal(original_dir.to_str().unwrap()) { - let mut error_location = PathBuf::from(&original_dir); - error_location.push("Error.txt"); - - let mut open = File::create(error_location)?; - let to_write = format!("{}\n{}", original_dir.to_str().unwrap(), err); - open.write_all(to_write.as_bytes())?; - process::exit(1); - } + && let Err(err) = start_terminal(original_dir.to_str().unwrap()) + { + let mut error_location = PathBuf::from(&original_dir); + error_location.push("Error.txt"); + + let mut open = File::create(error_location)?; + let to_write = format!("{}\n{}", original_dir.to_str().unwrap(), err); + open.write_all(to_write.as_bytes())?; + process::exit(1); + } // If the location was changed/json file found, change the db directory. let db_path = if let Some(mut location) = is_location_changed(original_db_path) { diff --git a/src/search_page/search_ui.rs b/src/search_page/search_ui.rs index b6688b1..52ab4f9 100644 --- a/src/search_page/search_ui.rs +++ b/src/search_page/search_ui.rs @@ -319,9 +319,10 @@ pub fn search_ui( } if let Some(index) = search_table.state.selected() - && index > 10 { - *search_table.state.offset_mut() = index - 10; - } + && index > 10 + { + *search_table.state.offset_mut() = index - 10; + } // Render the previously generated data into an interface f.render_widget(details_sec, chunks[1]); diff --git a/src/tx_handler/add_tx.rs b/src/tx_handler/add_tx.rs index 986d65b..09240ff 100644 --- a/src/tx_handler/add_tx.rs +++ b/src/tx_handler/add_tx.rs @@ -1,4 +1,5 @@ use rusqlite::{Connection, Result as sqlResult}; + use std::collections::HashMap; use crate::utility::{ diff --git a/src/utility/traits/autofiller.rs b/src/utility/traits/autofiller.rs index 52728a7..3e8671f 100644 --- a/src/utility/traits/autofiller.rs +++ b/src/utility/traits/autofiller.rs @@ -1,6 +1,7 @@ -use crate::utility::{get_all_details, get_all_tags, get_all_tx_methods, get_best_match}; use rusqlite::Connection; +use crate::utility::{get_all_details, get_all_tags, get_all_tx_methods, get_best_match}; + pub trait AutoFiller { fn autofill_tx_method(&self, user_input: &str, conn: &Connection) -> String { let all_tx_methods = get_all_tx_methods(conn); diff --git a/src/utility/traits/stepper.rs b/src/utility/traits/stepper.rs index 6a8559b..14737af 100644 --- a/src/utility/traits/stepper.rs +++ b/src/utility/traits/stepper.rs @@ -1,9 +1,10 @@ +use chrono::{Duration, NaiveDate}; +use rusqlite::Connection; + use crate::outputs::{NAType, StepType, SteppingError, VerifyingOutput}; use crate::page_handler::DateType; use crate::utility::traits::DataVerifier; use crate::utility::{get_all_tags, get_all_tx_methods}; -use chrono::{Duration, NaiveDate}; -use rusqlite::Connection; pub trait FieldStepper: DataVerifier { fn step_date( diff --git a/src/utility/traits/verifier.rs b/src/utility/traits/verifier.rs index ac1b458..a834674 100644 --- a/src/utility/traits/verifier.rs +++ b/src/utility/traits/verifier.rs @@ -1,11 +1,12 @@ -use crate::outputs::{AType, NAType, VerifyingOutput}; -use crate::page_handler::DateType; -use crate::utility::{get_all_tags, get_all_tx_methods, get_best_match}; use chrono::naive::NaiveDate; use rusqlite::Connection; use std::cmp::Ordering; use std::collections::HashSet; +use crate::outputs::{AType, NAType, VerifyingOutput}; +use crate::page_handler::DateType; +use crate::utility::{get_all_tags, get_all_tx_methods, get_best_match}; + pub trait DataVerifier { /// Checks if: /// From 5ac03d6bbd3ae724cac64311a863038cf4bf7480 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 18:18:45 +0600 Subject: [PATCH 5/7] Clippy fix --- src/key_checker/key_handler.rs | 8 ++++---- src/summary_page/summary_data.rs | 8 ++++---- src/utility/utils.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/key_checker/key_handler.rs b/src/key_checker/key_handler.rs index d2fe7dd..8f19252 100644 --- a/src/key_checker/key_handler.rs +++ b/src/key_checker/key_handler.rs @@ -864,7 +864,7 @@ impl<'a> InputKeyHandler<'a> { } else if earning_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", earning_increased_percentage)); + to_push.push(format!("↑{earning_increased_percentage:.2}")); } let expense_increased_percentage = @@ -875,7 +875,7 @@ impl<'a> InputKeyHandler<'a> { } else if expense_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", expense_increased_percentage)); + to_push.push(format!("↑{expense_increased_percentage:.2}")); } } else { to_push.push("∞".to_string()); @@ -1840,7 +1840,7 @@ impl InputKeyHandler<'_> { } else if earning_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", earning_increased_percentage)); + to_push.push(format!("↑{earning_increased_percentage:.2}")); } let expense_increased_percentage = @@ -1851,7 +1851,7 @@ impl InputKeyHandler<'_> { } else if expense_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", expense_increased_percentage)); + to_push.push(format!("↑{expense_increased_percentage:.2}")); } } else { to_push.push("∞".to_string()); diff --git a/src/summary_page/summary_data.rs b/src/summary_page/summary_data.rs index 8cedade..61eea78 100644 --- a/src/summary_page/summary_data.rs +++ b/src/summary_page/summary_data.rs @@ -398,7 +398,7 @@ impl SummaryData { } else if earning_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", earning_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", earning_increased_percentage)); + to_push.push(format!("↑{earning_increased_percentage:.2}")); } let expense_increased_percentage = @@ -409,7 +409,7 @@ impl SummaryData { } else if expense_increased_percentage < 0.0 { to_push.push(format!("↓{:.2}", expense_increased_percentage.abs())); } else { - to_push.push(format!("↑{:.2}", expense_increased_percentage)); + to_push.push(format!("↑{expense_increased_percentage:.2}")); } } method_data.push(to_push); @@ -424,8 +424,8 @@ impl SummaryData { ]]; if mode.index != 0 { - summary_data_1[0].push(format!("{:.2}", average_income)); - summary_data_1[0].push(format!("{:.2}", average_expense)); + summary_data_1[0].push(format!("{average_income:.2}")); + summary_data_1[0].push(format!("{average_expense:.2}")); } let summary_data_2 = vec![ diff --git a/src/utility/utils.rs b/src/utility/utils.rs index 26b3781..3977ccb 100644 --- a/src/utility/utils.rs +++ b/src/utility/utils.rs @@ -334,7 +334,7 @@ pub fn styled_block(title: &str) -> Block<'_> { )) } -pub fn styled_block_no_top(title: &str) -> Block<'_> { +#[must_use] pub fn styled_block_no_top(title: &str) -> Block<'_> { Block::default() .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) .border_type(BorderType::Rounded) @@ -345,7 +345,7 @@ pub fn styled_block_no_top(title: &str) -> Block<'_> { )) } -pub fn styled_block_no_bottom(title: &str) -> Block<'_> { +#[must_use] pub fn styled_block_no_bottom(title: &str) -> Block<'_> { Block::default() .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) .border_type(BorderType::Rounded) From fcfb4955015a49f325073e5e587c49facf4ba293 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 18:34:13 +0600 Subject: [PATCH 6/7] Fix tests --- tests/summary.rs | 134 ++++++++++------------------------------------- 1 file changed, 28 insertions(+), 106 deletions(-) diff --git a/tests/summary.rs b/tests/summary.rs index faa7a60..b31a1fa 100644 --- a/tests/summary.rs +++ b/tests/summary.rs @@ -54,36 +54,18 @@ fn check_summary_data_1() { let my_summary = SummaryData::new(&conn); let my_summary_text = my_summary.get_table_data(&summary_modes, 6, 1); - let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 6, 1, &conn); + let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 6, 1, &None, &conn); let expected_data_1 = vec![vec!["Food", "200.00", "100.00", "100.00", "100.00"]]; let expected_data_2 = ( - vec![ - vec![ - "Total Income".to_string(), - "200.00".to_string(), - "66.67".to_string(), - ], - vec![ - "Total Expense".to_string(), - "100.00".to_string(), - "33.33".to_string(), - ], - vec!["Net".to_string(), "100.00".to_string(), "-".to_string()], - ], - vec![ - vec![ - "Average Income".to_string(), - "200.00".to_string(), - "-".to_string(), - ], - vec![ - "Average Expense".to_string(), - "100.00".to_string(), - "-".to_string(), - ], - ], + vec![vec![ + "Net".to_string(), + "200.00".to_string(), + "100.00".to_string(), + "66.67".to_string(), + "33.33".to_string(), + ]], vec![ vec![ "Largest Income".to_string(), @@ -97,25 +79,17 @@ fn check_summary_data_1() { "100.00".to_string(), "Cash Cow".to_string(), ], - vec![ - "Months Checked".to_string(), - "1".to_string(), - "-".to_string(), - "-".to_string(), - ], ], vec![ vec![ "Peak Earning".to_string(), "07-2023".to_string(), "200.00".to_string(), - "-".to_string(), ], vec![ "Peak Expense".to_string(), "07-2023".to_string(), "100.00".to_string(), - "-".to_string(), ], ], vec![ @@ -125,8 +99,6 @@ fn check_summary_data_1() { "0.00".to_string(), "100.00".to_string(), "0.00".to_string(), - "200.00".to_string(), - "0.00".to_string(), ], vec![ "Cash Cow".to_string(), @@ -134,8 +106,6 @@ fn check_summary_data_1() { "100.00".to_string(), "0.00".to_string(), "100.00".to_string(), - "0.00".to_string(), - "100.00".to_string(), ], ], ); @@ -205,7 +175,7 @@ fn check_summary_data_2() { let my_summary = SummaryData::new(&conn); let my_summary_text = my_summary.get_table_data(&summary_modes, 0, 0); - let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 0, 0, &conn); + let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 0, 0, &None, &conn); let expected_data_1 = vec![ vec![ @@ -225,31 +195,15 @@ fn check_summary_data_2() { ]; let expected_data_2 = ( - vec![ - vec![ - "Total Income".to_string(), - "1700.00".to_string(), - "62.96".to_string(), - ], - vec![ - "Total Expense".to_string(), - "1000.00".to_string(), - "37.04".to_string(), - ], - vec!["Net".to_string(), "700.00".to_string(), "-".to_string()], - ], - vec![ - vec![ - "Average Income".to_string(), - "425.00".to_string(), - "-".to_string(), - ], - vec![ - "Average Expense".to_string(), - "250.00".to_string(), - "-".to_string(), - ], - ], + vec![vec![ + "Net".to_string(), + "1700.00".to_string(), + "1000.00".to_string(), + "62.96".to_string(), + "37.04".to_string(), + "425.00".to_string(), + "250.00".to_string(), + ]], vec![ vec![ "Largest Income".to_string(), @@ -263,25 +217,17 @@ fn check_summary_data_2() { "500.00".to_string(), "Super Special Bank".to_string(), ], - vec![ - "Months Checked".to_string(), - "4".to_string(), - "-".to_string(), - "-".to_string(), - ], ], vec![ vec![ "Peak Earning".to_string(), "05-2022".to_string(), "1000.00".to_string(), - "-".to_string(), ], vec![ "Peak Expense".to_string(), "01-2022".to_string(), "500.00".to_string(), - "-".to_string(), ], ], vec![ @@ -360,7 +306,7 @@ fn check_summary_data_3() { let my_summary = SummaryData::new(&conn); let my_summary_text = my_summary.get_table_data(&summary_modes, 0, 1); - let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 0, 1, &conn); + let my_summary_text_2 = my_summary.get_tx_data(&summary_modes, 0, 1, &None, &conn); let expected_data_1 = vec![ vec![ @@ -380,31 +326,15 @@ fn check_summary_data_3() { ]; let expected_data_2 = ( - vec![ - vec![ - "Total Income".to_string(), - "200.00".to_string(), - "66.67".to_string(), - ], - vec![ - "Total Expense".to_string(), - "100.00".to_string(), - "33.33".to_string(), - ], - vec!["Net".to_string(), "100.00".to_string(), "-".to_string()], - ], - vec![ - vec![ - "Average Income".to_string(), - "66.67".to_string(), - "-".to_string(), - ], - vec![ - "Average Expense".to_string(), - "33.33".to_string(), - "-".to_string(), - ], - ], + vec![vec![ + "Net".to_string(), + "200.00".to_string(), + "100.00".to_string(), + "66.67".to_string(), + "33.33".to_string(), + "66.67".to_string(), + "33.33".to_string(), + ]], vec![ vec![ "Largest Income".to_string(), @@ -418,25 +348,17 @@ fn check_summary_data_3() { "100.00".to_string(), "Super Special Bank".to_string(), ], - vec![ - "Months Checked".to_string(), - "3".to_string(), - "-".to_string(), - "-".to_string(), - ], ], vec![ vec![ "Peak Earning".to_string(), "07-2023".to_string(), "100.00".to_string(), - "-".to_string(), ], vec![ "Peak Expense".to_string(), "08-2022".to_string(), "100.00".to_string(), - "-".to_string(), ], ], vec![ From 70f123c4fbba1dec2b10519d448a10c91edf2580 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Sun, 17 Aug 2025 18:37:17 +0600 Subject: [PATCH 7/7] Clippy fix --- src/utility/utils.rs | 6 ++++-- tests/common.rs | 9 +++++---- tests/db_updates.rs | 8 ++++---- tests/stepper.rs | 8 ++++---- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/utility/utils.rs b/src/utility/utils.rs index 3977ccb..e7306c9 100644 --- a/src/utility/utils.rs +++ b/src/utility/utils.rs @@ -334,7 +334,8 @@ pub fn styled_block(title: &str) -> Block<'_> { )) } -#[must_use] pub fn styled_block_no_top(title: &str) -> Block<'_> { +#[must_use] +pub fn styled_block_no_top(title: &str) -> Block<'_> { Block::default() .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) .border_type(BorderType::Rounded) @@ -345,7 +346,8 @@ pub fn styled_block(title: &str) -> Block<'_> { )) } -#[must_use] pub fn styled_block_no_bottom(title: &str) -> Block<'_> { +#[must_use] +pub fn styled_block_no_bottom(title: &str) -> Block<'_> { Block::default() .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) .border_type(BorderType::Rounded) diff --git a/tests/common.rs b/tests/common.rs index d054af6..8b950c5 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,11 +2,12 @@ use rex_tui::db::create_db; use rusqlite::Connection; use std::fs; +#[must_use] pub fn create_test_db(file_name: &str) -> Connection { - if let Ok(metadata) = fs::metadata(file_name) { - if metadata.is_file() { - fs::remove_file(file_name).expect("Failed to delete existing file"); - } + if let Ok(metadata) = fs::metadata(file_name) + && metadata.is_file() + { + fs::remove_file(file_name).expect("Failed to delete existing file"); } let mut conn = Connection::open(file_name).unwrap(); diff --git a/tests/db_updates.rs b/tests/db_updates.rs index f7c80f5..256a53b 100644 --- a/tests/db_updates.rs +++ b/tests/db_updates.rs @@ -5,10 +5,10 @@ use rusqlite::Connection; use std::fs; fn check_test_db(file_name: &str) { - if let Ok(metadata) = fs::metadata(file_name) { - if metadata.is_file() { - fs::remove_file(file_name).expect("Failed to delete existing file"); - } + if let Ok(metadata) = fs::metadata(file_name) + && metadata.is_file() + { + fs::remove_file(file_name).expect("Failed to delete existing file"); } } diff --git a/tests/stepper.rs b/tests/stepper.rs index e0cdb47..d8544e1 100644 --- a/tests/stepper.rs +++ b/tests/stepper.rs @@ -16,10 +16,10 @@ impl FieldStepper for Testing {} impl DataVerifier for Testing {} fn create_test_db(file_name: &str) -> Connection { - if let Ok(metadata) = fs::metadata(file_name) { - if metadata.is_file() { - fs::remove_file(file_name).expect("Failed to delete existing file"); - } + if let Ok(metadata) = fs::metadata(file_name) + && metadata.is_file() + { + fs::remove_file(file_name).expect("Failed to delete existing file"); } let mut conn = Connection::open(file_name).unwrap();