import tkinter as tk from tkinter import ttk, messagebox, filedialog from datetime import datetime import csv from db import Database from tkcalendar import DateEntry class TheatreApp: def __init__(self, db): self.db = db self.root = tk.Tk() self.root.title("Театральная система") self.root.geometry("1100x650") self.create_widgets() self.refresh_all() def create_widgets(self): notebook = ttk.Notebook(self.root) notebook.pack(fill=tk.BOTH, expand=True) # Отделы self.depts_frame = ttk.Frame(notebook) notebook.add(self.depts_frame, text="Отделы") self.depts_tree = self._make_table(self.depts_frame, ["ID","Фамилия","Имя","Отчество","Email","Реквизиты"]) tk.Button(self.depts_frame, text="Обновить", command=self.refresh_departments).pack(pady=5) # Пользователи self.users_frame = ttk.Frame(notebook) notebook.add(self.users_frame, text="Пользователи") self.users_tree = self._make_table(self.users_frame, ["ID","Фамилия","Имя","Отчество","Дата рождения","Телефон"]) btn_frame = tk.Frame(self.users_frame) btn_frame.pack(pady=5) tk.Button(btn_frame, text="Добавить", command=self.add_user).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Редактировать", command=self.edit_user).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Удалить", command=self.delete_user).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Обновить", command=self.refresh_users).pack(side=tk.LEFT, padx=5) # Билеты self.tickets_frame = ttk.Frame(notebook) notebook.add(self.tickets_frame, text="Билеты") self.tickets_tree = self._make_table(self.tickets_frame, ["ID","Отдел","Пользователь","Спектакль","Цена","Место","Дата/время","Полная стоимость"]) btn_frame2 = tk.Frame(self.tickets_frame) btn_frame2.pack(pady=5) tk.Button(btn_frame2, text="Добавить", command=self.add_ticket).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame2, text="Редактировать", command=self.edit_ticket).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame2, text="Удалить", command=self.delete_ticket).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame2, text="Поиск", command=self.search_tickets).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame2, text="Отчёт (выручка)", command=self.revenue_report).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame2, text="Обновить", command=self.refresh_tickets).pack(side=tk.LEFT, padx=5) def _make_table(self, parent, columns): tree = ttk.Treeview(parent, columns=columns, show="headings", height=20) for col in columns: tree.heading(col, text=col) tree.column(col, width=120, anchor="center") tree.pack(fill=tk.BOTH, expand=True) return tree def _fill_tree(self, tree, rows): for item in tree.get_children(): tree.delete(item) for row in rows: tree.insert("", "end", values=row) def refresh_departments(self): rows = self.db.get_departments() self._fill_tree(self.depts_tree, rows) def refresh_users(self): rows = self.db.get_users() self._fill_tree(self.users_tree, rows) def refresh_tickets(self): rows = self.db.get_tickets() self._fill_tree(self.tickets_tree, rows) def add_user(self): dialog = UserDialog(self.root, self.db, title="Добавить пользователя") self.wait_for_dialog(dialog, self.refresh_users) def edit_user(self): selected = self.users_tree.selection() if not selected: messagebox.showerror("Ошибка", "Выберите пользователя") return values = self.users_tree.item(selected[0])["values"] user_id = values[0] defaults = { "last_name": values[1], "first_name": values[2], "middle_name": values[3] if values[3] else "", "birth_date": values[4], "phone": values[5] } dialog = UserDialog(self.root, self.db, user_id=user_id, defaults=defaults, title="Редактировать пользователя") self.wait_for_dialog(dialog, self.refresh_users) def delete_user(self): selected = self.users_tree.selection() if not selected: messagebox.showerror("Ошибка", "Выберите пользователя") return user_id = self.users_tree.item(selected[0])["values"][0] if messagebox.askyesno("Удаление", f"Удалить пользователя ID={user_id}?"): self.db.delete_user(user_id) self.refresh_users() def add_ticket(self): dialog = TicketDialog(self.root, self.db, title="Добавить билет") self.wait_for_dialog(dialog, self.refresh_tickets) def edit_ticket(self): selected = self.tickets_tree.selection() if not selected: messagebox.showerror("Ошибка", "Выберите билет") return values = self.tickets_tree.item(selected[0])["values"] ticket_id = values[0] defaults = { "department_id": values[1], "user_id": values[2], "performance_name": values[3], "ticket_price": values[4], "hall_seat": values[5], "event_datetime": values[6] } dialog = TicketDialog(self.root, self.db, ticket_id=ticket_id, defaults=defaults, title="Редактировать билет") self.wait_for_dialog(dialog, self.refresh_tickets) def delete_ticket(self): selected = self.tickets_tree.selection() if not selected: messagebox.showerror("Ошибка", "Выберите билет") return ticket_id = self.tickets_tree.item(selected[0])["values"][0] if messagebox.askyesno("Удаление", f"Удалить билет ID={ticket_id}?"): self.db.delete_ticket(ticket_id) self.refresh_tickets() def search_tickets(self): SearchDialog(self.root, self.db) def revenue_report(self): perf = tk.simpledialog.askstring("Отчёт", "Введите название спектакля:") if perf: total = self.db.revenue_by_performance(perf) messagebox.showinfo("Выручка", f"Выручка по спектаклю '{perf}': {total} руб.") def refresh_all(self): self.refresh_departments() self.refresh_users() self.refresh_tickets() def wait_for_dialog(self, dialog, refresh_callback): self.root.wait_window(dialog.top) refresh_callback() def run(self): self.root.mainloop() # ДИАЛОГ ПОЛЬЗОВАТЕЛЯ (календарь для даты рождения) class UserDialog: def __init__(self, parent, db, user_id=None, defaults=None, title="Пользователь"): self.db = db self.user_id = user_id self.top = tk.Toplevel(parent) self.top.title(title) self.top.grab_set() self.top.resizable(False, False) row = 0 tk.Label(self.top, text="Фамилия:").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.last_name_entry = tk.Entry(self.top, width=30) self.last_name_entry.grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Имя:").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.first_name_entry = tk.Entry(self.top, width=30) self.first_name_entry.grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Отчество:").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.middle_name_entry = tk.Entry(self.top, width=30) self.middle_name_entry.grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Дата рождения:").grid(row=row, column=0, padx=5, pady=5, sticky="e") frame_birth = tk.Frame(self.top) frame_birth.grid(row=row, column=1, padx=5, pady=5, sticky="w") self.birth_date = DateEntry(frame_birth, width=12, background='darkblue', foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd') self.birth_date.pack(side=tk.LEFT) tk.Button(frame_birth, text="Сегодня", command=self.set_today_birth).pack(side=tk.LEFT, padx=5) row += 1 tk.Label(self.top, text="Телефон:").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.phone_entry = tk.Entry(self.top, width=30) self.phone_entry.grid(row=row, column=1, padx=5, pady=5) row += 1 btn_frame = tk.Frame(self.top) btn_frame.grid(row=row, column=0, columnspan=2, pady=10) tk.Button(btn_frame, text="Сохранить", command=self.save).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Отмена", command=self.top.destroy).pack(side=tk.LEFT, padx=5) if defaults: self.last_name_entry.insert(0, defaults.get("last_name", "")) self.first_name_entry.insert(0, defaults.get("first_name", "")) self.middle_name_entry.insert(0, defaults.get("middle_name", "")) self.phone_entry.insert(0, defaults.get("phone", "")) birth = defaults.get("birth_date") if birth: try: if isinstance(birth, datetime): self.birth_date.set_date(birth) else: self.birth_date.set_date(datetime.strptime(str(birth), "%Y-%m-%d")) except: pass else: self.birth_date.set_date(datetime.now()) def set_today_birth(self): self.birth_date.set_date(datetime.now()) def save(self): last = self.last_name_entry.get().strip() first = self.first_name_entry.get().strip() middle = self.middle_name_entry.get().strip() or None phone = self.phone_entry.get().strip() birth_date = self.birth_date.get_date().strftime("%Y-%m-%d") if not last or not first or not phone: messagebox.showerror("Ошибка", "Заполните фамилию, имя и телефон") return if self.user_id: self.db.update_user(self.user_id, last, first, middle, birth_date, phone) else: self.db.add_user(last, first, middle, birth_date, phone) self.top.destroy() # ДИАЛОГ БИЛЕТА class TicketDialog: def __init__(self, parent, db, ticket_id=None, defaults=None, title="Билет"): self.db = db self.ticket_id = ticket_id self.top = tk.Toplevel(parent) self.top.title(title) self.top.grab_set() self.top.resizable(False, False) self.dept_list = db.get_departments_list() self.user_list = db.get_users_list() self.dept_var = tk.StringVar() self.user_var = tk.StringVar() self.perf_var = tk.StringVar() self.price_var = tk.StringVar() self.seat_var = tk.StringVar() self.date_var = tk.StringVar() self.hour_var = tk.StringVar() self.minute_var = tk.StringVar() row = 0 tk.Label(self.top, text="Отдел:").grid(row=row, column=0, padx=5, pady=5, sticky="e") dept_combo = ttk.Combobox(self.top, textvariable=self.dept_var, values=[t for _, t in self.dept_list], width=40) dept_combo.grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Пользователь:").grid(row=row, column=0, padx=5, pady=5, sticky="e") user_combo = ttk.Combobox(self.top, textvariable=self.user_var, values=[t for _, t in self.user_list], width=40) user_combo.grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Спектакль:").grid(row=row, column=0, padx=5, pady=5, sticky="e") tk.Entry(self.top, textvariable=self.perf_var, width=42).grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Цена:").grid(row=row, column=0, padx=5, pady=5, sticky="e") tk.Entry(self.top, textvariable=self.price_var, width=42).grid(row=row, column=1, padx=5, pady=5) row += 1 tk.Label(self.top, text="Место:").grid(row=row, column=0, padx=5, pady=5, sticky="e") tk.Entry(self.top, textvariable=self.seat_var, width=42).grid(row=row, column=1, padx=5, pady=5) row += 1 # Дата и время tk.Label(self.top, text="Дата:").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.date_picker = DateEntry(self.top, width=12, background='darkblue', foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd', textvariable=self.date_var) self.date_picker.grid(row=row, column=1, padx=5, pady=5, sticky="w") row += 1 tk.Label(self.top, text="Час (0-23):").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.hour_spin = tk.Spinbox(self.top, from_=0, to=23, width=5, format="%02.0f", textvariable=self.hour_var) self.hour_spin.grid(row=row, column=1, padx=5, pady=5, sticky="w") row += 1 tk.Label(self.top, text="Минуты (0-59):").grid(row=row, column=0, padx=5, pady=5, sticky="e") self.minute_spin = tk.Spinbox(self.top, from_=0, to=59, width=5, format="%02.0f", textvariable=self.minute_var) self.minute_spin.grid(row=row, column=1, padx=5, pady=5, sticky="w") row += 1 def set_now(): now = datetime.now() self.date_picker.set_date(now) self.hour_var.set(f"{now.hour:02d}") self.minute_var.set(f"{now.minute:02d}") tk.Button(self.top, text="Сейчас", command=set_now).grid(row=row, column=1, padx=5, pady=5, sticky="w") row += 1 if defaults: self.set_defaults(defaults) else: self.date_picker.set_date(datetime.now()) self.hour_var.set("00") self.minute_var.set("00") btn_frame = tk.Frame(self.top) btn_frame.grid(row=row, column=0, columnspan=2, pady=10) tk.Button(btn_frame, text="Сохранить", command=self.save).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Отмена", command=self.top.destroy).pack(side=tk.LEFT, padx=5) def set_defaults(self, defaults): dept_id = defaults.get("department_id") if dept_id: for id_, text in self.dept_list: if id_ == dept_id: self.dept_var.set(text) break user_id = defaults.get("user_id") if user_id: for id_, text in self.user_list: if id_ == user_id: self.user_var.set(text) break self.perf_var.set(defaults.get("performance_name", "")) self.price_var.set(str(defaults.get("ticket_price", ""))) self.seat_var.set(defaults.get("hall_seat", "")) dt = defaults.get("event_datetime") if dt: if isinstance(dt, datetime): self.date_picker.set_date(dt) self.hour_var.set(f"{dt.hour:02d}") self.minute_var.set(f"{dt.minute:02d}") else: try: dt_obj = datetime.strptime(str(dt), "%Y-%m-%d %H:%M:%S") self.date_picker.set_date(dt_obj) self.hour_var.set(f"{dt_obj.hour:02d}") self.minute_var.set(f"{dt_obj.minute:02d}") except: pass def save(self): dept_text = self.dept_var.get() dept_id = None for id_, text in self.dept_list: if text == dept_text: dept_id = id_ break if dept_id is None: messagebox.showerror("Ошибка", "Выберите отдел из списка") return user_text = self.user_var.get() user_id = None for id_, text in self.user_list: if text == user_text: user_id = id_ break if user_id is None: messagebox.showerror("Ошибка", "Выберите пользователя из списка") return perf = self.perf_var.get().strip() if not perf: messagebox.showerror("Ошибка", "Введите название спектакля") return try: price = float(self.price_var.get().strip()) except ValueError: messagebox.showerror("Ошибка", "Цена должна быть числом") return seat = self.seat_var.get().strip() if not seat: messagebox.showerror("Ошибка", "Введите место") return date_obj = self.date_picker.get_date() try: hour = int(self.hour_var.get().strip()) minute = int(self.minute_var.get().strip()) except ValueError: messagebox.showerror("Ошибка", "Время должно быть числом") return if hour < 0 or hour > 23 or minute < 0 or minute > 59: messagebox.showerror("Ошибка", "Некорректное время") return event_dt = datetime(date_obj.year, date_obj.month, date_obj.day, hour, minute) if self.ticket_id: self.db.update_ticket(self.ticket_id, dept_id, user_id, perf, price, seat, event_dt) else: self.db.add_ticket(dept_id, user_id, perf, price, seat, event_dt) self.top.destroy() # ДИАЛОГ ПОИСКА class SearchDialog: def __init__(self, parent, db): self.db = db self.top = tk.Toplevel(parent) self.top.title("Поиск билетов") self.top.grab_set() self.top.geometry("900x500") frame = tk.Frame(self.top) frame.pack(pady=10) tk.Label(frame, text="Фамилия (часть):").grid(row=0, column=0, padx=5, pady=5) self.last_entry = tk.Entry(frame, width=20) self.last_entry.grid(row=0, column=1, padx=5, pady=5) tk.Label(frame, text="Спектакль (часть):").grid(row=0, column=2, padx=5, pady=5) self.perf_entry = tk.Entry(frame, width=20) self.perf_entry.grid(row=0, column=3, padx=5, pady=5) tk.Button(frame, text="Искать", command=self.search).grid(row=0, column=4, padx=10) self.tree = ttk.Treeview(self.top, columns=("ID","Фамилия","Имя","Спектакль","Цена","Место","Дата","Полная"), show="headings") for col in self.tree["columns"]: self.tree.heading(col, text=col) self.tree.column(col, width=100, anchor="center") self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) tk.Button(self.top, text="Экспорт в CSV", command=self.export_csv).pack(pady=5) def search(self): last = self.last_entry.get().strip() perf = self.perf_entry.get().strip() rows = self.db.search_tickets(last, perf) for item in self.tree.get_children(): self.tree.delete(item) if rows: for row in rows: self.tree.insert("", "end", values=row) else: messagebox.showinfo("Результат", "Ничего не найдено") def export_csv(self): if not self.tree.get_children(): messagebox.showwarning("Нет данных", "Сначала выполните поиск") return filename = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV", "*.csv")]) if filename: data = [] for child in self.tree.get_children(): data.append(self.tree.item(child)["values"]) with open(filename, 'w', encoding='utf-8', newline='') as f: writer = csv.writer(f) writer.writerow(["ID","Фамилия","Имя","Спектакль","Цена","Место","Дата","Полная"]) writer.writerows(data) messagebox.showinfo("Успех", f"Экспортировано в {filename}")