""" Dialog Aggancio Multiplo/Listino. Chiede se i titoli fanno parte di un listino (SI/NO), eventualmente mostra il dropdown fornitori, poi PROSEGUI. Contiene anche SalvaFornitoreDialog — dialog "Esporta listino". """ import json import logging import time from pathlib import Path from PyQt6.QtWidgets import ( QApplication, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QFrame, QMessageBox, QRadioButton, QButtonGroup, QSizePolicy, ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont from app.modules.linker.src.gui.dialog_styles import ( BTN_PRIMARY, BTN_SECONDARY, INPUT, DIALOG_BG, TEXT_MAIN, TEXT_SUB, TEXT_MUTED, BORDER, ) logger = logging.getLogger(__name__) # ------------------------------------------------------------------ # # Cache fornitori # # ------------------------------------------------------------------ # _CACHE_FILE = Path(__file__).parents[2] / 'fornitori_cache.json' _CACHE_TTL = 86400 def _load_fornitori(linker_db) -> list: if _CACHE_FILE.exists(): try: with open(_CACHE_FILE, 'r', encoding='utf-8') as f: data = json.load(f) if time.time() - data.get('ts', 0) < _CACHE_TTL: return data.get('fornitori', []) except Exception: pass fornitori = [] QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: df = linker_db.get_fornitori() for _, row in df.iterrows(): v = str(row.get('FORNITORE', '') or '').strip() if v: fornitori.append(v) with open(_CACHE_FILE, 'w', encoding='utf-8') as f: json.dump({'ts': time.time(), 'fornitori': fornitori}, f) except Exception as e: logger.error(f"Errore caricamento fornitori: {e}") finally: QApplication.restoreOverrideCursor() return fornitori # ------------------------------------------------------------------ # # AgganciaDialog # # ------------------------------------------------------------------ # class AgganciaDialog(QDialog): def __init__(self, linker_db, titles: list = None, parent=None): super().__init__(parent) self.linker_db = linker_db self._titles = titles or [] self._is_listino = False self._fornitore = None self._is_seriale = False self.setWindowTitle("Avvia aggancio") self.setModal(True) self.setFixedWidth(460) self.setStyleSheet(DIALOG_BG) self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) self._setup_ui() # ---------------------------------------------------------------- # def _setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(24, 24, 24, 20) # Titolo title = QLabel("I titoli fanno parte di un listino?") title.setFont(QFont('Segoe UI', 13, QFont.Weight.Bold)) title.setStyleSheet(f'color: {TEXT_MAIN}; margin-bottom: 6px;') layout.addWidget(title) sub = QLabel("Seleziona il tipo di aggancio da eseguire.") sub.setStyleSheet(f'color: {TEXT_SUB}; font-size: 12px; margin-bottom: 16px;') layout.addWidget(sub) # ── Opzione Sì ────────────────────────────────────────────── self._card_si = self._make_option_card( 'Sì, fa parte di un listino', 'Associa i titoli a un fornitore specifico', 'si', ) self._card_no = self._make_option_card( 'No, ricerca libera', 'Aggancia i titoli senza associare un fornitore', 'no', ) layout.addWidget(self._card_si) layout.addSpacing(8) layout.addWidget(self._card_no) # ── Sezione fornitore (visibile solo se Sì) ───────────────── self._section_fornitore = QFrame() self._section_fornitore.setVisible(False) sec_layout = QVBoxLayout(self._section_fornitore) sec_layout.setContentsMargins(0, 14, 0, 0) sec_layout.setSpacing(8) sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet(f'color: {BORDER};') sec_layout.addWidget(sep) lbl_forn = QLabel('FORNITORE') lbl_forn.setStyleSheet(f'color: {TEXT_MUTED}; font-size: 11px; font-weight: 700; letter-spacing: 0.8px;') sec_layout.addWidget(lbl_forn) self.fornitore_combo = QComboBox() self.fornitore_combo.setStyleSheet(INPUT) self.fornitore_combo.addItem("— Nessun fornitore —", None) sec_layout.addWidget(self.fornitore_combo) self._lbl_suggerito = QLabel() self._lbl_suggerito.setStyleSheet( f'color: #43A047; font-size: 11px; font-style: italic;' ) self._lbl_suggerito.hide() sec_layout.addWidget(self._lbl_suggerito) lbl_tipo = QLabel('TIPO PRODOTTI') lbl_tipo.setStyleSheet(f'color: {TEXT_MUTED}; font-size: 11px; font-weight: 700; letter-spacing: 0.8px; margin-top: 4px;') sec_layout.addWidget(lbl_tipo) tipo_row = QHBoxLayout() tipo_row.setSpacing(20) self._tipo_group = QButtonGroup(self) self.rb_singoli = QRadioButton("Singoli (Film / TV Movie)") self.rb_seriali = QRadioButton("Seriali (Serie TV, Miniserie)") for rb in (self.rb_singoli, self.rb_seriali): rb.setStyleSheet(f'color: {TEXT_MAIN}; font-size: 12px;') self._tipo_group.addButton(rb) tipo_row.addWidget(rb) self.rb_singoli.setChecked(True) tipo_row.addStretch() sec_layout.addLayout(tipo_row) layout.addWidget(self._section_fornitore) # ── Bottoni ───────────────────────────────────────────────── layout.addSpacing(20) btn_row = QHBoxLayout() btn_row.setSpacing(10) btn_cancel = QPushButton("Annulla") btn_cancel.setStyleSheet(BTN_SECONDARY) btn_cancel.clicked.connect(self.reject) self._btn_prosegui = QPushButton("Prosegui →") self._btn_prosegui.setStyleSheet(BTN_PRIMARY) self._btn_prosegui.setEnabled(False) self._btn_prosegui.clicked.connect(self._on_prosegui) btn_row.addStretch() btn_row.addWidget(btn_cancel) btn_row.addWidget(self._btn_prosegui) layout.addLayout(btn_row) self._selected = None # 'si' | 'no' # ---------------------------------------------------------------- # def _make_option_card(self, label: str, sublabel: str, value: str) -> QFrame: card = QFrame() card.setObjectName(f'card_{value}') card.setStyleSheet( f'QFrame#card_{value} {{ border: 1.5px solid #DDE1EE; border-radius: 10px; background: white; }}' ) card.setCursor(Qt.CursorShape.PointingHandCursor) card.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) row = QHBoxLayout(card) row.setContentsMargins(16, 13, 16, 13) row.setSpacing(12) # Radio circle radio = QFrame() radio.setObjectName('radio') radio.setFixedSize(16, 16) radio.setStyleSheet( f'border: 1.5px solid #B0B8C8; border-radius: 8px; background: white;' ) row.addWidget(radio, 0, Qt.AlignmentFlag.AlignVCenter) col = QVBoxLayout() col.setSpacing(1) main_lbl = QLabel(label) main_lbl.setFont(QFont('Segoe UI', 11, QFont.Weight.Bold)) main_lbl.setStyleSheet(f'color: {TEXT_MAIN};') sub_lbl = QLabel(sublabel) sub_lbl.setStyleSheet(f'color: {TEXT_SUB}; font-size: 11px;') col.addWidget(main_lbl) col.addWidget(sub_lbl) row.addLayout(col, 1) card.mousePressEvent = lambda _, v=value: self._select_option(v) return card def _select_option(self, value: str): self._selected = value for card, v in ((self._card_si, 'si'), (self._card_no, 'no')): selected = (v == value) card.setStyleSheet( f'QFrame#card_{v} {{ border: 2px solid #5C6BC0; border-radius: 10px; background: #EEF0FB; }}' if selected else f'QFrame#card_{v} {{ border: 1.5px solid #DDE1EE; border-radius: 10px; background: white; }}' ) radio = card.findChild(QFrame, 'radio') if radio: if selected: radio.setStyleSheet( f'border: 1.5px solid #5C6BC0; border-radius: 8px;' f'background: #5C6BC0;' ) else: radio.setStyleSheet( f'border: 1.5px solid #B0B8C8; border-radius: 8px; background: white;' ) show_fornitore = (value == 'si') if show_fornitore and self.fornitore_combo.count() == 1: QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: for f in _load_fornitori(self.linker_db): self.fornitore_combo.addItem(f, f) self._try_suggest_fornitore() finally: QApplication.restoreOverrideCursor() self._section_fornitore.setVisible(show_fornitore) self._btn_prosegui.setEnabled(True) self.adjustSize() def _try_suggest_fornitore(self): if not self._titles: return fornitore, cnt = self.linker_db.guess_fornitore(self._titles) if not fornitore: return idx = self.fornitore_combo.findData(fornitore) if idx >= 0: self.fornitore_combo.setCurrentIndex(idx) self._lbl_suggerito.setText( f'Suggerito in base a {cnt} titoli trovati in archivio' ) self._lbl_suggerito.show() def _on_prosegui(self): if self._selected == 'si': self._is_listino = True idx = self.fornitore_combo.currentIndex() self._fornitore = self.fornitore_combo.itemData(idx) self._is_seriale = self.rb_seriali.isChecked() else: self._is_listino = False self._fornitore = None self._is_seriale = False self.accept() def get_result(self): return self._is_listino, self._fornitore, self._is_seriale # ------------------------------------------------------------------ # # SalvaFornitoreDialog # # ------------------------------------------------------------------ # class SalvaFornitoreDialog(QDialog): def __init__(self, linker_db, current_fornitore: str = None, parent=None): super().__init__(parent) self.linker_db = linker_db self._current_fornitore = current_fornitore or '' self._fornitore = None self.setWindowTitle("Salva agganci") self.setModal(True) self.setFixedWidth(420) self.setStyleSheet(DIALOG_BG) self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) self._setup_ui() self._load_fornitori_into_combo() def _setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(24, 24, 24, 20) title = QLabel("Salva nel repository") title.setFont(QFont('Segoe UI', 13, QFont.Weight.Bold)) title.setStyleSheet(f'color: {TEXT_MAIN}; margin-bottom: 6px;') layout.addWidget(title) sub = QLabel("Seleziona o inserisci il nome del fornitore/listino.") sub.setStyleSheet(f'color: {TEXT_SUB}; font-size: 12px; margin-bottom: 16px;') layout.addWidget(sub) lbl = QLabel('FORNITORE / LISTINO') lbl.setStyleSheet(f'color: {TEXT_MUTED}; font-size: 11px; font-weight: 700; letter-spacing: 0.8px;') layout.addWidget(lbl) layout.addSpacing(6) self.fornitore_combo = QComboBox() self.fornitore_combo.setEditable(True) self.fornitore_combo.setStyleSheet(INPUT) self.fornitore_combo.setMinimumWidth(300) layout.addWidget(self.fornitore_combo) layout.addSpacing(20) btn_row = QHBoxLayout() btn_row.setSpacing(10) btn_cancel = QPushButton("Annulla") btn_cancel.setStyleSheet(BTN_SECONDARY) btn_cancel.clicked.connect(self.reject) btn_ok = QPushButton("Salva →") btn_ok.setStyleSheet(BTN_PRIMARY) btn_ok.setDefault(True) btn_ok.clicked.connect(self._on_prosegui) btn_row.addStretch() btn_row.addWidget(btn_cancel) btn_row.addWidget(btn_ok) layout.addLayout(btn_row) def _load_fornitori_into_combo(self): try: for f in _load_fornitori(self.linker_db): self.fornitore_combo.addItem(f) except Exception as e: logger.error(f"Errore caricamento fornitori: {e}") if self._current_fornitore: idx = self.fornitore_combo.findText(self._current_fornitore) if idx >= 0: self.fornitore_combo.setCurrentIndex(idx) else: self.fornitore_combo.setCurrentText(self._current_fornitore) def _on_prosegui(self): value = self.fornitore_combo.currentText().strip() if not value: QMessageBox.warning(self, "Attenzione", "Inserire il nome del fornitore/listino.") return self._fornitore = value self.accept() def get_fornitore(self) -> str: return self._fornitore