class TransferPopup(tk.Frame): def __init__(self, master, acc, data_source: Callable, ontransfer: Callable): super(TransferPopup, self).__init__(master) self.data_source = data_source self.acc = acc self.chosen_account_id = None self.ontransfer = ontransfer self.acc_select = None self.transfer_info = None self.__create_widgets() def update(self): if self.acc_select: self.acc_select.update() if self.transfer_info: self.transfer_info.update() def __on_transfer(self, amount: Decimal): self.ontransfer(self.chosen_account_id, amount) def __on_select(self, ids): if self.transfer_info: self.transfer_info.destroy() self.transfer_info = None self.chosen_account_id = ids[0] if ids else None if self.chosen_account_id: self.transfer_info = TransferOverview(self, self.acc, self.__on_transfer) self.transfer_info.pack() def __create_widgets(self): wrapper = tk.Frame(self) self.acc_select = Multiselect(wrapper, self.data_source, singleselect=True, onselect=self.__on_select) self.acc_select.pack() wrapper.pack(side=tk.LEFT)
class CategoriesPopup(tk.Frame): def __init__(self, master, data_manager: DataManager, onupdate: Callable[[], []] = None, onadd: Callable[[], []] = None, ondelete: Callable[[], []] = None): super(CategoriesPopup, self).__init__(master) self.data_manager = data_manager self.onupdate = onupdate self.onadd = onadd self.ondelete = ondelete self.cat_select = None self.add_cat_popup = None self.cat_overview = None self.__create_widgets() def update(self): self.cat_select.update() if self.cat_overview: self.cat_overview.update() def __data_source(self): return {_id: cat.name for _id, cat in self.data_manager.categories.items()} def __reset_add_cat_popup(self, top): top.destroy() self.add_cat_popup = None def __open_add_cat_popup(self): if not self.add_cat_popup: top = tk.Toplevel(self) top.title("Add category") top.resizable(False, False) top.geometry("300x100") top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_add_cat_popup(top)) self.add_cat_popup = AddCategoryPopup(top, self.data_manager, self.onadd) self.add_cat_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5) def __remove_cat(self, cat_id): self.data_manager.remove_category(cat_id) if self.ondelete: self.ondelete() if self.cat_overview: self.cat_overview.destroy() self.cat_overview = None def __open_overview(self, ids): if self.cat_overview: self.cat_overview.destroy() self.cat_overview = None cat_id = ids[0] if ids else None if cat_id: self.cat_overview = CategoryOverview(self, self.data_manager.categories[cat_id], onupdate=self.onupdate, ondelete=lambda: self.__remove_cat(cat_id)) self.cat_overview.pack() def __create_widgets(self): wrapper = tk.Frame(self) self.cat_select = Multiselect(wrapper, self.__data_source, singleselect=True, onselect=self.__open_overview) self.cat_select.pack() btn = tk.Button(wrapper, text="Add category", command=self.__open_add_cat_popup) btn.pack(fill=tk.X) wrapper.pack(side=tk.LEFT)
class Dashboard(tk.Frame): def __init__(self, master, data_manager: DataManager, exchange_rates_store: ExchangeRatesStore): super(Dashboard, self).__init__(master) self.data_manager = data_manager self.exchange_rates_store = exchange_rates_store self.acc_list = None self.cat_list = None self.add_trans_popup = None self.trans_table = None self.accounts_popup = None self.categories_popup = None self.overview = None self.chosen_accounts = set() self.chosen_categories = set() self.__create_widgets() def __update_acc_list(self): self.acc_list.update() if self.add_trans_popup: self.add_trans_popup.update() if self.accounts_popup: self.accounts_popup.update() def __update_cat_list(self): self.cat_list.update() if self.add_trans_popup: self.add_trans_popup.update() if self.categories_popup: self.categories_popup.update() def __on_context_change(self): if self.trans_table: self.trans_table.update() if self.overview: self.overview.update() def __on_acc_select(self, ids: tuple[int]): self.chosen_accounts = set(ids) self.__on_context_change() def __on_cat_select(self, ids: tuple[int]): self.chosen_categories = set(ids) self.__on_context_change() def __reset_accounts_popup(self, top): top.destroy() self.accounts_popup = None def __on_acc_delete(self): self.__update_acc_list() self.__on_context_change() def __open_accounts_popup(self): if not self.accounts_popup: top = tk.Toplevel(self) top.title("Accounts") top.geometry("500x200") top.resizable(False, False) top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_accounts_popup(top)) self.accounts_popup = AccountsPopup(top, self.data_manager, self.exchange_rates_store, onadd=self.__update_acc_list, onupdate=self.__on_acc_delete, ondelete=self.__on_acc_delete) self.accounts_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5) def __reset_categories_popup(self, top): top.destroy() self.categories_popup = None def __on_cat_delete(self): self.__update_cat_list() self.__on_context_change() def __open_categories_popup(self): if not self.categories_popup: top = tk.Toplevel(self) top.title("Categories") top.geometry("500x200") top.resizable(False, False) top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_categories_popup(top)) self.categories_popup = CategoriesPopup( top, self.data_manager, onadd=self.__update_cat_list, onupdate=self.__on_cat_delete, ondelete=self.__on_cat_delete) self.categories_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5) def __reset_add_trans_popup(self, top): top.destroy() self.add_trans_popup = None def __on_trans_add_callback(self): self.__on_context_change() if self.accounts_popup: self.accounts_popup.update() def __open_trans_add_popup(self): if not self.add_trans_popup: top = tk.Toplevel(self) top.title("Add transaction") top.geometry("300x260") top.resizable(False, False) top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_add_trans_popup(top)) self.add_trans_popup = AddTransactionPopup( top, self.data_manager, self.exchange_rates_store, self.__on_trans_add_callback) self.add_trans_popup.pack() def __trans_data_source(self): min_d = datetime.fromisoformat(self.min_date_entry.get()) max_d = datetime.fromisoformat(self.max_date_entry.get()) return { t.id: t for acc in self.data_manager.accounts.values() for t in acc.transactions if acc.id in self.chosen_accounts and ( not t.category or t.category.id in self.chosen_categories) and min_d <= t.date <= max_d } def __create_lists(self, frame): def acc_data_source(): return { _id: acc.name for _id, acc in self.data_manager.accounts.items() } def cat_data_source(): return { _id: cat.name for _id, cat in self.data_manager.categories.items() } wrapper = tk.Frame(frame) acc_wrapper = tk.Frame(wrapper) label = tk.Label(acc_wrapper, text="Accounts", width=Account.NAME_MAX + 2, anchor="w", relief=tk.GROOVE) label.pack() self.acc_list = Multiselect(acc_wrapper, acc_data_source, self.__on_acc_select, width=Account.NAME_MAX) self.acc_list.pack() acc_wrapper.pack(side=tk.LEFT) cat_wrapper = tk.Frame(wrapper) label = tk.Label(cat_wrapper, text="Categories", width=Category.NAME_MAX + 2, anchor="w", relief=tk.GROOVE) label.pack() self.cat_list = Multiselect(cat_wrapper, cat_data_source, self.__on_cat_select, width=Category.NAME_MAX) self.cat_list.pack() cat_wrapper.pack(side=tk.LEFT) wrapper.pack() def __create_navbar(self, frame): pack_config = {"side": tk.LEFT, "padx": 2, "anchor": "n"} date_entry_config = { "width": 12, "selectmode": "day", "locale": "en_US", "date_pattern": "yyyy-mm-dd", "showweeknumbers": False } wrapper = tk.Frame(frame) lbl = tk.Label(wrapper, text="DATE MIN: ", relief=tk.GROOVE) lbl.pack(**pack_config) self.min_date_entry = DateEntry(wrapper, **date_entry_config, year=2020, month=1, day=1) self.min_date_entry.bind("<<DateEntrySelected>>", lambda *args: self.__on_context_change()) self.min_date_entry.pack(**pack_config) lbl = tk.Label(wrapper, text="DATE MAX: ", relief=tk.GROOVE) lbl.pack(**pack_config) self.max_date_entry = DateEntry(wrapper, **date_entry_config) self.max_date_entry.bind("<<DateEntrySelected>>", lambda *args: self.__on_context_change()) self.max_date_entry.pack(**pack_config) btn = tk.Button(wrapper, text="Accounts", command=self.__open_accounts_popup) btn.pack(**pack_config) btn = tk.Button(wrapper, text="Categories", command=self.__open_categories_popup) btn.pack(**pack_config) btn = tk.Button(wrapper, text="Add transaction", command=self.__open_trans_add_popup) btn.pack(**pack_config) wrapper.pack(fill=tk.Y) def __create_widgets(self): frame = tk.Frame() wrapper1 = tk.Frame(frame) self.__create_lists(wrapper1) wrapper1.pack(side=tk.LEFT, anchor="nw") wrapper2 = tk.Frame(frame) self.__create_navbar(wrapper2) self.overview = Overview(wrapper2, self.exchange_rates_store, self.__trans_data_source) self.overview.pack(side=tk.TOP) wrapper2.pack(side=tk.LEFT) frame.pack(fill=tk.X, padx=10, pady=10) wrapper3 = tk.Frame() labelframe = tk.LabelFrame(wrapper3, text="Transactions", font=10) self.trans_table = TransactionTable( labelframe, self.__trans_data_source, lambda *args: self.__on_context_change()) self.trans_table.pack(fill=tk.BOTH, expand=1) labelframe.pack(fill=tk.BOTH, expand="yes") wrapper3.pack(fill=tk.BOTH, expand="yes", padx=10, pady=10)
class AccountsPopup(tk.Frame): def __init__(self, master, data_manager: DataManager, exchange_rates_store, onupdate: Callable[[], []] = None, onadd: Callable[[], []] = None, ondelete: Callable[[], []] = None): super(AccountsPopup, self).__init__(master) self.data_manager = data_manager self.exchange_rates_store = exchange_rates_store self.onupdate = onupdate self.onadd = onadd self.ondelete = ondelete self.acc_select = None self.add_acc_popup = None self.acc_overview = None self.__create_widgets() def update(self): self.acc_select.update() if self.acc_overview: self.acc_overview.update() def __data_source(self): return { _id: acc.name for _id, acc in self.data_manager.accounts.items() } def __transfer_data_source(self, acc_id): return { _id: acc.name for _id, acc in self.data_manager.accounts.items() if _id != acc_id } def __reset_add_acc_popup(self, top): top.destroy() self.add_acc_popup = None def __open_add_acc_popup(self): if not self.add_acc_popup: top = tk.Toplevel(self) top.title("Add account") top.geometry("300x120") top.resizable(False, False) top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_add_acc_popup(top)) self.add_acc_popup = AddAccountPopup(top, self.data_manager, self.onadd) self.add_acc_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5) def __remove_acc(self, acc_id): self.data_manager.remove_account(acc_id) if self.ondelete: self.ondelete() if self.acc_overview: self.acc_overview.destroy() self.acc_overview = None def __on_transfer(self, _from, to_id, amount): _to = self.data_manager.accounts[to_id] rate = self.exchange_rates_store.get(_from.currency, _to.currency) _from.transfer(_to, amount, rate) if self.onupdate: self.onupdate() def __open_overview(self, ids): if self.acc_overview: self.acc_overview.destroy() self.acc_overview = None acc_id = ids[0] if ids else None if acc_id: acc = self.data_manager.accounts[acc_id] self.acc_overview = AccountOverview( self, acc, onupdate=self.onupdate, ondelete=lambda: self.__remove_acc(acc_id), transfer_data_source=self.__transfer_data_source, ontransfer=self.__on_transfer) self.acc_overview.pack() def __create_widgets(self): wrapper = tk.Frame(self) self.acc_select = Multiselect(wrapper, self.__data_source, singleselect=True, onselect=self.__open_overview) self.acc_select.pack() btn = tk.Button(wrapper, text="Add account", command=self.__open_add_acc_popup) btn.pack(fill=tk.X) wrapper.pack(side=tk.LEFT)