""" Violations Tab - Traffic violation detection and evidence management dashboard """ from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QTableWidget, QTableWidgetItem, QHeaderView, QGroupBox, QLabel, QPushButton, QComboBox, QDateEdit, QLineEdit, QTextEdit, QFrame, QCheckBox, QSpinBox, QProgressBar, QTabWidget) from PySide6.QtCore import Qt, Signal, QDateTime, QDate, QTimer from PySide6.QtGui import QFont, QColor, QPixmap, QIcon class ViolationItem: """Data class for violation items""" def __init__(self, violation_id, violation_type, timestamp, location, vehicle_id, evidence_path=None, status="pending"): self.violation_id = violation_id self.violation_type = violation_type self.timestamp = timestamp self.location = location self.vehicle_id = vehicle_id self.evidence_path = evidence_path self.status = status self.confidence = 0.95 self.reviewed = False class ViolationsTab(QWidget): """ Traffic Violations Dashboard with evidence management Features: - Real-time violation detection alerts - Evidence gallery with images/videos - Violation classification and filtering - Manual review and acknowledgment - Export capabilities for reporting - Statistics and trends analysis """ # Signals violation_acknowledged = Signal(str) violation_exported = Signal(list) evidence_viewed = Signal(str) def __init__(self, parent=None): super().__init__(parent) self.violations_data = [] self.filtered_violations = [] self._setup_ui() # Sample data for demonstration self._add_sample_violations() print("🚨 Violations Tab initialized") def _setup_ui(self): """Setup the violations dashboard UI""" # Main layout layout = QVBoxLayout(self) # Header with summary stats header = self._create_header() layout.addWidget(header) # Main content splitter main_splitter = QSplitter(Qt.Horizontal) layout.addWidget(main_splitter) # Left panel - Violations list and filters left_panel = self._create_left_panel() main_splitter.addWidget(left_panel) # Right panel - Evidence and details right_panel = self._create_right_panel() main_splitter.addWidget(right_panel) # Set splitter proportions main_splitter.setSizes([600, 400]) def _create_header(self): """Create header with violation statistics""" header = QFrame() header.setFixedHeight(80) header.setStyleSheet(""" QFrame { background-color: #34495e; border-radius: 8px; margin-bottom: 10px; } """) layout = QHBoxLayout(header) layout.setContentsMargins(20, 10, 20, 10) # Statistics cards stats = [ ("Total Violations", "127", "#e74c3c"), ("Pending Review", "23", "#f39c12"), ("Acknowledged", "104", "#27ae60"), ("Today's Count", "8", "#3498db") ] for title, value, color in stats: card = self._create_stat_card(title, value, color) layout.addWidget(card) layout.addStretch() # Quick actions actions_layout = QVBoxLayout() export_btn = QPushButton("📊 Export Report") export_btn.setFixedSize(120, 30) export_btn.setStyleSheet(f""" QPushButton {{ background-color: #3498db; color: white; border: none; border-radius: 4px; font-weight: bold; }} QPushButton:hover {{ background-color: #2980b9; }} """) export_btn.clicked.connect(self._export_violations) actions_layout.addWidget(export_btn) clear_btn = QPushButton("🗑️ Clear Old") clear_btn.setFixedSize(120, 30) clear_btn.setStyleSheet(f""" QPushButton {{ background-color: #95a5a6; color: white; border: none; border-radius: 4px; font-weight: bold; }} QPushButton:hover {{ background-color: #7f8c8d; }} """) clear_btn.clicked.connect(self._clear_old_violations) actions_layout.addWidget(clear_btn) layout.addLayout(actions_layout) return header def _create_stat_card(self, title, value, color): """Create a statistics card""" card = QFrame() card.setFixedSize(140, 50) card.setStyleSheet(f""" QFrame {{ background-color: {color}; border-radius: 6px; margin: 5px; }} """) layout = QVBoxLayout(card) layout.setContentsMargins(10, 5, 10, 5) # Value value_label = QLabel(value) value_label.setFont(QFont("Segoe UI", 16, QFont.Bold)) value_label.setStyleSheet("color: white;") value_label.setAlignment(Qt.AlignCenter) layout.addWidget(value_label) # Title title_label = QLabel(title) title_label.setFont(QFont("Segoe UI", 8)) title_label.setStyleSheet("color: white;") title_label.setAlignment(Qt.AlignCenter) layout.addWidget(title_label) return card def _create_left_panel(self): """Create left panel with violations list and filters""" panel = QFrame() layout = QVBoxLayout(panel) # Filters section filters = self._create_filters() layout.addWidget(filters) # Violations table table_group = QGroupBox("Violations List") table_layout = QVBoxLayout(table_group) self.violations_table = QTableWidget() self.violations_table.setColumnCount(7) self.violations_table.setHorizontalHeaderLabels([ "ID", "Type", "Time", "Location", "Vehicle", "Status", "Actions" ]) # Configure table header = self.violations_table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) self.violations_table.setSelectionBehavior(QTableWidget.SelectRows) self.violations_table.setAlternatingRowColors(True) # Connect selection change self.violations_table.itemSelectionChanged.connect(self._on_violation_selected) table_layout.addWidget(self.violations_table) layout.addWidget(table_group) return panel def _create_filters(self): """Create violation filters section""" filters = QGroupBox("Filters") filters.setFixedHeight(120) layout = QVBoxLayout(filters) # First row of filters row1 = QHBoxLayout() # Violation type filter row1.addWidget(QLabel("Type:")) self.type_filter = QComboBox() self.type_filter.addItems([ "All Types", "Red Light Violation", "Speed Violation", "Wrong Direction", "Illegal Turn", "Lane Violation" ]) self.type_filter.currentTextChanged.connect(self._apply_filters) row1.addWidget(self.type_filter) # Status filter row1.addWidget(QLabel("Status:")) self.status_filter = QComboBox() self.status_filter.addItems(["All Status", "Pending", "Acknowledged", "Dismissed"]) self.status_filter.currentTextChanged.connect(self._apply_filters) row1.addWidget(self.status_filter) layout.addLayout(row1) # Second row of filters row2 = QHBoxLayout() # Date range row2.addWidget(QLabel("From:")) self.date_from = QDateEdit() self.date_from.setDate(QDate.currentDate().addDays(-7)) self.date_from.dateChanged.connect(self._apply_filters) row2.addWidget(self.date_from) row2.addWidget(QLabel("To:")) self.date_to = QDateEdit() self.date_to.setDate(QDate.currentDate()) self.date_to.dateChanged.connect(self._apply_filters) row2.addWidget(self.date_to) # Search self.search_input = QLineEdit() self.search_input.setPlaceholderText("Search violations...") self.search_input.textChanged.connect(self._apply_filters) row2.addWidget(self.search_input) layout.addLayout(row2) # Filter controls row3 = QHBoxLayout() self.show_acknowledged_cb = QCheckBox("Show Acknowledged") self.show_acknowledged_cb.setChecked(True) self.show_acknowledged_cb.toggled.connect(self._apply_filters) row3.addWidget(self.show_acknowledged_cb) row3.addStretch() clear_filters_btn = QPushButton("Clear Filters") clear_filters_btn.clicked.connect(self._clear_filters) row3.addWidget(clear_filters_btn) layout.addLayout(row3) return filters def _create_right_panel(self): """Create right panel with evidence and details""" panel = QFrame() layout = QVBoxLayout(panel) # Evidence section evidence_section = self._create_evidence_section() layout.addWidget(evidence_section) # Details section details_section = self._create_details_section() layout.addWidget(details_section) # Actions section actions_section = self._create_actions_section() layout.addWidget(actions_section) return panel def _create_evidence_section(self): """Create evidence viewing section""" section = QGroupBox("Evidence") layout = QVBoxLayout(section) # Evidence tabs self.evidence_tabs = QTabWidget() # Image evidence tab image_tab = QWidget() image_layout = QVBoxLayout(image_tab) self.evidence_image = QLabel("Select a violation to view evidence") self.evidence_image.setMinimumSize(300, 200) self.evidence_image.setAlignment(Qt.AlignCenter) self.evidence_image.setStyleSheet(""" QLabel { border: 2px dashed #bdc3c7; border-radius: 8px; background-color: #ecf0f1; color: #7f8c8d; } """) image_layout.addWidget(self.evidence_image) # Image controls image_controls = QHBoxLayout() zoom_in_btn = QPushButton("🔍+") zoom_in_btn.setFixedSize(30, 30) image_controls.addWidget(zoom_in_btn) zoom_out_btn = QPushButton("🔍-") zoom_out_btn.setFixedSize(30, 30) image_controls.addWidget(zoom_out_btn) image_controls.addStretch() save_evidence_btn = QPushButton("💾 Save") save_evidence_btn.clicked.connect(self._save_evidence) image_controls.addWidget(save_evidence_btn) image_layout.addLayout(image_controls) self.evidence_tabs.addTab(image_tab, "📷 Image") # Video evidence tab video_tab = QWidget() video_layout = QVBoxLayout(video_tab) video_placeholder = QLabel("Video evidence player\n(Feature coming soon)") video_placeholder.setMinimumSize(300, 200) video_placeholder.setAlignment(Qt.AlignCenter) video_placeholder.setStyleSheet(""" QLabel { border: 2px dashed #bdc3c7; border-radius: 8px; background-color: #ecf0f1; color: #7f8c8d; } """) video_layout.addWidget(video_placeholder) self.evidence_tabs.addTab(video_tab, "🎬 Video") layout.addWidget(self.evidence_tabs) return section def _create_details_section(self): """Create violation details section""" section = QGroupBox("Violation Details") layout = QVBoxLayout(section) # Details text area self.details_text = QTextEdit() self.details_text.setMaximumHeight(120) self.details_text.setPlaceholderText("Select a violation to view details...") layout.addWidget(self.details_text) # Metadata grid metadata_layout = QHBoxLayout() # Left column left_metadata = QVBoxLayout() self.confidence_label = QLabel("Confidence: --") self.camera_label = QLabel("Camera: --") self.coordinates_label = QLabel("Coordinates: --") left_metadata.addWidget(self.confidence_label) left_metadata.addWidget(self.camera_label) left_metadata.addWidget(self.coordinates_label) metadata_layout.addLayout(left_metadata) # Right column right_metadata = QVBoxLayout() self.weather_label = QLabel("Weather: --") self.visibility_label = QLabel("Visibility: --") self.reviewed_by_label = QLabel("Reviewed by: --") right_metadata.addWidget(self.weather_label) right_metadata.addWidget(self.visibility_label) right_metadata.addWidget(self.reviewed_by_label) metadata_layout.addLayout(right_metadata) layout.addLayout(metadata_layout) return section def _create_actions_section(self): """Create violation actions section""" section = QGroupBox("Actions") layout = QVBoxLayout(section) # Primary actions primary_actions = QHBoxLayout() self.acknowledge_btn = QPushButton("✅ Acknowledge") self.acknowledge_btn.setStyleSheet(""" QPushButton { background-color: #27ae60; color: white; border: none; border-radius: 4px; padding: 8px 16px; font-weight: bold; } QPushButton:hover { background-color: #229954; } """) self.acknowledge_btn.clicked.connect(self._acknowledge_violation) self.acknowledge_btn.setEnabled(False) primary_actions.addWidget(self.acknowledge_btn) self.dismiss_btn = QPushButton("❌ Dismiss") self.dismiss_btn.setStyleSheet(""" QPushButton { background-color: #e74c3c; color: white; border: none; border-radius: 4px; padding: 8px 16px; font-weight: bold; } QPushButton:hover { background-color: #c0392b; } """) self.dismiss_btn.clicked.connect(self._dismiss_violation) self.dismiss_btn.setEnabled(False) primary_actions.addWidget(self.dismiss_btn) layout.addLayout(primary_actions) # Secondary actions secondary_actions = QHBoxLayout() flag_btn = QPushButton("🏃 Flag for Review") flag_btn.clicked.connect(self._flag_violation) secondary_actions.addWidget(flag_btn) notes_btn = QPushButton("📝 Add Notes") notes_btn.clicked.connect(self._add_notes) secondary_actions.addWidget(notes_btn) layout.addLayout(secondary_actions) # Bulk actions bulk_actions = QHBoxLayout() select_all_btn = QPushButton("Select All") select_all_btn.clicked.connect(self._select_all_violations) bulk_actions.addWidget(select_all_btn) bulk_acknowledge_btn = QPushButton("Bulk Acknowledge") bulk_acknowledge_btn.clicked.connect(self._bulk_acknowledge) bulk_actions.addWidget(bulk_acknowledge_btn) layout.addLayout(bulk_actions) return section def _add_sample_violations(self): """Add sample violation data for demonstration""" sample_violations = [ ViolationItem("V001", "Red Light Violation", QDateTime.currentDateTime().addSecs(-3600), "Main St & Oak Ave", "ABC123", "evidence_001.jpg", "pending"), ViolationItem("V002", "Speed Violation", QDateTime.currentDateTime().addSecs(-7200), "Highway 101", "XYZ789", "evidence_002.jpg", "acknowledged"), ViolationItem("V003", "Wrong Direction", QDateTime.currentDateTime().addSecs(-10800), "5th St & Pine St", "DEF456", "evidence_003.jpg", "pending"), ViolationItem("V004", "Illegal Turn", QDateTime.currentDateTime().addSecs(-14400), "Market St", "GHI321", "evidence_004.jpg", "dismissed"), ViolationItem("V005", "Lane Violation", QDateTime.currentDateTime().addSecs(-18000), "Broadway & 2nd St", "JKL654", "evidence_005.jpg", "pending"), ] self.violations_data = sample_violations self._populate_violations_table() def _populate_violations_table(self): """Populate the violations table with data""" self.violations_table.setRowCount(len(self.violations_data)) for row, violation in enumerate(self.violations_data): # ID self.violations_table.setItem(row, 0, QTableWidgetItem(violation.violation_id)) # Type type_item = QTableWidgetItem(violation.violation_type) if violation.violation_type == "Red Light Violation": type_item.setBackground(QColor(231, 76, 60, 50)) # Light red elif violation.violation_type == "Speed Violation": type_item.setBackground(QColor(243, 156, 18, 50)) # Light orange self.violations_table.setItem(row, 1, type_item) # Time time_str = violation.timestamp.toString("MM/dd hh:mm") self.violations_table.setItem(row, 2, QTableWidgetItem(time_str)) # Location self.violations_table.setItem(row, 3, QTableWidgetItem(violation.location)) # Vehicle self.violations_table.setItem(row, 4, QTableWidgetItem(violation.vehicle_id)) # Status status_item = QTableWidgetItem(violation.status.title()) if violation.status == "pending": status_item.setBackground(QColor(243, 156, 18, 50)) # Orange elif violation.status == "acknowledged": status_item.setBackground(QColor(39, 174, 96, 50)) # Green elif violation.status == "dismissed": status_item.setBackground(QColor(149, 165, 166, 50)) # Gray self.violations_table.setItem(row, 5, status_item) # Actions (placeholder) actions_item = QTableWidgetItem("View") self.violations_table.setItem(row, 6, actions_item) def _apply_filters(self): """Apply current filters to violations list""" # This would filter the violations based on current filter settings self._populate_violations_table() print("🚨 Filters applied to violations list") def _clear_filters(self): """Clear all filters""" self.type_filter.setCurrentIndex(0) self.status_filter.setCurrentIndex(0) self.date_from.setDate(QDate.currentDate().addDays(-7)) self.date_to.setDate(QDate.currentDate()) self.search_input.clear() self.show_acknowledged_cb.setChecked(True) print("🚨 Filters cleared") def _on_violation_selected(self): """Handle violation selection""" current_row = self.violations_table.currentRow() if current_row >= 0 and current_row < len(self.violations_data): violation = self.violations_data[current_row] self._show_violation_details(violation) # Enable action buttons self.acknowledge_btn.setEnabled(violation.status == "pending") self.dismiss_btn.setEnabled(violation.status == "pending") def _show_violation_details(self, violation): """Show details for selected violation""" # Update details text details = f""" Violation ID: {violation.violation_id} Type: {violation.violation_type} Timestamp: {violation.timestamp.toString("yyyy-MM-dd hh:mm:ss")} Location: {violation.location} Vehicle ID: {violation.vehicle_id} Status: {violation.status.title()} Description: This violation was automatically detected by the traffic monitoring system. The evidence has been captured and is available for review. """.strip() self.details_text.setPlainText(details) # Update metadata labels self.confidence_label.setText(f"Confidence: {violation.confidence:.1%}") self.camera_label.setText("Camera: Camera 1") self.coordinates_label.setText("Coordinates: 37.7749, -122.4194") self.weather_label.setText("Weather: Clear") self.visibility_label.setText("Visibility: Good") self.reviewed_by_label.setText("Reviewed by: --") # Update evidence display self.evidence_image.setText(f"Evidence: {violation.evidence_path or 'No evidence available'}") print(f"🚨 Showing details for violation: {violation.violation_id}") def _acknowledge_violation(self): """Acknowledge the selected violation""" current_row = self.violations_table.currentRow() if current_row >= 0: violation = self.violations_data[current_row] violation.status = "acknowledged" violation.reviewed = True # Update table self._populate_violations_table() # Disable buttons self.acknowledge_btn.setEnabled(False) self.dismiss_btn.setEnabled(False) # Emit signal self.violation_acknowledged.emit(violation.violation_id) print(f"🚨 Violation {violation.violation_id} acknowledged") def _dismiss_violation(self): """Dismiss the selected violation""" current_row = self.violations_table.currentRow() if current_row >= 0: violation = self.violations_data[current_row] violation.status = "dismissed" violation.reviewed = True # Update table self._populate_violations_table() # Disable buttons self.acknowledge_btn.setEnabled(False) self.dismiss_btn.setEnabled(False) print(f"🚨 Violation {violation.violation_id} dismissed") def _flag_violation(self): """Flag violation for manual review""" current_row = self.violations_table.currentRow() if current_row >= 0: violation = self.violations_data[current_row] print(f"🚨 Violation {violation.violation_id} flagged for review") def _add_notes(self): """Add notes to violation""" current_row = self.violations_table.currentRow() if current_row >= 0: violation = self.violations_data[current_row] print(f"🚨 Adding notes to violation {violation.violation_id}") def _select_all_violations(self): """Select all violations in table""" self.violations_table.selectAll() print("🚨 All violations selected") def _bulk_acknowledge(self): """Acknowledge all selected violations""" selected_rows = set() for item in self.violations_table.selectedItems(): selected_rows.add(item.row()) for row in selected_rows: if row < len(self.violations_data): violation = self.violations_data[row] if violation.status == "pending": violation.status = "acknowledged" violation.reviewed = True # Update table self._populate_violations_table() print(f"🚨 Bulk acknowledged {len(selected_rows)} violations") def _export_violations(self): """Export violations report""" violation_ids = [v.violation_id for v in self.violations_data] self.violation_exported.emit(violation_ids) print("🚨 Violations exported") def _clear_old_violations(self): """Clear old acknowledged violations""" # Remove violations older than 30 days and acknowledged cutoff_date = QDateTime.currentDateTime().addDays(-30) original_count = len(self.violations_data) self.violations_data = [ v for v in self.violations_data if not (v.status == "acknowledged" and v.timestamp < cutoff_date) ] removed_count = original_count - len(self.violations_data) # Update table self._populate_violations_table() print(f"🚨 Cleared {removed_count} old violations") def _save_evidence(self): """Save current evidence""" current_row = self.violations_table.currentRow() if current_row >= 0: violation = self.violations_data[current_row] self.evidence_viewed.emit(violation.violation_id) print(f"🚨 Evidence saved for violation {violation.violation_id}") def add_violation(self, violation_data): """Add a new violation to the list""" violation = ViolationItem(**violation_data) self.violations_data.insert(0, violation) # Add to beginning self._populate_violations_table() print(f"🚨 New violation added: {violation.violation_id}") def get_violation_summary(self): """Get summary of violations""" total = len(self.violations_data) pending = sum(1 for v in self.violations_data if v.status == "pending") acknowledged = sum(1 for v in self.violations_data if v.status == "acknowledged") dismissed = sum(1 for v in self.violations_data if v.status == "dismissed") return { 'total': total, 'pending': pending, 'acknowledged': acknowledged, 'dismissed': dismissed }