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


# ====================================================
# Файл №1: Dozor/gui/main_interface_old.py 
# - старый файл, который подвергается рефакторингу.
# ====================================================

# ---*--- Dozor/gui/main_interface_old.py  ---*---
# -*- coding: utf-8 -*-

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__()
        
        # Флаг-предохранитель от ошибок winfo при смене пользователя
        self._ui_blocked_by_logout = False
        
        # Текущий пользователь (единый источник истины)
        self.current_user = {"login": None}
        
        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() 
            self.manager = ProjectCreateController(db_conn) 
        except Exception as e:
            print(f"[FATAL] Не удалось инициализировать менеджер проектов: {e}")
            self.manager = None

        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
                        
                        answer = messagebox.askyesno(
                            "Требуются права администратора", 
                            f"У вашей учетной записи ('{clean_role.capitalize()}') недостаточно прав.\n\nХотите сменить пользователя и войти как Администратор?"
                        )
                        
                        if answer:
                            self.switch_user_radical()
                            
                        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:
            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)

        self.bind("<B1-Motion>", self._on_drag_main_window)
        self._drag_start_x = None
        self._drag_start_y = None

    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
        
        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 = {
            "Администратор": [("Пользователи", "users"), ("Базы данных", "db")],
            "Менеджер базы": [("Менеджер группы", "group"), ("Менеджер показателей", "metrics")],
            "Эксперты": [("Создание файлов", "create_files"), ("Агрегирование файлов", "aggregate")],
            "Проект": [("Создать", "new"), ("Редактировать", "edit")],
        }.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):
            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:
                        # === КРИТИЧЕСКИ ВАЖНО: Ставим блок ПЕРЕД уничтожением окна ===
                        app_instance._ui_blocked_by_logout = True
                        
                        disable_app(root_win)
                        root_win.withdraw() 
                        
                        login_callback = app_instance.do_login
                        new_auth = AuthInterface(root_win, on_login_click=login_callback)
                        
                        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})")
                            
                            # Разблокируем только после успешного входа
                            app_instance._ui_blocked_by_logout = False
                        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)
                        from tkinter import messagebox
                        messagebox.showwarning("Доступ запрещен", f"Раздел '{action_title}' доступен только пользователю с ролью Администратор.")
                        return

                if action_title == "Пользователи":
                    users_win = AdminUserInter(root_win)
                    root_win.register_child_window(users_win, offset_x=20, offset_y=20)
                    root_win.wait_window(users_win)
                    
                    # === ГЛАВНАЯ ПРОВЕРКА В ОКНЕ АДМИНКИ ===
                    # Если флаг блокировки стоит — значит, идет смена юзера, просто выходим.
                    if app_instance._ui_blocked_by_logout:
                        return

                    final_login = getattr(app_instance, 'current_user', {}).get("login")
                    if final_login is None:
                        print("[INFO] Пользователь инициировал смену через закрытие окна. Перезапуск...")
                        disable_app(root_win)
                        root_win.withdraw()
                        login_callback = app_instance.do_login
                        new_auth = AuthInterface(root_win, on_login_click=login_callback)
                        root_win.wait_window(new_auth)
                        after_login = getattr(app_instance, 'current_user', {}).get("login")
                        if after_login:
                            root_win.deiconify()
                            enable_app(root_win)
                            role_display = app_instance.current_user.get("role", "Без роли")
                            root_win.title(f"ДОЗОР -- {app_instance.current_user['login']} ({role_display})")
                            app_instance._ui_blocked_by_logout = False
                        else:
                            try:
                                root_win.destroy()
                            except Exception:
                                pass
                        return
                    else:
                        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:
            btn_command = make_command(sub_text)

            def wrap_execute(cmd=None, pop_ref=popup, r_win=root_win):
                if pop_ref and pop_ref.winfo_exists():
                    if hasattr(pop_ref, '_child_windows') and pop_ref._child_windows:
                         pop_ref.after(10, pop_ref.destroy)
                    else:
                         pop_ref.destroy()
                
                # === ФИНАЛЬНЫЙ БАРЬЕР ДЛЯ ЛЯМБДА-ФУНКЦИИ ===
                # Проверяем флаг перед тем, как вообще пытаться запустить команду cmd()
                if cmd is not None and not r_win._ui_blocked_by_logout:
                    cmd()
                else:
                    enable_app(r_win)

            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=lambda c=btn_command: wrap_execute(c)
            )
            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 switch_user_radical(self):
        """Радикальная смена пользователя: полный выход + немедленный вход."""
        root_win = self.winfo_toplevel()
        
        from modules.utils.app_state import disable_app
        disable_app(root_win)

        if hasattr(self, 'current_user'):
            self.current_user = {"login": None}
            
        if hasattr(self, '_child_windows'):
             self._child_windows.clear()

        try:
            root_win.destroy()
        except Exception:
            pass

        import sys, subprocess, os, shutil
        
        script_path = os.path.abspath("main.py")
        gui_python = shutil.which("pythonw.exe") 
        if not gui_python:
            current_dir = os.path.dirname(sys.executable)
            potential_path = os.path.join(current_dir, "pythonw.exe")
            gui_python = potential_path if os.path.exists(potential_path) else sys.executable

        try:
            subprocess.Popen([gui_python, script_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
        except Exception as e:
            print(f"[FATAL] Не удалось запустить новую копию Dozor: {e}")

        sys.exit(0)

    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(root)
        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):
        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()












# ====================================================
# Файл №2: Dozor/modules/manager/manager_group_inter.py 
# - файл организации интерфейса в Менеджере групп, как донор
# ====================================================

# ---*--- Dozor/modules/manager/manager_group_inter.py   ---*---
# -*- coding: utf-8 -*-

import sys
import sqlite3
from pathlib import Path

try:
    potential_base = Path.cwd()
    if "Dozor" not in str(potential_base):
        BASE_DIR = Path(__file__).resolve().parents[4]
    else:
        BASE_DIR = potential_base
    
    DB_FOLDER = BASE_DIR / "db"
    ACTIVE_FLAG_PATH = DB_FOLDER / "active.flag"
    
    DEFAULT_DB_PATH = DB_FOLDER / "database.db"
    MASTERDATA_PATH = DB_FOLDER / "masterdata.db"
except Exception as e:
    print(f"[FATAL] Не удалось вычислить путь к базе данных: {e}")
    sys.exit(1)

def _determine_active_path():
    try:
        if ACTIVE_FLAG_PATH.exists():
            with open(ACTIVE_FLAG_PATH, 'r') as f:
                name = f.read().strip()
                if name == "masterdata":
                    return MASTERDATA_PATH
    except Exception:
        pass
    return DEFAULT_DB_PATH

DB_PATH = _determine_active_path()

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SqlAlchemySession, declarative_base

engine = create_engine(f"sqlite:///{DB_PATH}", echo=False, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db_session = SessionLocal()
    try:
        yield db_session
    finally:
        db_session.close()

def get_db_connection():
    active_path = _determine_active_path()
    global DB_PATH
    DB_PATH = active_path
    
    if not DB_FOLDER.exists():
        print(f"[ACTION] Создаю папку для базы: {DB_FOLDER}")
        DB_FOLDER.mkdir(parents=True, exist_ok=True)
        
    conn = sqlite3.connect(active_path)
    conn.execute("PRAGMA foreign_keys = ON;")
    return conn

def _create_tables(cursor):
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS groups (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    gain_group REAL DEFAULT 0.0
    )
    ''')
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS indicators (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    group_code TEXT NOT NULL,
    FOREIGN KEY (group_code) REFERENCES groups(code) ON DELETE CASCADE
    )
    ''')

def _populate_test_data(conn):
    cursor = conn.cursor()
    cursor.execute("SELECT COUNT(*) FROM groups")
    count = cursor.fetchone()[0]
    if count == 0:
        print("[INFO] База данных пуста. Заполняю тестовыми данными...")
        groups_data = [("Клубы", "KB", 1.5), ("Фанаты", "FAN", 2.0)]
        cursor.executemany("INSERT INTO groups (name, code, gain_group) VALUES (?, ?, ?)", groups_data)
        indicators_data = [
            ("ЦСКА", "KB-1", "KB"), ("Спартак", "KB-2", "KB"), ("Зенит", "KB-3", "KB"),
            ("Мужчины", "FAN-1", "FAN"), ("Женщины", "FAN-2", "FAN"), ("Дети", "FAN-3", "FAN")
        ]
        cursor.executemany("INSERT INTO indicators (name, code, group_code) VALUES (?, ?, ?)", indicators_data)
        conn.commit()
        print("[SUCCESS] Тестовые данные успешно записаны.")
    else:
        print("[INFO] База данных уже содержит данные. Пропускаю инициализацию.")

def init_database():
    print("[INIT] Проверка наличия рабочих баз данных...")
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        _create_tables(cursor)
        _populate_test_data(conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации DATABASE.DB: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            conn.close()

    master_conn = None
    try:
        if not MASTERDATA_PATH.exists():
            print(f"[ACTION] База мастер-данных не найдена. Создаю новую: {MASTERDATA_PATH.name}")
            master_conn = sqlite3.connect(MASTERDATA_PATH)
            master_conn.execute("PRAGMA foreign_keys = ON;")
            master_cursor = master_conn.cursor()
            _create_tables(master_cursor)
            _populate_test_data(master_conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации MASTERDATA.DB: {e}")
        if master_conn:
            master_conn.rollback()
    finally:
        if master_conn:
            master_conn.close()

class Session:
    def __init__(self):
        pass

    # Эти функции объявлены СТАТИЧЕСКИМИ и НА УРОВНЕ МОДУЛЯ ниже
    pass 

# --- ФУНКЦИИ ДЛЯ ИМПОРТА (лежат строго вне класса Session) ---
def list_groups():
    """Функция-заглушка для прямого импорта"""
    return [(1, "Клубы"), (2, "Фанаты")]

def get_all_indicators_by_group(group_code):
    """Функция-заглушка для прямого импорта"""
    mock_data = {
        "KB": [(1, "ЦСКА"), (2, "Спартак"), (3, "Зенит")],
        "FAN": [(4, "Мужчины"), (5, "Женщины"), (6, "Дети")]
    }
    return mock_data.get(group_code, [])

if __name__ == "__main__":
    init_database()













# ====================================================
# Файл №3: Dozor/modules/manager/manager_group_work.py 
# - файл организации функционала в Менеджере групп, как донор
# ====================================================

# ---*--- Dozor/modules/manager/manager_group_work.py   ---*---
# -*- coding: utf-8 -*-

# Dozor/modules/manager/manager_group_work.py

import logging
from typing import List, Optional


class GroupDTO:
    """Простой объект данных для передачи информации о группе между интерфейсом и логикой."""
    def __init__(self, code: str, name: str, kov: Optional[float] = None):
        self.code = code
        self.name = name
        self.kov = kov


def load_config():
    """Загрузка конфигурации усилителя. Заглушка возвращает тестовое значение."""
    print("[WORK LOGIC] Вызов: load_config")
    return {"amplifier": "25.5"}


def save_config(data):
    """Сохранение конфигурации. Заглушка выводит данные в консоль."""
    print(f"[WORK LOGIC] Вызов: save_config | Данные: {data}")


def list_groups() -> List[GroupDTO]:
    """Получение списка групп. Заглушка возвращает статичные данные."""
    print("[WORK LOGIC] Вызов: list_groups")
    # TODO: Заменить на реальный запрос к БД через Session()
    return [
        GroupDTO(code="GRP-01", name="Группа А", kov=1.2),
        GroupDTO(code="GRP-02", name="Группа Б", kov=0.8),
    ]


def add_group(name: str, code: str, kov: Optional[float]):
    """Добавление группы. Заглушка имитирует запись в БД."""
    print(f"[WORK LOGIC] Вызов: add_group | Имя: {name}, Код: {code}, КОВ: {kov}")
    # TODO: Реализовать вызов modules.database_module.add_group(...)


def edit_group(group_code: str, new_name: str, new_code: str, new_kov: Optional[float]):
    """Редактирование группы. Заглушка имитирует обновление в БД."""
    print(f"[WORK LOGIC] Вызов: edit_group | Старый код: {group_code} -> Новый код: {new_code}")
    # TODO: Реализовать сессию БД и session.query(Group)...


def delete_group(group_code: str):
    """Удаление группы. Заглушка имитирует удаление из БД."""
    print(f"[WORK LOGIC] Вызов: delete_group | Код: {group_code}")
    # TODO: Реализовать сессию БД и session.delete(...)


# --- ЗАГУШКИ ДЛЯ СУЩЕСТВУЮЩИХ ОШИБОК ИМПОРТА ---
def get_all_indicators_by_group(group_code: str) -> List:
    """Заглушка для получения индикаторов группы (твой старый метод использует его при смене кода)."""
    print(f"[WORK LOGIC] Вызов: get_all_indicators_by_group | Группа: {group_code}")
    return []  # Возвращаем пустой список, чтобы цикл редактирования не упал


def edit_indicator(ind_id, new_code: str):
    """Заглушка для обновления кода индикатора."""
    print(f"[WORK LOGIC] Вызов: edit_indicator | ID: {ind_id}, Новый код: {new_code}")













# ====================================================
# Файл №4: Dozor/modules/manager/manager_metrics_inter.py 
# - файл организации интерфейса в Менеджере показателей, как донор
# ====================================================

# ---*--- Dozor/modules/manager/manager_metrics_inter.py   ---*---
# -*- coding: utf-8 -*-


import random
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from docx.shared import Cm, Pt
from docx import Document

from modules.manager.manager_metrics_work import (
    get_all_indicators_by_group,
    insert_indicator,
    delete_indicator_by_code,
    save_docx_template,
    generate_unique_code,
    IndicatorDTO,
)
from modules.manager.manager_group_work import list_groups
from modules.utils.window_behavior import LevelTwoBehavior


class ManagerMetricsInter(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent_main = parent
        
        behavior = LevelTwoBehavior()
        behavior.setup_level_two(self)
        
        self.title("Менеджер показателей")
        self.geometry("600x450")

        # Теперь проверяем наличие login у переданного родителя (который и есть MainInterface)
        if not hasattr(self.parent_main, 'current_user') or not self.parent_main.current_user.get("login"):
            messagebox.showwarning("Ошибка", "Сессия пользователя не найдена.")
            self.destroy()
            return

        # Отключаем использование master_db, так как в MainInterface его пока нет
        self.use_master_db = False 
        
        # Создаем заглушку сессии БД для работы list_groups без ошибок SQLAlchemy
        class FakeSession:
            def query(self, *args, **kwargs): 
                class QueryObj:
                    def filter_by(self, **kw): return self
                    def all(self): return [] 
                return QueryObj()
            def close(self): pass
            
        self.session = FakeSession()
        
        self.group_names_to_codes = {}
        self.create_widgets()
        self.update_group_combobox()
        self.update_indicators_list()

    def paste_from_clipboard(self):
        try:
            clipboard_text = self.parent_main.clipboard_get()
            self.name_entry.insert(tk.END, clipboard_text)
        except tk.TclError:
            messagebox.showwarning("Буфер обмена", "Буфер обмена пуст.")

    def create_widgets(self):
        top_panel = tk.Frame(self)
        top_panel.pack(padx=10, pady=10, fill="x")

        tk.Label(top_panel, text="Группа:").pack(anchor="w")
        self.group_var = tk.StringVar()
        self.group_combobox = ttk.Combobox(
            top_panel, textvariable=self.group_var, state="readonly"
        )
        self.group_combobox.pack(fill="x", pady=5)
        self.group_combobox.bind("<<ComboboxSelected>>", self.update_indicators_list)

        tk.Label(top_panel, text="Название показателя:").pack(anchor="w")
        self.name_entry = tk.Text(top_panel, height=3, width=40)
        self.name_entry.pack(fill="x", pady=5)

        paste_button = tk.Button(
            top_panel, text="Вставить", command=self.paste_from_clipboard
        )
        paste_button.pack()

        buttons_frame = tk.Frame(top_panel)
        buttons_frame.pack(fill="x", pady=5)
        tk.Button(buttons_frame, text="Добавить", command=self.add_indicator).pack(side="left", padx=2)
        tk.Button(buttons_frame, text="Удалить", command=self.delete_indicator).pack(side="left", padx=2)
        tk.Button(buttons_frame, text="Экспорт в Word", command=self.print_to_docx).pack(side="left", padx=5, pady=5)

        middle_panel = tk.Frame(self)
        middle_panel.pack(fill="both", expand=True, padx=10, pady=5)
        scrollbar = ttk.Scrollbar(middle_panel)
        scrollbar.pack(side="right", fill="y")

        columns = ("number", "name", "code")
        self.indicators_tree = ttk.Treeview(
            middle_panel, columns=columns, show="headings", yscrollcommand=scrollbar.set, selectmode="browse"
        )
        self.indicators_tree.heading("number", text="№ п/п"); self.indicators_tree.column("number", width=50, anchor="center")
        self.indicators_tree.heading("name", text="Название"); self.indicators_tree.column("name", width=400)
        self.indicators_tree.heading("code", text="Код"); self.indicators_tree.column("code", width=150)
        self.indicators_tree.pack(fill="both", expand=True)
        scrollbar.config(command=self.indicators_tree.yview)

        self.indicators_tree.bind("<Double-Button-1>", self.edit_indicator)

    def update_group_combobox(self):
        groups = list_groups()
        self.group_names_to_codes.clear()
        for group in groups:
            self.group_names_to_codes[group.name] = group.code
        group_names = [g.name for g in groups]
        self.group_combobox["values"] = group_names
        if group_names:
            self.group_combobox.current(0)
        self.update_indicators_list()

    def update_indicators_list(self, event=None):
        selected_group_name = self.group_var.get()
        selected_group_code = self.group_names_to_codes.get(selected_group_name)
        for item in self.indicators_tree.get_children():
            self.indicators_tree.delete(item)
        if not selected_group_code:
            return
            
        indicators = get_all_indicators_by_group(selected_group_code)
        for idx, ind in enumerate(indicators, start=1):
            self.indicators_tree.insert("", "end", iid=ind.code, values=(f"{idx}.", ind.name, ind.code))

    def add_indicator(self):
        group_name = self.group_var.get()
        group_code = self.group_names_to_codes.get(group_name)
        indicator_name = self.name_entry.get("1.0", tk.END).rstrip("\n").strip()
        if not group_code or not indicator_name:
            messagebox.showwarning("Ошибка", "Выберите группу и введите название!")
            return
            
        new_code = generate_unique_code(group_code)
        insert_indicator(new_code, indicator_name, group_code)
        self.name_entry.delete("1.0", tk.END)
        self.update_indicators_list()

    def edit_indicator(self, event):
        selected_items = self.indicators_tree.selection()
        if not selected_items:
            return
        code = selected_items[0]
        
        # Для заглушек ищем имя прямо в дереве
        current_name = ""
        item_values = self.indicators_tree.item(code)["values"]
        if len(item_values) > 1:
            current_name = item_values[1]

        edit_win = tk.Toplevel(self)
        edit_win.title("Редактировать")
        edit_win.geometry("350x150")
        tk.Label(edit_win, text="Новое название:").pack(pady=5)
        new_name_entry = tk.Text(edit_win, height=2, width=40)
        new_name_entry.insert(tk.END, current_name)
        new_name_entry.pack(pady=5)
        tk.Button(
            edit_win,
            text="Сохранить",
            command=lambda: self.save_changes(code, new_name_entry.get("1.0", tk.END), edit_win),
        ).pack(pady=5)

    def save_changes(self, code, new_name_raw, edit_win):
        new_name = new_name_raw.rstrip("\n").strip()
        if not new_name:
            messagebox.showwarning("Ошибка", "Название не может быть пустым.")
            return
        # В реальном коде тут был бы session.query...commit()
        messagebox.showinfo("Готово", "Изменения сохранены (заглушка).")
        self.update_indicators_list()
        edit_win.destroy()

    def delete_indicator(self):
        selected_items = self.indicators_tree.selection()
        if not selected_items:
            messagebox.showwarning("Ошибка", "Выберите индикатор для удаления.")
            return
        code = selected_items[0]
        if messagebox.askyesno("Подтверждение", f"Удалить индикатор с кодом {code}?"):
            success = delete_indicator_by_code(code)
            if success:
                messagebox.showinfo("Готово", "Удалено успешно.")
                self.update_indicators_list()
            else:
                messagebox.showerror("Ошибка", "Не удалось удалить.")

    def print_to_docx(self):
        group_name = self.group_var.get()
        if not group_name:
            messagebox.showwarning("Ошибка", "Выберите группу.")
            return
            
        group_code = self.group_names_to_codes.get(group_name)
        indicators = get_all_indicators_by_group(group_code)
        if not indicators:
            messagebox.showwarning("Ошибка", "В группе нет показателей.")
            return

        filename = f"{group_name.replace(' ', '_')}_indicators.docx"
        path = filedialog.asksaveasfilename(
            initialfile=filename,
            defaultextension=".docx",
            filetypes=[("Word files", "*.docx"), ("All files", "*.*")],
        )
        if path:
            success = save_docx_template(group_name, indicators, path)
            if success:
                messagebox.showinfo("Готово", f"Файл сохранен как:\n{os.path.basename(path)}")

    def on_close(self):
        self.destroy()













# ====================================================
# Файл №5: Dozor/modules/manager/manager_metrics_work.py 
# - файл организации функционала в Менеджере показателей, как донор
# ====================================================

# ---*--- Dozor/modules/manager/manager_metrics_work.py   ---*---
# -*- coding: utf-8 -*-

import os
import random
from typing import List, Optional


class IndicatorDTO:
    """Объект данных для передачи информации об индикаторе."""
    def __init__(self, code: str, name: str):
        self.code = code
        self.name = name


def generate_unique_code(group_code: str) -> str:
    """Генерация заглушечного уникального кода."""
    print(f"[WORK LOGIC] Вызов: generate_unique_code | Группа: {group_code}")
    return f"{group_code}-{random.randint(100, 999)}"


def list_groups():
    """Заглушка списка групп из manager_group_work."""
    from modules.manager.manager_group_work import list_groups as lg
    return lg()


def get_all_indicators_by_group(group_code: str) -> List[IndicatorDTO]:
    """Получение показателей группы. Заглушка возвращает статичные данные."""
    print(f"[WORK LOGIC] Вызов: get_all_indicators_by_group | Код: {group_code}")
    # TODO: Реальный запрос через DBSession.query(Indicator)...
    return [
        IndicatorDTO(code=f"{group_code}-1", name="Показатель А"),
        IndicatorDTO(code=f"{group_code}-2", name="Показатель Б"),
    ]


def insert_indicator(code: str, name: str, group_code: str):
    """Добавление показателя. Заглушка имитирует запись в БД."""
    print(f"[WORK LOGIC] Вызов: insert_indicator | Код: {code}, Имя: {name}, Группа: {group_code}")


def delete_indicator_by_code(code: str):
    """Удаление показателя. Заглушка имитирует удаление из БД."""
    print(f"[WORK LOGIC] Вызов: delete_indicator_by_code | Код: {code}")
    return True


def save_docx_template(group_name: str, indicators: List[IndicatorDTO], path: str):
    """Сохранение заглушечного DOCX файла."""
    print(f"[WORK LOGIC] Вызов: save_docx_template | Путь: {path} | Группа: {group_name}")
    try:
        with open(path, "w", encoding="utf-8") as f:
            f.write(f"Группа: {group_name}\n\n")
            for idx, ind in enumerate(indicators, start=1):
                f.write(f"{idx}. [{ind.code}] {ind.name}\n")
        return True
    except Exception as e:
        print(f"[WORK LOGIC ERROR] Не удалось сохранить файл: {e}")
        return False












# ====================================================
# Файл №6: Dozor/modules/data/database_module.py 
# Файл работы с базами данных
# ====================================================

# ---*--- Dozor/modules/data/database_module.py    ---*---
# -*- coding: utf-8 -*-

import sys
import sqlite3
from pathlib import Path

try:
    potential_base = Path.cwd()
    if "Dozor" not in str(potential_base):
        BASE_DIR = Path(__file__).resolve().parents[4]
    else:
        BASE_DIR = potential_base
    
    DB_FOLDER = BASE_DIR / "db"
    ACTIVE_FLAG_PATH = DB_FOLDER / "active.flag"
    
    DEFAULT_DB_PATH = DB_FOLDER / "database.db"
    MASTERDATA_PATH = DB_FOLDER / "masterdata.db"
except Exception as e:
    print(f"[FATAL] Не удалось вычислить путь к базе данных: {e}")
    sys.exit(1)

def _determine_active_path():
    try:
        if ACTIVE_FLAG_PATH.exists():
            with open(ACTIVE_FLAG_PATH, 'r') as f:
                name = f.read().strip()
                if name == "masterdata":
                    return MASTERDATA_PATH
    except Exception:
        pass
    return DEFAULT_DB_PATH

DB_PATH = _determine_active_path()

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SqlAlchemySession, declarative_base

engine = create_engine(f"sqlite:///{DB_PATH}", echo=False, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db_session = SessionLocal()
    try:
        yield db_session
    finally:
        db_session.close()

def get_db_connection():
    active_path = _determine_active_path()
    global DB_PATH
    DB_PATH = active_path
    
    if not DB_FOLDER.exists():
        print(f"[ACTION] Создаю папку для базы: {DB_FOLDER}")
        DB_FOLDER.mkdir(parents=True, exist_ok=True)
        
    conn = sqlite3.connect(active_path)
    conn.execute("PRAGMA foreign_keys = ON;")
    return conn

def _create_tables(cursor):
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS groups (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    gain_group REAL DEFAULT 0.0
    )
    ''')
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS indicators (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    group_code TEXT NOT NULL,
    FOREIGN KEY (group_code) REFERENCES groups(code) ON DELETE CASCADE
    )
    ''')

def _populate_test_data(conn):
    cursor = conn.cursor()
    cursor.execute("SELECT COUNT(*) FROM groups")
    count = cursor.fetchone()[0]
    if count == 0:
        print("[INFO] База данных пуста. Заполняю тестовыми данными...")
        groups_data = [("Клубы", "KB", 1.5), ("Фанаты", "FAN", 2.0)]
        cursor.executemany("INSERT INTO groups (name, code, gain_group) VALUES (?, ?, ?)", groups_data)
        indicators_data = [
            ("ЦСКА", "KB-1", "KB"), ("Спартак", "KB-2", "KB"), ("Зенит", "KB-3", "KB"),
            ("Мужчины", "FAN-1", "FAN"), ("Женщины", "FAN-2", "FAN"), ("Дети", "FAN-3", "FAN")
        ]
        cursor.executemany("INSERT INTO indicators (name, code, group_code) VALUES (?, ?, ?)", indicators_data)
        conn.commit()
        print("[SUCCESS] Тестовые данные успешно записаны.")
    else:
        print("[INFO] База данных уже содержит данные. Пропускаю инициализацию.")

def init_database():
    print("[INIT] Проверка наличия рабочих баз данных...")
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        _create_tables(cursor)
        _populate_test_data(conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации DATABASE.DB: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            conn.close()

    master_conn = None
    try:
        if not MASTERDATA_PATH.exists():
            print(f"[ACTION] База мастер-данных не найдена. Создаю новую: {MASTERDATA_PATH.name}")
            master_conn = sqlite3.connect(MASTERDATA_PATH)
            master_conn.execute("PRAGMA foreign_keys = ON;")
            master_cursor = master_conn.cursor()
            _create_tables(master_cursor)
            _populate_test_data(master_conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации MASTERDATA.DB: {e}")
        if master_conn:
            master_conn.rollback()
    finally:
        if master_conn:
            master_conn.close()

class Session:
    def __init__(self):
        pass

    # Эти функции объявлены СТАТИЧЕСКИМИ и НА УРОВНЕ МОДУЛЯ ниже
    pass 

# --- ФУНКЦИИ ДЛЯ ИМПОРТА (лежат строго вне класса Session) ---
def list_groups():
    """Функция-заглушка для прямого импорта"""
    return [(1, "Клубы"), (2, "Фанаты")]

def get_all_indicators_by_group(group_code):
    """Функция-заглушка для прямого импорта"""
    mock_data = {
        "KB": [(1, "ЦСКА"), (2, "Спартак"), (3, "Зенит")],
        "FAN": [(4, "Мужчины"), (5, "Женщины"), (6, "Дети")]
    }
    return mock_data.get(group_code, [])

if __name__ == "__main__":
    init_database()









# ====================================================
# Файл №7: Dozor/gui/main_interface.py 
# - НОВЫЙ ОСНОВНОЙ ФАЙЛ с которым работаем.
# - это основной файл для главного окна интерфейса 
# - в этом файле организовываем второю кнопку Менеджер базы
# ====================================================

# ---*--- Dozor/gui/main_interface.py  ---*---
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os

class MainInterface(tk.Tk):
    def __init__(self):
        super().__init__()
        self.current_user = {"login": None}
        
        # === ТОЛЬКО ГЕОМЕТРИЯ И ВИДЖЕТЫ ===
        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)

        # --- ЛЕВАЯ ПАНЕЛЬ ---
        left_buttons = [
            ("Администратор", self._show_admin_menu),
            ("Менеджер базы", self._show_manager_menu),
            ("Эксперты", self._show_experts_menu),
            ("Проект", self._show_project_menu),
            ("Прогнозирование", self._show_forecast_menu)
        ]

        for row_index, (btn_text, command_func) in enumerate(left_buttons):
            btn = tk.Button(
                main_frame,
                text=btn_text,
                font=button_font,
                width=button_width,
                height=button_height,
                anchor="w",
                command=command_func
            )
            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:
            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)

        # --- КОНТЕЙНЕРЫ ДЛЯ ПОДМЕНЮ ---
        self.menu_container = tk.Frame(main_frame, bg=bg_color)
        self.menu_container.grid_rowconfigure(0, weight=1)
        self.menu_container.grid_columnconfigure(0, weight=1)

        def _create_menu_panel(container, title_text):
            panel = tk.Frame(container, bg="#f0f0f0", bd=2, relief="sunken")
            panel.grid_propagate(False)
            
            header_frame = tk.Frame(panel, bg="#4a4a4a", height=30)
            header_frame.pack(fill='x')
            
            close_btn = tk.Button(
                header_frame, 
                text="← Назад", 
                font=("Arial", 9), 
                fg="white", 
                bg="#888888", 
                borderwidth=0,
                command=self._show_main_screen
            )
            close_btn.pack(side='right', padx=5, pady=2)
            
            content_frame = tk.Frame(panel, bg="#f0f0f0")
            content_frame.pack(fill='both', expand=True, padx=10, pady=10)
            ttk.Label(content_frame, text=f"Здесь будет функционал:\n{title_text}").pack(expand=True)
            
            return panel

        self.admin_panel = _create_menu_panel(self.menu_container, "Подменю Администратор")
        self.manager_panel = _create_menu_panel(self.menu_container, "Подменю Менеджер базы")
        self.experts_panel = _create_menu_panel(self.menu_container, "Подменю Эксперты")
        self.project_panel = _create_menu_panel(self.menu_container, "Подменю Проект")
        self.forecast_panel = _create_menu_panel(self.menu_container, "Подменю Прогнозирование")
        self.guide_panel = _create_menu_panel(self.menu_container, "Подменю Руководство")
        self.about_panel = _create_menu_panel(self.menu_container, "Подменю О программе")

        self._current_menu_visible = None

    # --- МЕТОД ВОЗВРАТА НА ГЛАВНЫЙ ЭКРАН ---
    def _show_main_screen(self):
        self._hide_current_menu()
        self.menu_container.grid_remove()
        
        self.grid_columnconfigure(0, minsize=210, weight=0)
        self.grid_columnconfigure(1, minsize=235, weight=0)

    def _hide_current_menu(self):
        panels = {
            'admin': self.admin_panel,
            'manager': self.manager_panel,
            'experts': self.experts_panel,
            'project': self.project_panel,
            'forecast': self.forecast_panel,
            'guide': self.guide_panel,
            'about': self.about_panel
        }
        if self._current_menu_visible and self._current_menu_visible in panels:
            panels[self._current_menu_visible].grid_remove()
        self._current_menu_visible = None

    # --- ОБРАБОТЧИКИ КНОПОК (БЕЗ .title()) ---
    def _show_admin_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.admin_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'admin'

    def _show_manager_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.manager_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'manager'

    def _show_experts_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.experts_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'experts'

    def _show_project_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.project_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'project'

    def _show_forecast_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.forecast_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'forecast'
        
    def _show_guide_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.guide_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'guide'
        
    def _show_about_menu(self):
        self._show_main_screen()
        self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
        self.about_panel.grid(row=0, column=0, sticky="nsew")
        self._current_menu_visible = 'about'
        
    def switch_user_radical(self):
        root_win = self.winfo_toplevel()
        from modules.utils.app_state import disable_app
        disable_app(root_win)
        if hasattr(self, 'current_user'):
            self.current_user = {"login": None}
        if hasattr(self, '_child_windows'):
            self._child_windows.clear()
        try:
            root_win.destroy()
        except Exception:
            pass
        import sys, subprocess, os, shutil
        script_path = os.path.abspath("main.py")
        gui_python = shutil.which("pythonw.exe")
        if not gui_python:
            current_dir = os.path.dirname(sys.executable)
            potential_path = os.path.join(current_dir, "pythonw.exe")
            gui_python = potential_path if os.path.exists(potential_path) else sys.executable
        try:
            subprocess.Popen([gui_python, script_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
        except Exception as e:
            print(f"[FATAL] Не удалось запустить новую копию Dozor: {e}")
        sys.exit(0)