import customtkinter as ctk from app.config import APP_SUBTITLE, APP_TITLE, MIN_HEIGHT, MIN_WIDTH, WINDOW_SIZE from app.services import FinanceService from app.ui.accounts_frame import AccountsFrame from app.ui.budgets_frame import BudgetsFrame from app.ui.categories_frame import CategoriesFrame from app.ui.dashboard_frame import DashboardFrame from app.ui.reports_frame import ReportsFrame from app.ui.subscriptions_frame import SubscriptionsFrame from app.ui.transactions_frame import TransactionsFrame class FinanceApp(ctk.CTk): """Main application window and screen coordinator.""" def __init__(self) -> None: super().__init__() self.service = FinanceService() self.frames: dict[str, ctk.CTkFrame] = {} self.nav_buttons: dict[str, ctk.CTkButton] = {} saved_theme = self.service.get_setting("theme", "Тёмная") ctk.set_appearance_mode(self._theme_to_mode(saved_theme)) ctk.set_default_color_theme("blue") self.title(APP_TITLE) self.geometry(WINDOW_SIZE) self.minsize(MIN_WIDTH, MIN_HEIGHT) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) self.sidebar = ctk.CTkFrame(self, width=260, corner_radius=0) self.sidebar.grid(row=0, column=0, sticky="nsew") self.sidebar.grid_rowconfigure(12, weight=1) self.content = ctk.CTkFrame(self, fg_color="transparent") self.content.grid(row=0, column=1, sticky="nsew", padx=18, pady=18) self.content.grid_rowconfigure(0, weight=1) self.content.grid_columnconfigure(0, weight=1) self._build_sidebar(saved_theme) self._build_frames() self.show_frame("Главная") self.refresh_all() def _build_sidebar(self, saved_theme: str) -> None: ctk.CTkLabel( self.sidebar, text="Finance Control", font=ctk.CTkFont(size=26, weight="bold"), ).grid(row=0, column=0, padx=20, pady=(24, 4), sticky="w") ctk.CTkLabel( self.sidebar, text=APP_SUBTITLE, text_color="gray70", wraplength=210, ).grid(row=1, column=0, padx=20, pady=(0, 18), sticky="w") pages = [ "Главная", "Счета", "Категории", "Операции", "Подписки", "Бюджеты", "Отчёты", ] for index, page_name in enumerate(pages, start=2): button = ctk.CTkButton( self.sidebar, text=page_name, height=44, anchor="w", command=lambda name=page_name: self.show_frame(name), ) button.grid(row=index, column=0, padx=20, pady=5, sticky="ew") self.nav_buttons[page_name] = button ctk.CTkLabel( self.sidebar, text="Тема интерфейса", text_color="gray70", ).grid(row=13, column=0, padx=20, pady=(12, 6), sticky="w") self.theme_menu = ctk.CTkOptionMenu( self.sidebar, values=["Тёмная", "Светлая", "Системная"], command=self.change_theme, ) self.theme_menu.set(saved_theme) self.theme_menu.grid(row=14, column=0, padx=20, pady=(0, 20), sticky="ew") def _build_frames(self) -> None: self.frames = { "Главная": DashboardFrame(self.content, self.refresh_all), "Счета": AccountsFrame(self.content, self.refresh_all), "Категории": CategoriesFrame(self.content, self.refresh_all), "Операции": TransactionsFrame(self.content, self.refresh_all), "Подписки": SubscriptionsFrame(self.content, self.refresh_all), "Бюджеты": BudgetsFrame(self.content, self.refresh_all), "Отчёты": ReportsFrame(self.content, self.refresh_all), } for frame in self.frames.values(): frame.grid(row=0, column=0, sticky="nsew") def show_frame(self, name: str) -> None: frame = self.frames[name] if hasattr(frame, "refresh_data"): frame.refresh_data() frame.tkraise() self._highlight_nav_button(name) def refresh_all(self) -> None: for frame in self.frames.values(): if hasattr(frame, "refresh_data"): frame.refresh_data() def _highlight_nav_button(self, active_name: str) -> None: for name, button in self.nav_buttons.items(): if name == active_name: button.configure(fg_color=("#2563eb", "#2563eb")) else: button.configure(fg_color=("#3b8ed0", "#1f6aa5")) def change_theme(self, theme_name: str) -> None: ctk.set_appearance_mode(self._theme_to_mode(theme_name)) self.service.set_setting("theme", theme_name) @staticmethod def _theme_to_mode(theme_name: str) -> str: theme_map = { "Тёмная": "dark", "Светлая": "light", "Системная": "system", } return theme_map.get(theme_name, "dark")