From 44a7ef61798bd777725705002df63d793d5387cd Mon Sep 17 00:00:00 2001 From: mel1sg Date: Sat, 18 Apr 2026 15:53:27 +0300 Subject: [PATCH] Initial commit with all project files --- .DS_Store | Bin 0 -> 6148 bytes .idea/.gitignore | 3 + .idea/Treker.iml | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + core.py | 103 ++++ main.py | 449 ++++++++++++++++++ 9 files changed, 590 insertions(+) create mode 100644 .DS_Store create mode 100644 .idea/.gitignore create mode 100644 .idea/Treker.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 core.py create mode 100644 main.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..73ccccebe3a119f505841235f9f02d261a0483ca GIT binary patch literal 6148 zcmeHKF-`+P4D>=IBBe=5xi3KC2dgM4sQCaSP#^^kMWUd)4!+21V8%94jteaX8cX)< zdOf#yQ=E6rtSnycF3sj$Q!JVEZ6ZLG7M z-P-}bTVsW<^#FhWbi;_%k9Ldf)6wdWAJtA#kOERb3P=GdAO-dcpl8#jvp_{DAO)nr zUIG3eDxBC7`#}G7An^Fk^$C%NvEL;|) t?6nns1OGPEdO5=25P%6=@%ceru@7`Q@=gcpAV6JYQs6HX_yW$OD0u(? literal 0 HcmV?d00001 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