diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..73cccce
Binary files /dev/null and b/.DS_Store differ
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/Treker.iml b/.idea/Treker.iml
new file mode 100644
index 0000000..d8b3f6c
--- /dev/null
+++ b/.idea/Treker.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..1d3ce46
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..dc2610e
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core.py b/core.py
new file mode 100644
index 0000000..b75bf50
--- /dev/null
+++ b/core.py
@@ -0,0 +1,103 @@
+from datetime import date, datetime
+
+
+class HarmCalculator:
+
+ harms = {
+ "Никотин": {"сигарета": 11, "стик": 5, "тяжка": 3},
+ "Алкоголь": {"бутылка (500мл)": 15, "банка (500мл)": 12, "бокал (150мл)": 8, "рюмка (50мл)": 28, "глоток (30мл)": 4, "порция (250мл)": 18},
+ "Кофе": {"чашка (200мл)": 1, "стакан (350мл)": 1.5, "глоток (30мл)": 0.2, "порция (200мл)": 1},
+ "Энергетики": {"банка (250мл)": 2, "банка (330мл)": 3, "банка (500мл)": 5},
+ }
+
+ icons = {"Никотин": "🚬", "Алкоголь": "🍷", "Кофе": "☕", "Энергетики": "⚡"}
+ colors = {"Никотин": "#8fb0d9", "Алкоголь": "#f0a2bc", "Кофе": "#c8a07a", "Энергетики": "#7ea8ff"}
+ gender_mult = {"Мужской": 1.0, "Женский": 1.2, "М": 1.0, "Ж": 1.2}
+
+ def __init__(self, gender="Мужской", age=20):
+ self.gender = gender
+ self.age = age
+
+ def set_profile(self, gender, age):
+ self.gender = gender
+ self.age = age
+
+ def age_mult(self, age):
+ if age < 18: return 0.9
+ if age < 25: return 0.95
+ if age < 40: return 1.0
+ if age < 60: return 1.2
+ return 1.5
+
+ def calc_one(self, kind, unit, qty):
+ base = self.harms[kind][unit]
+ g = self.gender_mult.get(self.gender, 1.0)
+ a = self.age_mult(self.age)
+ return base * qty * g * a
+
+ def calc_total(self, hist):
+ total = 0
+ for e in hist:
+ total += self.calc_one(e["kind"], e["unit"], e["qty"])
+ return total
+
+ def fmt_time(self, mins):
+ if mins < 60: return f"{int(mins)} мин"
+ if mins < 1440: return f"{mins/60:.1f} ч"
+ if mins < 43200: return f"{mins/1440:.2f} дн"
+ return f"{mins/43200:.2f} мес"
+
+ def fmt_timer(self, mins):
+ h = int(mins // 60)
+ m = int(mins % 60)
+ s = int((mins * 60) % 60)
+ return f"{h:02d}:{m:02d}:{s:02d}"
+
+ def get_units(self, kind):
+ return list(self.harms.get(kind, {}).keys())
+
+ def get_config(self, kind):
+ return {
+ "icon": self.icons.get(kind, "📌"),
+ "color": self.colors.get(kind, "#cccccc"),
+ "units": self.get_units(kind),
+ }
+
+ def period_harm(self, hist, period="day"):
+ today = date.today()
+ filt = []
+ for e in hist:
+ d = datetime.fromisoformat(e["date"]).date()
+ if period == "day" and d == today:
+ filt.append(e)
+ elif period == "month" and d.year == today.year and d.month == today.month:
+ filt.append(e)
+ elif period == "year" and d.year == today.year:
+ filt.append(e)
+ elif period == "all":
+ filt.append(e)
+ return self.calc_total(filt), len(filt)
+
+ def days_with_harms(self, hist, year, month):
+ days = {}
+ for e in hist:
+ d = datetime.fromisoformat(e["date"]).date()
+ if d.year == year and d.month == month:
+ h = self.calc_one(e["kind"], e["unit"], e["qty"])
+ days[d.day] = days.get(d.day, 0) + h
+ return days
+
+ def summary(self, hist):
+ day = self.period_harm(hist, "day")
+ month = self.period_harm(hist, "month")
+ year = self.period_harm(hist, "year")
+ allt = self.period_harm(hist, "all")
+ return {
+ "day": {"mins": day[0], "fmt": self.fmt_time(day[0]), "cnt": day[1]},
+ "month": {"mins": month[0], "fmt": self.fmt_time(month[0]), "cnt": month[1]},
+ "year": {"mins": year[0], "fmt": self.fmt_time(year[0]), "cnt": year[1]},
+ "all": {"mins": allt[0], "fmt": self.fmt_time(allt[0]), "cnt": allt[1]},
+ }
+
+ def to_days(self, mins):
+ return mins / 1440
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..3a3f888
--- /dev/null
+++ b/main.py
@@ -0,0 +1,449 @@
+import calendar
+from datetime import date
+import tkinter as tk
+from tkinter import ttk
+
+from core import HarmCalculator
+
+bg = "#eef6ff"
+surf = "#ffffff"
+surf_alt = "#f6fbff"
+head = "#dcebff"
+acc = "#4f8df7"
+acc_dark = "#3e73cc"
+acc_soft = "#d6e9ff"
+txt = "#15314a"
+muted = "#6b86a1"
+bord = "#c8dcf4"
+harm_col = "#ff6b6b"
+
+def_habits = [
+ {"kind": "Алкоголь", "title": "Вечер", "qty": 2, "unit": "бутылка (500мл)", "icon": "🍷", "accent": "#f0a2bc"},
+ {"kind": "Никотин", "title": "Перерыв", "qty": 4, "unit": "сигарета", "icon": "🚬", "accent": "#8fb0d9"},
+ {"kind": "Кофе", "title": "Утренний кофе", "qty": 1, "unit": "чашка (200мл)", "icon": "☕", "accent": "#c8a07a"},
+]
+
+
+class MainWindow(tk.Tk):
+ def __init__(self):
+ super().__init__()
+ self.title("Трекер вредных привычек")
+ self.geometry("430x780")
+ self.minsize(370, 700)
+ self.configure(bg=bg)
+
+ self.calc = HarmCalculator(gender="Мужской", age=30)
+ self.p_name = "Пользователь"
+ self.p_gender = "Мужской"
+ self.p_age = 30
+
+ self.sett_win = None
+ self.cal_win = None
+ self.hab_win = None
+ self.edit_idx = None
+ self.cur_date = date.today().replace(day=1)
+ self.habits = list(def_habits)
+
+ self._styles()
+ self._ui()
+ self._upd_timer()
+
+ def _styles(self):
+ self.style = ttk.Style(self)
+ self.style.theme_use("clam")
+ self.style.configure("Light.Vertical.TScrollbar", troughcolor=bg, background="#9ec5fb", bordercolor=bg, arrowcolor=acc, relief="flat", width=7)
+ self.style.configure("Light.TCombobox", fieldbackground=surf, foreground=txt, arrowcolor=acc, bordercolor=bord, padding=6)
+
+ def _ui(self):
+ root = tk.Frame(self, bg=bg)
+ root.pack(fill="both", expand=True)
+ self._top_bar(root)
+ self._center(root)
+ self._list(root)
+
+ def _circle(self, parent, text, cmd, size=36, fill=acc, outline=acc):
+ c = tk.Canvas(parent, width=size, height=size, bg=parent.cget("bg"), highlightthickness=0, cursor="hand2")
+ def draw(_=None):
+ c.delete("all")
+ c.create_oval(1, 1, size-1, size-1, fill=fill, outline=outline)
+ c.create_text(size/2, size/2, text=text, fill="white", font=("Helvetica", 14, "bold"))
+ c.bind("", draw)
+ draw()
+ c.bind("", lambda e: cmd())
+ return c
+
+ def _top_bar(self, parent):
+ bar = tk.Frame(parent, bg=head, padx=18, pady=16)
+ bar.pack(fill="x")
+ self.p_label = tk.Label(bar, text=self.p_name, bg=head, fg=txt, font=("Helvetica", 16, "bold"))
+ self.p_label.pack(side="left")
+ btns = tk.Frame(bar, bg=head)
+ btns.pack(side="right")
+ self._circle(btns, "🗓", self._cal, 36).pack(side="left", padx=(0, 10))
+ self._circle(btns, "⚙", self._sett, 36).pack(side="left")
+
+ def _center(self, parent):
+ c = tk.Frame(parent, bg=bg, pady=14)
+ c.pack(fill="x")
+ w = tk.Frame(c, bg=bg)
+ w.pack(pady=8)
+ self.circ = tk.Canvas(w, width=200, height=200, bg=bg, highlightthickness=0)
+ self.circ.pack()
+ self._draw_circ(self.circ, 100, 100, 72)
+ self.circ.bind("", lambda e: self._open_form())
+ self.timer = tk.Label(c, text="00:00:00", bg=bg, fg=txt, font=("Helvetica", 28, "bold"))
+ self.timer.pack(pady=2)
+ tk.Label(c, text="потеряно за сегодня", bg=bg, fg=muted, font=("Helvetica", 10)).pack(pady=6)
+
+ def _list(self, parent):
+ s = tk.Frame(parent, bg=bg, padx=18, pady=10)
+ s.pack(fill="both", expand=True)
+ tk.Label(s, text="Сегодня Вы употребляли", bg=bg, fg=txt, font=("Helvetica", 14, "bold"), anchor="w").pack(fill="x", pady=(0, 12))
+ w = tk.Frame(s, bg=bg)
+ w.pack(fill="both", expand=True)
+ self.canv = tk.Canvas(w, bg=bg, highlightthickness=0)
+ scr = ttk.Scrollbar(w, orient="vertical", command=self.canv.yview, style="Light.Vertical.TScrollbar")
+ self.canv.configure(yscrollcommand=scr.set)
+ scr.pack(side="right", fill="y")
+ self.canv.pack(side="left", fill="both", expand=True)
+ self.inner = tk.Frame(self.canv, bg=bg)
+ self.canv.create_window((0, 0), window=self.inner, anchor="nw")
+ self.inner.bind("", lambda e: self.canv.configure(scrollregion=self.canv.bbox("all")))
+ self.canv.bind("", lambda e: self.canv.itemconfig(self.canv.find_withtag("win")[0], width=e.width) if self.canv.find_withtag("win") else None)
+ self.canv.create_window((0, 0), window=self.inner, anchor="nw", tags="win")
+ self._render()
+
+ def _render(self):
+ for w in self.inner.winfo_children():
+ w.destroy()
+ for i, h in enumerate(self.habits):
+ self._card(self.inner, h, i)
+
+ def _card(self, parent, habit, idx):
+ card = tk.Canvas(parent, height=78, bg=bg, highlightthickness=0)
+ card.pack(fill="x", pady=7)
+ card.bind("", lambda e, c=card: self._draw_card(c, e.width, habit))
+ self._draw_card(card, 380, habit)
+ card.bind("", lambda e, i=idx: self._open_form(i))
+
+ def _draw_card(self, c, w, h):
+ c.delete("all")
+ self._round(c, 0, 0, w, 78, 20, fill=surf, outline=bord)
+ c.create_oval(18, 15, 66, 63, fill=h["accent"], outline="")
+ c.create_text(42, 39, text=h["icon"], fill="white", font=("Helvetica", 17, "bold"))
+ c.create_text(78, 28, anchor="w", text=h["title"], fill=txt, font=("Helvetica", 12, "bold"))
+ c.create_text(78, 50, anchor="w", text=h["kind"], fill=muted, font=("Helvetica", 10))
+ c.create_text(w-18, 39, anchor="e", text=f'{h["qty"]} {h["unit"]}', fill=acc_dark, font=("Helvetica", 12, "bold"))
+
+ def _round(self, c, x1, y1, x2, y2, r, **kw):
+ pts = [x1+r, y1, x2-r, y1, x2, y1, x2, y1+r, x2, y2-r, x2, y2, x2-r, y2, x1+r, y2, x1, y2, x1, y2-r, x1, y1+r, x1, y1]
+ c.create_polygon(pts, smooth=True, splinesteps=24, **kw)
+
+ def _draw_circ(self, c, cx, cy, r):
+ c.create_oval(cx-r-9, cy-r-9, cx+r+9, cy+r+9, fill="#dcecff", outline="")
+ c.create_oval(cx-r, cy-r, cx+r, cy+r, fill="white", outline=bord, width=2)
+ c.create_oval(cx-r+12, cy-r+12, cx-r+34, cy-r+34, fill=acc_soft, outline="")
+ c.create_text(cx, cy, text="+", fill=acc, font=("Helvetica", 26, "bold"))
+
+ def _upd_timer(self):
+ total = sum(self.calc.calc_one(h["kind"], h["unit"], h["qty"]) for h in self.habits)
+ self.timer.config(text=self.calc.fmt_timer(total))
+
+ def _day_stat(self):
+ total = sum(self.calc.calc_one(h["kind"], h["unit"], h["qty"]) for h in self.habits)
+ return total, len(self.habits)
+
+ def _month_stat(self):
+ d, c = self._day_stat()
+ return d * 30, c * 30
+
+ def _year_stat(self):
+ d, c = self._day_stat()
+ return d * 365, c * 365
+
+ def _open_form(self, idx=None):
+ if self.hab_win and self.hab_win.winfo_exists():
+ self.hab_win.destroy()
+ self.edit_idx = idx
+ data = self.habits[idx] if idx is not None else None
+
+ win = tk.Toplevel(self)
+ win.title("Привычка" if idx is None else "Редактировать")
+ win.configure(bg=bg)
+ win.geometry("450x520")
+ win.resizable(False, False)
+ win.transient(self)
+ win.grab_set()
+ self.hab_win = win
+
+ c = tk.Frame(win, bg=bg, padx=18, pady=18)
+ c.pack(fill="both", expand=True)
+
+ tk.Label(c, text="Привычка" if idx is None else "Редактировать", bg=bg, fg=txt, font=("Helvetica", 16, "bold")).pack(anchor="w", pady=(0, 16))
+
+ tk.Label(c, text="Тип", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
+ self.kind_var = tk.StringVar(value=data["kind"] if data else "Никотин")
+ kind_cb = ttk.Combobox(c, textvariable=self.kind_var, values=list(self.calc.harms.keys()), state="readonly", style="Light.TCombobox")
+ kind_cb.pack(fill="x", ipady=4, pady=(0, 12))
+ kind_cb.bind("<>", self._kind_chg)
+
+ tk.Label(c, text="Единица", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
+ self.unit_var = tk.StringVar()
+ self.unit_cb = ttk.Combobox(c, textvariable=self.unit_var, state="readonly", style="Light.TCombobox")
+ self.unit_cb.pack(fill="x", ipady=4, pady=(0, 12))
+
+ self.title_en = self._entry(c, "Название", data["title"] if data else "")
+ self.qty_en = self._entry(c, "Количество", str(data["qty"]) if data else "1")
+ self.qty_en.bind("", lambda e: self._prev())
+
+ self.prev = tk.Label(c, text="", bg=bg, fg=harm_col, font=("Helvetica", 10, "bold"))
+ self.prev.pack(anchor="w", pady=6)
+
+ self.err = tk.Label(c, text="", bg=bg, fg="#ef6f8a", font=("Helvetica", 10))
+ self.err.pack(anchor="w", pady=(10, 10))
+
+ self._upd_units()
+ if data:
+ self.unit_var.set(data["unit"])
+ self._prev()
+
+ if idx is None:
+ tk.Button(c, text="Добавить", command=self._save, bg=acc, fg="white", font=("Helvetica", 12, "bold"), relief="flat", padx=20, pady=10).pack(fill="x")
+ else:
+ row = tk.Frame(c, bg=bg)
+ row.pack(fill="x")
+ self._circle(row, "✓", self._save, 42, acc).pack(side="left")
+ self._circle(row, "✕", self._del, 42, "#ef6f8a").pack(side="right")
+
+ def _entry(self, parent, label, init):
+ f = tk.Frame(parent, bg=bg)
+ f.pack(fill="x", pady=(0, 12))
+ tk.Label(f, text=label, bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
+ e = tk.Entry(f, bg=surf, fg=txt, relief="flat", font=("Helvetica", 11), highlightthickness=1, highlightbackground=bord, highlightcolor=acc)
+ e.pack(fill="x", ipady=8)
+ e.insert(0, init)
+ return e
+
+ def _upd_units(self):
+ units = self.calc.get_units(self.kind_var.get())
+ self.unit_cb['values'] = units
+ if units:
+ self.unit_var.set(units[0])
+
+ def _kind_chg(self, e=None):
+ self._upd_units()
+ self._prev()
+
+ def _prev(self):
+ try:
+ q = float(self.qty_en.get().strip() if self.qty_en else "1")
+ k = self.kind_var.get()
+ u = self.unit_var.get()
+ if k and u:
+ h = self.calc.calc_one(k, u, q)
+ self.prev.config(text=f"⚠️ Потеря: {self.calc.fmt_time(h)} жизни")
+ except:
+ self.prev.config(text="⚠️ Введите число")
+
+ def _save(self):
+ k = self.kind_var.get()
+ u = self.unit_var.get()
+ t = self.title_en.get().strip()
+ try:
+ q = float(self.qty_en.get().strip())
+ except:
+ self.err.config(text="Количество — число.")
+ return
+ if not t:
+ t = k
+ cfg = self.calc.get_config(k)
+ self.err.config(text="")
+ pay = {"kind": k, "title": t, "qty": q, "unit": u, "icon": cfg["icon"], "accent": cfg["color"]}
+ if self.edit_idx is None:
+ self.habits.append(pay)
+ else:
+ self.habits[self.edit_idx] = pay
+ self._render()
+ self._upd_timer()
+ self._close()
+ h = self.calc.calc_one(k, u, q)
+ self._notify(k, q, u, h)
+
+ def _notify(self, k, q, u, h):
+ pop = tk.Toplevel(self)
+ pop.title("⚠️ Вред")
+ pop.configure(bg="#fff3e0")
+ pop.geometry("350x160")
+ pop.resizable(False, False)
+ pop.transient(self)
+ pop.update_idletasks()
+ x = self.winfo_x() + (self.winfo_width() - 350) // 2
+ y = self.winfo_y() + (self.winfo_height() - 160) // 2
+ pop.geometry(f"+{x}+{y}")
+ ic = self.calc.icons.get(k, "⚠️")
+ tk.Label(pop, text=f"{ic} {k}", font=("Helvetica", 14, "bold"), bg="#fff3e0", fg="#e65100").pack(pady=(20, 5))
+ tk.Label(pop, text=f"{q} {u}", font=("Helvetica", 12), bg="#fff3e0", fg=txt).pack()
+ tk.Label(pop, text=f"Потеряно: {self.calc.fmt_time(h)} жизни", font=("Helvetica", 12, "bold"), bg="#fff3e0", fg=harm_col).pack(pady=(10, 0))
+ pop.after(3000, pop.destroy)
+
+ def _del(self):
+ if self.edit_idx is not None and 0 <= self.edit_idx < len(self.habits):
+ del self.habits[self.edit_idx]
+ self._render()
+ self._upd_timer()
+ self._close()
+
+ def _close(self):
+ if self.hab_win:
+ self.hab_win.destroy()
+ self.hab_win = None
+ self.edit_idx = None
+
+ def _sett(self):
+ if self.sett_win and self.sett_win.winfo_exists():
+ self.sett_win.focus_set()
+ return
+ win = tk.Toplevel(self)
+ win.title("Настройки")
+ win.configure(bg=bg)
+ win.geometry("400x380")
+ win.resizable(False, False)
+ win.transient(self)
+ win.grab_set()
+ self.sett_win = win
+ c = tk.Frame(win, bg=bg, padx=18, pady=18)
+ c.pack(fill="both", expand=True)
+ tk.Label(c, text="Профиль", bg=bg, fg=txt, font=("Helvetica", 16, "bold")).pack(anchor="w", pady=(0, 16))
+ self.name_en = self._entry(c, "Имя", "" if self.p_name == "Пользователь" else self.p_name)
+ tk.Label(c, text="Пол", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
+ self.gen_var = tk.StringVar(value=self.p_gender)
+ ttk.Combobox(c, textvariable=self.gen_var, values=["Мужской", "Женский"], state="readonly", style="Light.TCombobox").pack(fill="x", ipady=4, pady=(0, 12))
+ self.age_en = self._entry(c, "Возраст", str(self.p_age))
+ tk.Button(c, text="Сохранить", command=self._save_prof, bg=acc, fg="white", font=("Helvetica", 12, "bold"), relief="flat", padx=20, pady=10).pack(fill="x", pady=(8, 0))
+
+ def _save_prof(self):
+ n = self.name_en.get().strip()
+ self.p_name = n or "Пользователь"
+ self.p_gender = self.gen_var.get()
+ try:
+ self.p_age = int(self.age_en.get().strip())
+ except:
+ self.p_age = 20
+ self.calc.set_profile(self.p_gender, self.p_age)
+ self.p_label.config(text=self.p_name)
+ self._upd_timer()
+ if self.sett_win:
+ self.sett_win.destroy()
+ self.sett_win = None
+
+ def _cal_title(self):
+ m = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"]
+ return f"{m[self.cur_date.month - 1]} {self.cur_date.year}"
+
+ def _cal(self):
+ if self.cal_win and self.cal_win.winfo_exists():
+ self.cal_win.focus_set()
+ return
+ win = tk.Toplevel(self)
+ win.title("Календарь")
+ win.configure(bg=bg)
+ win.geometry("460x600")
+ win.resizable(False, False)
+ win.transient(self)
+ win.grab_set()
+ self.cal_win = win
+
+ h = tk.Frame(win, bg=head, padx=18, pady=18)
+ h.pack(fill="x")
+ nav = tk.Frame(h, bg=head)
+ nav.pack(fill="x")
+ self._circle(nav, "←", self._prev_m, 30).pack(side="left")
+ self.cal_lbl = tk.Label(nav, text=self._cal_title(), bg=head, fg=txt, font=("Helvetica", 14, "bold"))
+ self.cal_lbl.pack(side="left", expand=True)
+ self._circle(nav, "→", self._next_m, 30).pack(side="right")
+
+ body = tk.Frame(win, bg=bg, padx=18, pady=4)
+ body.pack(fill="both", expand=True)
+ self.grid = tk.Frame(body, bg=surf)
+ self.grid.pack(fill="x", pady=(0, 10))
+
+ sf = tk.Frame(body, bg=surf)
+ sf.pack(fill="x", pady=5)
+ si = tk.Frame(sf, bg=surf, padx=12, pady=12)
+ si.pack(fill="x")
+ tk.Label(si, text="📊 Статистика", bg=surf, fg=txt, font=("Helvetica", 12, "bold"), anchor="w").pack(fill="x", pady=(0, 8))
+ self.day_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
+ self.day_lbl.pack(fill="x")
+ self.mon_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
+ self.mon_lbl.pack(fill="x")
+ self.yr_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
+ self.yr_lbl.pack(fill="x")
+
+ self.cht = tk.Text(body, bg=surf, fg=txt, font=("Courier", 9), height=10, relief="flat", borderwidth=0)
+ self.cht.pack(fill="both", expand=True, pady=5)
+ self._refresh()
+
+ def _upd_period(self):
+ d, dc = self._day_stat()
+ m, mc = self._month_stat()
+ y, yc = self._year_stat()
+ self.day_lbl.config(text=f"📅 Сегодня: {self.calc.fmt_time(d)} ({dc} пр.)")
+ self.mon_lbl.config(text=f"📆 Месяц: {self.calc.fmt_time(m)} (×30)")
+ self.yr_lbl.config(text=f"📈 Год: {self.calc.fmt_time(y)} (×365)")
+
+ def _draw_cht(self):
+ self.cht.delete("1.0", tk.END)
+ self.cht.insert(tk.END, f"{self._cal_title()}:\n\n")
+ d, _ = self._day_stat()
+ bar = "█" * min(30, int(d/10)) + "░" * (30 - min(30, int(d/10)))
+ self.cht.insert(tk.END, f"Сегодня: {bar} {self.calc.fmt_time(d)}\n")
+
+ def _refresh(self):
+ if not self.cal_win: return
+ self.cal_lbl.config(text=self._cal_title())
+ self._draw_grid(self.grid, self.cur_date.year, self.cur_date.month)
+ self._upd_period()
+ self._draw_cht()
+
+ def _draw_grid(self, p, y, m):
+ for w in p.winfo_children():
+ w.destroy()
+ today = date.today()
+ dm, _ = self._day_stat()
+ for col, day in enumerate(["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]):
+ tk.Label(p, text=day, bg=surf, fg=muted, font=("Helvetica", 10, "bold"), width=4, pady=6).grid(row=0, column=col, sticky="nsew")
+ for r, week in enumerate(calendar.monthcalendar(y, m), 1):
+ for c, day in enumerate(week):
+ if day == 0:
+ cell = tk.Label(p, text="", bg=surf, width=4, height=2)
+ else:
+ is_t = (today.year == y and today.month == m and today.day == day)
+ if is_t and dm > 0:
+ if dm > 120: bg_c, fg_c = "#ff4444", "white"
+ elif dm > 60: bg_c, fg_c = "#ff7777", "white"
+ else: bg_c, fg_c = "#ffaaaa", txt
+ elif is_t:
+ bg_c, fg_c = acc, "white"
+ else:
+ bg_c, fg_c = surf_alt, txt
+ cell = tk.Label(p, text=str(day), bg=bg_c, fg=fg_c, font=("Helvetica", 10, "bold"), width=4, height=2)
+ cell.grid(row=r, column=c, padx=3, pady=3, sticky="nsew")
+ for i in range(7):
+ p.grid_columnconfigure(i, weight=1)
+
+ def _prev_m(self):
+ y, m = self.cur_date.year, self.cur_date.month - 1
+ if m == 0: m, y = 12, y - 1
+ self.cur_date = date(y, m, 1)
+ self._refresh()
+
+ def _next_m(self):
+ y, m = self.cur_date.year, self.cur_date.month + 1
+ if m == 13: m, y = 1, y + 1
+ self.cur_date = date(y, m, 1)
+ self._refresh()
+
+
+if __name__ == "__main__":
+ app = MainWindow()
+ app.mainloop()
\ No newline at end of file