use iced::{Element, widget::{column, row}}; use schist_models::{Account, Bucket, DateUtc, Transaction}; use schist_traits::{Dateable, Nowlike}; use crate::{gui::components::{Text, button, input::input}, style::{SPACING_SM, SPACING_V_SM}, traits::{Component, Viewable}}; #[derive(Clone, Debug)] pub struct NewTransactionForm { active_account: Option, buckets: Vec, this_year: String, this_month: String, today: String, day: String, month: String, year: String, bucket: String, payee: String, quantity: String, submit_text: Text, validation_err: Option, } #[derive(Clone, Debug)] pub enum Message { DayInputChanged(String), MonthInputChanged(String), YearInputChanged(String), BucketInputChanged(String), PayeeInputChanged(String), QuantityInputChanged(String), Submit, SetActiveAccount(Account), SetBuckets(Vec), } pub enum Action { None, AddTransaction(Transaction) } impl NewTransactionForm { pub fn new(active_account: Option, buckets: Vec) -> Self { let today = DateUtc::now(); Self { active_account, buckets, this_year: today.year().to_string(), this_month: today.month().to_string(), today: today.day().to_string(), day: String::new(), month: String::new(), year: String::new(), bucket: String::new(), payee: String::new(), quantity: String::new(), submit_text: Text::new("+ Add"), validation_err: None, } } fn reset(&mut self) { self.day = String::new(); self.month = String::new(); self.year = String::new(); self.bucket = String::new(); self.payee = String::new(); self.quantity = String::new(); self.validation_err = None; } fn create_transaction_or_else_validation_error(&self) -> Result { Ok(Transaction::new( self.get_valid_account_id_or_else_validation_error()?, self.get_valid_quantity_or_else_validation_error()?, self.get_valid_bucket_id_or_else_validation_error()?, &self.payee, self.get_valid_date_or_else_validation_error()?, "")) } fn get_valid_account_id_or_else_validation_error(&self) -> Result { match &self.active_account { Option::Some(account) => Ok(account.id), Option::None => Err(format!("No account selected: select an account to add a transaction.")), } } fn get_valid_bucket_id_or_else_validation_error(&self) -> Result, String> { if self.bucket.trim().len() == 0 { return Ok(None); }; match self.buckets.iter().find(|b| String::eq(&b.name, &self.bucket)) { Option::Some(bucket) => Ok(Some(bucket.id)), Option::None => Err(format!( "There's no bucket called '{}': check the buckets view to list existing buckets or to add a new one.", self.bucket)), } } fn get_valid_date_or_else_validation_error(&self) -> Result { let day = if self.day.trim().len() == 0 { Ok(DateUtc::now().day()) } else { self.day.trim().parse::() }; let month = self.get_valid_month_or_else_validation_error()?; let year = self.get_valid_year_or_else_validation_error()?; let first_day_of_month = DateUtc::from_ymd(year, month, 1) .map_err(|err| format!("Unexpected error: {}", err))?; let days_in_month = first_day_of_month.days_in_month(); match day { Err(_) => Err(format!("'{}' is not a valid day. Choose a number between 1 and {}.", self.day, days_in_month)), Ok(day) => if day > 0 && day <= days_in_month { DateUtc::from_ymd(year, month, day) .map_err(|err| format!("Unexpected error: {}", err)) } else { Err(format!("'{}' is not a valid day: there are only {} days in {}. Choose a number between 1 and {}.", self.day, days_in_month, first_day_of_month.format("%m %Y"), days_in_month)) } } } fn get_valid_month_or_else_validation_error(&self) -> Result { if self.month.trim().len() == 0 { return Ok(DateUtc::now().month()) }; match self.month.trim().parse::() { Err(_) => Err(format!("'{}' is not a valid month. Choose a number between 1 and 12.", self.month)), Ok(month) => if month > 0 && month < 13 { Ok(month) } else { Err(format!("'{}' is not a valid month. Choose a number between 1 and 12.", self.month)) } } } fn get_valid_year_or_else_validation_error(&self) -> Result { if self.year.trim().len() == 0 { return Ok(DateUtc::now().year()) }; self.year.trim().parse::().map_err(|_| format!("'{}' is not a valid year.", self.year)) } fn get_valid_quantity_or_else_validation_error(&self) -> Result { if self.quantity.trim().len() == 0 { return Err(String::from("No quantity provided: insert a quantity.")); }; let pounds = self.quantity.parse::() .map_err(|_| format!("'{}' is not a valid quantity. Use a number.", self.quantity))?; Ok((pounds * 100.0).floor() as i32) } } impl<'a> Viewable<'a, Message> for NewTransactionForm { fn view(&'a self) -> Element<'a, Message> { column![ Text::new(&self.validation_err.clone().unwrap_or_else(String::new)).danger().small(), row![ input(&self.today, &self.day).on_input(Message::DayInputChanged).width(32.0), input(&self.this_month, &self.month).on_input(Message::MonthInputChanged).width(32.0), input(&self.this_year, &self.year).on_input(Message::YearInputChanged).width(48.0), input("", &self.bucket).on_input(Message::BucketInputChanged).width(138.0), input("", &self.payee).on_input(Message::PayeeInputChanged).width(266.0), input("", &self.quantity).on_input(Message::QuantityInputChanged).width(104.0), button(&self.submit_text, Message::Submit, false).width(64.0), ].spacing(u32::from(SPACING_SM)), ].spacing(u32::from(SPACING_V_SM)) .into() } } impl<'a> Component<'a, Message, Action> for NewTransactionForm { fn update(&mut self, message: Message) -> Action { match message { Message::DayInputChanged(day) => { self.day = day; Action::None } Message::MonthInputChanged(month) => { self.month = month; Action::None } Message::YearInputChanged(year) => { self.year = year; Action::None } Message::BucketInputChanged(bucket) => { self.bucket = bucket; Action::None } Message::PayeeInputChanged(payee) => { self.payee = payee; Action::None } Message::QuantityInputChanged(quantity) => { self.quantity = quantity; Action::None } Message::SetActiveAccount(account) => { self.active_account = Some(account); Action::None } Message::SetBuckets(buckets) => { self.buckets = buckets; Action::None } Message::Submit => { match self.create_transaction_or_else_validation_error() { Ok(transaction) => { self.reset(); Action::AddTransaction(transaction) } Err(validation_err) => { self.validation_err = Some(validation_err); Action::None } } } } } }