Gestion des enregistrements de maintenance
Gestion des enregistrements de maintenance
import sys
import json
import pandas as pd
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Dict, Optional
from dataclasses import dataclass, asdict
from [Link] import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QTabWidget, QFrame, QGridLayout, QTableWidget,
QHeaderView, QTableWidgetItem, QLineEdit, QComboBox, QDateEdit,
QFormLayout, QTextEdit, QDialog, QDialogButtonBox, QMessageBox,
QFileDialog, QListWidget, QListWidgetItem, QScrollArea,
QCalendarWidget
)
from [Link] import Qt, QDate, QTimer, Signal, QThread
from [Link] import QFont, QPixmap, QIcon, QPalette, QColor,
QPainter, QPen, QIntValidator
from [Link] import *
# ================================
# MODÈLES DE DONNÉES
# ================================
@dataclass
class MaintenanceRecord:
"""Modèle de données pour un enregistrement de maintenance."""
id: int = 0
date_maintenance: str = ""
type_maintenance: str = ""
aeronef: str = ""
immatriculation: str = ""
statut: str = "Programmé"
techniciens: List[str] = None
chef_equipe: str = ""
duree_prevue: int = 0 # en heures
duree_reelle: int = 0 # en heures
pieces_utilisees: List[str] = None
rapport: str = ""
priorite: str = "Normale"
localisation: str = ""
date_creation: str = ""
date_modification: str = ""
prochaine_maintenance: str = ""
def __post_init__(self):
"""Initialisation des listes si elles sont vides."""
if [Link] is None:
[Link] = []
if self.pieces_utilisees is None:
self.pieces_utilisees = []
class DataManager:
"""
Gestionnaire des données de maintenance avec persistance
et gestion des alertes.
"""
def __init__(self):
self.data_file = "maintenance_aeronautique.json"
[Link]: List[MaintenanceRecord] = []
self.load_data()
def load_data(self):
"""Charge les données depuis le fichier JSON."""
try:
if Path(self.data_file).exists():
with open(self.data_file, 'r', encoding='utf-8') as f:
data = [Link](f)
[Link] = [MaintenanceRecord(**record) for
record in data]
else:
self._create_sample_data()
except Exception as e:
print(f"Erreur chargement: {e}")
self._create_sample_data()
def _create_sample_data(self):
"""Crée des données d'exemple pour l'application."""
sample_data = [
MaintenanceRecord(
id=1,
date_maintenance= ([Link]() +
timedelta(days=2)).strftime("%Y-%m-%d"),
type_maintenance="Inspection 100H",
aeronef="Airbus A320",
immatriculation="F-GABC",
statut="Programmé",
techniciens=["Jean Dupont", "Marie Martin"],
chef_equipe="Pierre Durand",
duree_prevue=8,
duree_reelle=0,
pieces_utilisees=["Filtre hydraulique", "Pneu avant"],
rapport="",
priorite="Haute",
localisation="Hangar A",
date_creation=[Link]().isoformat(),
prochaine_maintenance="2025-04-25"
),
MaintenanceRecord(
id=2,
date_maintenance= ([Link]() -
timedelta(days=5)).strftime("%Y-%m-%d"),
type_maintenance="Révision moteur",
aeronef="Boeing 737",
immatriculation="F-GDEF",
statut="Terminé",
techniciens=["Paul Leblanc", "Sophie Bernard"],
chef_equipe="Antoine Moreau",
duree_prevue=24,
duree_reelle=20,
rapport="Révision majeure du moteur gauche effectuée.
Aucun problème signalé.",
priorite="Critique",
localisation="Hangar B",
date_creation=[Link]().isoformat(),
prochaine_maintenance="2025-07-28"
),
MaintenanceRecord(
id=3,
date_maintenance= ([Link]() +
timedelta(days=15)).strftime("%Y-%m-%d"),
type_maintenance="Inspection 500H",
aeronef="Embraer E190",
immatriculation="F-HIJK",
statut="Programmé",
techniciens=["Marc Lavoie", "Julie Roy"],
chef_equipe="Philippe Gagnon",
duree_prevue=16,
duree_reelle=0,
pieces_utilisees=[],
rapport="",
priorite="Normale",
localisation="Hangar C",
date_creation=[Link]().isoformat(),
prochaine_maintenance="2025-05-10"
),
MaintenanceRecord(
id=4,
date_maintenance= ([Link]()).strftime("%Y-%m-
%d"),
type_maintenance="Vérification pré-vol",
aeronef="Cessna 172",
immatriculation="C-ABCD",
statut="En cours",
techniciens=["Simon Caron"],
chef_equipe="Simon Caron",
duree_prevue=2,
duree_reelle=0,
pieces_utilisees=[],
rapport="",
priorite="Faible",
localisation="Piste 1",
date_creation=[Link]().isoformat(),
prochaine_maintenance=""
)
]
[Link] = sample_data
self.save_data()
def save_data(self):
"""Sauvegarde les données dans le fichier JSON."""
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
[Link]([asdict(record) for record in [Link]],
f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"Erreur sauvegarde: {e}")
return False
total = len([Link])
current_month = [Link]().strftime("%Y-%m")
# Durée totale
duree_totale = sum(r.duree_reelle or r.duree_prevue for r in
[Link])
duree_moyenne = duree_totale / total if total > 0 else 0
# Maintenances critiques
critiques = sum(1 for r in [Link] if [Link] ==
"Critique")
return {
"total": total,
"statut_counts": statut_counts,
"type_counts": type_counts,
"duree_totale": duree_totale,
"duree_moyenne": duree_moyenne,
"critiques": critiques,
"tendance_mensuelle": tendance_mensuelle
}
def _empty_stats(self):
"""Renvoie un dictionnaire de statistiques vides."""
return {
"total": 0, "statut_counts": {}, "type_counts": {},
"duree_totale": 0, "duree_moyenne": 0, "critiques": 0,
"tendance_mensuelle": []
}
df = [Link](df_data)
df.to_excel(filename, index=False,
sheet_name="Maintenances")
return True
except Exception as e:
print(f"Erreur export Excel: {e}")
return False
# ================================
# COMPOSANTS UI MODERNES
# ================================
class ModernCard(QFrame):
"""Carte moderne avec ombre et bordure arrondie."""
def __init__(self, title="", parent=None):
super().__init__(parent)
self.setup_style()
self.setup_layout(title)
def setup_style(self):
[Link]("""
QFrame {
background-color: white;
border: 1px solid #E5E7EB;
border-radius: 12px;
margin: 8px;
}
QFrame:hover {
border-color: #3B82F6;
background-color: #FAFBFF;
}
""")
[Link]([Link])
if title:
self.title_label = QLabel(title)
self.title_label.setStyleSheet("""
QLabel {
font-size: 16px;
font-weight: 600;
color: #1F2937;
border: none;
background: transparent;
margin-bottom: 8px;
}
""")
self.main_layout.addWidget(self.title_label)
self.content_widget = QWidget()
self.content_layout = QVBoxLayout(self.content_widget)
self.content_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.addWidget(self.content_widget)
class StatCard(ModernCard):
"""Carte de statistique avec valeur et icône."""
def __init__(self, title, value, icon="📊", color="#3B82F6",
parent=None):
super().__init__(parent=parent)
self.setup_stat_layout(title, value, icon, color)
[Link](120)
icon_label = QLabel(icon)
icon_label.setStyleSheet(f"""
QLabel {{
font-size: 32px;
color: {color};
background: rgba({self.hex_to_rgb(color)}, 0.1);
border-radius: 8px;
padding: 8px;
margin-right: 16px;
}}
""")
icon_label.setFixedSize(64, 64)
icon_label.setAlignment([Link])
[Link](icon_label)
content_layout = QVBoxLayout()
self.value_label = QLabel(str(value))
self.value_label.setStyleSheet(f"""
QLabel {{
font-size: 28px;
font-weight: 700;
color: {color};
border: none;
margin: 0;
}}
""")
title_label = QLabel(title)
title_label.setStyleSheet("""
QLabel {
font-size: 14px;
color: #6B7280;
border: none;
margin: 4px 0 0 0;
}
""")
content_layout.addWidget(self.value_label)
content_layout.addWidget(title_label)
content_layout.addStretch()
[Link](content_layout)
[Link]()
class ModernInput(QLineEdit):
"""Champ de saisie moderne."""
def __init__(self, placeholder="", parent=None):
super().__init__(parent)
[Link](placeholder)
self.setup_style()
[Link](44)
def setup_style(self):
[Link]("""
QLineEdit {
border: 2px solid #E5E7EB;
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
background-color: white;
color: #1F2937;
}
QLineEdit:focus {
border-color: #3B82F6;
background-color: #FAFBFF;
outline: none;
}
QLineEdit:hover {
border-color: #D1D5DB;
}
""")
class ModernComboBox(QComboBox):
"""Liste déroulante moderne."""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_style()
[Link](44)
def setup_style(self):
[Link]("""
QComboBox {
border: 2px solid #E5E7EB;
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
background-color: white;
color: #1F2937;
min-width: 150px;
}
QComboBox:focus {
border-color: #3B82F6;
background-color: #FAFBFF;
}
QComboBox:hover { border-color: #D1D5DB; }
QComboBox::drop-down {
border: none;
width: 30px;
padding-right: 8px;
}
QComboBox::down-arrow {
image: none;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #6B7280;
margin-right: 8px;
}
QComboBox QAbstractItemView {
background-color: white;
border: 2px solid #E5E7EB;
border-radius: 8px;
selection-background-color: #EBF4FF;
outline: none;
padding: 4px;
}
""")
class ModernTextEdit(QTextEdit):
"""Zone de texte moderne."""
def __init__(self, placeholder="", parent=None):
super().__init__(parent)
[Link](placeholder)
self.setup_style()
[Link](120)
def setup_style(self):
[Link]("""
QTextEdit {
border: 2px solid #E5E7EB;
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
background-color: white;
color: #1F2937;
}
QTextEdit:focus {
border-color: #3B82F6;
background-color: #FAFBFF;
}
QTextEdit:hover {
border-color: #D1D5DB;
}
""")
class ModernTable(QTableWidget):
"""Tableau moderne avec style professionnel."""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_style()
self.setup_behavior()
def setup_style(self):
[Link]("""
QTableWidget {
background-color: white;
border: 1px solid #E5E7EB;
border-radius: 12px;
gridline-color: #F3F4F6;
font-size: 13px;
color: #374151;
}
QTableWidget::item {
padding: 12px 8px;
border-bottom: 1px solid #F3F4F6;
}
QTableWidget::item:selected {
background-color: #EBF4FF;
color: #1D4ED8;
}
QTableWidget::item:hover {
background-color: #F8FAFC;
}
QHeaderView::section {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #F8FAFC, stop:1 #F1F5F9);
color: #374151;
padding: 12px 8px;
border: none;
border-bottom: 2px solid #E5E7EB;
font-weight: 600;
font-size: 13px;
}
QHeaderView::section:hover {
background: #E2E8F0;
}
""")
def setup_behavior(self):
[Link]([Link])
[Link]([Link])
[Link](True)
[Link]().setSectionResizeMode([Link]
etch)
[Link]().setVisible(False)
# ================================
# DIALOGS
# ================================
class MaintenanceDialog(QDialog):
"""Dialog pour ajouter/modifier une maintenance."""
def __init__(self, record=None, parent=None):
super().__init__(parent)
[Link] = record
self.is_edit_mode = record is not None
self.setup_dialog()
self.setup_ui()
if self.is_edit_mode:
self.fill_form()
def setup_dialog(self):
title = "Modifier la maintenance" if self.is_edit_mode else
"Nouvelle maintenance"
[Link](title)
[Link](800, 700)
[Link](True)
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](24, 24, 24, 24)
[Link](24)
form_layout = QFormLayout()
form_layout.setSpacing(15)
self.date_input = QDateEdit(calendarPopup=True)
self.date_input.setCalendarWidget(QCalendarWidget())
self.date_input.setMinimumHeight(44)
self.date_input.setDateTime([Link]())
self.date_input.setStyleSheet("QDateEdit {border: 2px solid
#E5E7EB; border-radius: 8px; padding: 12px 16px; font-size: 14px;}")
self.prochaine_maintenance_input.setCalendarWidget(QCalendarWidget())
self.prochaine_maintenance_input.setMinimumHeight(44)
self.prochaine_maintenance_input.setStyleSheet("QDateEdit
{border: 2px solid #E5E7EB; border-radius: 8px; padding: 12px 16px;
font-size: 14px;}")
[Link](form_layout)
# Boutons d'action
button_box = QDialogButtonBox([Link] |
[Link])
button_box.[Link]([Link])
button_box.[Link]([Link])
[Link](button_box)
def fill_form(self):
"""Remplit le formulaire avec les données existantes pour la
modification."""
self.date_input.setDate([Link]([Link].date_maintenance,
"yyyy-MM-dd"))
self.aeronef_input.setText([Link])
self.immat_input.setText([Link])
self.type_input.setText([Link].type_maintenance)
self.statut_input.setCurrentText([Link])
self.priorite_input.setCurrentText([Link])
self.localisation_input.setText([Link])
self.chef_input.setText([Link].chef_equipe)
self.duree_prevue_input.setText(str([Link].duree_prevue *
60))
self.duree_reelle_input.setText(str([Link].duree_reelle *
60) if [Link].duree_reelle > 0 else "")
self.techniciens_input.setText(",
".join([Link]))
self.pieces_input.setText(",
".join([Link].pieces_utilisees))
if [Link].prochaine_maintenance:
self.prochaine_maintenance_input.setDate([Link]([Link].p
rochaine_maintenance, "yyyy-MM-dd"))
self.rapport_input.setPlainText([Link])
def validate_form(self):
"""Valide les données du formulaire avant l'enregistrement."""
if not self.aeronef_input.text():
return False, "Le nom de l'aéronef est requis."
if not self.immat_input.text():
return False, "L'immatriculation est requise."
if not self.date_input.date().isValid():
return False, "La date de maintenance est invalide."
return True, ""
def get_maintenance_data(self):
"""Récupère les données du formulaire et les retourne comme un
objet MaintenanceRecord."""
duree_prevue_text = self.duree_prevue_input.text()
duree_reelle_text = self.duree_reelle_input.text()
duree_prevue_heures = int(duree_prevue_text) / 60 if
duree_prevue_text else 0
duree_reelle_heures = int(duree_reelle_text) / 60 if
duree_reelle_text else 0
return MaintenanceRecord(
date_maintenance=self.date_input.date().toString("yyyy-MM-
dd"),
type_maintenance=self.type_input.text(),
aeronef=self.aeronef_input.text(),
immatriculation=self.immat_input.text(),
statut=self.statut_input.currentText(),
techniciens=[[Link]() for t in
self.techniciens_input.text().split(",") if [Link]()],
chef_equipe=self.chef_input.text(),
duree_prevue=int(duree_prevue_heures),
duree_reelle=int(duree_reelle_heures) if
duree_reelle_heures > 0 else 0,
pieces_utilisees=[[Link]() for p in
self.pieces_input.text().split(",") if [Link]()],
rapport=self.rapport_input.toPlainText(),
priorite=self.priorite_input.currentText(),
localisation=self.localisation_input.text(),
prochaine_maintenance=self.prochaine_maintenance_input.date().toString(
"yyyy-MM-dd")
)
class MaintenanceDetailsDialog(QDialog):
"""Dialog pour afficher les détails d'une maintenance."""
def __init__(self, record: MaintenanceRecord, parent=None):
super().__init__(parent)
[Link] = record
[Link]("Détails de la Maintenance")
[Link](900, 700)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](24, 24, 24, 24)
[Link](20)
scroll = QScrollArea()
[Link](True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setSpacing(20)
# Carte de l'équipe
team_card = ModernCard("👨 🔧 Équipe et Pièces")
team_info = QFormLayout()
team_info.setSpacing(10)
team_info.addRow("👩✈️ Chef d'équipe:",
QLabel([Link].chef_equipe))
team_info.addRow("🛠️Techniciens:", QLabel(",
".join([Link])))
team_info.addRow("🔩 Pièces utilisées:", QLabel(",
".join([Link].pieces_utilisees) or "Aucune"))
team_card.content_layout.addLayout(team_info)
content_layout.addWidget(team_card)
# Carte du rapport
if [Link]:
rapport_card = ModernCard("📝 Rapport")
rapport_text = ModernTextEdit()
rapport_text.setPlainText([Link])
rapport_text.setReadOnly(True)
rapport_text.setMinimumHeight(150)
rapport_text.setMaximumHeight(200)
rapport_card.content_layout.addWidget(rapport_text)
content_layout.addWidget(rapport_card)
[Link](content_widget)
[Link](scroll)
button_layout = QHBoxLayout()
button_layout.addStretch()
edit_btn = ModernButton("✏️ Modifier", "primary")
edit_btn.[Link](self.edit_record)
button_layout.addWidget(edit_btn)
close_btn = ModernButton("❌ Fermer", "secondary")
close_btn.[Link]([Link])
button_layout.addWidget(close_btn)
[Link](button_layout)
def edit_record(self):
"""Ouvre le dialog d'édition."""
[Link]()
dialog = MaintenanceDialog([Link])
if [Link]() == [Link]:
is_valid, error_msg = dialog.validate_form()
if not is_valid:
[Link]([Link](), "Erreur de
validation", error_msg)
return
updated_record = dialog.get_maintenance_data()
if [Link]().data_manager.update_record([Link],
updated_record):
[Link]([Link](), "Succès",
"Maintenance modifiée avec succès !")
[Link]().refresh_all_tabs()
else:
[Link]([Link](), "Erreur", "Erreur
lors de la modification.")
# ================================
# FENÊTRE PRINCIPALE
# ================================
class MainWindow(QMainWindow):
"""Fenêtre principale de l'application."""
def __init__(self):
super().__init__()
self.data_manager = DataManager()
[Link]("Système de Gestion de Maintenance
Aéronautique")
[Link](1200, 800)
# Styles de l'application
self.setup_style()
self.setup_ui()
def setup_style(self):
"""Applique une palette de couleurs et une police à
l'application."""
palette = QPalette()
[Link]([Link], QColor("#F9FAFB"))
[Link]([Link], QColor("#1F2937"))
[Link]([Link], QColor("#4B5563"))
[Link]([Link], QColor("white"))
[Link]([Link], QColor("white"))
[Link]([Link], QColor("#F3F4F6"))
[Link](palette)
font = QFont("Helvetica Neue", 10)
[Link](font)
# Style global pour les widgets
[Link]("""
QWidget {
color: #1F2937;
}
QTabWidget::tab-bar {
alignment: center;
}
QTabWidget::pane {
border: 1px solid #E5E7EB;
border-top: none;
background: white;
}
QTabBar::tab {
background: #E5E7EB;
border: 1px solid #D1D5DB;
border-bottom: none;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
padding: 12px 20px;
font-weight: 600;
font-size: 14px;
}
QTabBar::tab:selected {
background: white;
border: 1px solid #E5E7EB;
border-bottom-color: white;
}
QTabBar::tab:hover {
background: #D1D5DB;
}
""")
def setup_ui(self):
"""Configure les composants de l'interface utilisateur."""
self.central_widget = QWidget()
[Link](self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
# Onglets
[Link] = QTabWidget()
self.main_layout.addWidget([Link])
self.dashboard_tab = DashboardTab(self.data_manager)
self.history_tab = HistoryTab(self.data_manager, self)
self.calendar_tab = CalendarTab(self.data_manager, self)
self.alert_tab = AlertTab(self.data_manager, self)
[Link](self.dashboard_tab, "🚀 Tableau de Bord")
[Link](self.history_tab, "📋 Historique & Gestion")
[Link](self.calendar_tab, "📅 Calendrier")
[Link](self.alert_tab, "🔔 Alertes")
def open_add_dialog(self):
"""Ouvre le dialogue pour ajouter une nouvelle maintenance."""
dialog = MaintenanceDialog(parent=self)
if [Link]() == [Link]:
is_valid, error_msg = dialog.validate_form()
if not is_valid:
[Link](self, "Erreur de validation",
error_msg)
return
new_record = dialog.get_maintenance_data()
if self.data_manager.add_record(new_record):
[Link](self, "Succès", "Nouvelle
maintenance ajoutée avec succès !")
self.refresh_all_tabs()
else:
[Link](self, "Erreur", "Erreur lors de
l'ajout de la maintenance.")
def refresh_all_tabs(self):
"""Actualise tous les onglets de l'application."""
self.dashboard_tab.refresh()
self.history_tab.refresh()
self.calendar_tab.refresh()
self.alert_tab.refresh()
class DashboardTab(QWidget):
"""Onglet du tableau de bord avec les statistiques et
graphiques."""
def __init__(self, data_manager: DataManager):
super().__init__()
self.data_manager = data_manager
self.setup_ui()
[Link]()
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](20)
[Link](20, 20, 20, 20)
# Graphiques
charts_layout = QHBoxLayout()
self.chart_view1 = QChartView()
self.chart_view2 = QChartView()
charts_layout.addWidget(self.chart_view1)
charts_layout.addWidget(self.chart_view2)
[Link](charts_layout)
[Link]()
def refresh(self):
"""Met à jour les données du tableau de bord."""
stats = self.data_manager.get_statistics()
upcoming_count =
len(self.data_manager.get_upcoming_maintenances(7))
self.total_card.update_value(stats["total"])
self.critique_card.update_value(stats["critiques"])
self.duration_card.update_value(f"{int(stats['duree_moyenne'] *
60)} min")
self.upcoming_card.update_value(upcoming_count)
self.create_charts(stats)
self.chart_view1.setRenderHint([Link])
self.chart_view2.setRenderHint([Link])
class HistoryTab(QWidget):
"""Onglet historique avec gestion avancée."""
def __init__(self, data_manager: 'DataManager', main_window):
super().__init__()
self.data_manager = data_manager
self.main_window = main_window
self.setup_ui()
[Link]()
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](20, 20, 20, 20)
[Link](header_layout)
# Filtres avancés
filters_card = ModernCard("🔍 Filtres Avancés")
filters_layout = QHBoxLayout(filters_card.content_widget)
filters_layout.addWidget(QLabel("Statut:"))
self.status_filter = ModernComboBox()
self.status_filter.addItems(["Tous", "Programmé", "En cours",
"Terminé", "En attente", "Annulé"])
self.status_filter.[Link](self.filter_data)
filters_layout.addWidget(self.status_filter)
filters_layout.addWidget(QLabel("Priorité:"))
self.priority_filter = ModernComboBox()
self.priority_filter.addItems(["Toutes", "Critique", "Haute",
"Normale", "Faible"])
self.priority_filter.[Link](self.filter_data)
filters_layout.addWidget(self.priority_filter)
filters_layout.addWidget(QLabel("Période:"))
self.period_filter = ModernComboBox()
self.period_filter.addItems(["Toutes", "Cette semaine", "Ce
mois", "3 derniers mois", "Cette année"])
self.period_filter.[Link](self.filter_data)
filters_layout.addWidget(self.period_filter)
filters_layout.addStretch()
[Link](filters_card)
# Tableau principal
[Link] = ModernTable()
[Link](8)
[Link]([
"ID", "Date", "Aéronef", "Immat.", "Type", "Statut",
"Priorité", "Chef d'équipe"
])
header = [Link]()
# MODIFICATION ICI POUR QUE LA TABLE OCCUPE TOUTE LA LARGEUR
# On définit la politique de redimensionnement de chaque
section.
# Par exemple, pour que la colonne "Aéronef" prenne l'espace
restant,
# vous pouvez utiliser [Link] pour
cette colonne.
# Les autres colonnes redimensionnent leur taille en fonction
du contenu.
# Si vous voulez que toutes les colonnes s'étirent, utilisez la
boucle ci-dessous.
#
[Link]([Link])
for i in range([Link]()):
[Link](i,
[Link])
[Link]("QHeaderView::section { background-color:
#F3F4F6; font-weight: bold; padding: 5px; }")
[Link](True)
[Link]("QTableView::item { padding: 5px; }")
[Link]().setVisible(False)
[Link]([Link])
# Boutons d'actions
actions_layout = QHBoxLayout()
actions_layout.addStretch()
self.edit_btn = ModernButton("✏️ Modifier", "primary")
self.edit_btn.setEnabled(False)
self.edit_btn.[Link](self.edit_selected)
actions_layout.addWidget(self.edit_btn)
[Link](actions_layout)
[Link](self.on_selection_changed)
[Link](self.edit_selected)
def refresh(self):
"""Actualise les données du tableau."""
self.filter_data()
def filter_data(self):
"""Applique tous les filtres et met à jour le tableau."""
records = self.data_manager.get_all_records()
search_text = self.search_input.text().lower()
if search_text:
records = [r for r in records if
search_text in [Link]() or
search_text in r.type_maintenance.lower() or
search_text in r.chef_equipe.lower() or
search_text in ' '.join([Link]).lower() or
search_text in [Link]()]
status_filter = self.status_filter.currentText()
if status_filter != "Tous":
records = [r for r in records if [Link] == status_filter]
priority_filter = self.priority_filter.currentText()
if priority_filter != "Toutes":
records = [r for r in records if [Link] ==
priority_filter]
period_filter = self.period_filter.currentText()
if period_filter != "Toutes":
records = self.apply_period_filter(records, period_filter)
self.populate_table(records)
date_item = QTableWidgetItem(record.date_maintenance)
[Link](row, 1, date_item)
aeronef_item = QTableWidgetItem([Link])
[Link](row, 2, aeronef_item)
immat_item = QTableWidgetItem([Link])
immat_item.setTextAlignment([Link])
[Link](row, 3, immat_item)
type_item = QTableWidgetItem(record.type_maintenance)
[Link](row, 4, type_item)
status_item.setBackground(QColor(status_colors[[Link]]))
# Optional: Make the text bold or centered
status_item.setTextAlignment([Link])
[Link](row, 5, status_item)
priority_item.setBackground(QColor(priority_colors[[Link]]))
# Optional: Make the text bold or centered
priority_item.setTextAlignment([Link])
[Link](row, 6, priority_item)
chef_equipe_item = QTableWidgetItem(record.chef_equipe)
[Link](row, 7, chef_equipe_item)
def clear_filters(self):
"""Efface tous les filtres."""
self.search_input.clear()
self.status_filter.setCurrentIndex(0)
self.priority_filter.setCurrentIndex(0)
self.period_filter.setCurrentIndex(0)
self.filter_data()
def on_selection_changed(self):
"""Gère le changement de sélection dans le tableau."""
has_selection = bool([Link]())
self.edit_btn.setEnabled(has_selection)
self.delete_btn.setEnabled(has_selection)
self.details_btn.setEnabled(has_selection)
def get_selected_record_id(self):
"""Récupère l'ID de l'enregistrement sélectionné."""
selected_items = [Link]()
if selected_items:
row = selected_items[0].row()
return int([Link](row, 0).text())
return None
def edit_selected(self):
"""Édite l'enregistrement sélectionné."""
record_id = self.get_selected_record_id()
if not record_id: return
record = self.data_manager.get_record_by_id(record_id)
if record:
dialog = MaintenanceDialog(record, self)
if [Link]() == [Link]:
is_valid, error_msg = dialog.validate_form()
if not is_valid:
[Link](self, "Erreur de validation",
error_msg)
return
updated_data = dialog.get_maintenance_data()
if self.data_manager.update_record(record_id,
updated_data):
[Link](self, "Succès",
"Maintenance mise à jour avec succès!")
self.main_window.refresh_all_tabs()
else:
[Link](self, "Erreur", "Erreur lors
de la mise à jour.")
def delete_selected(self):
"""Supprime l'enregistrement sélectionné."""
record_id = self.get_selected_record_id()
if not record_id: return
reply = [Link](
self, "Confirmer la suppression",
f"Êtes-vous sûr de vouloir supprimer la maintenance
#{record_id} ?\nCette action est irréversible.",
[Link] |
[Link],
[Link]
)
if reply == [Link]:
if self.data_manager.delete_record(record_id):
[Link](self, "Succès", "Maintenance
supprimée avec succès!")
self.main_window.refresh_all_tabs()
else:
[Link](self, "Erreur", "Erreur lors de la
suppression.")
def show_details(self):
"""Affiche les détails de l'enregistrement sélectionné."""
record_id = self.get_selected_record_id()
if not record_id: return
record = self.data_manager.get_record_by_id(record_id)
if record:
details_dialog = MaintenanceDetailsDialog(record, self)
details_dialog.exec()
def export_to_excel(self):
"""Exporte les données vers Excel."""
filename, _ = [Link](
self, "Exporter vers Excel",
f"maintenance_export_{[Link]().strftime('%Y%m%d_%H%M
%S')}.xlsx",
"Fichiers Excel (*.xlsx)"
)
if filename:
if self.data_manager.export_to_excel(filename):
[Link](self, "Succès", f"Export réussi
vers :\n{filename}")
else:
[Link](self, "Erreur", "Erreur lors de
l'export Excel.\nVérifiez que pandas est installé : pip install pandas
openpyxl")
class CalendarTab(QWidget):
"""Onglet du calendrier."""
def __init__(self, data_manager, main_window):
super().__init__()
self.data_manager = data_manager
self.main_window = main_window
self.setup_ui()
[Link]()
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](20, 20, 20, 20)
self.calendar_widget = QCalendarWidget()
self.calendar_widget.[Link](self.update_day_details)
[Link](self.calendar_widget)
self.details_list = QListWidget()
self.details_list.setMinimumHeight(200)
[Link](self.details_list)
self.details_list.[Link](self.show_maintenance_detai
ls)
def refresh(self):
"""Met à jour les maintenances affichées dans le calendrier."""
self.update_day_details(self.calendar_widget.selectedDate())
class AlertTab(QWidget):
"""Onglet des alertes."""
def __init__(self, data_manager, main_window):
super().__init__()
self.data_manager = data_manager
self.main_window = main_window
self.setup_ui()
[Link]()
def setup_ui(self):
layout = QVBoxLayout(self)
[Link](20, 20, 20, 20)
self.alert_list = QListWidget()
[Link](self.alert_list)
self.alert_list.[Link](self.show_maintenance_details
)
def refresh(self):
"""Met à jour la liste des alertes."""
self.alert_list.clear()
upcoming_maintenances =
self.data_manager.get_upcoming_maintenances(7)
if not upcoming_maintenances:
self.alert_list.addItem("Aucune maintenance critique ou à
venir dans les 7 prochains jours.")
return
if [Link] == "Critique":
alert_text = "🚨 PRIORITÉ CRITIQUE: " + alert_text
item = QListWidgetItem(alert_text)
[Link]([Link], [Link])
if [Link] == "Critique":
[Link](QColor("red"))
self.alert_list.addItem(item)
# ================================
# INITIALISATION DE L'APPLICATION
# ================================
def run_app():
app = QApplication([Link])
window = MainWindow()
[Link]()
[Link]([Link]())
if __name__ == "__main__":
run_app()