summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--schist_desktop_gui/src/gui/components.rs2
-rw-r--r--schist_desktop_gui/src/gui/components/transactions_table.rs266
-rw-r--r--schist_desktop_gui/src/gui/components/transactions_table/view_transactions_table.rs (renamed from schist_desktop_gui/src/gui/components/transactions_view/view_transactions_view.rs)32
-rw-r--r--schist_desktop_gui/src/gui/components/transactions_view.rs271
4 files changed, 340 insertions, 231 deletions
diff --git a/schist_desktop_gui/src/gui/components.rs b/schist_desktop_gui/src/gui/components.rs
index 606c418..3bb09e3 100644
--- a/schist_desktop_gui/src/gui/components.rs
+++ b/schist_desktop_gui/src/gui/components.rs
@@ -6,6 +6,7 @@ pub mod button;
pub mod navigation;
pub mod panel;
pub mod text;
+pub mod transactions_table;
pub mod transactions_view;
pub use account_navigation_entry::AccountNavigationEntry;
@@ -16,4 +17,5 @@ pub use button::{button, panel_button};
pub use navigation::Navigation;
pub use panel::Panel;
pub use text::Text;
+pub use transactions_table::TransactionsTable;
pub use transactions_view::TransactionsView;
diff --git a/schist_desktop_gui/src/gui/components/transactions_table.rs b/schist_desktop_gui/src/gui/components/transactions_table.rs
new file mode 100644
index 0000000..d4ab398
--- /dev/null
+++ b/schist_desktop_gui/src/gui/components/transactions_table.rs
@@ -0,0 +1,266 @@
+mod view_transactions_table;
+
+use std::collections::{HashMap, VecDeque};
+use std::ops::Neg;
+
+use iced::font;
+use itertools::Itertools;
+use schist_models::{Account, AccountTransfer, Bucket, DateUtc, Transaction};
+
+use crate::{
+ traits::Component,
+};
+
+#[derive(Clone, Debug)]
+pub struct TransactionsTable {
+ accounts: Vec<Account>,
+ account_transfers: Vec<AccountTransfer>,
+ active_account: Option<Account>,
+ buckets: Vec<Bucket>,
+ transactions: Vec<Transaction>,
+ transaction_rows_by_account_id: HashMap<i32, Vec<TransactionRow>>,
+}
+
+#[derive(Clone, Debug)]
+pub enum Message {
+ SetAccounts(Vec<Account>),
+ SetAccountTransfers(Vec<AccountTransfer>),
+ SetActiveAccount(Account),
+ SetBuckets(Vec<Bucket>),
+ SetTransactions(Vec<Transaction>),
+}
+
+pub enum Action {
+ None,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+struct TransactionRowWithoutAggregations {
+ amount: i32,
+ bucket: String,
+ bucket_font_style: font::Style,
+ date: DateUtc,
+ payee: String,
+ payee_font_style: font::Style,
+}
+
+impl TransactionRowWithoutAggregations {
+ fn from_transaction(
+ account_id: i32,
+ transaction: &Transaction,
+ buckets: &[Bucket],
+ ) -> Option<Self> {
+ if transaction.account_id == account_id {
+ let (bucket, bucket_font_style) = match transaction.bucket_id {
+ Some(bucket_id) => match buckets.iter().find(|b| b.id == bucket_id) {
+ Some(bucket) => (bucket.name.clone(), font::Style::Normal),
+ Option::None => (format!("Bucket <{}>", bucket_id), font::Style::Italic),
+ },
+ Option::None => (String::from("None"), font::Style::Italic),
+ };
+ Some(Self {
+ amount: transaction.amount,
+ bucket,
+ bucket_font_style,
+ date: transaction.date,
+ payee: transaction.counterparty.clone(),
+ payee_font_style: font::Style::Normal,
+ })
+ } else {
+ None
+ }
+ }
+
+ fn from_account_transfer(
+ account_id: i32,
+ account_transfer: &AccountTransfer,
+ accounts: &[Account],
+ ) -> Option<Self> {
+ if account_transfer.from_account_id == account_id {
+ let (payee, payee_font_style) = if let Some(account) = accounts
+ .iter()
+ .find(|a| a.id == account_transfer.to_account_id)
+ {
+ (account.name.clone(), font::Style::Normal)
+ } else {
+ (format!("<{}>", account_id), font::Style::Italic)
+ };
+ Some(Self {
+ amount: account_transfer.amount.neg(),
+ bucket: String::from("Account transfer"),
+ bucket_font_style: font::Style::Italic,
+ date: account_transfer.date,
+ payee,
+ payee_font_style,
+ })
+ } else if account_transfer.to_account_id == account_id {
+ let (payee, payee_font_style) = if let Some(account) = accounts
+ .iter()
+ .find(|a| a.id == account_transfer.from_account_id)
+ {
+ (account.name.clone(), font::Style::Normal)
+ } else {
+ (format!("Account <{}>", account_id), font::Style::Italic)
+ };
+ Some(Self {
+ amount: account_transfer.amount,
+ bucket: String::from("Account transfer"),
+ bucket_font_style: font::Style::Italic,
+ date: account_transfer.date,
+ payee,
+ payee_font_style,
+ })
+ } else {
+ None
+ }
+ }
+
+ fn with_aggregations(self, balance: i32) -> TransactionRow {
+ TransactionRow {
+ amount: self.amount,
+ balance,
+ bucket: self.bucket,
+ bucket_font_style: self.bucket_font_style,
+ date: self.date,
+ payee: self.payee,
+ payee_font_style: self.payee_font_style,
+ }
+ }
+
+ fn compare_date_desc(&self, other: &Self) -> std::cmp::Ordering {
+ other.date.cmp(&self.date)
+ }
+}
+
+impl std::cmp::PartialOrd for TransactionRowWithoutAggregations {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.compare_date_desc(other))
+ }
+}
+
+impl std::cmp::Ord for TransactionRowWithoutAggregations {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.compare_date_desc(other)
+ }
+}
+
+#[derive(Clone, Debug, Hash)]
+struct TransactionRow {
+ amount: i32,
+ balance: i32,
+ bucket: String,
+ bucket_font_style: font::Style,
+ date: DateUtc,
+ payee: String,
+ payee_font_style: font::Style,
+}
+
+impl TransactionsTable {
+ pub fn new(
+ accounts: &[Account],
+ active_account: &Option<Account>,
+ account_transfers: &[AccountTransfer],
+ buckets: &[Bucket],
+ transactions: &[Transaction],
+ ) -> Self {
+ let transaction_rows_by_account_id =
+ calculate_rows(accounts, account_transfers, buckets, transactions);
+ Self {
+ accounts: accounts.to_vec(),
+ account_transfers: account_transfers.to_vec(),
+ active_account: active_account.clone(),
+ buckets: buckets.to_vec(),
+ transactions: transactions.to_vec(),
+ transaction_rows_by_account_id,
+ }
+ }
+}
+
+impl<'a> Component<'a, Message, Action> for TransactionsTable {
+ fn update(&mut self, message: Message) -> Action {
+ match message {
+ Message::SetAccounts(accounts) => {
+ self.accounts = accounts.clone();
+ self.transaction_rows_by_account_id = calculate_rows(
+ &self.accounts,
+ &self.account_transfers,
+ &self.buckets,
+ &self.transactions,
+ );
+ Action::None
+ }
+
+ Message::SetAccountTransfers(account_transfers) => {
+ self.account_transfers = account_transfers;
+ self.transaction_rows_by_account_id = calculate_rows(
+ &self.accounts,
+ &self.account_transfers,
+ &self.buckets,
+ &self.transactions,
+ );
+ Action::None
+ }
+
+ Message::SetActiveAccount(account) => {
+ self.active_account = Some(account);
+ Action::None
+ }
+
+ Message::SetBuckets(buckets) => {
+ self.buckets = buckets;
+ self.transaction_rows_by_account_id = calculate_rows(
+ &self.accounts,
+ &self.account_transfers,
+ &self.buckets,
+ &self.transactions,
+ );
+ Action::None
+ }
+
+ Message::SetTransactions(transactions) => {
+ self.transactions = transactions;
+ self.transaction_rows_by_account_id = calculate_rows(
+ &self.accounts,
+ &self.account_transfers,
+ &self.buckets,
+ &self.transactions,
+ );
+ Action::None
+ }
+ }
+ }
+}
+
+fn calculate_rows(
+ accounts: &[Account],
+ account_transfers: &[AccountTransfer],
+ buckets: &[Bucket],
+ transactions: &[Transaction],
+) -> HashMap<i32, Vec<TransactionRow>> {
+ HashMap::from_iter(accounts.iter().map(|account| {
+ let transaction_rows_without_aggs = transactions
+ .iter()
+ .filter_map(|t| {
+ TransactionRowWithoutAggregations::from_transaction(account.id, t, buckets)
+ })
+ .chain(account_transfers.iter().filter_map(|at| {
+ TransactionRowWithoutAggregations::from_account_transfer(account.id, at, accounts)
+ }))
+ .sorted();
+
+ let mut running_balance: i32 = 0;
+ let mut transaction_rows = VecDeque::with_capacity(transaction_rows_without_aggs.len());
+ for row in transaction_rows_without_aggs.rev() {
+ if running_balance.checked_add(row.amount).is_none() {
+ panic!(
+ "can't add {} + {}. row: {:?}",
+ running_balance, row.amount, row
+ );
+ }
+ running_balance += row.amount;
+ transaction_rows.push_front(row.with_aggregations(running_balance));
+ }
+
+ (account.id, transaction_rows.into())
+ }))
+}
diff --git a/schist_desktop_gui/src/gui/components/transactions_view/view_transactions_view.rs b/schist_desktop_gui/src/gui/components/transactions_table/view_transactions_table.rs
index 268984b..b6e8599 100644
--- a/schist_desktop_gui/src/gui/components/transactions_view/view_transactions_view.rs
+++ b/schist_desktop_gui/src/gui/components/transactions_table/view_transactions_table.rs
@@ -1,23 +1,19 @@
use iced::{
- alignment,
widget::{
- row,
scrollable::{self, Scrollbar},
table,
table::column,
- Container, Scrollable,
+ Scrollable,
},
- Element, Length,
+ Element,
};
-use crate::{gui::components::Text, style::SPACING_LG, traits::Viewable};
+use crate::{gui::components::Text, traits::Viewable};
-use super::{Message, TransactionRow, TransactionsView};
+use super::{Message, TransactionRow, TransactionsTable};
-impl<'a> Viewable<'a, Message> for TransactionsView {
+impl<'a> Viewable<'a, Message> for TransactionsTable {
fn view(&'a self) -> Element<'a, Message> {
- let navigation = self.navigation.view().map(Message::NavigationMessage);
-
let columns = [
column(
Text::default("Date").width(iced::Length::Fixed(128.0)),
@@ -46,12 +42,11 @@ impl<'a> Viewable<'a, Message> for TransactionsView {
];
let rows = self
- .navigation
- .active_option
- .clone()
- .map_or_else(Vec::default, |option| {
+ .active_account
+ .as_ref()
+ .map_or_else(Vec::default, |active_account| {
self.transaction_rows_by_account_id
- .get(&option.account.id)
+ .get(&active_account.id)
.cloned()
.unwrap_or_else(Vec::default)
});
@@ -61,14 +56,7 @@ impl<'a> Viewable<'a, Message> for TransactionsView {
vertical: Scrollbar::default(),
});
- row![
- Container::new(navigation).width(Length::FillPortion(1)),
- Container::new(table).width(Length::FillPortion(3)),
- ]
- .align_y(alignment::Vertical::Center)
- .height(Length::Fill)
- .spacing(u32::from(SPACING_LG))
- .into()
+ table.into()
}
}
diff --git a/schist_desktop_gui/src/gui/components/transactions_view.rs b/schist_desktop_gui/src/gui/components/transactions_view.rs
index a52c419..5627e71 100644
--- a/schist_desktop_gui/src/gui/components/transactions_view.rs
+++ b/schist_desktop_gui/src/gui/components/transactions_view.rs
@@ -1,24 +1,27 @@
-mod view_transactions_view;
-
-use std::collections::{HashMap, VecDeque};
+use iced::{
+ alignment,
+ widget::{row, Container},
+ Element, Length,
+};
-use iced::font;
-use itertools::Itertools;
-use schist_models::{Account, AccountTransfer, Bucket, DateUtc, Transaction};
+use schist_models::{Account, AccountTransfer, Bucket, Transaction};
use crate::{
- gui::components::{navigation, AccountNavigationEntry, Navigation},
- traits::Component,
+ gui::components::{
+ AccountNavigationEntry,
+ Navigation,
+ TransactionsTable,
+ navigation,
+ transactions_table
+ },
+ traits::{Component, Viewable},
+ style::SPACING_LG,
};
#[derive(Clone, Debug)]
pub struct TransactionsView {
- accounts: Vec<Account>,
- account_transfers: Vec<AccountTransfer>,
- buckets: Vec<Bucket>,
navigation: Navigation<AccountNavigationEntry>,
- transactions: Vec<Transaction>,
- transaction_rows_by_account_id: HashMap<i32, Vec<TransactionRow>>,
+ transactions_table: TransactionsTable,
}
#[derive(Clone, Debug)]
@@ -30,133 +33,13 @@ pub enum Message {
SetAccountTransfers(Vec<AccountTransfer>),
SetBuckets(Vec<Bucket>),
SetTransactions(Vec<Transaction>),
+ TransactionsTableMessage(transactions_table::Message),
}
pub enum Action {
None,
}
-#[derive(Debug, PartialEq, Eq)]
-struct TransactionRowWithoutAggregations {
- amount: i32,
- bucket: String,
- bucket_font_style: font::Style,
- date: DateUtc,
- payee: String,
- payee_font_style: font::Style,
-}
-
-impl TransactionRowWithoutAggregations {
- fn from_transaction(
- account_id: i32,
- transaction: &Transaction,
- buckets: &[Bucket],
- ) -> Option<Self> {
- if transaction.account_id == account_id {
- let (bucket, bucket_font_style) = match transaction.bucket_id {
- Some(bucket_id) => match buckets.iter().find(|b| b.id == bucket_id) {
- Some(bucket) => (bucket.name.clone(), font::Style::Normal),
- None => (format!("Bucket <{}>", bucket_id), font::Style::Italic),
- },
- None => (String::from("None"), font::Style::Italic),
- };
- Some(Self {
- amount: transaction.amount,
- bucket,
- bucket_font_style,
- date: transaction.date,
- payee: transaction.counterparty.clone(),
- payee_font_style: font::Style::Normal,
- })
- } else {
- None
- }
- }
-
- fn from_account_transfer(
- account_id: i32,
- account_transfer: &AccountTransfer,
- accounts: &[Account],
- ) -> Option<Self> {
- if account_transfer.from_account_id == account_id {
- let (payee, payee_font_style) = if let Some(account) = accounts
- .iter()
- .find(|a| a.id == account_transfer.to_account_id)
- {
- (account.name.clone(), font::Style::Normal)
- } else {
- (format!("<{}>", account_id), font::Style::Italic)
- };
- Some(Self {
- amount: -account_transfer.amount,
- bucket: String::from("Account transfer"),
- bucket_font_style: font::Style::Italic,
- date: account_transfer.date,
- payee,
- payee_font_style,
- })
- } else if account_transfer.to_account_id == account_id {
- let (payee, payee_font_style) = if let Some(account) = accounts
- .iter()
- .find(|a| a.id == account_transfer.from_account_id)
- {
- (account.name.clone(), font::Style::Normal)
- } else {
- (format!("Account <{}>", account_id), font::Style::Italic)
- };
- Some(Self {
- amount: account_transfer.amount,
- bucket: String::from("Account transfer"),
- bucket_font_style: font::Style::Italic,
- date: account_transfer.date,
- payee,
- payee_font_style,
- })
- } else {
- None
- }
- }
-
- fn with_aggregations(self, balance: i32) -> TransactionRow {
- TransactionRow {
- amount: self.amount,
- balance,
- bucket: self.bucket,
- bucket_font_style: self.bucket_font_style,
- date: self.date,
- payee: self.payee,
- payee_font_style: self.payee_font_style,
- }
- }
-
- fn compare_date_desc(&self, other: &Self) -> std::cmp::Ordering {
- other.date.cmp(&self.date)
- }
-}
-
-impl std::cmp::PartialOrd for TransactionRowWithoutAggregations {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.compare_date_desc(other))
- }
-}
-
-impl std::cmp::Ord for TransactionRowWithoutAggregations {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.compare_date_desc(other)
- }
-}
-
-#[derive(Clone, Debug, Hash)]
-struct TransactionRow {
- amount: i32,
- balance: i32,
- bucket: String,
- bucket_font_style: font::Style,
- date: DateUtc,
- payee: String,
- payee_font_style: font::Style,
-}
-
impl TransactionsView {
pub fn new(
accounts: &[Account],
@@ -169,19 +52,14 @@ impl TransactionsView {
.cloned()
.map(AccountNavigationEntry::new)
.collect();
+ let active_nav_entry = account_navigation_entries.first().cloned();
let navigation = Navigation::new(
- account_navigation_entries.first().cloned(),
- account_navigation_entries,
- );
- let transaction_rows_by_account_id =
- calculate_rows(accounts, account_transfers, buckets, transactions);
+ active_nav_entry.clone(), account_navigation_entries);
+ let transactions_table =
+ TransactionsTable::new(accounts, &active_nav_entry.map(|entry| entry.account), account_transfers, buckets, transactions);
Self {
- accounts: accounts.to_vec(),
- account_transfers: account_transfers.to_vec(),
- buckets: buckets.to_vec(),
navigation: navigation,
- transactions: transactions.to_vec(),
- transaction_rows_by_account_id,
+ transactions_table,
}
}
}
@@ -191,52 +69,36 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
match message {
Message::NavigationMessage(message) => self.update_navigation(message),
+ Message::TransactionsTableMessage(message) => self.update_transactions_table(message),
+
Message::SetAccounts(accounts) => {
- self.accounts = accounts.clone();
- self.transaction_rows_by_account_id = calculate_rows(
- &self.accounts,
- &self.account_transfers,
- &self.buckets,
- &self.transactions,
- );
+ let action_1 = self.update_transactions_table(
+ transactions_table::Message::SetAccounts(accounts.clone()));
+
let account_navigation_entries: Vec<AccountNavigationEntry> = accounts
.into_iter()
.map(AccountNavigationEntry::new)
.collect();
- self.update_navigation(navigation::Message::SetOptions(account_navigation_entries))
+ let action_2 = self.update_navigation(navigation::Message::SetOptions(account_navigation_entries));
+
+ match (action_1, action_2) {
+ (Action::None, Action::None) => Action::None
+ }
}
Message::SetAccountTransfers(account_transfers) => {
- self.account_transfers = account_transfers;
- self.transaction_rows_by_account_id = calculate_rows(
- &self.accounts,
- &self.account_transfers,
- &self.buckets,
- &self.transactions,
- );
- Action::None
+ self.update_transactions_table(
+ transactions_table::Message::SetAccountTransfers(account_transfers.clone()))
}
Message::SetBuckets(buckets) => {
- self.buckets = buckets;
- self.transaction_rows_by_account_id = calculate_rows(
- &self.accounts,
- &self.account_transfers,
- &self.buckets,
- &self.transactions,
- );
- Action::None
+ self.update_transactions_table(
+ transactions_table::Message::SetBuckets(buckets.clone()))
}
Message::SetTransactions(transactions) => {
- self.transactions = transactions;
- self.transaction_rows_by_account_id = calculate_rows(
- &self.accounts,
- &self.account_transfers,
- &self.buckets,
- &self.transactions,
- );
- Action::None
+ self.update_transactions_table(
+ transactions_table::Message::SetTransactions(transactions.clone()))
}
Message::SelectNextAccount => self.update_navigation(navigation::Message::SelectNext),
@@ -252,44 +114,35 @@ impl TransactionsView {
message: navigation::Message<AccountNavigationEntry>,
) -> Action {
match self.navigation.update(message) {
- navigation::Action::ActivateOption(_) | navigation::Action::SelectOption(_) => {
- Action::None
+ navigation::Action::ActivateOption(option) | navigation::Action::SelectOption(option) => {
+ self.update_transactions_table(transactions_table::Message::SetActiveAccount(option.account))
}
navigation::Action::None => Action::None,
}
}
-}
-
-fn calculate_rows(
- accounts: &[Account],
- account_transfers: &[AccountTransfer],
- buckets: &[Bucket],
- transactions: &[Transaction],
-) -> HashMap<i32, Vec<TransactionRow>> {
- HashMap::from_iter(accounts.iter().map(|account| {
- let transaction_rows_without_aggs = transactions
- .iter()
- .filter_map(|t| {
- TransactionRowWithoutAggregations::from_transaction(account.id, t, buckets)
- })
- .chain(account_transfers.iter().filter_map(|at| {
- TransactionRowWithoutAggregations::from_account_transfer(account.id, at, accounts)
- }))
- .sorted();
- let mut running_balance: i32 = 0;
- let mut transaction_rows = VecDeque::with_capacity(transaction_rows_without_aggs.len());
- for row in transaction_rows_without_aggs.rev() {
- if running_balance.checked_add(row.amount).is_none() {
- panic!(
- "can't add {} + {}. row: {:?}",
- running_balance, row.amount, row
- );
- }
- running_balance += row.amount;
- transaction_rows.push_front(row.with_aggregations(running_balance));
+ fn update_transactions_table(
+ &mut self,
+ message: transactions_table::Message,
+ ) -> Action {
+ match self.transactions_table.update(message) {
+ transactions_table::Action::None => Action::None,
}
+ }
+}
- (account.id, transaction_rows.into())
- }))
+impl<'a> Viewable<'a, Message> for TransactionsView {
+ fn view(&'a self) -> Element<'a, Message> {
+ let navigation = self.navigation.view().map(Message::NavigationMessage);
+ let table = self.transactions_table.view().map(Message::TransactionsTableMessage);
+
+ row![
+ Container::new(navigation).width(Length::FillPortion(1)),
+ Container::new(table).width(Length::FillPortion(3)),
+ ]
+ .align_y(alignment::Vertical::Center)
+ .height(Length::Fill)
+ .spacing(u32::from(SPACING_LG))
+ .into()
+ }
}