""" Main Window for Portfolio Manager. Central window with tab widget for Snapshot, Transactions, and Analytics. """ import logging from PyQt6.QtWidgets import ( QMainWindow, QTabWidget, QWidget, QVBoxLayout, QStatusBar, QMessageBox, QMenuBar, QMenu ) from PyQt6.QtCore import Qt, QTimer, QTime from PyQt6.QtGui import QAction from src.data.db_manager import DBManager from src.data.price_fetcher import PriceFetcher from src.core.portfolio import Portfolio from src.core.transaction import TransactionManager from src.analytics.basic_analytics import AnalyticsEngine from src.gui.snapshot_tab import SnapshotTab from src.gui.transactions_tab import TransactionsTab from src.gui.analytics_tab import AnalyticsTab logger = logging.getLogger(__name__) class MainWindow(QMainWindow): """Main application window with tab widget.""" def __init__(self): """Initialize main window.""" super().__init__() # Initialize backend components logger.info("Initializing backend components...") self.db_manager = DBManager() self.price_fetcher = PriceFetcher(self.db_manager) self.portfolio = Portfolio(self.db_manager) self.transaction_manager = TransactionManager(self.db_manager, self.portfolio) self.analytics = AnalyticsEngine(self.portfolio, self.db_manager) # Load portfolio data self.portfolio.load_holdings() # Setup UI self.init_ui() # Setup auto-refresh timer self.setup_auto_refresh() logger.info("Main window initialized successfully") def init_ui(self): """Initialize user interface.""" # Window properties self.setWindowTitle("Portfolio Manager v1.0") self.setGeometry(100, 100, 1200, 800) self.setMinimumSize(900, 600) # Create central widget central_widget = QWidget() self.setCentralWidget(central_widget) # Create main layout layout = QVBoxLayout(central_widget) layout.setContentsMargins(0, 0, 0, 0) # Create tab widget self.tab_widget = QTabWidget() layout.addWidget(self.tab_widget) # Create tabs logger.debug("Creating tabs...") self.snapshot_tab = SnapshotTab(self) self.transactions_tab = TransactionsTab(self) self.analytics_tab = AnalyticsTab(self) # Add tabs to widget self.tab_widget.addTab(self.snapshot_tab, "📊 Snapshot") self.tab_widget.addTab(self.transactions_tab, "📝 Transactions") self.tab_widget.addTab(self.analytics_tab, "📈 Analytics") # Connect tab change signal self.tab_widget.currentChanged.connect(self.on_tab_changed) # Create menu bar self.create_menu_bar() # Create status bar self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("Ready") logger.debug("UI initialized") def create_menu_bar(self): """Create application menu bar.""" menubar = self.menuBar() # File menu file_menu = menubar.addMenu("&File") refresh_action = QAction("&Refresh All", self) refresh_action.setShortcut("Ctrl+R") refresh_action.triggered.connect(self.refresh_all_tabs) file_menu.addAction(refresh_action) file_menu.addSeparator() backup_action = QAction("&Backup Database", self) backup_action.triggered.connect(self.backup_database) file_menu.addAction(backup_action) file_menu.addSeparator() exit_action = QAction("E&xit", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # Help menu help_menu = menubar.addMenu("&Help") about_action = QAction("&About", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) stats_action = QAction("&Database Stats", self) stats_action.triggered.connect(self.show_database_stats) help_menu.addAction(stats_action) def setup_auto_refresh(self): """Setup auto-refresh timer for price updates.""" # Auto-refresh every 4 hours self.auto_refresh_timer = QTimer(self) self.auto_refresh_timer.timeout.connect(self.auto_refresh_prices) self.auto_refresh_timer.start(4 * 60 * 60 * 1000) # 4 hours in milliseconds logger.info("Auto-refresh timer set to 4 hours") def auto_refresh_prices(self): """Auto-refresh prices (triggered by timer).""" # Check if during reasonable hours (6:00 - 23:00) current_time = QTime.currentTime() if 6 <= current_time.hour() < 23: logger.info("Auto-refresh triggered") self.snapshot_tab.update_prices(auto_refresh=True) else: logger.debug("Auto-refresh skipped (outside 6:00-23:00)") def on_tab_changed(self, index: int): """ Handle tab change event. Args: index: New tab index """ tab_names = ["Snapshot", "Transactions", "Analytics"] if 0 <= index < len(tab_names): logger.debug(f"Tab changed to: {tab_names[index]}") # Refresh analytics tab when opened (to get latest data) if index == 2: # Analytics tab self.analytics_tab.load_data() def refresh_all_tabs(self): """Refresh all tabs (reload data).""" logger.info("Refreshing all tabs...") try: # Reload portfolio data self.portfolio.load_holdings() # Refresh each tab self.snapshot_tab.load_data() self.transactions_tab.load_data() self.analytics_tab.load_data() self.status_bar.showMessage("All tabs refreshed", 3000) logger.info("All tabs refreshed successfully") except Exception as e: logger.error(f"Error refreshing tabs: {e}", exc_info=True) QMessageBox.critical( self, "Refresh Error", f"Failed to refresh tabs:\n{str(e)}" ) def backup_database(self): """Create database backup.""" try: backup_path = self.db_manager.backup_database() QMessageBox.information( self, "Backup Complete", f"Database backed up to:\n{backup_path}" ) logger.info(f"Database backup created: {backup_path}") except Exception as e: logger.error(f"Backup failed: {e}", exc_info=True) QMessageBox.critical( self, "Backup Error", f"Failed to create backup:\n{str(e)}" ) def show_about(self): """Show About dialog.""" about_text = """

Portfolio Manager v1.0

Desktop application for managing investment portfolio.

Target Portfolio: 180k EUR (Core ETF 160k + Satellite Stock 20k)


Features:


Tech Stack: Python 3.12 • PyQt6 • SQLite • yfinance • matplotlib

License: Personal Use

""" QMessageBox.about(self, "About Portfolio Manager", about_text) def show_database_stats(self): """Show database statistics.""" try: stats = self.db_manager.get_database_stats() summary = self.portfolio.get_portfolio_summary() stats_text = f"""

Database Statistics

Holdings:{stats['holdings']}
Transactions:{stats['transactions']}
Cached Prices:{stats['price_cache']}

Portfolio Summary

Total Value:{summary['total_value']:,.2f} €
Total Invested:{summary['total_invested']:,.2f} €
P&L:{summary['pnl_amount']:+,.2f} € ({summary['pnl_percent']:+.1f}%)
Cash:{summary['cash']:,.2f} €
""" QMessageBox.information(self, "Database Statistics", stats_text) except Exception as e: logger.error(f"Failed to get stats: {e}", exc_info=True) QMessageBox.critical( self, "Error", f"Failed to retrieve statistics:\n{str(e)}" ) def closeEvent(self, event): """ Handle window close event. Args: event: Close event """ logger.info("Application closing...") # Stop auto-refresh timer if hasattr(self, 'auto_refresh_timer'): self.auto_refresh_timer.stop() # Accept close event event.accept() logger.info("Application closed")