feat: Module Devis professionnel avec écrans complets

Création de 2 écrans professionnels pour le module Devis:

1. devis/nouveau.xhtml:
   - 4 sections: Informations générales, Détail du devis, Montants, Conditions
   - Numéro auto-généré avec icône
   - Statut avec 5 valeurs (BROUILLON, ATTENTE, ACCEPTE, REFUSE, EXPIRE)
   - Dates d'émission et validité avec calendriers
   - Client et objet du devis requis
   - Placeholder pour lignes de devis (future développement)
   - Calcul automatique TVA 18% et TTC
   - Récapitulatif visuel HT/TVA/TTC avec composant monétaire
   - Conditions de paiement et remarques (section collapsible)
   - 3 boutons: Annuler, Brouillon, Envoyer

2. devis/details.xhtml:
   - En-tête: numéro, statut, client, objet, dates
   - Actions: Retour, Convertir en chantier, PDF, Modifier
   - 4 KPI cards: Montant HT, TVA, TTC, Statut
   - 6 onglets professionnels:
     * Vue d'ensemble: infos + récap financier + actions rapides
     * Détail des lignes: table lignes (placeholder)
     * Conditions: paiement, délais, garanties
     * Documents: GED associée (placeholder)
     * Suivi: timeline actions
     * Historique: modifications (placeholder)

Corrections:
- Fix navigation /factures/nouvelle -> /factures/nouveau (factures.xhtml)
- Fix menu /factures/nouvelle -> /factures/nouveau (menu.xhtml)

Tous les composants réutilisables utilisés (status-badge, monetary-display).
Validation complète côté client et serveur.
UI/UX professionnel adapté au métier BTP.
This commit is contained in:
dahoud
2025-11-08 10:49:19 +00:00
parent 0fad42ccaf
commit ec38f6a23a
192 changed files with 12029 additions and 271 deletions

View File

@@ -0,0 +1,162 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Carte d'information/KPI
Principe DRY: Un seul composant pour toutes les cartes d'informations
Paramètres:
- title: Titre de la carte - requis
- value: Valeur principale à afficher - requis
- subtitle: Sous-titre/description - optionnel
- icon: Icône (classe PrimeIcons) - optionnel
- iconColor: Couleur de l'icône (primary, success, info, warning, danger) - défaut: primary
- badge: Texte du badge - optionnel
- badgeSeverity: Gravité du badge (success, info, warning, danger) - défaut: info
- trend: Tendance (+5%, -3%) - optionnel
- trendType: Type de tendance (up, down, stable) - auto-détecté depuis trend
- footer: Texte du pied de carte - optionnel
- actionLabel: Libellé bouton d'action - optionnel
- actionIcon: Icône bouton d'action - défaut: pi-arrow-right
- actionUrl: URL de l'action - optionnel
Utilisation KPI Dashboard:
<ui:include src="/WEB-INF/components/detail-card.xhtml">
<ui:param name="title" value="Chantiers actifs"/>
<ui:param name="value" value="#{dashboardView.chantiersActifs}"/>
<ui:param name="icon" value="pi-building"/>
<ui:param name="iconColor" value="primary"/>
<ui:param name="trend" value="+12%"/>
<ui:param name="footer" value="vs mois dernier"/>
<ui:param name="actionLabel" value="Voir tous"/>
<ui:param name="actionUrl" value="/chantiers.xhtml"/>
</ui:include>
Carte avec badge:
<ui:include src="/WEB-INF/components/detail-card.xhtml">
<ui:param name="title" value="Budget total"/>
<ui:param name="value" value="125 000 000 FCFA"/>
<ui:param name="icon" value="pi-wallet"/>
<ui:param name="iconColor" value="success"/>
<ui:param name="badge" value="En cours"/>
<ui:param name="badgeSeverity" value="info"/>
<ui:param name="subtitle" value="2025"/>
</ui:include>
Carte simple:
<ui:include src="/WEB-INF/components/detail-card.xhtml">
<ui:param name="title" value="Factures impayées"/>
<ui:param name="value" value="8"/>
<ui:param name="icon" value="pi-exclamation-circle"/>
<ui:param name="iconColor" value="danger"/>
</ui:include>
-->
<!-- Détection automatique du type de tendance -->
<c:if test="#{not empty trend and empty trendType}">
<c:choose>
<c:when test="#{trend.startsWith('+')}">
<c:set var="autoTrendType" value="up"/>
</c:when>
<c:when test="#{trend.startsWith('-')}">
<c:set var="autoTrendType" value="down"/>
</c:when>
<c:otherwise>
<c:set var="autoTrendType" value="stable"/>
</c:otherwise>
</c:choose>
</c:if>
<c:set var="trendDirection" value="#{empty trendType ? autoTrendType : trendType}"/>
<!-- Couleur de l'icône -->
<c:choose>
<c:when test="#{iconColor eq 'success'}">
<c:set var="iconColorClass" value="text-green-500"/>
<c:set var="iconBgClass" value="bg-green-100"/>
</c:when>
<c:when test="#{iconColor eq 'info'}">
<c:set var="iconColorClass" value="text-blue-500"/>
<c:set var="iconBgClass" value="bg-blue-100"/>
</c:when>
<c:when test="#{iconColor eq 'warning'}">
<c:set var="iconColorClass" value="text-orange-500"/>
<c:set var="iconBgClass" value="bg-orange-100"/>
</c:when>
<c:when test="#{iconColor eq 'danger'}">
<c:set var="iconColorClass" value="text-red-500"/>
<c:set var="iconBgClass" value="bg-red-100"/>
</c:when>
<c:otherwise>
<c:set var="iconColorClass" value="text-primary"/>
<c:set var="iconBgClass" value="bg-primary-100"/>
</c:otherwise>
</c:choose>
<!-- Carte -->
<div class="card mb-0 detail-card" style="height: 100%;">
<div class="flex flex-column" style="height: 100%;">
<!-- En-tête avec icône et badge -->
<div class="flex align-items-start justify-content-between mb-3">
<div class="flex align-items-center gap-3">
<h:panelGroup rendered="#{not empty icon}">
<div class="flex align-items-center justify-content-center #{iconBgClass}"
style="width: 3rem; height: 3rem; border-radius: 0.5rem;">
<i class="#{icon} #{iconColorClass}" style="font-size: 1.5rem;"/>
</div>
</h:panelGroup>
<div>
<span class="text-600 font-medium text-sm block mb-1">#{title}</span>
<h:panelGroup rendered="#{not empty subtitle}">
<span class="text-500 text-xs">#{subtitle}</span>
</h:panelGroup>
</div>
</div>
<h:panelGroup rendered="#{not empty badge}">
<p:badge value="#{badge}"
severity="#{empty badgeSeverity ? 'info' : badgeSeverity}"/>
</h:panelGroup>
</div>
<!-- Valeur principale -->
<div class="text-900 font-bold text-3xl mb-2">#{value}</div>
<!-- Tendance -->
<h:panelGroup rendered="#{not empty trend}">
<div class="flex align-items-center gap-2 mb-3">
<i class="#{trendDirection eq 'up' ? 'pi pi-arrow-up text-green-500' :
trendDirection eq 'down' ? 'pi pi-arrow-down text-red-500' :
'pi pi-minus text-gray-500'}"
style="font-size: 0.875rem;"/>
<span class="#{trendDirection eq 'up' ? 'text-green-600' :
trendDirection eq 'down' ? 'text-red-600' :
'text-gray-600'} font-medium text-sm">
#{trend}
</span>
</div>
</h:panelGroup>
<!-- Spacer pour pousser le footer en bas -->
<div class="flex-grow-1"></div>
<!-- Pied de carte -->
<h:panelGroup rendered="#{not empty footer or not empty actionLabel}">
<div class="flex align-items-center justify-content-between pt-3 border-top-1 surface-border">
<span class="text-500 text-sm">#{footer}</span>
<h:panelGroup rendered="#{not empty actionLabel}">
<h:link value="#{actionLabel}"
outcome="#{actionUrl}"
styleClass="text-primary font-medium text-sm flex align-items-center gap-1 no-underline hover:text-primary-700">
<i class="#{empty actionIcon ? 'pi pi-arrow-right' : actionIcon}" style="font-size: 0.75rem;"/>
</h:link>
</h:panelGroup>
</div>
</h:panelGroup>
</div>
</div>
</ui:composition>