Загрузка данных


import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os
from gui.auth_interface import AuthInterface
from modules.administrator.admin_user_inter import AdminUserInter
from modules.utils.window_behavior import LevelOneBehavior
from modules.administrator.admin_db_inter import AdminDBInter
from modules.project.project_create_inter import ProjectCreateInterface
from modules.project.project_edit_inter import ProjectEditInterface
from modules.project.project_create_work import ProjectCreateController
from modules.project.services.project_service import ProjectService


def open_window(title):
    """
    Универсальная заглушка для всех окон, кроме "Пользователей".
    Теперь она просто выводит сообщение в консоль,
    так как у неё нет доступа к главному окну напрямую.
    """
    print(f"[DEBUG] Вызван универсальный open_window для: {title}")
    
    # Создаем временное независимое окно, чтобы программа не вылетала
    temp_win = tk.Toplevel()
    temp_win.title(title)
    temp_win.geometry("300x150")
    
    ttk.Label(temp_win, text=f"Интерфейс '{title}'", font=("Arial", 14)).pack(pady=20)
    ttk.Button(temp_win, text="Закрыть", command=temp_win.destroy).pack(pady=10)

class MainInterface(tk.Tk):
    def __init__(self):
        super().__init__()
        
        # Сохраняем текущего пользователя как атрибут самого окна
        self.current_user = {"login": None}
        
        # --- Настройка поведения Уровня 1 (Главное окно) ---
        behavior = LevelOneBehavior()
        behavior.setup_level_one(self)
        
        self.title("ДОЗОР -- Heri-Hodie-Cras")
        self.geometry("458x360")
        self.resizable(False, False)
        
        bg_color = "#EEEEEE"
        self.configure(bg=bg_color)
        
        main_frame = tk.Frame(self, bg=bg_color)
        main_frame.pack(padx=1, pady=1, fill='both', expand=True)
        
        button_width = 20
        button_height = 2
        button_font = ("Arial", 12, "bold")
        
        for i in range(7):
            main_frame.grid_rowconfigure(i, weight=0)
        main_frame.grid_columnconfigure(0, minsize=210, weight=0)
        main_frame.grid_columnconfigure(1, minsize=235, weight=0)
        
        self.active_popup = None

        # --- ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРА ПРОЕКТОВ ---
        try:
            from modules.project.project_create_work import ProjectCreateController
            from modules.data.database_module import get_db_connection

            db_conn = get_db_connection() 
            
            # КРИТИЧЕСКИ ВАЖНО: здесь должно быть db_conn, а не db
            self.manager = ProjectCreateController(db_conn) 
            
        except Exception as e:
            print(f"[FATAL] Не удалось инициализировать менеджер проектов: {e}")
            self.manager = None

        # ФИКС ОШИБКИ: Привязываем событие к самому экземпляру класса (self), а не к внутреннему объекту Tk
        self.bind("<Button-1>", self.close_popup_global)

        left_texts = [
            ["Администратор", [("Пользователи", "users"), ("Базы данных", "db")]],
            ["Менеджер базы", [("Менеджер группы", "group"), ("Менеджер показателей", "metrics")]],
            ["Эксперты", [("Создание файлов", "create_files"), ("Агрегирование файлов", "aggregate")]],
            ["Проект", [("Создать", "new"), ("Редактировать", "edit")]],
            ["Прогнозирование", []]
        ]

        # --- ЛЕВАЯ ПАНЕЛЬ С ПРОВЕРКОЙ РОЛЕЙ ---
        for row_index, item in enumerate(left_texts):
            btn_text = item[0]
            
            def execute_button(title=btn_text, index=row_index):
                if title == "Администратор":
                    user_role = getattr(self, 'current_user', {}).get("role")
                    clean_role = str(user_role).strip().lower() if user_role else ""
                    
                    if clean_role != "администратор":
                        from tkinter import messagebox
                        messagebox.showwarning(
                            "Доступ запрещен", 
                            f"Кнопка '{title}' доступна только Администратору."
                        )
                        return # Прерываем выполнение кнопки
                
                # Если проверка пройдена или это другой пункт — открываем меню
                self.show_dropdown(title, index)

            btn = tk.Button(
                main_frame,
                text=btn_text,
                font=button_font,
                width=button_width,
                height=button_height,
                anchor="w",
                command=execute_button
            )
            btn.grid(row=row_index, column=0, padx=5, pady=5, sticky="ew")

        # --- ПРАВАЯ ПАНЕЛЬ (оставил без изменений твою графику) ---
        right_box = tk.Frame(main_frame, bg=bg_color, width=235, height=300)
        right_box.grid(row=0, column=1, rowspan=6, pady=5, sticky="nsew", padx=(0, 5))
        right_box.grid_propagate(False)
        right_box.columnconfigure(0, weight=1)
        
        picture_frame = tk.Frame(right_box, bg="#d9ead3", highlightbackground="gray", highlightthickness=1, width=225, height=160)
        picture_frame.place(relx=0.5, y=5, anchor="n")
        picture_frame.grid_propagate(False)
        
        current_dir = os.path.dirname(os.path.abspath(__file__))
        image_path = os.path.join(current_dir, "images", "Сова1.png")
        try:
            from PIL import Image, ImageTk
            original_img = Image.open(image_path)
            target_w, target_h = 215, 130
            img_ratio = original_img.width / original_img.height
            box_ratio = target_w / target_h
            if img_ratio > box_ratio:
                new_w, new_h = target_w, int(target_w / img_ratio)
            else:
                new_w, new_h = int(target_h * img_ratio), target_h
            resized_img = original_img.resize((new_w, new_h), resample=Image.LANCZOS)
            self.tk_image = ImageTk.PhotoImage(resized_img)
            label = tk.Label(picture_frame, image=self.tk_image, bg="#d9ead3", borderwidth=0)
            label.image = self.tk_image
            label.place(relx=0.5, rely=0.5, anchor='center', width=new_w, height=new_h)
        except Exception as e:
            print(f"[FATAL IMAGE ERROR]: {e}")
            canvas = tk.Canvas(picture_frame, width=225, height=160, bg="red", highlightthickness=0)
            canvas.create_text(112, 80, text="IMG\nFAIL", fill="white")
            canvas.place(x=0, y=0, relwidth=1, relheight=1)
            
        spacer = tk.Frame(right_box, bg=bg_color, height=1)
        spacer.grid(row=0, column=0, pady=(170, 10), sticky="ew")
        
        upper_padding = tk.Frame(right_box, bg=bg_color, height=3)
        upper_padding.grid(row=1, column=0)
        
        btn6 = tk.Button(right_box, text="Руководство", font=button_font, width=button_width, height=button_height)
        btn6.grid(row=2, column=0, ipadx=10, pady=(0, 5), sticky="ew")
        btn7 = tk.Button(right_box, text="О программе", font=button_font, width=button_width, height=button_height)
        btn7.grid(row=3, column=0, ipadx=10, pady=(5, 15), sticky="ew")

        # --- ПОДВАЛ ---
        footer_frame = tk.Frame(main_frame, bg="#0078D7")
        footer_frame.grid(row=6, column=0, columnspan=2, sticky="ew", pady=(10, 0))
        footer_label = tk.Label(
            footer_frame,
            text="Copyright © Минск, НИИ, 2027 год",
            fg="#FF8C00",
            bg="#0078D7",
            font=("Arial", 12, "bold")
        )
        footer_label.pack(expand=True, ipady=4)

        # --- FIX: Добавляем отслеживание движения главного окна для перемещения попапов ---
        self.bind("<B1-Motion>", self._on_drag_main_window)
        self._drag_start_x = None
        self._drag_start_y = None

    # --- FIX: НОВЫЙ МЕТОД для синхронизации координат при перетаскивании ---
    def _on_drag_main_window(self, event):
        """Обновляет позиции открытых попапов во время перетаскивания главного окна."""
        if self._drag_start_x is None:
            self._drag_start_x = event.x_root
            self._drag_start_y = event.y_root
            return

        delta_x = event.x_root - self._drag_start_x
        delta_y = event.y_root - self._drag_start_y

        if abs(delta_x) > 2 or abs(delta_y) > 2:
            if self.active_popup and self.active_popup.winfo_exists():
                geo = self.active_popup.geometry()
                parts = geo.split('+')
                if len(parts) == 3:
                    new_x = int(parts[1]) + delta_x
                    new_y = int(parts[2]) + delta_y
                    self.active_popup.geometry(f"+{new_x}+{new_y}")
            
            self._update_child_positions(event.x_root - self.winfo_rootx(), event.y_root - self.winfo_rooty())

            self._drag_start_x = event.x_root
            self._drag_start_y = event.y_root

    def show_dropdown(self, menu_name, base_row):
        """Показывает всплывающее окно под кнопкой."""
        from modules.utils.app_state import disable_app, enable_app
        
        # --- FIX: Уничтожаем старый попап ---
        if self.active_popup is not None:
            if hasattr(self.active_popup, '_child_windows'):
                for child_data in list(self.active_popup._child_windows):
                    win, _, _ = child_data
                    try:
                        win.destroy()
                    except Exception:
                        pass
            self.active_popup.destroy()
        
        self.active_popup = None 
        
        root_win = self.winfo_toplevel()
        
        data = None
        lookup = {
            "Администратор": [("Пользователи", "users"), ("Базы данных", "db")],
            "Менеджер базы": [("Менеджер группы", "group"), ("Менеджер показателей", "metrics")],
            "Эксперты": [("Создание файлов", "create_files"), ("Агрегирование файлов", "aggregate")],
            "Проект": [("Создать", "new"), ("Редактировать", "edit")],
        }
        data = lookup.get(menu_name, [])
        if not data:
            return

        x = self.winfo_rootx() + 5
        y = self.winfo_rooty() + (base_row * 35) + 50
        
        popup = tk.Toplevel(self)
        popup.overrideredirect(True)
        popup.geometry(f"+{x}+{y}")
        
        self.active_popup = popup

        popup_frame = tk.Frame(popup, bg="#F9F9F9", bd=1, relief="solid")
        popup_frame.pack()

    def make_command(action_title, target_popup=None):
        # Локальный импорт для защиты от NameError во вложенных областях
        from tkinter import messagebox 

        def execute():
            if target_popup:
                target_popup.destroy()
            
            disable_app(root_win)

            app_instance = self 

            def handle_logout(restart=False):
                global current_user_session, current_user
                
                current_user_session = {"login": None}
                current_user = {"login": None} 
                
                if hasattr(app_instance, 'current_user'):
                    app_instance.current_user = {"login": None}
                
                print("[DEBUG] handle_logout вызван")

                if restart:
                    disable_app(root_win)
                    
                    root_win.withdraw() 

                    # === НАДЕЖНЫЙ СПОСОБ ВЫЗОВА АВТОРИЗАЦИИ ===
                    # Мы передаем ссылку на функцию do_login из ГЛОБАЛЬНОЙ области видимости main.py
                    try:
                        # Импортируем глобальную функцию прямо здесь
                        from gui.main_interface import do_login as global_do_login
                        
                        new_auth = AuthInterface(root_win, on_login_click=global_do_login)
                        
                    except Exception as e:
                        print(f"[FATAL AUTH SETUP]: {e}")
                        messagebox.showerror("Критическая ошибка", "Ошибка инициализации входа.\nПроверьте файл main.py")
                        enable_app(root_win)
                        return

                    root_win.wait_window(new_auth) 
                    
                    print(f"[DEBUG] Авторизация завершена. Текущий юзер: {current_user.get('login')}")
                    
                    if current_user.get("login"):
                        root_win.deiconify()
                        enable_app(root_win)
                        
                        role_display = current_user.get("role", "Без роли")
                        root_win.title(f"ДОЗОР -- {current_user['login']} ({role_display})")
                    else:
                        try:
                            root_win.destroy()
                        except Exception:
                            pass
                else:
                    root_win.destroy()

            # Проверка доступа к Администратору
            if action_title == "Администратор":
                user_role = getattr(self, 'current_user', {}).get("role")
                clean_role = str(user_role).strip().lower() if user_role else ""
                
                if clean_role != "администратор":
                    enable_app(root_win)
                    messagebox.showwarning(
                        "Доступ запрещен", 
                        f"Раздел '{action_title}' доступен только пользователю с ролью Администратор."
                    )
                    return

            # Открытие окон разделов
            if action_title == "Пользователи":
                users_win = AdminUserInter(root_win, on_logout_callback=handle_logout)
                root_win.register_child_window(users_win, offset_x=20, offset_y=20)
                root_win.wait_window(users_win)
                if not root_win.winfo_exists():
                    return
                enable_app(root_win)
                return

            elif action_title == "Базы данных":
                db_win = AdminDBInter(root_win)
                root_win.register_child_window(db_win, offset_x=20, offset_y=20)
                root_win.wait_window(db_win)
            
            elif action_title == "Менеджер группы":
                from modules.manager.manager_group_inter import ManagerGroupInter
                win = ManagerGroupInter(root_win)
                root_win.register_child_window(win, offset_x=20, offset_y=20)
                win.lift()
                
            elif action_title == "Менеджер показателей":
                from modules.manager.manager_metrics_inter import ManagerMetricsInter
                win = ManagerMetricsInter(root_win)
                root_win.register_child_window(win, offset_x=20, offset_y=20)
                win.lift()
                
            # --- НОВЫЕ ОКНА ЭКСПЕРТОВ ---
            elif action_title == "Создание файлов":
                from modules.excel.excel_export_inter import ExpertExportInterface
                export_win = ExpertExportInterface(root_win)
                root_win.register_child_window(export_win, offset_x=20, offset_y=20)
                export_win.lift()
                
            elif action_title == "Агрегирование файлов":
                from modules.excel.excel_aggregate_inter import ExpertAggregateInterface
                agg_win = ExpertAggregateInterface(root_win)
                root_win.register_child_window(agg_win, offset_x=20, offset_y=20)
                agg_win.lift()
                
            elif action_title == "Создать":
                from modules.project.project_create_inter import ProjectCreateInterface
                create_win = ProjectCreateInterface(root_win, self.manager)
                root_win.register_child_window(create_win, offset_x=20, offset_y=20)
                create_win.lift()
                
            elif action_title == "Редактировать":
                from modules.project.project_edit_inter import ProjectEditInterface
                
                def on_project_selected(data_dict):
                    from modules.project.project_event_inter import ProjectEventWindow
                    event_win = ProjectEventWindow(
                        manager=self.manager,
                        selected_data=data_dict,
                        root_window=root_win
                    )
                    root_win.register_child_window(event_win, offset_x=20, offset_y=20)
                    event_win.lift()

                edit_win = ProjectEditInterface(
                    root_win, 
                    manager=self.manager, 
                    on_select_callback=on_project_selected
                )
                root_win.register_child_window(edit_win, offset_x=20, offset_y=20)
                edit_win.lift()                    
                
            else:
                open_window(action_title)

            enable_app(root_win)
        
        return execute

        # --- ЦИКЛ СОЗДАНИЯ КНОПОК В ОКНЕ ---
        for sub_text, action_key in data:
            print(f"[SETUP] Building button: {sub_text}")
            
            try:
                btn_command = make_command(sub_text)
                
                if btn_command is None:
                    raise RuntimeError("make_command returned None")
                    
                print(f"[SETUP] Success: Command for '{sub_text}' created.")
                
            except Exception as e:
                # Если здесь ошибка - кнопка вообще не создастся или будет сломана
                print(f"[FATAL BUTTON ERROR] Button '{sub_text}' failed to initialize:")
                import traceback
                traceback.print_exc() 
                
                # Создаем заглушку, чтобы окно не крашилось совсем
                def broken_button():
                    from tkinter import messagebox
                    messagebox.showerror("Broken", f"Button '{sub_text}' is corrupted:\n{e}")
                
                btn_command = broken_button

            # Используем самый надежный способ передачи аргумента
            sub_btn = tk.Button(
                popup_frame,
                text=sub_text,
                font=("Arial", 11),
                width=20,
                height=1,
                anchor="w",
                bd=0,
                relief="flat",
                cursor="hand2",
                command=btn_command # Прямая ссылка
            )
            
            sub_btn.pack(fill="x", ipadx=5, pady=3)
            sep = tk.Frame(popup_frame, bg="#E0E0E0", height=1)
            sep.pack(fill="x")

        def check_leave(e):
            if not popup.winfo_exists():
                return
            
            mouse_x = e.x_root
            mouse_y = e.y_root
            
            pop_x = popup.winfo_rootx()
            pop_y = popup.winfo_rooty()
            pop_w = popup.winfo_width()
            pop_h = popup.winfo_height()

            safe_zone_x1 = pop_x - 30
            safe_zone_y1 = pop_y - 30
            safe_zone_x2 = pop_x + pop_w + 30
            safe_zone_y2 = pop_y + pop_h + 30

            if not (safe_zone_x1 <= mouse_x <= safe_zone_x2 and safe_zone_y1 <= mouse_y <= safe_zone_y2):
                popup.destroy()

        popup.bind("<Motion>", check_leave)

    def open_admin_users(self):
        from modules.utils.app_state import disable_app, enable_app
        root = self.winfo_toplevel()
        if not root or not hasattr(root, 'winfo_exists') or not root.winfo_exists():
            return
        disable_app(root)
        users_win = AdminUserInter(self)
        self.wait_window(users_win)
        enable_app(root)

    def open_admin_dbs(self):
        from modules.utils.app_state import disable_app, enable_app
        root = self.winfo_toplevel()
        if not root or not hasattr(root, 'winfo_exists') or not root.winfo_exists():
            return
        disable_app(root)
        db_win = AdminDBInter(self)
        self.wait_window(db_win)
        enable_app(root)

    def close_popup_global(self, event):
        """Закрытие меню, если кликнули по пустому месту."""
        if self.active_popup is not None:
            widget = event.widget
            while widget:
                if widget == self.active_popup:
                    return
                widget = widget.master
            self.active_popup.destroy()
            self.active_popup = None

    def _update_child_positions(self, new_x, new_y):
        """Обновляет координаты всех дочерних окон второго уровня"""
        if hasattr(self, '_child_windows'):
            for child_data in self._child_windows:
                win, offset_x, offset_y = child_data
                try:
                    win.geometry(f"+{new_x + offset_x}+{new_y + offset_y}")
                except Exception:
                    pass

    def register_child_window(self, window, offset_x=0, offset_y=0):
        """Регистрирует дочернее окно 2 уровня."""
        if not hasattr(self, '_child_windows'):
            self._child_windows = []
        for item in self._child_windows:
            if item[0] == window:
                return
        self._child_windows.append((window, offset_x, offset_y))

if __name__ == "__main__":
    app = MainInterface()
    app.mainloop()