diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/.gitignore b/submissions/week-2/day-5/jude_abara-expense_tracker/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/.gitignore @@ -0,0 +1 @@ +/target diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.lock b/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.lock new file mode 100644 index 0000000..6370801 --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.lock @@ -0,0 +1,284 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "expense_tracker" +version = "0.1.0" +dependencies = [ + "chrono", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.toml b/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.toml new file mode 100644 index 0000000..0f6cd38 --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "expense_tracker" +version = "0.1.0" +edition = "2024" + +[dependencies] +chrono = "0.4" diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/src/expense.rs b/submissions/week-2/day-5/jude_abara-expense_tracker/src/expense.rs new file mode 100644 index 0000000..e783a4c --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/src/expense.rs @@ -0,0 +1,154 @@ +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq)] +pub enum TransactionType { + Credit, + Debit, +} + +impl std::fmt::Display for TransactionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TransactionType::Credit => write!(f, "Credit"), + TransactionType::Debit => write!(f, "Debit"), + } + } +} + +#[derive(Debug, Clone)] +pub struct Expense { + pub id: u8, + pub description: String, + pub amount: f64, + pub tx_type: TransactionType, +} + +#[derive(Debug)] +pub struct ExpenseTracker { + values: HashMap, + next_id: u8, +} + +impl ExpenseTracker { + /// Creates a new empty ExpenseTracker + pub fn new() -> Self { + Self { + values: HashMap::new(), + next_id: 1, + } + } + + /// Adds a new expense and returns the created expense + pub fn add(&mut self, description: String, amount: f64, tx_type: TransactionType) -> Expense { + let current_id = self.next_id; + let new_expense = Expense { + id: current_id, + description, + amount, + tx_type, + }; + + self.values.insert(current_id, new_expense.clone()); + self.next_id += 1; + + new_expense + } + + /// Returns all expenses as a vector of references + pub fn view_all(&self) -> Vec<&Expense> { + let mut expenses: Vec<&Expense> = self.values.values().collect(); + expenses.sort_by_key(|e| e.id); + expenses + } + + /// Updates an existing expense by ID + pub fn update(&mut self, id: u8, expense: Expense) -> bool { + match self.values.get_mut(&id) { + Some(exp) => { + exp.description = expense.description; + exp.amount = expense.amount; + exp.tx_type = expense.tx_type; + true + } + None => false, + } + } + + /// Deletes an expense by ID and returns true if successful + pub fn delete(&mut self, id: u8) -> bool { + self.values.remove(&id).is_some() + } + + /// Checks if an expense with the given ID exists + pub fn exists(&self, id: u8) -> bool { + self.values.contains_key(&id) + } + + /// Gets a reference to an expense by ID + pub fn get(&self, id: u8) -> Option<&Expense> { + self.values.get(&id) + } + + // /// Returns the number of expenses + // pub fn count(&self) -> usize { + // self.values.len() + // } +} + +impl Default for ExpenseTracker { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_expense() { + let mut tracker = ExpenseTracker::new(); + let expense = tracker.add("Groceries".to_string(), 50.0, TransactionType::Debit); + + assert_eq!(expense.id, 1); + assert_eq!(expense.description, "Groceries"); + assert_eq!(expense.amount, 50.0); + } + + #[test] + fn test_view_all() { + let mut tracker = ExpenseTracker::new(); + tracker.add("Item 1".to_string(), 10.0, TransactionType::Credit); + tracker.add("Item 2".to_string(), 20.0, TransactionType::Debit); + + let expenses = tracker.view_all(); + assert_eq!(expenses.len(), 2); + } + + #[test] + fn test_update_expense() { + let mut tracker = ExpenseTracker::new(); + let expense = tracker.add("Original".to_string(), 100.0, TransactionType::Credit); + + let updated = Expense { + id: expense.id, + description: "Updated".to_string(), + amount: 150.0, + tx_type: TransactionType::Debit, + }; + + assert!(tracker.update(expense.id, updated)); + let result = tracker.get(expense.id).unwrap(); + assert_eq!(result.description, "Updated"); + assert_eq!(result.amount, 150.0); + } + + #[test] + fn test_delete_expense() { + let mut tracker = ExpenseTracker::new(); + let expense = tracker.add("To Delete".to_string(), 75.0, TransactionType::Debit); + + assert!(tracker.delete(expense.id)); + assert!(!tracker.exists(expense.id)); + } +} diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/src/file_handler.rs b/submissions/week-2/day-5/jude_abara-expense_tracker/src/file_handler.rs new file mode 100644 index 0000000..aded7fa --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/src/file_handler.rs @@ -0,0 +1,124 @@ +use crate::expense::{/* Expense, */ ExpenseTracker, TransactionType}; +use std::fs::OpenOptions; +use std::io::{self, Write}; + +pub struct FileHandler { + filename: String, +} + +impl FileHandler { + /// Creates a new FileHandler with the specified filename + pub fn new(filename: &str) -> Self { + Self { + filename: filename.to_string(), + } + } + + /// Saves all expenses to the file + pub fn save_expenses(&self, tracker: &ExpenseTracker) -> io::Result<()> { + let mut file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&self.filename)?; + + writeln!( + file, + "═══════════════════════════════════════════════════════════" + )?; + writeln!( + file, + " EXPENSE TRACKER REPORT " + )?; + writeln!( + file, + "═══════════════════════════════════════════════════════════" + )?; + writeln!(file)?; + + let expenses = tracker.view_all(); + + if expenses.is_empty() { + writeln!(file, "No transactions recorded.")?; + } else { + let mut total_credit = 0.0; + let mut total_debit = 0.0; + + for expense in &expenses { + writeln!( + file, + "───────────────────────────────────────────────────────────" + )?; + writeln!(file, "ID: {}", expense.id)?; + writeln!(file, "Description: {}", expense.description)?; + writeln!(file, "Amount: ${:.2}", expense.amount)?; + writeln!(file, "Type: {:?}", expense.tx_type)?; + + match expense.tx_type { + TransactionType::Credit => total_credit += expense.amount, + TransactionType::Debit => total_debit += expense.amount, + } + } + + writeln!( + file, + "───────────────────────────────────────────────────────────" + )?; + writeln!(file)?; + writeln!(file, "SUMMARY:")?; + writeln!(file, " Total Transactions: {}", expenses.len())?; + writeln!(file, " Total Credit: ${:.2}", total_credit)?; + writeln!(file, " Total Debit: ${:.2}", total_debit)?; + writeln!( + file, + " Net Balance: ${:.2}", + total_credit - total_debit + )?; + } + + writeln!(file)?; + writeln!( + file, + "═══════════════════════════════════════════════════════════" + )?; + writeln!( + file, + "Report generated: {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S") + )?; + + Ok(()) + } + + // /// Appends a single expense to the file (for logging individual operations) + // pub fn log_operation(&self, operation: &str, expense: &Expense) -> io::Result<()> { + // let mut file = OpenOptions::new() + // .create(true) + // .append(true) + // .open(format!("{}.log", self.filename))?; + + // writeln!( + // file, + // "[{}] {} - ID: {}, Description: {}, Amount: ${:.2}, Type: {:?}", + // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + // operation, + // expense.id, + // expense.description, + // expense.amount, + // expense.tx_type + // )?; + + // Ok(()) + // } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_file_handler_creation() { + let handler = FileHandler::new("test_expenses.txt"); + assert_eq!(handler.filename, "test_expenses.txt"); + } +} diff --git a/submissions/week-2/day-5/jude_abara-expense_tracker/src/main.rs b/submissions/week-2/day-5/jude_abara-expense_tracker/src/main.rs new file mode 100644 index 0000000..e285e67 --- /dev/null +++ b/submissions/week-2/day-5/jude_abara-expense_tracker/src/main.rs @@ -0,0 +1,280 @@ +mod expense; +mod file_handler; + +use expense::{Expense, ExpenseTracker, TransactionType}; +use file_handler::FileHandler; +use std::io::{self, Write}; + +fn main() { + let mut expense_tracker = ExpenseTracker::new(); + let file_handler = FileHandler::new("expenses.txt"); + + println!("╔════════════════════════════════════════╗"); + println!("║ Welcome to Expense Tracker v0.1.0 ║"); + println!("╚════════════════════════════════════════╝"); + + loop { + print_menu(); + + let command = read_input("Enter your choice: "); + let command = command.trim().to_lowercase(); + + match command.as_str() { + "1" => handle_add(&mut expense_tracker), + "2" => handle_view_all(&expense_tracker), + "3" => handle_update(&mut expense_tracker), + "4" => handle_delete(&mut expense_tracker), + "q" => { + if confirm_quit() { + // Save all transactions to file before exiting + if let Err(e) = file_handler.save_expenses(&expense_tracker) { + eprintln!("Error saving expenses: {}", e); + } else { + println!("All transactions saved successfully!"); + } + println!("Goodbye!"); + break; + } + } + _ => println!("Invalid command '{}'. Please try again.\n", command), + } + } +} + +fn print_menu() { + println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + println!("AVAILABLE OPERATIONS:"); + println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + println!(" 1 → Add Transaction"); + println!(" 2 → View All Transactions"); + println!(" 3 → Update Transaction by ID"); + println!(" 4 → Delete Transaction by ID"); + println!(" q → Quit Program"); + println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); +} + +fn handle_add(tracker: &mut ExpenseTracker) { + println!("\nADD NEW TRANSACTION"); + println!("─────────────────────────────────────────"); + + let description = read_input("Enter description: "); + let description = description.trim().to_string(); + + if description.is_empty() { + println!("Description cannot be empty!"); + return; + } + + let amount = read_amount(); + let tx_type = read_transaction_type(); + + let expense = tracker.add(description, amount, tx_type); + + println!("\nTransaction added successfully!"); + println!("─────────────────────────────────────────"); + print_expense(&expense); + println!("─────────────────────────────────────────"); +} + +fn handle_view_all(tracker: &ExpenseTracker) { + println!("\nALL TRANSACTIONS"); + println!("─────────────────────────────────────────"); + + let expenses = tracker.view_all(); + + if expenses.is_empty() { + println!("No transactions found."); + } else { + let (total_credit, total_debit) = calculate_totals(&expenses); + + for expense in expenses { + print_expense(expense); + println!("─────────────────────────────────────────"); + } + + println!("\nSUMMARY:"); + println!(" Total Credit: ${:.2}", total_credit); + println!(" Total Debit: ${:.2}", total_debit); + println!(" Balance: ${:.2}", total_credit - total_debit); + } +} + +fn handle_update(tracker: &mut ExpenseTracker) { + println!("\nUPDATE TRANSACTION"); + println!("─────────────────────────────────────────"); + + // First show all transactions + let expenses = tracker.view_all(); + if expenses.is_empty() { + println!("No transactions to update."); + return; + } + + println!("Current transactions:"); + for expense in expenses { + println!( + " ID {}: {} - ${:.2} ({})", + expense.id, + expense.description, + expense.amount, + format!("{:?}", expense.tx_type) + ); + } + + let id = read_u8("Enter transaction ID to update: "); + + // Check if ID exists + if !tracker.exists(id) { + println!("Transaction with ID {} not found!", id); + return; + } + + println!("\nEnter new details:"); + let description = read_input("Enter new description: "); + let description = description.trim().to_string(); + + if description.is_empty() { + println!("Description cannot be empty!"); + return; + } + + let amount = read_amount(); + let tx_type = read_transaction_type(); + + let updated_expense = Expense { + id, + description, + amount, + tx_type, + }; + + if tracker.update(id, updated_expense.clone()) { + println!("\nTransaction updated successfully!"); + println!("─────────────────────────────────────────"); + print_expense(&updated_expense); + println!("─────────────────────────────────────────"); + } else { + println!("Failed to update transaction with ID {}", id); + } +} + +fn handle_delete(tracker: &mut ExpenseTracker) { + println!("\nDELETE TRANSACTION"); + println!("─────────────────────────────────────────"); + + // First show all transactions + let expenses = tracker.view_all(); + if expenses.is_empty() { + println!("No transactions to delete."); + return; + } + + println!("Current transactions:"); + for expense in expenses { + println!( + " ID {}: {} - ${:.2} ({})", + expense.id, + expense.description, + expense.amount, + format!("{:?}", expense.tx_type) + ); + } + + let id = read_u8("Enter transaction ID to delete: "); + + // Get the expense before deleting to show it + if let Some(expense) = tracker.get(id) { + let expense_clone = expense.clone(); + + if tracker.delete(id) { + println!("\nTransaction deleted successfully!"); + println!("─────────────────────────────────────────"); + println!("Deleted transaction:"); + print_expense(&expense_clone); + println!("─────────────────────────────────────────"); + } else { + println!("Failed to delete transaction with ID {}", id); + } + } else { + println!("Transaction with ID {} not found!", id); + } +} + +fn read_input(prompt: &str) -> String { + print!("{}", prompt); + io::stdout().flush().unwrap(); + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + input +} + +fn read_amount() -> f64 { + loop { + let input = read_input("Enter amount: "); + match input.trim().parse::() { + Ok(value) if value > 0.0 => return value, + Ok(_) => println!("Amount must be greater than 0. Please try again."), + Err(_) => println!("Invalid amount. Please enter a valid number."), + } + } +} + +fn read_u8(prompt: &str) -> u8 { + loop { + let input = read_input(prompt); + match input.trim().parse::() { + Ok(value) => return value, + Err(_) => println!("Invalid ID. Please enter a valid number."), + } + } +} + +fn read_transaction_type() -> TransactionType { + loop { + let input = read_input("Enter transaction type (Credit/Debit): "); + match input.trim().to_lowercase().as_str() { + "credit" => return TransactionType::Credit, + "debit" => return TransactionType::Debit, + _ => println!("Invalid type. Please enter 'Credit' or 'Debit'."), + } + } +} + +fn confirm_quit() -> bool { + loop { + let input = read_input("\nAre you sure you want to quit? (Y/n): "); + match input.trim().to_lowercase().as_str() { + "y" | "yes" => return true, + "n" | "no" => return false, + _ => println!("Please enter 'Y' for Yes or 'n' for No."), + } + } +} + +fn print_expense(expense: &Expense) { + let tx_symbol = match expense.tx_type { + TransactionType::Credit => "Credit", + TransactionType::Debit => "Debit", + }; + + println!(" {} ID: {}", tx_symbol, expense.id); + println!(" Description: {}", expense.description); + println!(" Amount: ${:.2}", expense.amount); + println!(" Type: {:?}", expense.tx_type); +} + +fn calculate_totals(expenses: &[&Expense]) -> (f64, f64) { + let mut total_credit = 0.0; + let mut total_debit = 0.0; + + for expense in expenses { + match expense.tx_type { + TransactionType::Credit => total_credit += expense.amount, + TransactionType::Debit => total_debit += expense.amount, + } + } + + (total_credit, total_debit) +}