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


Работаем с модулем modules/project/project_window.py - необходимо сделать косметические правки.
Показываю для образца окно Панель Администратора (окно Администрирование)
К этотму образцу нужно привести окно Наполнение проекта.
Обращаю внимание что я вайб кодер,поэтому замены не кусками,а целыми методами.

В Панели администратора структура:
    Заголовок,
    3 заголовка аккордеона,
    Кнопка Закрыть.

В Наполнении проекта необходимо:
    Заголовок - Все что сейчас в окне есть опустить ниже и тем же шрифтом как в Панели Администратора написать заголовок Наполнение проекта (окно соответственно изменить на Журнал событий) над блоком Выбрать проект,
    3 заголовка изменить на 4, сразу после блока Выбрать проект добавить новый первый заголовок Создать проект,
    Нижние кнопки - сейчас внизу 2 кнопки Отмена и Сохранить изменения - сделать как в Администрировании одну Закрыть. 
    
Необходимо поправить схлопываение заголовков - сейчас их 3 и когда их пользователь закрывает нижние кнопки наполовину прячутся. Учитывая что добавляется 4й заголовок необходимо высоту наверное скорректировать.

Ниже даюдля образца код Администратора и для исправления Наполнение проекта.


 #================================================
# modules\admin\admin_accordion_window.py
#================================================
import customtkinter as ctk
from tkinter import ttk, messagebox

# modules/admin/admin_accordion_window.py
from .users_tab import AdminUsersPanel 
from .db_manager_panel import DBManagerPanel
from .migration_panel import MigrationPanel


class AdminAccordionWindow(ctk.CTkToplevel):
    def __init__(self, master):
        super().__init__(master)
        
        self.parent_main = master
        self.title("Администрирование")
        
        # --- 1. БАЗОВЫЕ ГАБАРИТЫ ОКНА ---
        window_width = 650
        initial_height = 380 
        
        screen_w = self.winfo_screenwidth()
        screen_h = self.winfo_screenheight()
        
        x_cord = int((screen_w - window_width) / 2)
        y_cord = int((screen_h - initial_height) / 2)
        
        self.geometry(f"{window_width}x{initial_height}+{x_cord}+{y_cord}")
        
        # === 2. ЖЕСТКИЕ ОГРАНИЧЕНИЯ РАЗМЕРА ===
        self.minsize(width=window_width, height=250)
        self.maxsize(width=900, height=screen_h - 50)
        
        self.resizable(True, False)
        self.grab_set() 
        self.transient(master)
        
        # --- КОНТЕЙНЕРЫ ---
        main_container = ctk.CTkFrame(self, corner_radius=10)
        main_container.pack(padx=10, pady=10, fill="both", expand=True)
        
        title_lbl = ctk.CTkLabel(main_container, text="Панель администратора", font=ctk.CTkFont(size=20, weight="bold"))
        title_lbl.pack(pady=(0, 15))
        
        accordion_frame = ctk.CTkFrame(main_container)
        accordion_frame.pack(fill="both", expand=True, pady=(10, 0))
        
        self.accordion_data = {}
        self.currently_open_button = None
        
        # === ЗАГОЛОВКИ АККОРДЕОНА ===
        users_item = self._create_accordion_header(accordion_frame, "Управление пользователями")
        security_item = self._create_accordion_header(accordion_frame, "Безопасность")
        migration_item = self._create_accordion_header(accordion_frame, "Миграция")
        
        # Наполняем секции контентом
        self._fill_users_section(users_item["content"])
        self._fill_security_section(security_item["content"]) 
        self._fill_migration_section(migration_item["content"]) 
        
        # Привязка команд переключения
        for item in [users_item, security_item, migration_item]:
            btn = item["button"]
            content = item["content"]
            self.accordion_data[btn] = {"content": content}
            btn.configure(command=lambda b=btn, c=content: self.toggle_section(b, c))
            
        # Открываем первую секцию
        self.toggle_section(users_item["button"], users_item["content"])
            
        # Кнопки внизу
        button_frame = ctk.CTkFrame(main_container)
        button_frame.pack(fill="x", pady=(10, 0))
        
        close_btn = ctk.CTkButton(button_frame, text="Закрыть", width=120, command=self.destroy)
        close_btn.pack(side="right", padx=10, pady=10)

        self.protocol("WM_DELETE_WINDOW", self.on_closing)

        # === 3. ЦЕНТРИРОВАНИЕ ПОСЛЕ ПОЛНОЙ ОТРИСТОВКИ ===
        self.after(100, self._center_over_parent)

    def _center_over_parent(self):
        if not self.parent_main or not self.parent_main.winfo_exists():
            return

        # Обновляем данные о геометрии окна после того, как аккордеон раскрылся
        self.update_idletasks()
        
        w = self.winfo_width() # ширина вашего аккордеона
        
        # === НОВАЯ ЛОГИКА ПОЗИЦИОНИРОВАНИЯ ===
        
        # 1. Считаем горизонтальный центр относительно всего экрана
        screen_w = self.winfo_screenwidth()
        final_x = int((screen_w - w) / 2)
        
        # 2. Фиксированная позиция по вертикали (отступ от верхнего края экрана)
        taskbar_offset_top = 40 
        final_y = taskbar_offset_top
        
        # Применяем только смещение, размер трогать нельзя (иначе сломается maxsize/minsize)
        self.geometry(f"+{final_x}+{final_y}")

    # === МЕТОД toggle_section (твой оригинальный) ===
    def toggle_section(self, button, content):
        is_currently_open = (self.currently_open_button == button)
        if self.currently_open_button and not is_currently_open:
            prev_content = self.accordion_data[self.currently_open_button]["content"]
            prev_content.pack_forget()
            self.currently_open_button.configure(fg_color="#34495e")
        if is_currently_open:
            content.pack_forget()
            button.configure(fg_color="#34495e")
            self.currently_open_button = None
            new_geo = self._calculate_window_geometry(collapsed=True)
        else:
            content.pack(fill="x", padx=5, pady=(0, 5), anchor="center")
            button.configure(fg_color="#2c3e50")
            self.currently_open_button = button
            new_geo = self._calculate_window_geometry(collapsed=False)
        
        self.geometry(new_geo)

    # === РАСЧЕТ ГЕОМЕТРИИ (Исправлено!) ===
    def _calculate_window_geometry(self, collapsed=False):
        current_width = self.winfo_width()
        
        # Если ширина еще не посчиталась (например, при первом запуске)
        if current_width == 1:
            current_width = 650

        header_height = 50 * 3 + 30 

        # === ФИКС ДЛЯ КНОПОК: ДОБАВИЛИ ОТСТУП ПОД НИЖНЮЮ ПАНЕЛЬ ===
        button_panel_height = 70  # Высота рамки с кнопкой "Закрыть" + её отступы

        if collapsed or not self.currently_open_button:
            target_height = header_height + 80 + button_panel_height
        else:
            data = self.accordion_data[self.currently_open_button]
            content_widget = data["content"]
            
            # Обязательно дожидаемся перерисовки виджета перед замером высоты!
            self.update_idletasks() 
            open_content_height = content_widget.winfo_reqheight()
            
            # Считаем общую высоту: шапка + контент + нижняя панель
            target_height = header_height + open_content_height + button_panel_height

        max_h = self.winfo_screenheight() - 50
        min_h = 250 

        final_height = min(target_height, max_h)  # Не выше экрана
        final_height = max(final_height, min_h)   # Не ниже порога для кнопок

        return f"{current_width}x{final_height}"


    # === СОЗДАНИЕ ХЕДЕРА ===
    def _create_accordion_header(self, parent, title):
        item_container = ctk.CTkFrame(parent, fg_color="#f0f2f5", border_color="#d1d8e0", border_width=1, corner_radius=8)
        item_container.pack(fill="x", pady=5)
        header_btn = ctk.CTkButton(
            item_container,
            text=title,
            height=45,
            font=ctk.CTkFont(weight="bold", size=16),
            anchor="w",
            fg_color="#34495e",
            hover_color="#3c5a72",
        )
        header_btn.pack(fill="x")
        content_frame = ctk.CTkFrame(item_container, fg_color="transparent", corner_radius=0)
        return {"button": header_btn, "content": content_frame}

    # === НАПОЛНЕНИЕ СЕКЦИЙ ===
    def _fill_users_section(self, frame):
        panel = AdminUsersPanel(frame)
        panel.pack(fill="both", expand=True, padx=10, pady=10)

    def _fill_security_section(self, frame):
        panel = DBManagerPanel(frame)
        panel.pack(fill="both", expand=True, padx=10, pady=10)

    def _fill_migration_section(self, frame):
        panel = MigrationPanel(frame)
        panel.pack(fill="both", expand=True, padx=10, pady=10)

    def on_closing(self):
        try:
            self.after_cancel("all")
        except Exception:
            pass
        try:
            self.destroy()
        except Exception:
            pass

# ======================================









# ======================================
# modules/project/project_window.py
# ======================================
import customtkinter as ctk
from tkinter import ttk, messagebox
import datetime
import os

# Попытка импорта внешних библиотек (оставляем ваши try/except)
try:
    from tkcalendar import DateEntry
except ImportError:
    DateEntry = None
try:
    import docx
except ImportError:
    docx = None

class ProjectWindow(ctk.CTkToplevel):
    def __init__(self, master):
        super().__init__(master)
        
        self.title("Наполнение проекта")
        self.current_user = getattr(master, 'current_user', None) # На случай передачи пользователя
        
        # --- Настройка размеров ---
        screen_height = self.winfo_screenheight()
        window_width = 650
        initial_height = 380 

        # Горизонтальный центр экрана
        screen_w = self.winfo_screenwidth()
        x_cord = int((screen_w - window_width) / 2)

        # Фиксированный отступ от верхнего края (под панель задач Windows)
        taskbar_offset_top = 40 
        y_cord = taskbar_offset_top

        self.geometry(f"{window_width}x{initial_height}+{x_cord}+{y_cord}")
        
        self.geometry(f"{window_width}x{initial_height}+{x_cord}+{y_cord}")
        self.resizable(True, False)
        
        # Делаем окно МОДАЛЬНЫМ (блокируем родителя)
        self.grab_set() 
        self.transient(master)
        
        # --- КОНТЕЙНЕРЫ ---
     
        
        main_container = ctk.CTkFrame(self, corner_radius=10)
        main_container.pack(padx=10, pady=10, fill="both", expand=True)
        
        self._build_project_selector(main_container)
        
        accordion_frame = ctk.CTkFrame(main_container)
        accordion_frame.pack(fill="both", expand=True, pady=(10, 0))
        
        self.accordion_data = {}
        self.currently_open_button = None
        
        # Создание секций (берем ваши методы наполнения)
        events_item = self._create_accordion_header(accordion_frame, "Открыть для ввода событий")
        self._fill_events_section(events_item["content"])
        
        charts_item = self._create_accordion_header(accordion_frame, "Показать графики")
        self._fill_charts_section(charts_item["content"])
        
        print_item = self._create_accordion_header(accordion_frame, "Настройка печати")
        self._fill_print_section(print_item["content"])
        
        # Привязка команд переключения
        for item in [events_item, charts_item, print_item]:
            btn = item["button"]
            content = item["content"]
            self.accordion_data[btn] = {"content": content}
            btn.configure(command=lambda b=btn, c=content: self.toggle_section(b, c))
            
        # Кнопки внизу (Ваша логика сохранения/отмены)
        button_frame = ctk.CTkFrame(main_container)
        button_frame.pack(fill="x", pady=(10, 0))
        
        save_btn = ctk.CTkButton(button_frame, text="Сохранить изменения", width=150)
        save_btn.pack(side="right", padx=10)
        
        cancel_btn = ctk.CTkButton(button_frame, text="Отмена", width=100, fg_color="transparent", border_color="#666666", text_color="#666666", command=self.destroy)
        cancel_btn.pack(side="right")
        
        # Обработчик закрытия крестиком
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    # === ВАШ МЕТОД toggle_section БЕЗ ИЗМЕНЕНИЙ ===
    def toggle_section(self, button, content):
        is_currently_open = (self.currently_open_button == button)
        if self.currently_open_button and not is_currently_open:
            prev_content = self.accordion_data[self.currently_open_button]["content"]
            prev_content.pack_forget()
            self.currently_open_button.configure(fg_color="#34495e")
        if is_currently_open:
            content.pack_forget()
            button.configure(fg_color="#34495e")
            self.currently_open_button = None
            new_geo = self._calculate_window_geometry(collapsed=True)
        else:
            content.pack(fill="x", padx=5, pady=(0, 5), anchor="center")
            button.configure(fg_color="#2c3e50")
            self.currently_open_button = button
            new_geo = self._calculate_window_geometry(collapsed=False)
        self.after_idle(lambda g=new_geo: self.geometry(g))

    # === ВАШИ МЕТОДЫ _calculate... и _build... БЕЗ ИЗМЕНЕНИЙ ===
    def _calculate_window_geometry(self, collapsed=False):
        current_width = self.winfo_width()
        if current_width == 1:
            current_width = 650
        header_height = 50 * 3 + 30 
        
        if collapsed or not self.currently_open_button:
            target_height = header_height + 80
        else:
            data = self.accordion_data[self.currently_open_button]
            content_widget = data["content"]
            self.update_idletasks()
            open_content_height = content_widget.winfo_reqheight()
            target_height = header_height + open_content_height + 120
        max_h = self.winfo_screenheight() - 50
        final_height = min(target_height, max_h)
        return f"{current_width}x{final_height}"

    def _build_project_selector(self, container):
        frame = ctk.CTkFrame(container)
        frame.pack(fill="x", pady=(0, 10))
        label_project = ctk.CTkLabel(frame, text="Выбрать проект:", anchor="w")
        label_project.pack(side="left", padx=(0, 10))
        projects_list = ["Проект Альфа", "Бета-версия 2.0", "Гамма-отчет", "Тестовый стенд"]
        
        self.project_selector = ctk.CTkComboBox(
            frame,
            values=projects_list,
            state="readonly",
            width=250
        )
        self.project_selector.set(projects_list[0])
        self.project_selector.pack(side="left")

    def _create_accordion_header(self, parent, title):
        item_container = ctk.CTkFrame(parent, fg_color="#f0f2f5", border_color="#d1d8e0", border_width=1, corner_radius=8)
        item_container.pack(fill="x", pady=5)
        header_btn = ctk.CTkButton(
            item_container,
            text=title,
            height=45,
            font=ctk.CTkFont(weight="bold", size=16),
            anchor="w",
            fg_color="#34495e",
            hover_color="#3c5a72",
        )
        header_btn.pack(fill="x")
        content_frame = ctk.CTkFrame(item_container, fg_color="transparent", corner_radius=0)
        return {"button": header_btn, "content": content_frame}

    # === ВАШИ МЕТОДЫ НАПОЛНЕНИЯ (_fill_...) ОСТАЮТСЯ ПРЕЖНИМИ ===
    def _fill_events_section(self, frame):
        form = ctk.CTkFrame(frame, fg_color="transparent")
        form.pack(fill="x", expand=True, padx=10, pady=10)
        form.grid_columnconfigure(1, weight=1)
        # ... (Весь ваш огромный код формы со Treeview остается здесь без изменений) ...
        lbl_group = ctk.CTkLabel(form, text="Выбрать группу:", anchor="w")
        lbl_group.grid(row=0, column=0, sticky="w", pady=(0, 10), padx=(0, 10))
        cmb_group = ctk.CTkComboBox(form, values=["Группа А", "Группа Б", "Складской учет"], width=200)
        cmb_group.set("Группа А")
        cmb_group.grid(row=0, column=1, sticky="ew", pady=(0, 10))
        
        
        
    def _fill_charts_section(self, frame):
        if DateEntry is None:
            error_lbl = ctk.CTkLabel(frame, text="Ошибка: установите библиотеку\npip install tkcalendar", text_color="red")
            error_lbl.pack(expand=True)
            return
        
    
    def _fill_print_section(self, frame):
        if DateEntry is None:
            error_lbl = ctk.CTkLabel(frame, text="Ошибка: установите библиотеку\npip install tkcalendar", text_color="red")
            error_lbl.pack(expand=True)
            return
        
        
    def _generate_word_report(self, group, report_date, orientation):
        if docx is None:
            messagebox.showerror("Библиотека не найдена", "Для экспорта нужна библиотека 'python-docx'.\nВыполните:\npip install python-docx")
            return
        

    def on_closing(self):
        # Ваша логика очистки потоков...
        try:
            self.after_cancel("all")
        except Exception:
            pass
        for item in list(self.accordion_data.values()):
            try:
                content = item.get("content")
                if content:
                    content.destroy()
            except Exception:
                pass
        try:
            self.destroy()
        except Exception:
            pass
        # Уходим мягко, если это было отдельное приложение, или просто закрываемся
        # os._exit(0) ЗДЕСЬ НЕ НУЖНО, ТАК КАК МЫ ВНУТРИ ОСНОВНОГО ОКНА