summaryrefslogtreecommitdiff
path: root/schist_desktop_gui
diff options
context:
space:
mode:
Diffstat (limited to 'schist_desktop_gui')
-rw-r--r--schist_desktop_gui/Cargo.toml1
-rw-r--r--schist_desktop_gui/src/gui/components/input.rs3
-rw-r--r--schist_desktop_gui/src/gui/components/new_transaction_form.rs185
-rw-r--r--schist_desktop_gui/src/gui/components/transactions_view.rs23
-rw-r--r--schist_desktop_gui/src/style.rs1
5 files changed, 193 insertions, 20 deletions
diff --git a/schist_desktop_gui/Cargo.toml b/schist_desktop_gui/Cargo.toml
index e5937d8..8de5a85 100644
--- a/schist_desktop_gui/Cargo.toml
+++ b/schist_desktop_gui/Cargo.toml
@@ -17,6 +17,7 @@ schist_fakes = { workspace = true }
schist_models = { workspace = true }
schist_queries = { workspace = true }
schist_schema = { workspace = true }
+schist_traits = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
unwrap-infallible = { workspace = true }
diff --git a/schist_desktop_gui/src/gui/components/input.rs b/schist_desktop_gui/src/gui/components/input.rs
index ce4d228..8a4653a 100644
--- a/schist_desktop_gui/src/gui/components/input.rs
+++ b/schist_desktop_gui/src/gui/components/input.rs
@@ -4,9 +4,10 @@ use crate::style::{BORDER_WIDTH, BORDER_WIDTH_THICK, TEXT_SIZE_SM};
pub fn input<'a, Message: Clone>(
placeholder: &'a str,
+ content: &'a str,
) -> TextInput<'a, Message>
{
- iced::widget::text_input(placeholder, "")
+ iced::widget::text_input(placeholder, content)
.style(move |theme, status| match status {
iced::widget::text_input::Status::Active => hovered_input_style(theme),
iced::widget::text_input::Status::Disabled => disabled_input_style(theme),
diff --git a/schist_desktop_gui/src/gui/components/new_transaction_form.rs b/schist_desktop_gui/src/gui/components/new_transaction_form.rs
index 58509b1..7fb9b85 100644
--- a/schist_desktop_gui/src/gui/components/new_transaction_form.rs
+++ b/schist_desktop_gui/src/gui/components/new_transaction_form.rs
@@ -1,9 +1,16 @@
-use iced::{Element, widget::row};
+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, traits::{Component, Viewable}};
+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<Account>,
+ buckets: Vec<Bucket>,
+ this_year: String,
+ this_month: String,
+ today: String,
day: String,
month: String,
year: String,
@@ -11,6 +18,7 @@ pub struct NewTransactionForm {
payee: String,
quantity: String,
submit_text: Text,
+ validation_err: Option<String>,
}
#[derive(Clone, Debug)]
@@ -22,13 +30,21 @@ pub enum Message {
PayeeInputChanged(String),
QuantityInputChanged(String),
Submit,
+ SetActiveAccount(Account),
+ SetBuckets(Vec<Bucket>),
}
-pub enum Action { None }
+pub enum Action { None, AddTransaction(Transaction) }
impl NewTransactionForm {
- pub fn new() -> Self {
+ pub fn new(active_account: Option<Account>, buckets: Vec<Bucket>) -> 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(),
@@ -36,27 +52,168 @@ impl NewTransactionForm {
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<Transaction, String> {
+ 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<i32, String> {
+ 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<Option<i32>, 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<DateUtc, String> {
+ let day = if self.day.trim().len() == 0 {
+ Ok(DateUtc::now().day())
+ } else {
+ self.day.trim().parse::<u32>()
+ };
+ 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<u32, String> {
+ if self.month.trim().len() == 0 {
+ return Ok(DateUtc::now().month())
+ };
+ match self.month.trim().parse::<u32>() {
+ 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<i32, String> {
+ if self.year.trim().len() == 0 {
+ return Ok(DateUtc::now().year())
+ };
+ self.year.trim().parse::<i32>().map_err(|_| format!("'{}' is not a valid year.", self.year))
+ }
+
+ fn get_valid_quantity_or_else_validation_error(&self) -> Result<i32, String> {
+ if self.quantity.trim().len() == 0 {
+ return Err(String::from("No quantity provided: insert a quantity."));
+ };
+ let pounds = self.quantity.parse::<f32>()
+ .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> {
- row![
- input("25").on_input(Message::DayInputChanged).width(32.0),
- input("12").on_input(Message::MonthInputChanged).width(32.0),
- input("2026").on_input(Message::YearInputChanged).width(48.0),
- input("").on_input(Message::BucketInputChanged).width(138.0),
- input("").on_input(Message::PayeeInputChanged).width(266.0),
- input("").on_input(Message::QuantityInputChanged).width(104.0),
- button(&self.submit_text, Message::Submit, false).width(64.0),
- ].spacing(u32::from(SPACING_SM))
+ 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 {
- Action::None
+ 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
+ }
+ }
+ }
+ }
}
}
diff --git a/schist_desktop_gui/src/gui/components/transactions_view.rs b/schist_desktop_gui/src/gui/components/transactions_view.rs
index cc4e17c..f11b713 100644
--- a/schist_desktop_gui/src/gui/components/transactions_view.rs
+++ b/schist_desktop_gui/src/gui/components/transactions_view.rs
@@ -51,11 +51,12 @@ impl TransactionsView {
let active_nav_entry = account_navigation_entries.first().cloned();
let navigation = Navigation::new(
active_nav_entry.clone(), account_navigation_entries);
+ let active_account = active_nav_entry.map(|entry| entry.account);
let transactions_table =
- TransactionsTable::new(accounts, &active_nav_entry.map(|entry| entry.account), account_transfers, buckets, transactions);
+ TransactionsTable::new(accounts, &active_account, account_transfers, buckets, transactions);
Self {
navigation: navigation,
- new_transaction_form: NewTransactionForm::new(),
+ new_transaction_form: NewTransactionForm::new(active_account, buckets.to_vec()),
transactions_table,
}
}
@@ -91,8 +92,13 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
}
Message::SetBuckets(buckets) => {
- self.update_transactions_table(
- transactions_table::Message::SetBuckets(buckets.clone()))
+ let action_1 = self.update_transactions_table(
+ transactions_table::Message::SetBuckets(buckets.clone()));
+ let action_2 = self.update_new_transaction_form(
+ new_transaction_form::Message::SetBuckets(buckets.clone()));
+ match (action_1, action_2) {
+ (Action::None, Action::None) => Action::None,
+ }
}
Message::SetTransactions(transactions) => {
@@ -114,7 +120,13 @@ impl TransactionsView {
) -> Action {
match self.navigation.update(message) {
navigation::Action::ActivateOption(option) | navigation::Action::SelectOption(option) => {
- self.update_transactions_table(transactions_table::Message::SetActiveAccount(option.account))
+ let action_1 = self.update_transactions_table(
+ transactions_table::Message::SetActiveAccount(option.account.clone()));
+ let action_2 = self.update_new_transaction_form(
+ new_transaction_form::Message::SetActiveAccount(option.account));
+ match (action_1, action_2) {
+ (Action::None, Action::None) => Action::None,
+ }
}
navigation::Action::None => Action::None,
}
@@ -135,6 +147,7 @@ impl TransactionsView {
) -> Action {
match self.new_transaction_form.update(message) {
new_transaction_form::Action::None => Action::None,
+ new_transaction_form::Action::AddTransaction(transaction) => Action::None,
}
}
}
diff --git a/schist_desktop_gui/src/style.rs b/schist_desktop_gui/src/style.rs
index a94d540..3bceb0c 100644
--- a/schist_desktop_gui/src/style.rs
+++ b/schist_desktop_gui/src/style.rs
@@ -2,6 +2,7 @@ pub const BORDER_RADIUS: u16 = 2;
pub const BORDER_WIDTH: u16 = 1;
pub const BORDER_WIDTH_THICK: u16 = 2;
+pub const SPACING_V_SM: u16 = 2;
pub const SPACING_SM: u16 = 12;
pub const SPACING_MD: u16 = 24;
pub const SPACING_LG: u16 = 32;