Files
IPSExtractor/gui/tabs/results_tab.py
T

259 lines
10 KiB
Python

"""Pestaña de Resultados de la aplicación GUI."""
import os
import customtkinter as ctk
import tkinter.ttk as ttk
from gui.styles import AppStyles
class ResultsTab:
"""Encapsula la UI de la pestaña Resultados."""
def __init__(self, parent_tab):
"""Inicializa la pestaña de Resultados.
Args:
parent_tab: El CTkFrame que representa la pestaña.
"""
self.tab = parent_tab
self.fonts = AppStyles.get_fonts()
self.colors = AppStyles.get_colors()
self.spacing = AppStyles.get_spacing()
self._setup_layout()
self._create_subtabs()
self._setup_treeview_style()
self._create_extractor_tab()
self._create_preclass_results_tab()
def _setup_layout(self):
"""Configura el layout base de la pestaña."""
self.tab.grid_columnconfigure(0, weight=1)
self.tab.grid_rowconfigure(0, weight=1)
self.tabs_res = ctk.CTkTabview(self.tab)
self.tabs_res.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
def _create_subtabs(self):
"""Crea las sub-pestañas de Resultados."""
self.tab_res_extractor = self.tabs_res.add("Extractor (Próximamente)")
self.tab_res_preclass = self.tabs_res.add("Preclasificación")
def _setup_treeview_style(self):
"""Configura el estilo de los treeviews."""
style = ttk.Style()
style.theme_use("clam")
style.configure("Treeview", rowheight=25)
def _create_extractor_tab(self):
"""Crea la sub-pestaña del Extractor (en construcción)."""
lbl_const = ctk.CTkLabel(
self.tab_res_extractor,
text="En construcción...",
font=ctk.CTkFont(size=20)
)
lbl_const.place(relx=0.5, rely=0.5, anchor="center")
def _create_preclass_results_tab(self):
"""Crea la sub-pestaña de resultados de preclasificación."""
self.tab_res_preclass.grid_columnconfigure(0, weight=1)
self.tab_res_preclass.grid_rowconfigure(1, weight=1)
self._create_filters_section()
self._create_tables_section()
self._create_actions_section()
def _create_filters_section(self):
"""Crea la sección de filtros."""
self.frame_res_filters = ctk.CTkFrame(self.tab_res_preclass)
self.frame_res_filters.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
ctk.CTkLabel(self.frame_res_filters, text="Run ID:").grid(row=0, column=0, padx=5, pady=5)
self.combo_res_run = ctk.CTkComboBox(self.frame_res_filters, values=["Todos"], width=150)
self.combo_res_run.grid(row=0, column=1, padx=5, pady=5)
ctk.CTkLabel(self.frame_res_filters, text="Estado:").grid(row=0, column=2, padx=5, pady=5)
self.combo_res_status = ctk.CTkComboBox(
self.frame_res_filters,
values=["Todos", "MATCH", "NO_MATCH", "ERROR", "NO_FILES"],
width=120
)
self.combo_res_status.grid(row=0, column=3, padx=5, pady=5)
self.entry_res_search = ctk.CTkEntry(
self.frame_res_filters,
placeholder_text="Buscar radicado/archivo...",
width=200
)
self.entry_res_search.grid(row=0, column=4, padx=5, pady=5)
self.btn_res_search = ctk.CTkButton(self.frame_res_filters, text="Buscar", width=100)
self.btn_res_search.grid(row=0, column=5, padx=10, pady=5)
def _create_tables_section(self):
"""Crea la sección con las tablas (principal y de detalle)."""
self.frame_res_table = ctk.CTkFrame(self.tab_res_preclass, fg_color="transparent")
self.frame_res_table.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
self.frame_res_table.grid_columnconfigure(0, weight=1)
self.frame_res_table.grid_rowconfigure(0, weight=3)
self.frame_res_table.grid_rowconfigure(2, weight=2)
self._create_main_results_table()
self._create_detail_label()
self._create_matches_table()
def _create_main_results_table(self):
"""Crea la tabla principal de resultados."""
cols = ("ID", "Run", "Fecha", "Radicado", "Estado", "Archivo")
self.tree_results = ttk.Treeview(
self.frame_res_table,
columns=cols,
show="headings",
selectmode="browse"
)
for c in cols:
self.tree_results.heading(c, text=c)
self.tree_results.column(c, width=100)
self.tree_results.column("Archivo", width=300)
self.tree_results.column("ID", width=50)
self.tree_results.column("Run", width=50)
# Scrollbars
sb_y = ttk.Scrollbar(self.frame_res_table, orient="vertical", command=self.tree_results.yview)
sb_x = ttk.Scrollbar(self.frame_res_table, orient="horizontal", command=self.tree_results.xview)
self.tree_results.configure(yscrollcommand=sb_y.set, xscrollcommand=sb_x.set)
self.tree_results.grid(row=0, column=0, sticky="nsew")
sb_y.grid(row=0, column=1, sticky="ns")
sb_x.grid(row=1, column=0, sticky="ew")
def _create_detail_label(self):
"""Crea la etiqueta de la sección de detalle."""
lbl_detail = ctk.CTkLabel(
self.frame_res_table,
text="Detalle de Matches (Seleccionar fila arriba)",
font=ctk.CTkFont(size=12, weight="bold")
)
lbl_detail.grid(row=1, column=0, sticky="w", pady=(10, 5))
def _create_matches_table(self):
"""Crea la tabla de detalle de matches."""
cols_detail = ("Key", "Valor Encontrado", "Existe", "Valor Esperado")
self.tree_matches = ttk.Treeview(
self.frame_res_table,
columns=cols_detail,
show="headings",
selectmode="browse",
height=5
)
for c in cols_detail:
self.tree_matches.heading(c, text=c)
self.tree_matches.column(c, width=150)
sb_detail_y = ttk.Scrollbar(self.frame_res_table, orient="vertical", command=self.tree_matches.yview)
self.tree_matches.configure(yscrollcommand=sb_detail_y.set)
self.tree_matches.grid(row=2, column=0, sticky="nsew")
sb_detail_y.grid(row=2, column=1, sticky="ns")
def _create_actions_section(self):
"""Crea la sección de acciones."""
self.frame_res_actions = ctk.CTkFrame(self.tab_res_preclass)
self.frame_res_actions.grid(row=2, column=0, sticky="ew", padx=10, pady=10)
self.btn_res_export = ctk.CTkButton(
self.frame_res_actions,
text="Exportar Vista a Excel",
fg_color=self.colors["success"]
)
self.btn_res_export.pack(side="right", padx=10, pady=5)
self.btn_res_organize = ctk.CTkButton(
self.frame_res_actions,
text="Organizar Carpetas (Run)",
fg_color=self.colors["warning"],
hover_color=self.colors["warning_hover"]
)
self.btn_res_organize.pack(side="right", padx=10, pady=5)
self.btn_res_refresh = ctk.CTkButton(self.frame_res_actions, text="Refrescar Runs")
self.btn_res_refresh.pack(side="left", padx=10, pady=5)
# --- API Pública ---
def set_on_search(self, callback):
"""Vincula el callback al botón de búsqueda."""
self.btn_res_search.configure(command=callback)
def set_on_export(self, callback):
"""Vincula el callback al botón de exportación."""
self.btn_res_export.configure(command=callback)
def set_on_refresh(self, callback):
"""Vincula el callback al botón de refrescar."""
self.btn_res_refresh.configure(command=callback)
def set_on_organize(self, callback):
"""Vincula el callback al botón de organizar."""
self.btn_res_organize.configure(command=callback)
def set_on_select_result(self, callback):
"""Vincula el callback al evento de selección de fila."""
self.tree_results.bind("<<TreeviewSelect>>", callback)
def get_selected_result_values(self) -> tuple | None:
"""Devuelve los valores de la fila seleccionada o None si no hay selección."""
selection = self.tree_results.selection()
if not selection:
return None
return self.tree_results.item(selection[0], "values") or None
def update_matches_table(self, rows):
"""Actualiza la tabla de matches con los datos proporcionados."""
for item in self.tree_matches.get_children():
self.tree_matches.delete(item)
for r in rows:
exists_str = "✅" if r.get("exists_bool") else "❌"
vals = (r.get("key"), r.get("found_value"), exists_str, r.get("expected_value") or "-")
self.tree_matches.insert("", "end", values=vals)
def update_runs_combo(self, runs: list):
"""Actualiza el combo box con los IDs de ejecuciones disponibles."""
vals = ["Todos"] + [str(r) for r in runs]
self.combo_res_run.configure(values=vals)
self.combo_res_run.set("Todos")
def set_run_id(self, run_id: int | str):
"""Establece el ID de ejecución seleccionado."""
try:
self.combo_res_run.set(str(run_id))
except Exception:
pass
def get_filters(self) -> dict:
"""Devuelve los filtros actuales."""
return {
"run_id": self.combo_res_run.get(),
"status": self.combo_res_status.get(),
"text": self.entry_res_search.get()
}
def update_results_table(self, rows):
"""Actualiza la tabla de resultados con los datos proporcionados."""
for item in self.tree_results.get_children():
self.tree_results.delete(item)
for r in rows:
vals = (
r.get("id"),
r.get("run_id"),
r.get("timestamp"),
r.get("radicado"),
r.get("status"),
os.path.basename(r.get("file_path") or "")
)
self.tree_results.insert("", "end", values=vals)