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


# dukora/modules/manager/manager_metrics_panel.py
import customtkinter as ctk
from tkinter import ttk, messagebox, filedialog
import tkinter as tk
import pyperclip
from docx import Document
from modules.manager.manager_metrics_work import (
    get_all_indicators_by_group, insert_indicator, delete_indicator_by_code, 
    IndicatorDTO, generate_unique_code, save_docx_template
)
from modules.manager.manager_group_work import list_groups as group_list

class ManagerMetricsPanel(ctk.CTkFrame):
    def __init__(self, master):
        super().__init__(master)
        
        self.parent_main = master
        self.group_names_to_codes = {}

        # === ГЛАВНАЯ СЕТКА ОКНА ===
        self.grid_columnconfigure(0, weight=1) 
        self.grid_columnconfigure(1, weight=1)
        
        # Уменьшена доля высоты таблицы 
        self.grid_rowconfigure(0, weight=0)  # Группа
        self.grid_rowconfigure(1, weight=2)  # Поле ввода (базовая единица высоты)
        self.grid_rowconfigure(2, weight=0)  # Кнопки действий
        self.grid_rowconfigure(3, weight=1)  # Таблица (теперь имеет такой же вес, но она сжата)
        self.grid_rowconfigure(4, weight=0)  # Нижние кнопки
        
        # === 1. СТРОКА: ГРУППА ===
        ctk.CTkLabel(self, text="Группа:").grid(row=0, column=0, sticky="w", padx=(10, 5), pady=(10, 0))
        self.group_var = ctk.StringVar(value="")
        self.group_combobox = ctk.CTkComboBox(self)
        self.group_combobox.grid(row=0, column=0, columnspan=2, sticky="ew", padx=10, pady=(10, 0))
        self.group_combobox.configure(state="readonly")

        # === 2. СТРОКА: НАЗВАНИЕ ПОКАЗАТЕЛЯ  ===
        input_frame = ctk.CTkFrame(self)
        # Увеличено пространство снизу для отделения от кнопок
        input_frame.grid(row=1, column=0, columnspan=2, sticky="ew", padx=10, pady=(5, 10))
        
        input_frame.grid_columnconfigure(0, weight=1)
        input_frame.grid_columnconfigure(1, weight=0)

        ctk.CTkLabel(input_frame, text="Название показателя:", anchor="w").grid(row=0, column=0, sticky="w")
        # Было height=3, стало height=4 (на 1 строчку выше)
        self.name_entry = ctk.CTkTextbox(input_frame, height=3)
        self.name_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5), pady=5)

        paste_btn = ctk.CTkButton(input_frame, text="Вставить", fg_color="#2B2B2B", hover_color="#3C3C3C")
        paste_btn.grid(row=0, column=1, sticky="nsew", padx=(5, 0), pady=5)

        self.name_entry.bind("<Button-3>", self._paste_via_pyperclip)

        # === 3. СТРОКА: КНОПКИ ДЕЙСТВИЙ ===
        action_frame = ctk.CTkFrame(self)
        action_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=10, pady=5)
        
        for i in range(3):
            action_frame.grid_columnconfigure(i, weight=1, uniform="actions")

        ctk.CTkButton(action_frame, text="Добавить", fg_color="#3B82F6", command=self.add_indicator).pack(side="left", fill="both", expand=True, padx=2, pady=2)
        ctk.CTkButton(action_frame, text="Редактировать", fg_color="#10B981", command=self.edit_indicator).pack(side="left", fill="both", expand=True, padx=2, pady=2)
        ctk.CTkButton(action_frame, text="Удалить", fg_color="#EF4444", command=self.delete_indicator).pack(side="left", fill="both", expand=True, padx=2, pady=2)

        # === 4. СТРОКА: ТАБЛИЦА ===
        tree_container = ctk.CTkFrame(self)
        tree_container.grid(row=3, column=0, columnspan=2, sticky="nsew", padx=10, pady=(0, 10))
        
        tree_container.grid_columnconfigure(0, weight=1)
        tree_container.grid_rowconfigure(0, weight=1)
        
        columns = ("number", "name", "code")
        
        self.indicators_tree = ttk.Treeview(tree_container, columns=columns, show="headings", selectmode="browse")
        self.indicators_tree.pack(fill="both", expand=True, side="left")
        
        scrollbar = tk.Scrollbar(tree_container, orient="vertical", command=self.indicators_tree.yview)
        scrollbar.pack(side="right", fill="y")
        self.indicators_tree.configure(yscrollcommand=scrollbar.set)
        
        # ПЕРЕНОС И ВЫСОТА СТРОК:
         
        self.indicators_tree.heading("number", text="№"); self.indicators_tree.column("number", width=60, anchor="center", stretch=False)
        self.indicators_tree.heading("name", text="Название"); self.indicators_tree.column("name", anchor="w")
        self.indicators_tree.heading("code", text="Код"); self.indicators_tree.column("code", width=self._get_pixels_for_chars(10), anchor="w", stretch=False)

        # === 5. СТРОКА: НИЖНИЕ КНОПКИ ===
        bottom_frame = ctk.CTkFrame(self)
        bottom_frame.grid(row=4, column=0, columnspan=2, sticky="e", padx=10, pady=(0, 10))
        
        ctk.CTkButton(bottom_frame, text="Экспорт в WORD", fg_color="#2B2B2B", hover_color="#3C3C3C", command=self.print_to_docx).pack(side="right", padx=5)

        # Инициализация данных
        self.update_group_combobox()
        self.update_indicators_list()

    def _get_pixels_for_chars(self, char_count: int) -> int:
        return char_count * 8 + 20

    def update_group_combobox(self):
        groups = group_list()
        self.group_names_to_codes.clear()
        for group in groups:
            self.group_names_to_codes[group.name] = getattr(group, 'code', '')
        
        group_names = [g.name for g in groups]
        self.group_combobox.configure(values=group_names)
        if group_names: 
            self.group_combobox.set(group_names[0])
            self.update_indicators_list()

    def update_indicators_list(self, event=None):
        selected_group_name = self.group_combobox.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)
            
        indicators = []
        if selected_group_code:
            indicators = get_all_indicators_by_group(selected_group_code)
            
        for idx, ind in enumerate(indicators, start=1):
            val_name = getattr(ind, 'name', '--')
            val_code = getattr(ind, 'code', '--')
            
            # Перенос каждые 20 знаков (вместо 30), чтобы лучше влезать в половину ширины
            wrapped_name = '\n'.join([val_name[i:i+20] for i in range(0, len(val_name), 20)])
            
            self.indicators_tree.insert("", "end", iid=val_code, values=(f"{idx}.", wrapped_name, val_code))

    def _paste_via_pyperclip(self, event=None):
        try:
            clipboard_text = pyperclip.paste()
            if clipboard_text:
                self.name_entry.insert("insert", clipboard_text)
        except Exception:
            pass
        return "break"

    def add_indicator(self):
        group_name = self.group_combobox.get()
        group_code = self.group_names_to_codes.get(group_name)
        indicator_name = self.name_entry.get("1.0", "end").rstrip("\n").strip()
        
        if not group_code or not indicator_name:
            messagebox.showwarning("Ошибка", "Выберите группу и введите название!")
            return
            
        new_code = generate_unique_code(group_code)
        try:
            insert_indicator(new_code, indicator_name, group_code)
            messagebox.showinfo("Готово", "Индикатор добавлен.")
            self.update_indicators_list()
            self.name_entry.delete("1.0", "end")
        except Exception as e:
            messagebox.showerror("Ошибка", str(e))

    def edit_indicator(self):
        selected = self.indicators_tree.selection()
        if not selected:
            messagebox.showwarning("Ошибка", "Выберите показатель."); return
            
        code = selected[0]
        item = self.indicators_tree.item(code)
        current_name = item["values"][1].replace('\n', ' ')
        
        self.name_entry.delete("1.0", "end")
        self.name_entry.insert("1.0", current_name)

    def delete_indicator(self):
        selected = self.indicators_tree.selection()
        if not selected:
            messagebox.showwarning("Ошибка", "Выберите показатель."); return
            
        code = selected[0]
        if messagebox.askyesno("Подтверждение", f"Удалить показатель?"):
            try:
                delete_indicator_by_code(code)
                messagebox.showinfo("Готово", "Удалено успешно.")
                self.update_indicators_list()
                self.name_entry.delete("1.0", "end")
            except Exception as e:
                messagebox.showerror("Ошибка", str(e))

    def print_to_docx(self):
        group_name = self.group_combobox.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
            
        from datetime import date
        safe_group_name = group_name.replace(' ', '_')
        filename = f"{safe_group_name}_{date.today().strftime('%Y-%m-%d')}.docx"
        
        path = filedialog.asksaveasfilename(
            initialfile=filename, 
            defaultextension=".docx",
            filetypes=[("Word files", "*.docx"), ("All files", "*.*")]
        )
        if not path: return
            
        try:
            save_docx_template(group_name, indicators, path)
            messagebox.showinfo("Успех", f"Файл успешно сохранён:\n{path}")
        except Exception as e:
            messagebox.showerror("Ошибка записи", f"Не удалось сохранить файл:\n{e}")

    def on_close(self):
        self.destroy()