""" Progress Panel - Right sidebar with progress tracking and statistics """ from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar, QPushButton, QFrame, QScrollArea ) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont from app import config from app.models import Course class ProgressPanel(QWidget): """Right sidebar panel with progress statistics and tracking""" def __init__(self, course: Course, parent=None): super().__init__(parent) self.course = course self.init_ui() def init_ui(self): """Initialize the UI components""" # Main layout main_layout = QVBoxLayout(self) main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(10) # Title title = QLabel("Your Progress") title.setStyleSheet(f"font-size: 14pt; font-weight: bold; color: {config.COLOR_PRIMARY};") main_layout.addWidget(title) # Scroll area for content scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setFrameShape(QFrame.NoFrame) # Content widget content_widget = QWidget() layout = QVBoxLayout(content_widget) layout.setContentsMargins(0, 0, 5, 0) layout.setSpacing(15) # === Overall Progress Section === layout.addWidget(self._create_section_header("Overall Progress")) self.overall_progress_bar = QProgressBar() self.overall_progress_bar.setStyleSheet(f""" QProgressBar {{ border: 2px solid {config.COLOR_PRIMARY}; border-radius: 5px; text-align: center; height: 25px; }} QProgressBar::chunk {{ background-color: {config.COLOR_SUCCESS}; }} """) layout.addWidget(self.overall_progress_bar) self.overall_stats_label = QLabel("0 / 30 lessons completed") self.overall_stats_label.setStyleSheet("font-size: 10pt; color: #666;") layout.addWidget(self.overall_stats_label) layout.addWidget(self._create_separator()) # === Points and Level Section === layout.addWidget(self._create_section_header("Points & Level")) points_layout = QHBoxLayout() self.points_label = QLabel("0 pts") self.points_label.setStyleSheet(f"font-size: 24pt; font-weight: bold; color: {config.COLOR_WARNING};") points_layout.addWidget(self.points_label) points_layout.addStretch() layout.addLayout(points_layout) self.level_label = QLabel("Level 1: Novice") self.level_label.setStyleSheet("font-size: 11pt; color: #666;") layout.addWidget(self.level_label) self.level_progress_bar = QProgressBar() self.level_progress_bar.setStyleSheet(f""" QProgressBar {{ border: 1px solid #ccc; border-radius: 3px; text-align: center; height: 15px; }} QProgressBar::chunk {{ background-color: {config.COLOR_WARNING}; }} """) layout.addWidget(self.level_progress_bar) layout.addWidget(self._create_separator()) # === Part Progress Section === layout.addWidget(self._create_section_header("Progress by Part")) self.part_progress_widgets = [] for part in self.course.parts: part_widget = self._create_part_progress(part.number, part.title, 0) self.part_progress_widgets.append(part_widget) layout.addWidget(part_widget) layout.addWidget(self._create_separator()) # === Study Stats Section === layout.addWidget(self._create_section_header("Study Statistics")) stats_grid = QVBoxLayout() stats_grid.setSpacing(8) self.time_stat = self._create_stat_row("⏱", "Total Time", "0 min") self.streak_stat = self._create_stat_row("🔥", "Streak", "0 days") self.exercises_stat = self._create_stat_row("📝", "Exercises", "0 / 18") stats_grid.addWidget(self.time_stat) stats_grid.addWidget(self.streak_stat) stats_grid.addWidget(self.exercises_stat) layout.addLayout(stats_grid) layout.addWidget(self._create_separator()) # === Current Lesson Section === layout.addWidget(self._create_section_header("Current Lesson")) self.current_lesson_label = QLabel("No lesson selected") self.current_lesson_label.setStyleSheet("font-size: 10pt; color: #666; padding: 10px;") self.current_lesson_label.setWordWrap(True) layout.addWidget(self.current_lesson_label) # Push everything to top layout.addStretch() # Set content widget to scroll area scroll.setWidget(content_widget) main_layout.addWidget(scroll, 1) # Initialize with default values self.update_progress(0, 0, 0) self.setMinimumWidth(config.PROGRESS_PANEL_MIN_WIDTH) def _create_section_header(self, text: str) -> QLabel: """Create a section header label""" label = QLabel(text) label.setStyleSheet(f"font-size: 11pt; font-weight: bold; color: {config.COLOR_SECONDARY};") return label def _create_separator(self) -> QFrame: """Create a horizontal separator line""" line = QFrame() line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) line.setStyleSheet("color: #ddd;") return line def _create_part_progress(self, part_number: int, part_title: str, progress: int) -> QWidget: """Create a part progress widget""" widget = QWidget() layout = QVBoxLayout(widget) layout.setContentsMargins(0, 5, 0, 5) layout.setSpacing(5) # Part title title_label = QLabel(f"Part {part_number}: {part_title[:30]}...") title_label.setStyleSheet("font-size: 9pt; font-weight: bold;") layout.addWidget(title_label) # Progress bar progress_bar = QProgressBar() progress_bar.setValue(progress) progress_bar.setStyleSheet(f""" QProgressBar {{ border: 1px solid #ccc; border-radius: 3px; text-align: center; height: 12px; font-size: 8pt; }} QProgressBar::chunk {{ background-color: {config.COLOR_PRIMARY}; }} """) layout.addWidget(progress_bar) # Store reference for updates widget.progress_bar = progress_bar return widget def _create_stat_row(self, icon: str, label: str, value: str) -> QWidget: """Create a statistics row""" widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(5, 5, 5, 5) layout.setSpacing(10) # Icon icon_label = QLabel(icon) icon_label.setStyleSheet("font-size: 16pt;") layout.addWidget(icon_label) # Label text_label = QLabel(label) text_label.setStyleSheet("font-size: 10pt; color: #666;") layout.addWidget(text_label) layout.addStretch() # Value value_label = QLabel(value) value_label.setStyleSheet("font-size: 10pt; font-weight: bold;") layout.addWidget(value_label) # Store reference for updates widget.value_label = value_label return widget def update_progress(self, completed_lessons: int, total_points: int, total_time_minutes: int): """Update overall progress display""" # Overall progress total_lessons = self.course.total_lessons progress_percent = int((completed_lessons / total_lessons) * 100) if total_lessons > 0 else 0 self.overall_progress_bar.setValue(progress_percent) self.overall_stats_label.setText(f"{completed_lessons} / {total_lessons} lessons completed") # Points self.points_label.setText(f"{total_points} pts") # Level level_info = self._get_level_info(total_points) self.level_label.setText(f"Level {level_info['level']}: {level_info['title']}") self.level_progress_bar.setValue(level_info['progress']) # Time if total_time_minutes < 60: time_str = f"{total_time_minutes} min" else: hours = total_time_minutes // 60 minutes = total_time_minutes % 60 time_str = f"{hours}h {minutes}m" self.time_stat.value_label.setText(time_str) def update_part_progress(self, part_number: int, completed: int, total: int): """Update progress for a specific part""" if 0 < part_number <= len(self.part_progress_widgets): widget = self.part_progress_widgets[part_number - 1] progress = int((completed / total) * 100) if total > 0 else 0 widget.progress_bar.setValue(progress) widget.progress_bar.setFormat(f"{completed}/{total} ({progress}%)") def update_streak(self, days: int): """Update study streak""" self.streak_stat.value_label.setText(f"{days} days") def update_exercises(self, completed: int, total: int): """Update exercise completion""" self.exercises_stat.value_label.setText(f"{completed} / {total}") def update_current_lesson(self, lesson_title: str, lesson_points: int, estimated_time: int): """Update current lesson information""" text = f""" {lesson_title}
Points: {lesson_points} | Est. time: {estimated_time} min """ self.current_lesson_label.setText(text) def _get_level_info(self, points: int) -> dict: """Get level information based on points""" for i, (threshold, title, subtitle) in enumerate(config.LEVELS): if i < len(config.LEVELS) - 1: next_threshold = config.LEVELS[i + 1][0] if points < next_threshold: progress = int(((points - threshold) / (next_threshold - threshold)) * 100) return { 'level': i + 1, 'title': title, 'subtitle': subtitle, 'progress': progress, 'next_threshold': next_threshold } # Max level return { 'level': len(config.LEVELS), 'title': config.LEVELS[-1][1], 'subtitle': config.LEVELS[-1][2], 'progress': 100, 'next_threshold': config.LEVELS[-1][0] }