110 lines
4.2 KiB
Python
110 lines
4.2 KiB
Python
from tkinter import filedialog, messagebox
|
|
|
|
import customtkinter as ctk
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
from matplotlib.figure import Figure
|
|
|
|
from app.ui.base_frame import BaseAppFrame
|
|
|
|
|
|
class ReportsFrame(BaseAppFrame):
|
|
"""Screen with text reports and charts."""
|
|
|
|
def __init__(self, master, refresh_callback) -> None:
|
|
super().__init__(master, refresh_callback)
|
|
self.last_report_text = ""
|
|
self.grid_columnconfigure((0, 1), weight=1)
|
|
self.grid_rowconfigure(1, weight=1)
|
|
self._build_header()
|
|
self._build_left_panel()
|
|
self._build_right_panel()
|
|
|
|
def _build_header(self) -> None:
|
|
header = ctk.CTkFrame(self, fg_color="transparent")
|
|
header.grid(row=0, column=0, columnspan=2, sticky="ew", padx=8, pady=(8, 12))
|
|
header.grid_columnconfigure(0, weight=1)
|
|
|
|
self.title_label(header, "Аналитика и отчёты").grid(row=0, column=0, sticky="w")
|
|
self.subtitle_label(
|
|
header,
|
|
"Текстовая сводка, диаграмма расходов и экспорт отчёта.",
|
|
).grid(row=1, column=0, sticky="w", pady=(4, 0))
|
|
|
|
self.export_button = self.primary_button(
|
|
header,
|
|
"Сохранить отчёт в TXT",
|
|
lambda: self.run_safe(self.export_report, self.export_button),
|
|
)
|
|
self.export_button.grid(row=0, column=1, rowspan=2, padx=(12, 0))
|
|
|
|
def _build_left_panel(self) -> None:
|
|
left = ctk.CTkFrame(self, corner_radius=20)
|
|
left.grid(row=1, column=0, sticky="nsew", padx=(8, 12), pady=8)
|
|
left.grid_rowconfigure(1, weight=1)
|
|
left.grid_columnconfigure(0, weight=1)
|
|
|
|
self.section_label(left, "Отчёт за текущий месяц").grid(
|
|
row=0,
|
|
column=0,
|
|
sticky="w",
|
|
padx=18,
|
|
pady=(18, 8),
|
|
)
|
|
self.report_box = ctk.CTkTextbox(left)
|
|
self.report_box.grid(row=1, column=0, sticky="nsew", padx=18, pady=(0, 18))
|
|
|
|
def _build_right_panel(self) -> None:
|
|
right = ctk.CTkFrame(self, corner_radius=20)
|
|
right.grid(row=1, column=1, sticky="nsew", padx=(0, 8), pady=8)
|
|
right.grid_rowconfigure(1, weight=1)
|
|
right.grid_columnconfigure(0, weight=1)
|
|
|
|
self.section_label(right, "Структура расходов").grid(
|
|
row=0,
|
|
column=0,
|
|
sticky="w",
|
|
padx=18,
|
|
pady=(18, 8),
|
|
)
|
|
self.chart_container = ctk.CTkFrame(right, fg_color="transparent")
|
|
self.chart_container.grid(row=1, column=0, sticky="nsew", padx=18, pady=(0, 18))
|
|
|
|
self.figure = Figure(figsize=(5, 4), dpi=100)
|
|
self.canvas = FigureCanvasTkAgg(self.figure, master=self.chart_container)
|
|
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
|
|
|
def refresh_data(self) -> None:
|
|
self.last_report_text = self.service.build_report_text()
|
|
self.report_box.delete("1.0", "end")
|
|
self.report_box.insert("1.0", self.last_report_text)
|
|
self._draw_chart()
|
|
|
|
def _draw_chart(self) -> None:
|
|
data = self.service.report_data()
|
|
rows = data["categories"]
|
|
self.figure.clear()
|
|
axis = self.figure.add_subplot(111)
|
|
axis.set_title("Расходы по категориям")
|
|
if rows:
|
|
labels = [row["name"] for row in rows]
|
|
values = [row["total"] for row in rows]
|
|
axis.pie(values, labels=labels, autopct="%1.1f%%")
|
|
else:
|
|
axis.text(0.5, 0.5, "Нет данных для диаграммы", ha="center", va="center")
|
|
axis.axis("off")
|
|
self.canvas.draw()
|
|
|
|
def export_report(self) -> None:
|
|
if not self.last_report_text:
|
|
self.refresh_data()
|
|
file_path = filedialog.asksaveasfilename(
|
|
defaultextension=".txt",
|
|
filetypes=[("Text files", "*.txt")],
|
|
title="Сохранить отчёт",
|
|
)
|
|
if not file_path:
|
|
return
|
|
with open(file_path, "w", encoding="utf-8") as file:
|
|
file.write(self.last_report_text)
|
|
messagebox.showinfo("Готово", "Отчёт сохранён.")
|