Filtrer le tableau de bord selon les permissions
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
699d094796
commit
ee8c0ad5a0
2 changed files with 128 additions and 90 deletions
|
|
@ -13,6 +13,7 @@ from ..models.college import Building, Room
|
||||||
from ..models.company import Part
|
from ..models.company import Part
|
||||||
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
|
from app_new.constants import INTERVENTION_STATUSES, EQUIPMENT_STATUSES
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from ..authorization import has_permission
|
||||||
|
|
||||||
dashboard_bp = Blueprint('dashboard', __name__)
|
dashboard_bp = Blueprint('dashboard', __name__)
|
||||||
|
|
||||||
|
|
@ -25,20 +26,33 @@ def index():
|
||||||
from ..services.planning_service import PlanningService
|
from ..services.planning_service import PlanningService
|
||||||
from ..models.planning import ScheduledTask, AdminTask
|
from ..models.planning import ScheduledTask, AdminTask
|
||||||
from ..models.maintenance import Lot
|
from ..models.maintenance import Lot
|
||||||
|
|
||||||
|
dashboard_permissions = {
|
||||||
|
'intervention': has_permission('intervention.view', current_user),
|
||||||
|
'intervention_manage': has_permission('intervention.manage', current_user),
|
||||||
|
'patrimoine': has_permission('patrimoine.view', current_user),
|
||||||
|
'planning': has_permission('planning.view', current_user),
|
||||||
|
'planning_manage': has_permission('planning.manage', current_user),
|
||||||
|
'stock': has_permission('stock.view', current_user),
|
||||||
|
'prevention': has_permission('prevention.view', current_user),
|
||||||
|
'ent': has_permission('integration.ent.view', current_user),
|
||||||
|
'outlook': has_permission('integration.outlook.view', current_user),
|
||||||
|
'ai': has_permission('system.configure', current_user),
|
||||||
|
}
|
||||||
|
|
||||||
# Statistiques
|
# Statistiques
|
||||||
stats = {
|
stats = {
|
||||||
'interventions_total': Intervention.query.count(),
|
'interventions_total': Intervention.query.count() if dashboard_permissions['intervention'] else 0,
|
||||||
'interventions_en_cours': Intervention.query.filter_by(status='en_cours').count(),
|
'interventions_en_cours': Intervention.query.filter_by(status='en_cours').count() if dashboard_permissions['intervention'] else 0,
|
||||||
'interventions_en_attente': Intervention.query.filter_by(status='en_attente').count(),
|
'interventions_en_attente': Intervention.query.filter_by(status='en_attente').count() if dashboard_permissions['intervention'] else 0,
|
||||||
'interventions_urgentes': Intervention.query.filter_by(priority='urgente', is_deleted=False).count(),
|
'interventions_urgentes': Intervention.query.filter_by(priority='urgente', is_deleted=False).count() if dashboard_permissions['intervention'] else 0,
|
||||||
'equipments_total': Equipment.query.count(),
|
'equipments_total': Equipment.query.count() if dashboard_permissions['patrimoine'] else 0,
|
||||||
'equipments_actifs': Equipment.query.filter_by(status='en_service', is_deleted=False).count(),
|
'equipments_actifs': Equipment.query.filter_by(status='en_service', is_deleted=False).count() if dashboard_permissions['patrimoine'] else 0,
|
||||||
'equipments_panne': Equipment.query.filter(Equipment.status.in_(['en_panne', 'hors_service', 'hs']), Equipment.is_deleted.is_(False)).count(),
|
'equipments_panne': Equipment.query.filter(Equipment.status.in_(['en_panne', 'hors_service', 'hs']), Equipment.is_deleted.is_(False)).count() if dashboard_permissions['patrimoine'] else 0,
|
||||||
'buildings_total': Building.query.count(),
|
'buildings_total': Building.query.count() if dashboard_permissions['patrimoine'] else 0,
|
||||||
'rooms_total': Room.query.count(),
|
'rooms_total': Room.query.count() if dashboard_permissions['patrimoine'] else 0,
|
||||||
'companies_total': Company.query.count(),
|
'companies_total': Company.query.count() if dashboard_permissions['intervention'] else 0,
|
||||||
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).count(),
|
'parts_low_stock': Part.query.filter(Part.quantity <= Part.min_quantity).count() if dashboard_permissions['stock'] else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Contrôle des lots : un lot présent doit être rattaché à au moins un
|
# Contrôle des lots : un lot présent doit être rattaché à au moins un
|
||||||
|
|
@ -52,8 +66,9 @@ def index():
|
||||||
)
|
)
|
||||||
.group_by(Equipment.lot_id)
|
.group_by(Equipment.lot_id)
|
||||||
.all()
|
.all()
|
||||||
|
if dashboard_permissions['patrimoine'] else {}
|
||||||
)
|
)
|
||||||
lots = Lot.query.order_by(Lot.name).all()
|
lots = Lot.query.order_by(Lot.name).all() if dashboard_permissions['patrimoine'] else []
|
||||||
lots_without_equipment = [
|
lots_without_equipment = [
|
||||||
lot for lot in lots
|
lot for lot in lots
|
||||||
if lot.is_present and equipment_counts.get(lot.id, 0) == 0
|
if lot.is_present and equipment_counts.get(lot.id, 0) == 0
|
||||||
|
|
@ -77,14 +92,14 @@ def index():
|
||||||
stats['lots_missing_duration'] = len(lots_missing_duration)
|
stats['lots_missing_duration'] = len(lots_missing_duration)
|
||||||
|
|
||||||
# Dernières interventions
|
# Dernières interventions
|
||||||
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all()
|
recent_interventions = Intervention.query.order_by(Intervention.created_at.desc()).limit(10).all() if dashboard_permissions['intervention'] else []
|
||||||
|
|
||||||
# Interventions urgentes
|
# Interventions urgentes
|
||||||
urgent_interventions = Intervention.query.filter_by(priority='urgente', is_deleted=False).limit(5).all()
|
urgent_interventions = Intervention.query.filter_by(priority='urgente', is_deleted=False).limit(5).all() if dashboard_permissions['intervention'] else []
|
||||||
|
|
||||||
# Interventions du jour (planning)
|
# Interventions du jour (planning)
|
||||||
today = date.today()
|
today = date.today()
|
||||||
day_schedule = PlanningService.get_day_schedule(today)
|
day_schedule = PlanningService.get_day_schedule(today) if dashboard_permissions['planning'] else None
|
||||||
|
|
||||||
# Interprétations en attente (non traitées)
|
# Interprétations en attente (non traitées)
|
||||||
from ...outlook.models import OutlookMailInterpretation
|
from ...outlook.models import OutlookMailInterpretation
|
||||||
|
|
@ -94,12 +109,12 @@ def index():
|
||||||
outlook_interpretations = OutlookMailInterpretation.query.filter_by(
|
outlook_interpretations = OutlookMailInterpretation.query.filter_by(
|
||||||
status='pending',
|
status='pending',
|
||||||
user_id=current_user.id
|
user_id=current_user.id
|
||||||
).order_by(OutlookMailInterpretation.created_at.desc()).limit(10).all()
|
).order_by(OutlookMailInterpretation.created_at.desc()).limit(10).all() if dashboard_permissions['outlook'] else []
|
||||||
|
|
||||||
# Récupérer les interprétations ENT
|
# Récupérer les interprétations ENT
|
||||||
ent_interpretations = EntMessageInterpretation.query.filter_by(
|
ent_interpretations = EntMessageInterpretation.query.filter_by(
|
||||||
status='pending'
|
status='pending'
|
||||||
).order_by(EntMessageInterpretation.created_at.desc()).limit(10).all()
|
).order_by(EntMessageInterpretation.created_at.desc()).limit(10).all() if dashboard_permissions['ent'] else []
|
||||||
|
|
||||||
# Combiner les deux listes avec une source
|
# Combiner les deux listes avec une source
|
||||||
pending_interpretations = []
|
pending_interpretations = []
|
||||||
|
|
@ -148,7 +163,8 @@ def index():
|
||||||
lots_without_equipment=lots_without_equipment,
|
lots_without_equipment=lots_without_equipment,
|
||||||
lots_missing_duration=lots_missing_duration,
|
lots_missing_duration=lots_missing_duration,
|
||||||
today=today,
|
today=today,
|
||||||
statuses=INTERVENTION_STATUSES)
|
statuses=INTERVENTION_STATUSES,
|
||||||
|
dashboard_permissions=dashboard_permissions)
|
||||||
|
|
||||||
|
|
||||||
@dashboard_bp.route('/api/stats')
|
@dashboard_bp.route('/api/stats')
|
||||||
|
|
@ -158,28 +174,33 @@ def api_stats():
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
can_intervention = has_permission('intervention.view', current_user)
|
||||||
|
can_patrimoine = has_permission('patrimoine.view', current_user)
|
||||||
|
|
||||||
# Interventions par mois (6 derniers mois)
|
# Interventions par mois (6 derniers mois)
|
||||||
six_months_ago = date.today() - timedelta(days=180)
|
six_months_ago = date.today() - timedelta(days=180)
|
||||||
monthly = db.session.query(
|
monthly = db.session.query(
|
||||||
func.date_format(Intervention.created_at, '%Y-%m').label('month'),
|
func.date_format(Intervention.created_at, '%Y-%m').label('month'),
|
||||||
func.count().label('count')
|
func.count().label('count')
|
||||||
).filter(Intervention.created_at >= six_months_ago).group_by('month').order_by('month').all()
|
).filter(Intervention.created_at >= six_months_ago).group_by('month').order_by('month').all() if can_intervention else []
|
||||||
|
|
||||||
monthly_data = [{'month': r.month, 'count': r.count} for r in monthly]
|
monthly_data = [{'month': r.month, 'count': r.count} for r in monthly]
|
||||||
|
|
||||||
# Interventions par statut
|
# Interventions par statut
|
||||||
status_data = {}
|
status_data = {}
|
||||||
for status_val, info in INTERVENTION_STATUSES.items():
|
if can_intervention:
|
||||||
count = Intervention.query.filter_by(status=status_val).count()
|
for status_val, info in INTERVENTION_STATUSES.items():
|
||||||
if count > 0:
|
count = Intervention.query.filter_by(status=status_val).count()
|
||||||
status_data[info['label']] = count
|
if count > 0:
|
||||||
|
status_data[info['label']] = count
|
||||||
|
|
||||||
# Equipements par statut
|
# Equipements par statut
|
||||||
equip_status = {}
|
equip_status = {}
|
||||||
for s in EQUIPMENT_STATUSES:
|
if can_patrimoine:
|
||||||
count = Equipment.query.filter_by(status=s).count()
|
for s in EQUIPMENT_STATUSES:
|
||||||
if count > 0:
|
count = Equipment.query.filter_by(status=s).count()
|
||||||
equip_status[s] = count
|
if count > 0:
|
||||||
|
equip_status[s] = count
|
||||||
|
|
||||||
# Top 5 equipements les plus intervenus
|
# Top 5 equipements les plus intervenus
|
||||||
top_equip = db.session.query(
|
top_equip = db.session.query(
|
||||||
|
|
@ -188,7 +209,7 @@ def api_stats():
|
||||||
).join(Intervention, Intervention.equipment_id == Equipment.id)\
|
).join(Intervention, Intervention.equipment_id == Equipment.id)\
|
||||||
.group_by(Equipment.id)\
|
.group_by(Equipment.id)\
|
||||||
.order_by(func.count(Intervention.id).desc())\
|
.order_by(func.count(Intervention.id).desc())\
|
||||||
.limit(5).all()
|
.limit(5).all() if can_intervention and can_patrimoine else []
|
||||||
|
|
||||||
top_data = [{'name': r.name, 'count': r.count} for r in top_equip]
|
top_data = [{'name': r.name, 'count': r.count} for r in top_equip]
|
||||||
|
|
||||||
|
|
@ -213,26 +234,26 @@ def search():
|
||||||
}
|
}
|
||||||
|
|
||||||
if query:
|
if query:
|
||||||
# Recherche interventions
|
if has_permission('intervention.view', current_user):
|
||||||
results['interventions'] = Intervention.query.filter(
|
results['interventions'] = Intervention.query.filter(
|
||||||
db.or_(
|
db.or_(
|
||||||
Intervention.title.ilike(f'%{query}%'),
|
Intervention.title.ilike(f'%{query}%'),
|
||||||
Intervention.description.ilike(f'%{query}%')
|
Intervention.description.ilike(f'%{query}%')
|
||||||
)
|
)
|
||||||
).limit(10).all()
|
).limit(10).all()
|
||||||
|
|
||||||
# Recherche équipements
|
if has_permission('patrimoine.view', current_user):
|
||||||
results['equipments'] = Equipment.query.filter(
|
results['equipments'] = Equipment.query.filter(
|
||||||
db.or_(
|
db.or_(
|
||||||
Equipment.name.ilike(f'%{query}%'),
|
Equipment.name.ilike(f'%{query}%'),
|
||||||
Equipment.serial_number.ilike(f'%{query}%')
|
Equipment.serial_number.ilike(f'%{query}%')
|
||||||
)
|
)
|
||||||
).limit(10).all()
|
).limit(10).all()
|
||||||
|
|
||||||
# Recherche salles
|
if has_permission('patrimoine.view', current_user):
|
||||||
results['rooms'] = Room.query.filter(
|
results['rooms'] = Room.query.filter(
|
||||||
Room.name.ilike(f'%{query}%')
|
Room.name.ilike(f'%{query}%')
|
||||||
).limit(10).all()
|
).limit(10).all()
|
||||||
|
|
||||||
return render_template('dashboard/search.html', query=query, results=results)
|
return render_template('dashboard/search.html', query=query, results=results)
|
||||||
|
|
||||||
|
|
@ -245,31 +266,36 @@ def alerts():
|
||||||
from app_new.core.models.company import Alert
|
from app_new.core.models.company import Alert
|
||||||
alerts_list = Alert.query.filter_by(is_read=False).order_by(Alert.created_at.desc()).all()
|
alerts_list = Alert.query.filter_by(is_read=False).order_by(Alert.created_at.desc()).all()
|
||||||
|
|
||||||
|
can_intervention = has_permission('intervention.view', current_user)
|
||||||
|
can_patrimoine = has_permission('patrimoine.view', current_user)
|
||||||
|
can_outlook = has_permission('integration.outlook.view', current_user)
|
||||||
|
can_ent = has_permission('integration.ent.view', current_user)
|
||||||
|
|
||||||
# Interventions urgentes (statut haute priorité)
|
# Interventions urgentes (statut haute priorité)
|
||||||
urgent_interventions = Intervention.query.filter(
|
urgent_interventions = Intervention.query.filter(
|
||||||
Intervention.priority == 'haute',
|
Intervention.priority == 'haute',
|
||||||
Intervention.status.in_(['planifiee', 'en_cours', 'en_attente'])
|
Intervention.status.in_(['planifiee', 'en_cours', 'en_attente'])
|
||||||
).order_by(Intervention.scheduled_date.desc()).all()
|
).order_by(Intervention.scheduled_date.desc()).all() if can_intervention else []
|
||||||
|
|
||||||
# Équipements en panne
|
# Équipements en panne
|
||||||
from app_new.core.models.equipment import Equipment
|
from app_new.core.models.equipment import Equipment
|
||||||
equipments_panne = Equipment.query.filter(
|
equipments_panne = Equipment.query.filter(
|
||||||
Equipment.status.in_(['en_panne', 'hors_service', 'hs']),
|
Equipment.status.in_(['en_panne', 'hors_service', 'hs']),
|
||||||
Equipment.is_deleted.is_(False),
|
Equipment.is_deleted.is_(False),
|
||||||
).all()
|
).all() if can_patrimoine else []
|
||||||
|
|
||||||
# Équipements à jeter
|
# Équipements à jeter
|
||||||
equipments_to_trash = Equipment.query.filter_by(status='a_jeter').all()
|
equipments_to_trash = Equipment.query.filter_by(status='a_jeter').all() if can_patrimoine else []
|
||||||
|
|
||||||
# Interprétations en attente
|
# Interprétations en attente
|
||||||
from ...outlook.models import OutlookMailInterpretation
|
from ...outlook.models import OutlookMailInterpretation
|
||||||
from ...ent.interpretation_models import EntMessageInterpretation
|
from ...ent.interpretation_models import EntMessageInterpretation
|
||||||
pending_outlook = OutlookMailInterpretation.query.filter_by(
|
pending_outlook = OutlookMailInterpretation.query.filter_by(
|
||||||
status='pending', user_id=current_user.id
|
status='pending', user_id=current_user.id
|
||||||
).order_by(OutlookMailInterpretation.created_at.desc()).all()
|
).order_by(OutlookMailInterpretation.created_at.desc()).all() if can_outlook else []
|
||||||
pending_ent = EntMessageInterpretation.query.filter_by(
|
pending_ent = EntMessageInterpretation.query.filter_by(
|
||||||
status='pending'
|
status='pending'
|
||||||
).order_by(EntMessageInterpretation.created_at.desc()).all()
|
).order_by(EntMessageInterpretation.created_at.desc()).all() if can_ent else []
|
||||||
|
|
||||||
return render_template('dashboard/alerts.html',
|
return render_template('dashboard/alerts.html',
|
||||||
alerts=alerts_list,
|
alerts=alerts_list,
|
||||||
|
|
@ -301,6 +327,10 @@ def create_month_interventions():
|
||||||
from ..models.maintenance import Intervention
|
from ..models.maintenance import Intervention
|
||||||
from ..models.company import Company
|
from ..models.company import Company
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
if not (has_permission('planning.manage', current_user) or has_permission('intervention.manage', current_user)):
|
||||||
|
from flask import abort
|
||||||
|
abort(403)
|
||||||
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
one_month_later = today + timedelta(days=30)
|
one_month_later = today + timedelta(days=30)
|
||||||
|
|
|
||||||
|
|
@ -11,33 +11,35 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="mb-3 h3"><i class="bi bi-speedometer2"></i> Tableau de bord</h1>
|
<h1 class="mb-3 h3"><i class="bi bi-speedometer2"></i> Tableau de bord</h1>
|
||||||
|
|
||||||
<!-- Alerte modele IA -->
|
{% if dashboard_permissions.ai %}<!-- Alerte modele IA -->
|
||||||
<div id="ai-alert" style="display:none;" class="alert alert-danger mb-3">
|
<div id="ai-alert" style="display:none;" class="alert alert-danger mb-3">
|
||||||
<i class="bi bi-exclamation-triangle"></i>
|
<i class="bi bi-exclamation-triangle"></i>
|
||||||
<strong>Modele IA indisponible !</strong>
|
<strong>Modele IA indisponible !</strong>
|
||||||
<span id="ai-alert-msg"></span>
|
<span id="ai-alert-msg"></span>
|
||||||
<a href="{{ url_for('ai_config.index') }}" class="alert-link">Configurer le modele</a>
|
<a href="{{ url_for('ai_config.index') }}" class="alert-link">Configurer le modele</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Alerte retards -->
|
{% if dashboard_permissions.planning %}<!-- Alerte retards -->
|
||||||
<div id="overdue-alert" style="display:none;" class="alert alert-warning mb-3">
|
<div id="overdue-alert" style="display:none;" class="alert alert-warning mb-3">
|
||||||
<i class="bi bi-clock-history"></i>
|
<i class="bi bi-clock-history"></i>
|
||||||
<strong>Retards detectes !</strong>
|
<strong>Retards detectes !</strong>
|
||||||
<span id="overdue-alert-msg"></span>
|
<span id="overdue-alert-msg"></span>
|
||||||
<a href="{{ url_for('scheduler.index') }}" class="alert-link">Voir le planificateur</a>
|
<a href="{{ url_for('scheduler.index') }}" class="alert-link">Voir le planificateur</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Stats rapides -->
|
<!-- Stats rapides -->
|
||||||
<div class="row g-2 g-md-3 mb-3">
|
<div class="row g-2 g-md-3 mb-3">
|
||||||
<div class="col-12">
|
{% if dashboard_permissions.intervention_manage or dashboard_permissions.planning_manage %}<div class="col-12">
|
||||||
<form method="POST" action="{{ url_for('dashboard.create_month_interventions') }}" class="d-inline" onsubmit="return confirm('Créer les interventions pour le mois prochain?')">
|
<form method="POST" action="{{ url_for('dashboard.create_month_interventions') }}" class="d-inline" onsubmit="return confirm('Créer les interventions pour le mois prochain?')">
|
||||||
<button type="submit" class="btn btn-primary btn-sm">
|
<button type="submit" class="btn btn-primary btn-sm">
|
||||||
<i class="bi bi-calendar-plus me-1"></i>Créer interventions (1 mois)
|
<i class="bi bi-calendar-plus me-1"></i>Créer interventions (1 mois)
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<div class="col-6 col-md-3">
|
{% if dashboard_permissions.intervention %}<div class="col-6 col-md-3">
|
||||||
<div class="card stat-card text-white bg-primary">
|
<div class="card stat-card text-white bg-primary">
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
<small class="card-title">Interventions</small>
|
<small class="card-title">Interventions</small>
|
||||||
|
|
@ -45,8 +47,8 @@
|
||||||
<small>{{ stats.interventions_en_cours }} en cours</small>
|
<small>{{ stats.interventions_en_cours }} en cours</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="col-6 col-md-3">
|
{% if dashboard_permissions.intervention %}<div class="col-6 col-md-3">
|
||||||
<div class="card stat-card text-white bg-success">
|
<div class="card stat-card text-white bg-success">
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
<small class="card-title">Curatif / Préventif</small>
|
<small class="card-title">Curatif / Préventif</small>
|
||||||
|
|
@ -54,8 +56,8 @@
|
||||||
<small>{{ stats.interventions_recurrentes }} récurrentes</small>
|
<small>{{ stats.interventions_recurrentes }} récurrentes</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="col-6 col-md-3">
|
{% if dashboard_permissions.patrimoine %}<div class="col-6 col-md-3">
|
||||||
<div class="card stat-card text-white bg-warning">
|
<div class="card stat-card text-white bg-warning">
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
<small class="card-title">Équipements</small>
|
<small class="card-title">Équipements</small>
|
||||||
|
|
@ -63,8 +65,8 @@
|
||||||
<small>{{ stats.equipments_hors_service }} hors service</small>
|
<small>{{ stats.equipments_hors_service }} hors service</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="col-6 col-md-3">
|
{% if dashboard_permissions.stock %}<div class="col-6 col-md-3">
|
||||||
<div class="card stat-card {% if stats.parts_low_stock > 0 %}bg-danger{% else %}bg-info{% endif %} text-white">
|
<div class="card stat-card {% if stats.parts_low_stock > 0 %}bg-danger{% else %}bg-info{% endif %} text-white">
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
<small class="card-title">Stock bas</small>
|
<small class="card-title">Stock bas</small>
|
||||||
|
|
@ -72,10 +74,10 @@
|
||||||
<small>pièce(s)</small>
|
<small>pièce(s)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Échéances préventif -->
|
{% if dashboard_permissions.planning %}<!-- Échéances préventif -->
|
||||||
<div class="row g-2 g-md-3 mb-3">
|
<div class="row g-2 g-md-3 mb-3">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
|
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
|
||||||
|
|
@ -117,9 +119,9 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<!-- Contrôles des référentiels de maintenance -->
|
{% if dashboard_permissions.patrimoine %}<!-- Contrôles des référentiels de maintenance -->
|
||||||
<div class="row g-2 g-md-3 mb-3">
|
<div class="row g-2 g-md-3 mb-3">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="card h-100 {% if stats.lots_without_equipment %}border-warning{% else %}border-success{% endif %}">
|
<div class="card h-100 {% if stats.lots_without_equipment %}border-warning{% else %}border-success{% endif %}">
|
||||||
|
|
@ -177,9 +179,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<!-- Alertes -->
|
{% if dashboard_permissions.intervention or dashboard_permissions.patrimoine or dashboard_permissions.ent or dashboard_permissions.outlook %}<!-- Alertes -->
|
||||||
{% if alerts %}
|
{% if alerts %}
|
||||||
<div class="card border-warning mb-3">
|
<div class="card border-warning mb-3">
|
||||||
<div class="card-header bg-warning text-dark py-2">
|
<div class="card-header bg-warning text-dark py-2">
|
||||||
|
|
@ -196,9 +198,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}{% endif %}
|
||||||
|
|
||||||
<!-- Maintenances du jour -->
|
{% if dashboard_permissions.planning %}<!-- Maintenances du jour -->
|
||||||
{% if day_schedule and day_schedule.items %}
|
{% if day_schedule and day_schedule.items %}
|
||||||
<div class="card border-primary mb-3">
|
<div class="card border-primary mb-3">
|
||||||
<div class="card-header bg-primary text-white py-2">
|
<div class="card-header bg-primary text-white py-2">
|
||||||
|
|
@ -299,9 +301,9 @@
|
||||||
<p class="mb-0">{{ day_schedule.leave }}</p>
|
<p class="mb-0">{{ day_schedule.leave }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}{% endif %}
|
||||||
|
|
||||||
<!-- 2 colonnes : Maintenances + Fréquence -->
|
{% if dashboard_permissions.planning %}<!-- 2 colonnes : Maintenances + Fréquence -->
|
||||||
<div class="row g-2 g-md-3 mb-3">
|
<div class="row g-2 g-md-3 mb-3">
|
||||||
<!-- Maintenances à venir -->
|
<!-- Maintenances à venir -->
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
|
|
@ -376,9 +378,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<!-- Workflow + Activité récente -->
|
{% if dashboard_permissions.intervention %}<!-- Workflow + Activité récente -->
|
||||||
<div class="row g-2 g-md-3 mb-3">
|
<div class="row g-2 g-md-3 mb-3">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="card h-100 border-warning">
|
<div class="card h-100 border-warning">
|
||||||
|
|
@ -433,39 +435,40 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<!-- Graphiques -->
|
{% if dashboard_permissions.intervention or dashboard_permissions.patrimoine %}<!-- Graphiques -->
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-6">
|
{% if dashboard_permissions.intervention %}<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Interventions par mois</h6></div>
|
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Interventions par mois</h6></div>
|
||||||
<div class="card-body"><canvas id="chart-monthly" height="200"></canvas></div>
|
<div class="card-body"><canvas id="chart-monthly" height="200"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="col-md-6">
|
{% if dashboard_permissions.intervention %}<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Interventions par statut</h6></div>
|
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Interventions par statut</h6></div>
|
||||||
<div class="card-body"><canvas id="chart-status" height="200"></canvas></div>
|
<div class="card-body"><canvas id="chart-status" height="200"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="row g-3 mb-3">
|
{% if dashboard_permissions.patrimoine or (dashboard_permissions.intervention and dashboard_permissions.patrimoine) %}<div class="row g-3 mb-3">
|
||||||
<div class="col-md-6">
|
{% if dashboard_permissions.patrimoine %}<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Equipements par statut</h6></div>
|
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Equipements par statut</h6></div>
|
||||||
<div class="card-body"><canvas id="chart-equip" height="200"></canvas></div>
|
<div class="card-body"><canvas id="chart-equip" height="200"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
<div class="col-md-6">
|
{% if dashboard_permissions.intervention and dashboard_permissions.patrimoine %}<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Top 5 equipements</h6></div>
|
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Top 5 equipements</h6></div>
|
||||||
<div class="card-body"><canvas id="chart-top" height="200"></canvas></div>
|
<div class="card-body"><canvas id="chart-top" height="200"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Dernières interventions -->
|
{% if dashboard_permissions.intervention %}<!-- Dernières interventions -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center py-2">
|
<div class="card-header d-flex justify-content-between align-items-center py-2">
|
||||||
<span><i class="bi bi-clock-history"></i> Dernières interventions</span>
|
<span><i class="bi bi-clock-history"></i> Dernières interventions</span>
|
||||||
|
|
@ -516,10 +519,10 @@
|
||||||
<div class="p-3 text-muted">Aucune intervention.</div>
|
<div class="p-3 text-muted">Aucune intervention.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endif %}
|
||||||
|
|
||||||
<!-- Interprétations en attente -->
|
<!-- Interprétations en attente -->
|
||||||
{% if pending_interpretations %}
|
{% if pending_interpretations and (dashboard_permissions.ent or dashboard_permissions.outlook) %}
|
||||||
<div class="card mb-3 border-warning">
|
<div class="card mb-3 border-warning">
|
||||||
<div class="card-header bg-warning bg-opacity-10 d-flex justify-content-between align-items-center py-2">
|
<div class="card-header bg-warning bg-opacity-10 d-flex justify-content-between align-items-center py-2">
|
||||||
<span><i class="bi bi-robot"></i> Interprétations en attente <span class="badge bg-warning text-dark">{{ pending_interpretations|length }}</span></span>
|
<span><i class="bi bi-robot"></i> Interprétations en attente <span class="badge bg-warning text-dark">{{ pending_interpretations|length }}</span></span>
|
||||||
|
|
@ -684,7 +687,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
{% if dashboard_permissions.ai or dashboard_permissions.planning %}<script>
|
||||||
|
{% if dashboard_permissions.ai %}
|
||||||
// Verifier le statut du modele IA au chargement
|
// Verifier le statut du modele IA au chargement
|
||||||
fetch('/ai-config/api/status').then(r => r.json()).then(d => {
|
fetch('/ai-config/api/status').then(r => r.json()).then(d => {
|
||||||
if (d.status === 'error' || d.status === 'no_model' || d.status === 'no_key') {
|
if (d.status === 'error' || d.status === 'no_model' || d.status === 'no_key') {
|
||||||
|
|
@ -700,7 +704,9 @@ fetch('/ai-config/api/status').then(r => r.json()).then(d => {
|
||||||
alert.style.display = 'block';
|
alert.style.display = 'block';
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if dashboard_permissions.planning %}
|
||||||
// Verifier les retards
|
// Verifier les retards
|
||||||
fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
|
fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
|
||||||
const total = (d.overdue_tasks || 0) + (d.overdue_interventions || 0);
|
const total = (d.overdue_tasks || 0) + (d.overdue_interventions || 0);
|
||||||
|
|
@ -711,5 +717,7 @@ fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
|
||||||
alert.style.display = 'block';
|
alert.style.display = 'block';
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue