"""
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]
}