: i6dZddlZddlmZddlmZmZmZmZm Z m Z m Z ddl m Z mZmZddlmZmZmZddlmZddlmZdd lmZejeZGd d eZdS) u Base Dashboard Class ==================== Classe base per tutti i dashboard della MyICR Suite. Elimina duplicazione di codice fornendo funzionalità comuni. Pattern comuni estratti: - Gestione filter panel - Creazione action buttons (reset, fullscreen, export) - Export to Excel - Thread management per data loading - Loading overlay - Signal connections base - Lifecycle methods (page_activated/deactivated) N)Path)QWidget QVBoxLayout QHBoxLayoutQFrame QPushButton QFileDialog QMessageBox)Qt pyqtSignalQSize)QIcon QKeySequence QShortcut)LoadingOverlay) resource_path)export_qtablewidget_to_excelc$eZdZdZeeeeZeeeee Z d"de ffd Z defdZ defdZ defdZd Zd e fd Zd Zd ZdZdZdZdededefdZd#dededefdZdZdZdZdZdZdZ dZ!de"fdZ#d Z$fd!Z%xZ&S)$ BaseDashboardus Classe base per dashboard con funzionalità comuni. Le sottoclassi devono implementare: - get_dashboard_title() -> str - get_export_filename() -> str - create_main_content() -> tuple (summary_widget, detail_widget) Opzionalmente possono override: - setup_custom_signals() - on_filters_applied(filters: dict) - cleanup_threads() N filter_datac\t||_|j|_|pi|_i|_d|_d|_d|_d|_ d|_ d|_ d|_ d|_ g|_td|jjddS)Nu📊 z inizializzato.)super__init__ main_windowthemercurrent_filters data_thread detail_thread filter_panelloading_overlay detail_table reset_buttonfullscreen_button export_button _shortcutsloggerinfo __class____name__)selfrrr(s 4C:\PYTHON\MyICR_Suite\app\features\base_dashboard.pyrzBaseDashboard.__init__5s & & &," "!!# !!%! DDN3DDDEEEEEreturnc td)z;Ritorna il titolo del dashboard (es. 'Dashboard Tipologie')z8Le sottoclassi devono implementare get_dashboard_title()NotImplementedErrorr*s r+get_dashboard_titlez!BaseDashboard.get_dashboard_titleQ!"\]]]r,c td)zLRitorna il nome file di default per export Excel (es. 'dettaglio_tipologie')z8Le sottoclassi devono implementare get_export_filename()r/r1s r+get_export_filenamez!BaseDashboard.get_export_filenameUr3r,c td)u Crea il contenuto principale del dashboard. Ritorna: (summary_widget, detail_widget) o solo detail_widget se non c'è summary. z8Le sottoclassi devono implementare create_main_content()r/r1s r+create_main_contentz!BaseDashboard.create_main_contentYs ""\]]]r,cdS)z7Override per collegare segnali custom della sottoclasseNr1s r+setup_custom_signalsz"BaseDashboard.setup_custom_signalsb r,filterscN||_td|dS)z(Override per gestire applicazione filtrizFiltri applicati: N)rr&r')r*r<s r+on_filters_appliedz BaseDashboard.on_filters_appliedfs+& 22233333r,cdS)z"Override per cleanup thread customNr9r1s r+cleanup_threadszBaseDashboard.cleanup_threadskr;r,cttd|}|j|j|j|tttjj |}|j|j |j|tttjj |}|j|j |j|t|jjddS)ao Configura keyboard shortcuts comuni per tutti i dashboard. Shortcuts implementati: - Ctrl+E: Export to Excel - Esc: Reset selection - F11: Toggle fullscreen - Ctrl+R: Refresh data (se implementato) Le sottoclassi possono chiamare questo metodo in init_ui() e poi aggiungere shortcuts custom. zCtrl+Ez : Keyboard shortcuts configuratiN)rr activatedconnect_shortcut_exportr%appendr Key Key_Escape_shortcut_resetKey_F11_shortcut_fullscreenr&debugr(r))r*export_shortcutreset_shortcutfullscreen_shortcuts r+setup_keyboard_shortcutsz&BaseDashboard.setup_keyboard_shortcutsqs$L$:$:DAA!))$*?@@@ ///#<0A#B#BDII (()=>>> ~...( RV^(D(DdKK%--d.GHHH 2333 /QQQRRRRRr,c|jrI|jr2td|dSdSdS)zHandler per Ctrl+Ez"Keyboard shortcut: Ctrl+E (Export)N)r$ isVisibler&r'export_detail_to_excelr1s r+rDzBaseDashboard._shortcut_exportsb   *$"4">">"@"@ * KK< = = =  ' ' ) ) ) ) ) * * * *r,c|jrI|jr2td|dSdSdS)zHandler per EsczKeyboard shortcut: Esc (Reset)N)r"rQr&r'reset_selectionr1s r+rHzBaseDashboard._shortcut_resetsb   #!2!!> # KK8 9 9 9  " " " " " # # # #r,c|jrI|jr2td|dSdSdS)zHandler per F11z#Keyboard shortcut: F11 (Fullscreen)N)r#rQr&r'open_detail_fullscreenr1s r+rJz"BaseDashboard._shortcut_fullscreensb  ! *d&<&F&F&H&H * KK= > > >  ' ' ) ) ) ) ) * * * *r, icon_nametooltipct}|||ddtd|}t |rS|tt|| tddn/dddd}| | |d| d |j d d d |j d dd|j ddd |j ddd|j ddd|j ddd|j ddd |j d dd|S)z Crea un action button stilizzato in stile Microsoft Office. Args: icon_name: Nome del file icona (senza path) tooltip: Tooltip del bottone Returns: QPushButton configurato z assets/icons/u🔄u⛶u📊)z reset.pngzfullscreen.pngz export.png?z= QPushButton { background-color: button_bgz#FAFAFAz$; border: 1px solid border_light#E1DFDDz; border-radius: 2px; padding: 4px; } QPushButton:hover { background-color: button_hover_bgz#F3F2F1 accent_bluez#0078D4zT; } QPushButton:pressed { background-color: button_pressed_bgz#EDEBE9zU; } QPushButton:disabled { background-color: background_disabledz; color: text_disabledz#A19F9Dz; } )r setToolTip setFixedSizerrexistssetIconrstr setIconSizer setTextget setStyleSheetr)r*rWrXbtn icon_path emoji_maps r+_create_action_buttonz#BaseDashboard._create_action_buttonsmm w R   ""=)"="=>>  ?? ! ! # # 7 KKc)nn-- . . . OOE"bMM * * * *$"'$I KK i55 6 6 6  #':>>+y#I#I  $(:>>.)#L#L  $(:>>2CY#O#O  $(:>>-#K#K  $(:>>2Ey#Q#Q  $(:>>2G#S#S    BB!  "$(:>>.)#L#L#      * r,rsrsrstitlemarginscpt}|tjj|d|jdddt|}|j|| d|rddl m }ddl m }||}|||jd d d |jj|d |jd dd|||S)z Crea un pannello stilizzato con bordo Microsoft Office. Args: title: Titolo opzionale del pannello margins: (left, top, right, bottom) Returns: QFrame configurato za QFrame { background-color: white; border: 1px solid r^r_z<; border-radius: 2px; } rsr)QLabel)QFont font_familyzSegoe UI zcolor: text_primaryz#323130z(; background: transparent; border: none;)r setFrameShapeShape StyledPanelrmrrlrsetContentsMargins setSpacingPyQt6.QtWidgetsrw PyQt6.QtGuirxsetFontWeightBold addWidget)r*rtrupanellayoutrwrx title_labels r+_create_styled_panelz"BaseDashboard._create_styled_panelsk FL4555  $(:>>.)#L#L      U##!!7++!  * . . . . . . ) ) ) ) ) ) &--K   djnn]J&O&OQSUZUaUf g g h h h  % %'D ~y0Y0Y'D'D'D E E E   [ ) ) ) r,c |jstj|dddS|jdkrtj|dddS|d}t j|d|d \}}|sdS t|j|tj|d d ||j d t|j d ddS#t$rl}td|d tj|ddt#||j dd dYd}~dSd}~wwxYw)zn Esporta la tabella di dettaglio in formato Excel. Metodo comune a tutti i dashboard. Attenzionez6Nessuna tabella di dettaglio disponibile per l'export.NrInfou;La tabella di dettaglio è vuota. Nessun dato da esportare.z.xlsxzSalva File ExcelzExcel Files (*.xlsx)Successoz Dati esportati con successo in: u✅ Export completato: TizErrore durante l'export Excel: )exc_infoErrorez)Impossibile esportare i dati. Dettagli: u❌ Export fallito)r!r warningrowCount informationr5r getSaveFileNamerstatus_updatedemitrname Exceptionr&errorcriticalri)r*default_filename file_path_es r+rRz$BaseDashboard.export_detail_to_excels     l4l m m m F   % % ' '1 , ,  #D&2o p p p F"6688???"2    "   1  F G ():I F F F  #?I??      $ $%UtI?S%U%UW[]a b b b b b G G G LL>1>>L N N N  Fc!ffFF      $ $%94 F F F F F F F F F Gs AC,, E"6A!EE"ctd|jr3|j|jd|jddddS)zY Resetta la selezione corrente. Override se serve logica custom. zReset selezioneru✅ Selezione resettataTN)r&r'r! clearContents setRowCountrrr1s r+rTzBaseDashboard.reset_selection%sp  %&&&   -   + + - - -   ) )! , , ,   !:D$GGGGGr,cftdtj|dddS)z Apre la vista fullscreen del dettaglio. Override nelle sottoclassi per specificare il page_id corretto. z;open_detail_fullscreen() non implementato nella sottoclasseruFFunzionalità fullscreen non ancora implementata per questo dashboard.N)r&rr rr1s r+rVz$BaseDashboard.open_detail_fullscreen0s? TUUU   T     r,cTt|jjddS)z*Chiamato quando la pagina diventa visibilez attivato.N)r&r'r(r)r1s r+page_activatedzBaseDashboard.page_activated>s' t~.:::;;; r,c|t|jjd|dS)z(Chiamato quando la pagina viene nascostaz disattivato.N)r&r'r(r)_cleanup_all_threadsr1s r+page_deactivatedzBaseDashboard.page_deactivatedCs: t~.===>>> !!#####r,cddlm}|jr||jdd|jr||jdd|dS)z Cleanup sicuro di tutti i thread attivi. Usa cancellazione graceful con fallback a terminate() solo se necessario. r)safe_terminate_threadrT)wait_msforceN)app.utils.cancelable_threadrrrr@)r*rs r+rz"BaseDashboard._cleanup_all_threadsHs FEEEEE   N ! !$"2D M M M M   P ! !$"4d$ O O O O r,cddlm}|}|}|D]4}||td|5|dS)z Ricarica i dati invalidando la cache. Questo metodo forza il ricaricamento dei dati dal database ignorando eventuali risultati cached. Utile quando si sa che i dati sono stati aggiornati esternamente. r)get_cache_managerz Cache invalidata per namespace: N)app.utils.cache_managerr"get_cache_namespaces_to_invalidateinvalidate_namespacer&r'on_refresh_requested)r*rcache namespaces namespaces r+refresh_data_with_cache_clearz+BaseDashboard.refresh_data_with_cache_clearYs >=====!!##<<>> # H HI  & &y 1 1 1 KKF9FF G G G G !!#####r,cgS)a Restituisce la lista dei namespace della cache da invalidare. Le sottoclassi dovrebbero override questo metodo per specificare quali namespace devono essere invalidati durante il refresh. Returns: Lista di namespace strings r9r1s r+rz0BaseDashboard.get_cache_namespaces_to_invalidateps  r,cTt|jjddS)z Callback chiamato quando viene richiesto un refresh dei dati. Le sottoclassi dovrebbero override questo metodo per implementare la logica di ricaricamento specifica (es. chiamare load_data()). z': on_refresh_requested non implementatoN)r&rr(r)r1s r+rz"BaseDashboard.on_refresh_requested|s* $.1ZZZ[[[[[r,cr|t|dS)z$Override del close event per cleanupN)rr closeEvent)r*eventr(s r+rzBaseDashboard.closeEvents3 !!### 5!!!!!r,)N)Nrr)'r) __module__ __qualname____doc__r riboolintrdictopen_new_tab_requestedrr2r5tupler7r:r>r@rOrDrHrJrrqrrrRrTrVrrrrlistrrr __classcell__)r(s@r+rr"sn   ZT3//N'ZS$==FFFFFFFF8^S^^^^^S^^^^^U^^^^   4$4444    SSS<*** ### ***3s3S3[3333j""#"u"X^""""H'G'G'GR H H H       $$$ "$$$. D    \\\"""""""""r,r)rloggingpathlibrrrrrrrr r PyQt6.QtCorer r r rrrrapp.widgets.loading_overlayrapp.utils.pathsrapp.utils.excel_exportr getLoggerr)r&rr9r,r+rsJ /.........6666666666666666))))))??????  8 $ $f"f"f"f"f"Gf"f"f"f"f"r,