0% ont trouvé ce document utile (0 vote)
23 vues35 pages

Gestion des enregistrements de maintenance

Ce document présente un système de gestion de maintenance aéronautique, incluant des modèles de données pour les enregistrements de maintenance, ainsi qu'une classe pour la gestion des données avec des fonctionnalités de chargement, sauvegarde, ajout, mise à jour et suppression d'enregistrements. Il inclut également des statistiques sur les maintenances et des options d'exportation vers Excel. Enfin, le document contient des composants d'interface utilisateur modernes pour une application basée sur PySide6.

Transféré par

Mbaye Sarr
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
23 vues35 pages

Gestion des enregistrements de maintenance

Ce document présente un système de gestion de maintenance aéronautique, incluant des modèles de données pour les enregistrements de maintenance, ainsi qu'une classe pour la gestion des données avec des fonctionnalités de chargement, sauvegarde, ajout, mise à jour et suppression d'enregistrements. Il inclut également des statistiques sur les maintenances et des options d'exportation vers Excel. Enfin, le document contient des composants d'interface utilisateur modernes pour une application basée sur PySide6.

Transféré par

Mbaye Sarr
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

# -*- coding: utf-8 -*-

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

def add_record(self, record: MaintenanceRecord) -> bool:


"""Ajoute un nouvel enregistrement."""
[Link] = max([[Link] for r in [Link]], default=0) + 1
record.date_creation = [Link]().isoformat()
[Link](record)
return self.save_data()

def update_record(self, record_id: int, updated_record:


MaintenanceRecord) -> bool:
"""Met à jour un enregistrement."""
for i, record in enumerate([Link]):
if [Link] == record_id:
updated_record.id = record_id
updated_record.date_creation = record.date_creation
updated_record.date_modification =
[Link]().isoformat()
[Link][i] = updated_record
return self.save_data()
return False

def delete_record(self, record_id: int) -> bool:


"""Supprime un enregistrement."""
initial_count = len([Link])
[Link] = [r for r in [Link] if [Link] != record_id]
if len([Link]) < initial_count:
return self.save_data()
return False

def get_record_by_id(self, record_id: int) ->


Optional[MaintenanceRecord]:
"""Récupère un enregistrement par ID."""
return next((r for r in [Link] if [Link] == record_id),
None)

def get_all_records(self) -> List[MaintenanceRecord]:


"""Retourne tous les enregistrements triés par date de
création."""
return sorted([Link], key=lambda x: x.date_creation,
reverse=True)

def get_upcoming_maintenances(self, days_ahead=7) ->


List[MaintenanceRecord]:
"""Retourne les maintenances planifiées pour les jours à
venir."""
today = [Link]().date()
end_date = today + timedelta(days=days_ahead)
upcoming = []
for r in [Link]:
try:
maintenance_date =
[Link](r.date_maintenance, "%Y-%m-%d").date()
if today <= maintenance_date <= end_date and [Link]
== "Programmé":
[Link](r)
except ValueError:
continue
return sorted(upcoming, key=lambda x: x.date_maintenance)

def get_statistics(self) -> Dict:


"""Calcule les statistiques complètes de l'application."""
if not [Link]:
return self._empty_stats()

total = len([Link])
current_month = [Link]().strftime("%Y-%m")

# Compteurs par statut


statut_counts = {}
for record in [Link]:
statut = [Link]
statut_counts[statut] = statut_counts.get(statut, 0) + 1

# Compteurs par type


type_counts = {}
for record in [Link]:
type_maintenance = record.type_maintenance
type_counts[type_maintenance] =
type_counts.get(type_maintenance, 0) + 1

# 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")

# Tendance mensuelle (6 derniers mois)


tendance_mensuelle = []
for i in range(6):
month = ([Link]() -
timedelta(days=30*i)).strftime("%Y-%m")
count = sum(1 for r in [Link] if
r.date_maintenance.startswith(month))
tendance_mensuelle.append({"month": month, "count": count})
tendance_mensuelle.reverse()

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

def export_to_excel(self, filename: str) -> bool:


"""Export des données vers Excel."""
try:
df_data = []
for record in [Link]:
df_data.append({
"ID": [Link],
"Date Maintenance": record.date_maintenance,
"Type": record.type_maintenance,
"Aéronef": [Link],
"Immatriculation": [Link],
"Statut": [Link],
"Techniciens": ", ".join([Link]),
"Chef d'équipe": record.chef_equipe,
"Durée prévue (h)": record.duree_prevue,
"Durée réelle (h)": record.duree_reelle,
"Priorité": [Link],
"Localisation": [Link],
"Rapport": [Link],
"Prochaine maintenance":
record.prochaine_maintenance
})

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])

def setup_layout(self, title):


self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(16)

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)

def setup_stat_layout(self, title, value, icon, color):


layout = QHBoxLayout(self.content_widget)

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]()

def hex_to_rgb(self, hex_color):


hex_color = hex_color.lstrip('#')
return f"{int(hex_color[1:3], 16)}, {int(hex_color[3:5], 16)},
{int(hex_color[5:7], 16)}"

def update_value(self, value):


self.value_label.setText(str(value))
class ModernButton(QPushButton):
"""Bouton moderne avec différents styles."""
def __init__(self, text, style="primary", icon=None, parent=None):
super().__init__(text, parent)
self.setup_button_style(style)
if icon:
[Link](QIcon(icon))
[Link](44)
[Link]([Link])

def setup_button_style(self, style):


styles = {
"primary": """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #3B82F6, stop:1 #2563EB);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-weight: 600;
font-size: 14px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #2563EB, stop:1 #1D4ED8);
}
QPushButton:pressed { background: #1E40AF; }
""",
"success": """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #10B981, stop:1 #059669);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-weight: 600;
font-size: 14px;
}
QPushButton:hover { background: #047857; }
""",
"warning": """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #F59E0B, stop:1 #D97706);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-weight: 600;
font-size: 14px;
}
QPushButton:hover { background: #B45309; }
""",
"danger": """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #EF4444, stop:1 #DC2626);
color: white;
border: none;
border-radius: 8px;
padding: 12px 24px;
font-weight: 600;
font-size: 14px;
}
QPushButton:hover { background: #B91C1C; }
""",
"secondary": """
QPushButton {
background: white;
color: #374151;
border: 2px solid #D1D5DB;
border-radius: 8px;
padding: 12px 24px;
font-weight: 600;
font-size: 14px;
}
QPushButton:hover {
background: #F9FAFB;
border-color: #9CA3AF;
}
"""
}
[Link]([Link](style, styles["primary"]))

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)

title_label = QLabel("Nouvelle Maintenance" if not


self.is_edit_mode else "Modifier Maintenance")
title_label.setStyleSheet("font-size: 24px; font-weight: bold;
color: #1F2937;")
[Link](title_label)

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.aeronef_input = ModernInput("Nom de l'aéronef")


self.immat_input = ModernInput("Immatriculation")
self.type_input = ModernInput("Type de maintenance (ex: 100H,
500H, Révision)")
self.statut_input = ModernComboBox()
self.statut_input.addItems(["Programmé", "En cours", "Terminé",
"En attente", "Annulé"])
self.priorite_input = ModernComboBox()
self.priorite_input.addItems(["Critique", "Haute", "Normale",
"Faible"])
self.localisation_input = ModernInput("Localisation (ex: Hangar
A)")
self.chef_input = ModernInput("Nom du chef d'équipe")
self.duree_prevue_input = ModernInput("Durée prévue (en
minutes)")
self.duree_prevue_input.setValidator(QIntValidator())
self.duree_reelle_input = ModernInput("Durée réelle (en
minutes)")
self.duree_reelle_input.setValidator(QIntValidator())
self.techniciens_input = ModernInput("Liste des techniciens
(séparés par une virgule)")
self.pieces_input = ModernInput("Pièces utilisées (séparées par
une virgule)")
self.prochaine_maintenance_input =
QDateEdit(calendarPopup=True)

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;}")

self.rapport_input = ModernTextEdit("Rapport détaillé de la


maintenance...")

form_layout.addRow("Date de maintenance:", self.date_input)


form_layout.addRow("Aéronef:", self.aeronef_input)
form_layout.addRow("Immatriculation:", self.immat_input)
form_layout.addRow("Type:", self.type_input)
form_layout.addRow("Statut:", self.statut_input)
form_layout.addRow("Priorité:", self.priorite_input)
form_layout.addRow("Localisation:", self.localisation_input)
form_layout.addRow("Chef d'équipe:", self.chef_input)
form_layout.addRow("Durée prévue:", self.duree_prevue_input)
form_layout.addRow("Durée réelle:", self.duree_reelle_input)
form_layout.addRow("Techniciens:", self.techniciens_input)
form_layout.addRow("Pièces utilisées:", self.pieces_input)
form_layout.addRow("Prochaine maintenance:",
self.prochaine_maintenance_input)
form_layout.addRow("Rapport:", self.rapport_input)

[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)

title_label = QLabel(f"Détails de maintenance pour


{[Link]}")
title_label.setStyleSheet("font-size: 24px; font-weight: bold;
color: #1F2937;")
[Link](title_label)

scroll = QScrollArea()
[Link](True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setSpacing(20)

# Carte d'informations générales


general_card = ModernCard("📋 Informations Générales")
general_info = QFormLayout()
general_info.setSpacing(10)
general_info.addRow("📅 Date:",
QLabel([Link].date_maintenance))
general_info.addRow("✈️ Aéronef:",
QLabel(f"{[Link]} ({[Link]})"))
general_info.addRow("🔧 Type:",
QLabel([Link].type_maintenance))
general_info.addRow("🟢 Statut:", QLabel([Link]))
general_info.addRow("🚨 Priorité:",
QLabel([Link]))
general_info.addRow("📍 Localisation:",
QLabel([Link]))
general_info.addRow("⏱️ Durée prévue:",
QLabel(f"{int([Link].duree_prevue * 60)} min"))
general_info.addRow("⏱️ Durée réelle:",
QLabel(f"{int([Link].duree_reelle * 60)} min" if
[Link].duree_reelle > 0 else "N/A"))
general_info.addRow("⚙️ Prochaine maintenance:",
QLabel([Link].prochaine_maintenance or "N/A"))
general_card.content_layout.addLayout(general_info)
content_layout.addWidget(general_card)

# 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)

system_card = ModernCard("ℹ️ Informations Système")


system_info = QFormLayout()
system_info.addRow("🆔 ID:", QLabel(str([Link])))
system_info.addRow("📅 Créé le:",
QLabel([Link].date_creation[:19] if [Link].date_creation else
"Non disponible"))
if [Link].date_modification:
system_info.addRow("✏️ Modifié le:",
QLabel([Link].date_modification[:19]))
system_card.content_layout.addLayout(system_info)
content_layout.addWidget(system_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)

# En-tête avec titre et bouton


header_layout = QHBoxLayout()
title_label = QLabel("✈️ Système de Gestion de Maintenance
Aéronautique")
title_label.setStyleSheet("font-size: 32px; font-weight: 700;
color: #1F2937;")
header_layout.addWidget(title_label)
header_layout.addStretch()
add_btn = ModernButton("+ Nouvelle Maintenance", "primary")
add_btn.[Link](self.open_add_dialog)
header_layout.addWidget(add_btn)
self.main_layout.addLayout(header_layout)

# 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")

# Mise à jour des onglets au changement


[Link](self.refresh_current_tab)

# Mise à jour initiale


self.refresh_all_tabs()

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()

def refresh_current_tab(self, index):


"""Actualise l'onglet actuellement sélectionné."""
current_widget = [Link](index)
if hasattr(current_widget, 'refresh'):
current_widget.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)

# Grille de cartes de statistiques


stats_layout = QGridLayout()
self.total_card = StatCard("Maintenances Totales", 0, "✈️",
"#3B82F6")
self.critique_card = StatCard("Priorité Critique", 0, "🚨",
"#EF4444")
self.duration_card = StatCard("Durée Moyenne", "0 min", "️",
"#F59E0B")
self.upcoming_card = StatCard("À venir (7j)", 0, "🔔",
"#10B981")
stats_layout.addWidget(self.total_card, 0, 0)
stats_layout.addWidget(self.critique_card, 0, 1)
stats_layout.addWidget(self.duration_card, 0, 2)
stats_layout.addWidget(self.upcoming_card, 0, 3)
[Link](stats_layout)

# 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)

def create_charts(self, stats):


"""Génère les graphiques à partir des statistiques."""
# Graphique à barres: Tendance mensuelle
bar_chart = QChart()
bar_chart.setTitle("Tendance Mensuelle")
series_bar = QBarSeries()
set_bar = QBarSet("Maintenances")
categories = []
for trend in stats["tendance_mensuelle"]:
set_bar.append(trend["count"])
[Link](trend["month"])
series_bar.append(set_bar)
bar_chart.addSeries(series_bar)
axisX = QBarCategoryAxis()
[Link](categories)
bar_chart.addAxis(axisX, [Link])
series_bar.attachAxis(axisX)
axisY = QValueAxis()
[Link]("Nombre de maintenances")
[Link]("%i")
bar_chart.addAxis(axisY, [Link])
series_bar.attachAxis(axisY)
self.chart_view1.setChart(bar_chart)

self.chart_view1.setRenderHint([Link])

# Graphique en secteurs: Répartition par statut


pie_chart = QChart()
pie_chart.setTitle("Répartition par Statut")
series_pie = QPieSeries()
for statut, count in stats["statut_counts"].items():
slice_ = QPieSlice(statut, count)
series_pie.append(slice_)
if statut == "Terminé":
slice_.setExploded()
slice_.setLabelVisible()
pie_chart.addSeries(series_pie)
self.chart_view2.setChart(pie_chart)

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)

# En-tête avec recherche et actions


header_layout = QHBoxLayout()
title = QLabel("📋 Historique des Maintenances")
[Link]("font-size: 28px; font-weight: 700; color:
#1F2937;")
header_layout.addWidget(title)
header_layout.addStretch()

self.search_input = ModernInput("🔍 Rechercher par aéronef,


type...")
self.search_input.setMinimumWidth(300)
self.search_input.[Link](self.filter_data)
header_layout.addWidget(self.search_input)

export_btn = ModernButton("📊 Exporter Excel", "secondary")


export_btn.[Link](self.export_to_excel)
header_layout.addWidget(export_btn)

[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()

clear_filters_btn = ModernButton("🗑️Effacer filtres",


"secondary")
clear_filters_btn.[Link](self.clear_filters)
filters_layout.addWidget(clear_filters_btn)

[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)

self.delete_btn = ModernButton("🗑️Supprimer", "danger")


self.delete_btn.setEnabled(False)
self.delete_btn.[Link](self.delete_selected)
actions_layout.addWidget(self.delete_btn)

self.details_btn = ModernButton("📋 Détails", "secondary")


self.details_btn.setEnabled(False)
self.details_btn.[Link](self.show_details)
actions_layout.addWidget(self.details_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)

def apply_period_filter(self, records, period):


"""Applique le filtre de période."""
today = [Link]()
if period == "Cette semaine":
start_date = (today - timedelta(days=7)).strftime("%Y-%m-
%d")
return [r for r in records if r.date_maintenance >=
start_date]
elif period == "Ce mois":
current_month = [Link]("%Y-%m")
return [r for r in records if
r.date_maintenance.startswith(current_month)]
elif period == "3 derniers mois":
start_date = (today - timedelta(days=90)).strftime("%Y-%m-
%d")
return [r for r in records if r.date_maintenance >=
start_date]
elif period == "Cette année":
current_year = [Link]("%Y")
return [r for r in records if
r.date_maintenance.startswith(current_year)]
return records

def populate_table(self, records):


"""Remplit le tableau avec les données."""
[Link](len(records))
for row, record in enumerate(records):
id_item = QTableWidgetItem(str([Link]))
id_item.setTextAlignment([Link])
[Link](row, 0, id_item)

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)

# Statut avec couleur et style


status_item = QTableWidgetItem([Link])
status_colors = {"Programmé": "#E0F2FE", "En cours":
"#FEF3C7", "Terminé": "#D1FAE5", "En attente": "#F3F4F6", "Annulé":
"#FEE2E2"}
if [Link] in status_colors:

status_item.setBackground(QColor(status_colors[[Link]]))
# Optional: Make the text bold or centered

status_item.setTextAlignment([Link])
[Link](row, 5, status_item)

# Priorité avec couleur et style


priority_item = QTableWidgetItem([Link])
priority_colors = {"Critique": "#FEE2E2", "Haute":
"#FEF3C7", "Normale": "#F0FDF4", "Faible": "#F8FAFC"}
if [Link] in priority_colors:

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())

def update_day_details(self, date):


"""Met à jour la liste des maintenances pour le jour
sélectionné."""
self.details_list.clear()
date_str = [Link]("yyyy-MM-dd")
for record in self.data_manager.get_all_records():
if record.date_maintenance == date_str:
item_text = f"[{[Link]}] {[Link]} -
{record.type_maintenance} ({[Link]})"
item = QListWidgetItem(item_text)
[Link]([Link], [Link])
self.details_list.addItem(item)

def show_maintenance_details(self, item):


"""Affiche les détails d'une maintenance en double-cliquant sur
un élément de la liste."""
record_id = [Link]([Link])
record = self.data_manager.get_record_by_id(record_id)
if record:
dialog = MaintenanceDetailsDialog(record,
parent=self.main_window)
[Link]()

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)

[Link](QLabel("<h2>Alertes & Notifications</h2>"))

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

for record in upcoming_maintenances:


days_left = ([Link](record.date_maintenance,
"%Y-%m-%d").date() - [Link]().date()).days
alert_text = f"Maintenance à venir dans {days_left} jours:
{[Link]} ({[Link]}) -
{record.type_maintenance}"

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)

def show_maintenance_details(self, item):


"""Affiche les détails d'une alerte en double-cliquant."""
record_id = [Link]([Link])
record = self.data_manager.get_record_by_id(record_id)
if record:
dialog = MaintenanceDetailsDialog(record,
parent=self.main_window)
[Link]()

# ================================
# INITIALISATION DE L'APPLICATION
# ================================

def run_app():
app = QApplication([Link])
window = MainWindow()
[Link]()
[Link]([Link]())

if __name__ == "__main__":
run_app()

Vous aimerez peut-être aussi