Files
Finance-Control/app/ui/base_frame.py

112 lines
3.2 KiB
Python

from collections.abc import Callable
from tkinter import messagebox
import customtkinter as ctk
from app.config import (
DANGER_COLOR,
DANGER_HOVER_COLOR,
PRIMARY_COLOR,
PRIMARY_HOVER_COLOR,
SUCCESS_COLOR,
WARNING_COLOR,
)
from app.services import FinanceService
from app.utils.formatters import format_money
class BaseAppFrame(ctk.CTkFrame):
"""Base class for application screens with shared UI helpers."""
def __init__(self, master, refresh_callback: Callable[[], None]) -> None:
super().__init__(master)
self.refresh_callback = refresh_callback
self.service = FinanceService()
self._busy = False
def run_safe(
self,
action: Callable[[], None],
button: ctk.CTkButton | None = None,
) -> None:
"""Run UI action with protection from repeated rapid clicks."""
if self._busy:
return
self._busy = True
if button is not None:
button.configure(state="disabled")
try:
action()
except ValueError as error:
messagebox.showerror("Ошибка ввода", str(error))
except Exception as error: # noqa: BLE001 - user-facing desktop app guard
messagebox.showerror("Ошибка", f"Действие не выполнено: {error}")
finally:
if button is not None:
self.after(350, lambda: button.configure(state="normal"))
self.after(350, self._unlock)
else:
self._unlock()
def _unlock(self) -> None:
self._busy = False
@staticmethod
def clear(container: ctk.CTkBaseClass) -> None:
for widget in container.winfo_children():
widget.destroy()
@staticmethod
def money(value: float | int | None) -> str:
return format_money(value)
@staticmethod
def title_label(master, text: str) -> ctk.CTkLabel:
return ctk.CTkLabel(
master,
text=text,
font=ctk.CTkFont(size=28, weight="bold"),
)
@staticmethod
def section_label(master, text: str) -> ctk.CTkLabel:
return ctk.CTkLabel(
master,
text=text,
font=ctk.CTkFont(size=18, weight="bold"),
)
@staticmethod
def subtitle_label(master, text: str) -> ctk.CTkLabel:
return ctk.CTkLabel(master, text=text, text_color="gray70")
@staticmethod
def primary_button(master, text: str, command) -> ctk.CTkButton:
return ctk.CTkButton(
master,
text=text,
height=42,
fg_color=PRIMARY_COLOR,
hover_color=PRIMARY_HOVER_COLOR,
command=command,
)
@staticmethod
def danger_button(master, text: str, command, width: int = 92) -> ctk.CTkButton:
return ctk.CTkButton(
master,
text=text,
width=width,
fg_color=DANGER_COLOR,
hover_color=DANGER_HOVER_COLOR,
command=command,
)
@staticmethod
def status_color(value: float) -> str:
if value >= 1:
return DANGER_COLOR
if value >= 0.8:
return WARNING_COLOR
return SUCCESS_COLOR