Исходный код gui

import datetime
import tkinter as tk
from tkinter import ttk, messagebox
from models import Transaction
from storage import save_transactions, load_transactions
from utils import validate_amount, validate_date, validate_category
from analysis import transactions_to_df, plot_pie_by_category, plot_income_expence_over_time


[документация] class FinancialPlannerApp: """Управляющий класс графического интерфейса «Финансовый Планер». Класс инкапсулирует логику визуализации данных через Tkinter, обработку пользовательского ввода, хранение текущего состояния транзакций и вызов функций аналитического анализа (Pandas/Matplotlib). Attributes: root (tk.Tk): Главное окно приложения. transactions (list[Transaction]): Список объектов транзакций, загруженных из хранилища. amount_var (tk.StringVar): Буфер для ввода суммы операции. category_var (tk.StringVar): Буфер для ввода категории. date_var (tk.StringVar): Буфер для ввода даты (формат YYYY-MM-DD). desc_var (tk.StringVar): Буфер для ввода описания. type_var (tk.StringVar): Переключатель типа операции ('expense'/'income'). tree (ttk.Treeview): Виджет таблицы для отображения истории транзакций. """ def __init__(self, root): """Инициализирует приложение, настраивает главное окно и загружает данные. При создании объекта происходит автоматическая загрузка истории из файла через :func:`load_transactions` и первичная отрисовка всех компонентов интерфейса. Args: root (tk.Tk): Корневой объект окна Tkinter, в котором будет развернуто приложение. """ self.root = root self.root.title('Финансовый Планер') self.root.geometry('800x600') self.root.minsize(700, 500) # Загружаем существующие операции self.transactions = load_transactions() # Создаём виджеты self.create_widgets() self.refresh_transaction_table()
[документация] def create_widgets(self): """Создает и размещает все элементы пользовательского интерфейса. Метод выполняет визуальную компоновку приложения, разделенную на три логических блока: 1. Форма ввода (LabelFrame): сбор данных о сумме, категории, дате и описании. 2. Таблица истории (Treeview): интерактивный список всех транзакций с прокруткой. 3. Панель аналитики (LabelFrame): кнопки вызова графических отчетов. В процессе выполнения инициализируются связанные переменные Tkinter (`amount_var`, `category_var` и др.), необходимые для реактивного взаимодействия с пользователем. Note: Использует менеджеры геометрии `pack` для основных контейнеров и `grid` для элементов внутри форм ввода и аналитики. """ # === Верхняя панель: форма ввода === input_frame = ttk.LabelFrame(self.root, text=' ➕ Новая операция ', padding=(10, 10)) input_frame.pack(fill='x', padx=10, pady=(10, 5)) # Сумма ttk.Label(input_frame, text='Сумма (RUB):').grid(row=0, column=0, sticky='w', padx=(0, 10)) self.amount_var = tk.StringVar() amount_entry = ttk.Entry(input_frame, textvariable=self.amount_var, width=15) amount_entry.grid(row=0, column=1, sticky='w') # Категория ttk.Label(input_frame, text='Категория:').grid(row=0, column=2, sticky='w', padx=(20, 10)) self.category_var = tk.StringVar() category_entry = ttk.Entry(input_frame, textvariable=self.category_var, width=20) category_entry.grid(row=0, column=3, sticky='w') # Дата _timestamp = datetime.datetime.now().strftime('%Y-%m-%d') ttk.Label(input_frame, text='Дата (ГГГГ-ММ-ДД):').grid(row=1, column=0, sticky='w', padx=(0, 10), pady=(10, 0)) self.date_var = tk.StringVar(value=_timestamp) date_entry = ttk.Entry(input_frame, textvariable=self.date_var, width=15) date_entry.grid(row=1, column=1, sticky='w', pady=(10, 0)) # Описание ttk.Label(input_frame, text='Описание:').grid(row=1, column=2, sticky='w', padx=(20, 10), pady=(10, 0)) self.desc_var = tk.StringVar() desc_entry = ttk.Entry(input_frame, textvariable=self.desc_var, width=30) desc_entry.grid(row=1, column=3, sticky='w', pady=(10, 0)) # Тип операции ttk.Label(input_frame, text='Тип:').grid(row=2, column=0, sticky='w', pady=(10, 0)) self.type_var = tk.StringVar(value='expense') expense_rb = ttk.Radiobutton(input_frame, text='Расход', variable=self.type_var, value='expense') income_rb = ttk.Radiobutton(input_frame, text='Доход', variable=self.type_var, value='income') expense_rb.grid(row=2, column=1, sticky='w', pady=(10, 0)) income_rb.grid(row=2, column=1, sticky='w', padx=(80, 0), pady=(10, 0)) # Кнопка 'Добавить' add_btn = ttk.Button(input_frame, text=' Добавить операцию', command=self.add_transaction) add_btn.grid(row=3, column=0, columnspan=4, pady=(15, 0)) # === Таблица операций === table_frame = ttk.LabelFrame(self.root, text=' 📜 История операций ', padding=(10, 10)) table_frame.pack(fill='both', expand=True, padx=10, pady=5) # Создаём Treeview (таблицу) columns = ('type', 'amount', 'category', 'date', 'description') self.tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=12) # Заголовки self.tree.heading('type', text='Тип') self.tree.heading('amount', text='Сумма (RUB)') self.tree.heading('category', text='Категория') self.tree.heading('date', text='Дата') self.tree.heading('description', text='Описание') # Ширина колонок self.tree.column('type', width=80, anchor='center') self.tree.column('amount', width=100, anchor='e') self.tree.column('category', width=150) self.tree.column('date', width=100, anchor='center') self.tree.column('description', width=250) # Полоса прокрутки scrollbar = ttk.Scrollbar(table_frame, orient='vertical', command=self.tree.yview) self.tree.configure(yscroll=scrollbar.set) # Размещение self.tree.pack(side='left', fill='both', expand=True) scrollbar.pack(side='right', fill='y') # === Нижняя панель: аналитика === analyze_frame = ttk.LabelFrame(self.root, text=' 📊 Аналитика', padding=(10, 10)) analyze_frame.pack(fill='x', padx=10, pady=(10, 5)) # Кнопка 'Расходы' expense_btn = ttk.Button(analyze_frame, text=' Расходы', command=self.expense_dia) expense_btn.grid(row=0, column=0, padx=10) # Кнопка 'Доходы' income_btn = ttk.Button(analyze_frame, text=' Доходы', command=self.income_dia) income_btn.grid(row=0, column=1, padx=10) # Кнопка 'Динамика' trends_btn = ttk.Button(analyze_frame, text=' Динамика', command=self.cashflow_trends) trends_btn.grid(row=0, column=2, padx=10)
[документация] def add_transaction(self): """Обрабатывает добавление новой транзакции через интерфейс. Метод выполняет последовательность действий: 1. Извлекает значения из строковых переменных Tkinter (`StringVar`). 2. Вызывает функции внешней валидации: :func:`validate_amount`, :func:`validate_category` и :func:`validate_date`. 3. При успешной проверке создает объект :class:`Transaction`. 4. Инициирует сохранение в CSV-файл и обновляет локальный список. 5. Перерисовывает таблицу в интерфейсе и очищает поля ввода. В случае любой ошибки валидации или записи процесс прерывается, и пользователю выводится модальное окно с описанием проблемы. Raises: Exception: Перехватывает все исключения (ValueError, IOError и др.), возникающие в процессе валидации или сохранения, и отображает их через `messagebox.showerror`. """ try: # 1. Получаем и валидируем данные amount = validate_amount(self.amount_var.get()) category = validate_category(self.category_var.get()) date = validate_date(self.date_var.get()) description = self.desc_var.get().strip() trans_type = self.type_var.get() # 2. Создаём объект transaction = Transaction( amount=amount, category=category, date=date, description=description, transaction_type=trans_type ) # 3. Сохраняем save_transactions([transaction]) self.transactions.append(transaction) # 4. Обновляем интерфейс self.refresh_transaction_table() self.clear_input_fields() messagebox.showinfo('Успех', 'Операция добавлена') except Exception as e: messagebox.showerror('Ошибка ввода', f'Не удалось добавить операцию:\n{e}')
[документация] def clear_input_fields(self): """Сбрасывает значения в текстовых полях формы ввода. Метод очищает переменные `amount_var`, `category_var` и `desc_var`, что приводит к визуальному удалению текста из соответствующих виджетов `ttk.Entry`. """ self.amount_var.set('') self.category_var.set('') self.desc_var.set('')
[документация] def refresh_transaction_table(self): """Синхронизирует виджет таблицы с актуальным списком транзакций. Метод полностью очищает текущие записи в графическом виджете `Treeview` и заново заполняет его данными из атрибута `self.transactions`. В процессе выполняется форматирование данных: преобразование типов в человекочитаемый вид (например, 'income' в 'Доход') и округление сумм. После обновления таблицы выполняется автоматическая прокрутка к последней (самой новой) записи. """ # Очищаем текущие строки for item in self.tree.get_children(): self.tree.delete(item) # Добавляем все операции for t in self.transactions: row_type = 'Доход' if t.transaction_type == 'income' else 'Расход' self.tree.insert('', 'end', values=( row_type, f'{t.amount:.2f}', t.category, t.date, t.description )) # Скролл вниз (к новой операции) self.tree.yview_moveto(1.0)
[документация] def expense_dia(self): """Обработчик события: генерирует и отображает круговую диаграмму расходов. Метод подготавливает данные, конвертируя текущий список транзакций в формат DataFrame, и инициирует построение графика распределения расходов по категориям. """ df = transactions_to_df(self.transactions) # Круговая диаграмма расходов plot_pie_by_category(df, 'expense')
[документация] def income_dia(self): """Обработчик события: генерирует и отображает круговую диаграмму доходов. Метод подготавливает данные, конвертируя текущий список транзакций в формат DataFrame, и инициирует построение графика распределения доходов по категориям. """ df = transactions_to_df(self.transactions) # Круговая диаграмма доходов plot_pie_by_category(df, 'income')
# === Точка входа для запуска GUI === if __name__ == '__main__': root = tk.Tk() app = FinancialPlannerApp(root) root.mainloop()