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

163 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from tkinter import messagebox
import customtkinter as ctk
from app.ui.base_frame import BaseAppFrame
from app.utils.validators import (
normalize_text,
parse_billing_day,
parse_positive_float,
)
class SubscriptionsFrame(BaseAppFrame):
"""Screen for recurring payments and subscriptions."""
def __init__(self, master, refresh_callback) -> None:
super().__init__(master, refresh_callback)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
self._build_form()
self._build_list()
def _build_form(self) -> None:
form_frame = ctk.CTkFrame(self, corner_radius=20)
form_frame.grid(row=0, column=0, sticky="ns", padx=(8, 12), pady=8)
self.title_label(form_frame, "Подписки").pack(
anchor="w", padx=18, pady=(18, 4)
)
self.subtitle_label(
form_frame,
"Контроль регулярных списаний и платных сервисов.",
).pack(anchor="w", padx=18, pady=(0, 18))
self.name_entry = ctk.CTkEntry(
form_frame,
width=320,
placeholder_text="Название сервиса",
)
self.name_entry.pack(padx=18, pady=7)
self.amount_entry = ctk.CTkEntry(
form_frame,
width=320,
placeholder_text="Стоимость",
)
self.amount_entry.pack(padx=18, pady=7)
self.day_entry = ctk.CTkEntry(
form_frame,
width=320,
placeholder_text="День списания: 131",
)
self.day_entry.pack(padx=18, pady=7)
self.period_menu = ctk.CTkOptionMenu(
form_frame,
width=320,
values=["Месяц", "Год"],
)
self.period_menu.set("Месяц")
self.period_menu.pack(padx=18, pady=7)
self.status_menu = ctk.CTkOptionMenu(
form_frame,
width=320,
values=["Активна", "Пауза"],
)
self.status_menu.set("Активна")
self.status_menu.pack(padx=18, pady=7)
self.add_button = self.primary_button(
form_frame,
"Добавить подписку",
lambda: self.run_safe(self.add_subscription, self.add_button),
)
self.add_button.pack(fill="x", padx=18, pady=(12, 18))
def _build_list(self) -> None:
self.list_frame = ctk.CTkScrollableFrame(self, label_text="Список подписок")
self.list_frame.grid(row=0, column=1, sticky="nsew", padx=(0, 8), pady=8)
def add_subscription(self) -> None:
name = normalize_text(self.name_entry.get(), "Название")
amount = parse_positive_float(self.amount_entry.get(), "Стоимость")
day = parse_billing_day(self.day_entry.get())
self.service.add_subscription(
name=name,
amount=amount,
billing_day=day,
period=self.period_menu.get(),
status=self.status_menu.get(),
)
self.name_entry.delete(0, "end")
self.amount_entry.delete(0, "end")
self.day_entry.delete(0, "end")
self.refresh_callback()
def delete_subscription(self, subscription_id: int) -> None:
if not messagebox.askyesno("Подтверждение", "Удалить выбранную подписку?"):
return
self.service.delete_subscription(subscription_id)
self.refresh_callback()
def toggle_status(self, subscription_id: int) -> None:
self.service.toggle_subscription_status(subscription_id)
self.refresh_callback()
def refresh_data(self) -> None:
self.clear(self.list_frame)
rows = self.service.list_subscriptions()
if not rows:
ctk.CTkLabel(
self.list_frame,
text="Подписок пока нет.",
text_color="gray70",
).pack(anchor="w", padx=10, pady=10)
return
for row in rows:
card = ctk.CTkFrame(self.list_frame, corner_radius=16)
card.pack(fill="x", padx=6, pady=6)
card.grid_columnconfigure(0, weight=1)
ctk.CTkLabel(
card,
text=row["name"],
font=ctk.CTkFont(size=18, weight="bold"),
).grid(row=0, column=0, sticky="w", padx=16, pady=(12, 4))
toggle_text = "Пауза" if row["status"] == "Активна" else "Активировать"
ctk.CTkButton(
card,
text=toggle_text,
width=110,
command=lambda sub_id=row["id"]: self.run_safe(
lambda: self.toggle_status(sub_id)
),
).grid(row=0, column=1, sticky="e", padx=(8, 4), pady=(12, 4))
self.danger_button(
card,
"Удалить",
lambda sub_id=row["id"]: self.run_safe(
lambda: self.delete_subscription(sub_id)
),
).grid(row=0, column=2, sticky="e", padx=(4, 16), pady=(12, 4))
monthly = row["amount"] / 12 if row["period"] == "Год" else row["amount"]
info = (
f"{self.money(row['amount'])} | {row['period']} | "
f"списание {row['billing_day']} числа | {row['status']} | "
f"{self.money(monthly)} в месяц"
)
ctk.CTkLabel(card, text=info, text_color="gray70").grid(
row=1,
column=0,
columnspan=3,
sticky="w",
padx=16,
pady=(0, 14),
)