feat(assets): color-code asset types by technical domain for enhanced visual scanning

This commit is contained in:
kdeterme 2026-10-05 11:48:44 +02:00
parent 10e9f1de50
commit f324eb00bd
3 changed files with 151 additions and 9 deletions

View file

@ -566,6 +566,139 @@ class BuildingAsset(AbstractGeoAsset):
} }
return icons.get(self.asset_type, 'bi-gear') return icons.get(self.asset_type, 'bi-gear')
def get_asset_theme(self):
"""
Retourne la configuration graphique (couleurs d'icône et de badge)
selon la typologie métier de l'asset.
"""
# 1. Sécurité incendie (Rouge alerte)
if self.asset_type in ('fire_safety', 'smoke_detector', 'fire_extinguisher',
'smoke_exhaust', 'fire_door'):
return {
'color': '#b91c1c', # red-700
'bg_color': '#fef2f2', # red-50
'border_color': '#fecaca', # red-200
'icon_color': '#dc2626', # red-600
'badge_class': 'bg-danger-subtle text-danger border',
}
# 2. Génie climatique & CVC (Orange thermique / Chaleur & Froid)
if self.asset_type in ('boiler', 'heat_pump', 'heat_substation', 'radiator_emitter',
'air_handling_unit', 'air_conditioning', 'water_heater', 'hvac'):
return {
'color': '#c2410c', # orange-700
'bg_color': '#fff7ed', # orange-50
'border_color': '#fed7aa', # orange-200
'icon_color': '#ea580c', # orange-600
'badge_class': 'bg-warning-subtle text-danger-emphasis border',
}
# 3. Électricité, Énergie & Éclairage (Ambre / Jaune or)
if self.asset_type in ('electricity', 'inverter_generator', 'lighting', 'emergency_lighting',
'meter', 'solar_panel', 'ev_charging'):
return {
'color': '#a16207', # yellow-700
'bg_color': '#fefce8', # yellow-50
'border_color': '#fef08a', # yellow-200
'icon_color': '#ca8a04', # yellow-600
'badge_class': 'bg-warning-subtle text-dark border',
}
# 4. Sûreté, Contrôle d'accès & Vidéosurveillance (Pourpre / Violet)
if self.asset_type in ('access_control', 'alarm', 'cctv_surveillance'):
return {
'color': '#7e22ce', # purple-700
'bg_color': '#faf5ff', # purple-50
'border_color': '#e9d5ff', # purple-200
'icon_color': '#9333ea', # purple-600
'badge_class': 'bg-primary-subtle text-primary border',
}
# 5. Clos & Couvert / Enveloppe (Teal / Vert canard)
if self.asset_type in ('roof', 'roof_drainage', 'facade', 'window', 'skylight',
'external_door', 'shutter_blind', 'insulation'):
return {
'color': '#0f766e', # teal-700
'bg_color': '#f0fdfa', # teal-50
'border_color': '#99f6e4', # teal-200
'icon_color': '#0d9488', # teal-600
'badge_class': 'bg-success-subtle text-success border',
}
# 6. Plomberie, Sanitaires & Gestion de l'eau (Bleu azur / Sky)
if self.asset_type in ('sanitary', 'plumbing_piping', 'sump_pump', 'rainwater_harvesting'):
return {
'color': '#0369a1', # sky-700
'bg_color': '#f0f9ff', # sky-50
'border_color': '#bae6fd', # sky-200
'icon_color': '#0284c7', # sky-600
'badge_class': 'bg-info-subtle text-info border',
}
# 7. Mobilité verticale / Ascenseurs (Bleu cobalt)
if self.asset_type in ('elevator', 'disabled_lift'):
return {
'color': '#1d4ed8', # blue-700
'bg_color': '#eff6ff', # blue-50
'border_color': '#bfdbfe', # blue-200
'icon_color': '#2563eb', # blue-600
'badge_class': 'bg-primary-subtle text-primary border',
}
# 8. Réseaux & Numérique / GTB (Indigo informatique)
if self.asset_type in ('network_rack', 'bms_system'):
return {
'color': '#4338ca', # indigo-700
'bg_color': '#eef2ff', # indigo-50
'border_color': '#c7d2fe', # indigo-200
'icon_color': '#4f46e5', # indigo-600
'badge_class': 'bg-dark-subtle text-dark border',
}
# 9. Second œuvre & Aménagements intérieurs (Ardoise neutre)
if self.asset_type in ('wall', 'interior_door', 'ceiling', 'flooring', 'stairs'):
return {
'color': '#334155', # slate-700
'bg_color': '#f8fafc', # slate-50
'border_color': '#cbd5e1', # slate-300
'icon_color': '#64748b', # slate-500
'badge_class': 'bg-secondary-subtle text-secondary border',
}
# Par défaut (Gros œuvre & Autre)
return {
'color': '#475569',
'bg_color': '#f1f5f9',
'border_color': '#cbd5e1',
'icon_color': '#64748b',
'badge_class': 'bg-light text-muted border',
}
@property
def theme_color(self):
return self.get_asset_theme()['color']
@property
def theme_icon_color(self):
return self.get_asset_theme()['icon_color']
@property
def theme_bg_color(self):
return self.get_asset_theme()['bg_color']
@property
def theme_border_color(self):
return self.get_asset_theme()['border_color']
@property
def theme_badge_style(self):
t = self.get_asset_theme()
return f"background-color: {t['bg_color']} !important; color: {t['color']} !important; border: 1px solid {t['border_color']} !important;"
@property
def theme_badge_class(self):
return self.get_asset_theme()['badge_class']
# Backward compatibility alias # Backward compatibility alias
BuildingEquipment = BuildingAsset BuildingEquipment = BuildingAsset

View file

@ -17,15 +17,24 @@
{% else %} {% else %}
<span class="d-inline-block text-center text-muted" style="width: 1.35rem;"><i class="bi bi-dash small"></i></span> <span class="d-inline-block text-center text-muted" style="width: 1.35rem;"><i class="bi bi-dash small"></i></span>
{% endif %} {% endif %}
<i class="bi {{ asset_node.asset.get_icon_class|default:'bi-tools' }} text-info" style="font-size: 0.95rem;"></i>
<!-- Icône colorée selon la famille d'asset -->
<i class="bi {{ asset_node.asset.get_icon_class|default:'bi-tools' }}" style="font-size: 1rem; color: {{ asset_node.asset.theme_icon_color }};"></i>
<!-- Code en bleu pour indiquer clairement un lien cliquable --> <!-- Code cliquable avec couleur thématique -->
<a href="{{ asset_node.asset.get_absolute_url }}" class="fw-bold text-info-emphasis text-decoration-underline small"> <a href="{{ asset_node.asset.get_absolute_url }}" class="fw-bold text-decoration-underline small" style="color: {{ asset_node.asset.theme_color }};">
{{ asset_node.asset.code }} {{ asset_node.asset.code }}
</a> </a>
<span class="small text-dark">{{ asset_node.asset.get_name }}</span> <span class="small text-dark">{{ asset_node.asset.get_name }}</span>
<span class="badge bg-secondary-subtle text-secondary border small">{{ asset_node.asset.get_macro_lot }}</span>
<span class="badge bg-info-subtle text-info border small">{{ asset_node.asset.get_asset_type_display }}</span> <!-- Badge Macro-lot sobre -->
<span class="badge bg-light text-muted border small">{{ asset_node.asset.get_macro_lot }}</span>
<!-- Badge Typologie avec couleur thématique différenciée -->
<span class="badge {{ asset_node.asset.theme_badge_class }} small" style="{{ asset_node.asset.theme_badge_style }}">
{{ asset_node.asset.get_asset_type_display }}
</span>
{% if asset_node.asset.status == 'inactive' %} {% if asset_node.asset.status == 'inactive' %}
<span class="badge bg-danger-subtle text-danger border small">{% translate "Inactif" %}</span> <span class="badge bg-danger-subtle text-danger border small">{% translate "Inactif" %}</span>
{% endif %} {% endif %}

View file

@ -507,14 +507,14 @@
{% for eq in equipments %} {% for eq in equipments %}
<tr> <tr>
<td> <td>
<a href="{{ eq.get_absolute_url }}" class="fw-bold text-decoration-none"> <a href="{{ eq.get_absolute_url }}" class="fw-bold text-decoration-none" style="color: {{ eq.theme_color }};">
<i class="bi {{ eq.get_icon_class }} me-1 text-primary"></i>{{ eq.code }} <i class="bi {{ eq.get_icon_class }} me-1" style="color: {{ eq.theme_icon_color }};"></i>{{ eq.code }}
</a> </a>
<div class="small text-muted">{{ eq.get_name }}</div> <div class="small text-muted">{{ eq.get_name }}</div>
</td> </td>
<td> <td>
<span class="badge bg-primary-subtle text-primary border me-1">{{ eq.get_macro_lot }}</span> <span class="badge bg-light text-muted border me-1">{{ eq.get_macro_lot }}</span>
<span class="badge bg-info-subtle text-info border d-block mt-1"> <span class="badge {{ eq.theme_badge_class }} d-block mt-1" style="{{ eq.theme_badge_style }}">
{{ eq.get_asset_type_display }} {{ eq.get_asset_type_display }}
</span> </span>
</td> </td>