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,84 @@
<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">
<!--
Composant réutilisable: Dialogue de confirmation d'action
Principe DRY: Un seul composant pour toutes les confirmations (suppression, archivage, etc.)
Paramètres:
- message: Message de confirmation (requis)
- header: Titre du dialogue (défaut: "Confirmation")
- icon: Icône d'alerte (défaut: "pi pi-exclamation-triangle")
- severity: Gravité (success, info, warn, danger - défaut: warn)
- acceptLabel: Texte bouton confirmation (défaut: "Oui")
- rejectLabel: Texte bouton annulation (défaut: "Non")
- acceptIcon: Icône bouton confirmation (défaut: "pi pi-check")
- rejectIcon: Icône bouton annulation (défaut: "pi pi-times")
Utilisation en ligne (simple):
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade">
<ui:include src="/WEB-INF/components/confirmation-dialog.xhtml"/>
</p:confirmDialog>
<!-- Dans votre action -->
<p:commandButton value="Supprimer"
action="#{viewBean.delete}"
update="dataTable">
<p:confirm header="Confirmer la suppression"
message="Êtes-vous sûr de vouloir supprimer cet élément ?"
icon="pi pi-trash"/>
</p:commandButton>
Utilisation personnalisée (avancée):
<ui:include src="/WEB-INF/components/confirmation-dialog.xhtml">
<ui:param name="message" value="Cette action est irréversible. Continuer ?"/>
<ui:param name="header" value="Attention"/>
<ui:param name="severity" value="danger"/>
<ui:param name="acceptLabel" value="Confirmer"/>
<ui:param name="rejectLabel" value="Annuler"/>
</ui:include>
-->
<!-- Dialogue de confirmation global (style moderne) -->
<p:confirmDialog global="true"
showEffect="fade"
hideEffect="fade"
responsive="true"
width="350">
<div class="flex align-items-center gap-3 mb-3">
<!-- Icône avec couleur selon la gravité -->
<i class="#{empty icon ? 'pi pi-exclamation-triangle' : icon}
#{severity eq 'danger' ? 'text-red-500' :
severity eq 'warn' ? 'text-orange-500' :
severity eq 'success' ? 'text-green-500' : 'text-blue-500'}"
style="font-size: 2rem"></i>
<!-- Message -->
<span class="font-bold text-900">
<h:outputText value="#{message}" escape="false"/>
</span>
</div>
<!-- Boutons d'action -->
<div class="flex align-items-center justify-content-end gap-2">
<p:commandButton value="#{empty rejectLabel ? 'Non' : rejectLabel}"
icon="#{empty rejectIcon ? 'pi pi-times' : rejectIcon}"
styleClass="ui-button-secondary"
type="button"
onclick="PF(arguments[0]).hide()"/>
<p:commandButton value="#{empty acceptLabel ? 'Oui' : acceptLabel}"
icon="#{empty acceptIcon ? 'pi pi-check' : acceptIcon}"
styleClass="#{severity eq 'danger' ? 'ui-button-danger' :
severity eq 'warn' ? 'ui-button-warning' :
severity eq 'success' ? 'ui-button-success' : 'ui-button-primary'}"
type="button"
onclick="PF(arguments[0]).accept()"/>
</div>
</p:confirmDialog>
</ui:composition>

View File

@@ -0,0 +1,128 @@
<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">
<!--
Composant réutilisable: Filtre par plage de dates
Principe DRY: Un seul composant pour tous les filtres de dates
Paramètres:
- fromDate: Date de début (backing bean property) - requis
- toDate: Date de fin (backing bean property) - requis
- label: Libellé du filtre (défaut: "Période")
- fromLabel: Libellé date début (défaut: "Du")
- toLabel: Libellé date fin (défaut: "Au")
- pattern: Format d'affichage (défaut: "dd/MM/yyyy")
- showButtonBar: Afficher barre d'actions (défaut: true)
- showTime: Afficher sélection heure (défaut: false)
- locale: Locale (défaut: fr_FR)
- inline: Affichage inline (défaut: false)
- showPresets: Afficher raccourcis période (défaut: true)
Utilisation basique:
<ui:include src="/WEB-INF/components/date-range-filter.xhtml">
<ui:param name="fromDate" value="#{rapportView.dateDebut}"/>
<ui:param name="toDate" value="#{rapportView.dateFin}"/>
</ui:include>
Avec libellés personnalisés:
<ui:include src="/WEB-INF/components/date-range-filter.xhtml">
<ui:param name="fromDate" value="#{factureView.periodeDebut}"/>
<ui:param name="toDate" value="#{factureView.periodeFin}"/>
<ui:param name="label" value="Période de facturation"/>
<ui:param name="fromLabel" value="Début"/>
<ui:param name="toLabel" value="Fin"/>
</ui:include>
Avec heure:
<ui:include src="/WEB-INF/components/date-range-filter.xhtml">
<ui:param name="fromDate" value="#{planningView.debut}"/>
<ui:param name="toDate" value="#{planningView.fin}"/>
<ui:param name="showTime" value="true"/>
<ui:param name="pattern" value="dd/MM/yyyy HH:mm"/>
</ui:include>
-->
<div class="date-range-filter p-fluid">
<div class="card">
<h:panelGroup rendered="#{not empty label}">
<h5 class="mb-3">#{label}</h5>
</h:panelGroup>
<div class="formgrid grid">
<!-- Date de début -->
<div class="field col-12 md:col-6">
<label for="dateFrom">#{empty fromLabel ? 'Du' : fromLabel}</label>
<p:calendar id="dateFrom"
value="#{fromDate}"
pattern="#{empty pattern ? 'dd/MM/yyyy' : pattern}"
locale="#{empty locale ? 'fr_FR' : locale}"
showButtonBar="#{empty showButtonBar ? true : showButtonBar}"
showTime="#{empty showTime ? false : showTime}"
showIcon="true"
monthNavigator="true"
yearNavigator="true"
yearRange="2020:2030"
placeholder="#{empty fromLabel ? 'Du' : fromLabel}">
<p:ajax event="dateSelect" update="dateTo"/>
</p:calendar>
</div>
<!-- Date de fin -->
<div class="field col-12 md:col-6">
<label for="dateTo">#{empty toLabel ? 'Au' : toLabel}</label>
<p:calendar id="dateTo"
value="#{toDate}"
pattern="#{empty pattern ? 'dd/MM/yyyy' : pattern}"
locale="#{empty locale ? 'fr_FR' : locale}"
showButtonBar="#{empty showButtonBar ? true : showButtonBar}"
showTime="#{empty showTime ? false : showTime}"
showIcon="true"
monthNavigator="true"
yearNavigator="true"
yearRange="2020:2030"
mindate="#{fromDate}"
placeholder="#{empty toLabel ? 'Au' : toLabel}"/>
</div>
</div>
<!-- Raccourcis de période -->
<div class="flex gap-2 mt-3" style="#{empty showPresets or showPresets eq false ? 'display: none;' : ''}">
<p:commandButton value="Aujourd'hui"
styleClass="ui-button-sm ui-button-outlined"
icon="pi pi-calendar"
actionListener="#{cc.setDateRangeToToday()}"
update="@this dateFrom dateTo"
immediate="true"/>
<p:commandButton value="7 derniers jours"
styleClass="ui-button-sm ui-button-outlined"
icon="pi pi-calendar"
actionListener="#{cc.setDateRangeToLast7Days()}"
update="@this dateFrom dateTo"
immediate="true"/>
<p:commandButton value="Ce mois"
styleClass="ui-button-sm ui-button-outlined"
icon="pi pi-calendar"
actionListener="#{cc.setDateRangeToThisMonth()}"
update="@this dateFrom dateTo"
immediate="true"/>
<p:commandButton value="Ce trimestre"
styleClass="ui-button-sm ui-button-outlined"
icon="pi pi-calendar"
actionListener="#{cc.setDateRangeToThisQuarter()}"
update="@this dateFrom dateTo"
immediate="true"/>
<p:commandButton value="Cette année"
styleClass="ui-button-sm ui-button-outlined"
icon="pi pi-calendar"
actionListener="#{cc.setDateRangeToThisYear()}"
update="@this dateFrom dateTo"
immediate="true"/>
</div>
</div>
</div>
</ui:composition>

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>

View File

@@ -0,0 +1,107 @@
<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">
<!--
Composant réutilisable: Barre d'outils d'export
Principe DRY: Un seul composant pour toutes les fonctionnalités d'export
Paramètres:
- tableId: ID du DataTable à exporter - requis
- filename: Nom du fichier sans extension (défaut: "export")
- showPDF: Afficher bouton PDF (défaut: true)
- showExcel: Afficher bouton Excel (défaut: true)
- showCSV: Afficher bouton CSV (défaut: true)
- showPrint: Afficher bouton Imprimer (défaut: false)
- pageOnly: Exporter page courante uniquement (défaut: false)
- selectionOnly: Exporter sélection uniquement (défaut: false)
- alignment: Alignement (left, center, right - défaut: right)
- label: Libellé avant les boutons - optionnel
Utilisation basique:
<ui:include src="/WEB-INF/components/export-toolbar.xhtml">
<ui:param name="tableId" value="dataTable"/>
<ui:param name="filename" value="liste_chantiers"/>
</ui:include>
Export personnalisé:
<ui:include src="/WEB-INF/components/export-toolbar.xhtml">
<ui:param name="tableId" value="facturesTable"/>
<ui:param name="filename" value="factures_#{factureView.mois}"/>
<ui:param name="showPDF" value="true"/>
<ui:param name="showExcel" value="true"/>
<ui:param name="showCSV" value="false"/>
<ui:param name="showPrint" value="true"/>
<ui:param name="label" value="Exporter :"/>
</ui:include>
Export avec sélection:
<ui:include src="/WEB-INF/components/export-toolbar.xhtml">
<ui:param name="tableId" value="devisTable"/>
<ui:param name="filename" value="devis_selectionnes"/>
<ui:param name="selectionOnly" value="true"/>
</ui:include>
-->
<div class="export-toolbar flex align-items-center gap-2"
style="justify-content: #{empty alignment ? 'flex-end' :
alignment eq 'center' ? 'center' :
alignment eq 'left' ? 'flex-start' : 'flex-end'};">
<!-- Libellé optionnel -->
<h:panelGroup rendered="#{not empty label}">
<span class="text-900 font-medium mr-2">#{label}</span>
</h:panelGroup>
<!-- Bouton PDF -->
<h:panelGroup rendered="#{empty showPDF or showPDF eq true}">
<p:commandButton icon="pi pi-file-pdf"
styleClass="ui-button-danger ui-button-outlined"
title="Exporter en PDF">
<p:dataExporter type="pdf"
target="#{tableId}"
fileName="#{empty filename ? 'export' : filename}"
pageOnly="#{empty pageOnly ? false : pageOnly}"
selectionOnly="#{empty selectionOnly ? false : selectionOnly}"/>
</p:commandButton>
</h:panelGroup>
<!-- Bouton Excel -->
<h:panelGroup rendered="#{empty showExcel or showExcel eq true}">
<p:commandButton icon="pi pi-file-excel"
styleClass="ui-button-success ui-button-outlined"
title="Exporter en Excel">
<p:dataExporter type="xlsx"
target="#{tableId}"
fileName="#{empty filename ? 'export' : filename}"
pageOnly="#{empty pageOnly ? false : pageOnly}"
selectionOnly="#{empty selectionOnly ? false : selectionOnly}"/>
</p:commandButton>
</h:panelGroup>
<!-- Bouton CSV -->
<h:panelGroup rendered="#{showCSV eq true}">
<p:commandButton icon="pi pi-file"
styleClass="ui-button-info ui-button-outlined"
title="Exporter en CSV">
<p:dataExporter type="csv"
target="#{tableId}"
fileName="#{empty filename ? 'export' : filename}"
pageOnly="#{empty pageOnly ? false : pageOnly}"
selectionOnly="#{empty selectionOnly ? false : selectionOnly}"/>
</p:commandButton>
</h:panelGroup>
<!-- Bouton Imprimer -->
<h:panelGroup rendered="#{showPrint eq true}">
<p:commandButton icon="pi pi-print"
styleClass="ui-button-secondary ui-button-outlined"
title="Imprimer"
onclick="window.print(); return false;"/>
</h:panelGroup>
</div>
</ui:composition>

View File

@@ -0,0 +1,87 @@
<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">
<!--
Composant réutilisable: Dialogue de formulaire CRUD
Principe DRY: Un seul composant pour tous les formulaires de création/édition
Paramètres:
- dialogId: ID du dialogue (requis)
- header: Titre du dialogue (ex: "Nouveau Chantier")
- widgetVar: Variable widget PrimeFaces (ex: "chantierDialog")
- formId: ID du formulaire (requis)
- viewBean: Bean de vue pour les actions (requis)
- modal: true/false (défaut: true)
- width: Largeur du dialogue (défaut: 600px)
- height: Hauteur du dialogue (défaut: auto)
- showHeader: Afficher l'entête (défaut: true)
- closable: Dialogue fermable (défaut: true)
- draggable: Dialogue déplaçable (défaut: true)
- resizable: Dialogue redimensionnable (défaut: false)
- updateTarget: ID à mettre à jour après save (requis)
Utilisation:
<ui:include src="/WEB-INF/components/form-dialog.xhtml">
<ui:param name="dialogId" value="chantierDialog"/>
<ui:param name="header" value="#{chantiersView.editing ? 'Modifier Chantier' : 'Nouveau Chantier'}"/>
<ui:param name="widgetVar" value="chantierDlg"/>
<ui:param name="formId" value="chantierForm"/>
<ui:param name="viewBean" value="#{chantiersView}"/>
<ui:param name="updateTarget" value="@form:dataTable"/>
<ui:define name="form-content">
<!-- Vos champs de formulaire ici -->
<div class="p-fluid">
<div class="field">
<label for="nom">Nom</label>
<p:inputText id="nom" value="#{chantiersView.entity.nom}" required="true"/>
</div>
</div>
</ui:define>
</ui:include>
-->
<p:dialog id="#{dialogId}"
header="#{header}"
widgetVar="#{widgetVar}"
modal="#{empty modal ? true : modal}"
width="#{empty width ? '600px' : width}"
height="#{empty height ? 'auto' : height}"
showHeader="#{empty showHeader ? true : showHeader}"
closable="#{empty closable ? true : closable}"
draggable="#{empty draggable ? true : draggable}"
resizable="#{empty resizable ? false : resizable}">
<h:form id="#{formId}">
<p:messages id="messages" showDetail="true" closable="true"/>
<!-- Contenu du formulaire injecté par la page appelante -->
<ui:insert name="form-content">
<div class="p-fluid">
<p class="text-color-secondary">
Aucun contenu de formulaire défini. Utilisez ui:define name="form-content" pour ajouter vos champs.
</p>
</div>
</ui:insert>
<!-- Barre d'actions -->
<div class="flex align-items-center justify-content-end gap-2 pt-4">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="ui-button-secondary"
onclick="PF('#{widgetVar}').hide()"
type="button"/>
<p:commandButton value="#{viewBean.editing ? 'Modifier' : 'Créer'}"
icon="pi pi-save"
styleClass="ui-button-primary"
action="#{viewBean.save()}"
update="#{updateTarget} #{formId}:messages"
oncomplete="if (args &amp;&amp; !args.validationFailed) PF('#{widgetVar}').hide()"/>
</div>
</h:form>
</p:dialog>
</ui:composition>

View File

@@ -0,0 +1,135 @@
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Affichage monétaire formaté
Principe DRY: Un seul composant pour tous les montants monétaires
Format standardisé pour l'Afrique de l'Ouest (FCFA)
Paramètres:
- amount: Montant à afficher (requis)
- currency: Devise (défaut: FCFA)
- showCurrency: Afficher le symbole de devise (true/false - défaut: true)
- showSymbol: Afficher le symbole avant le montant (défaut: false)
- decimals: Nombre de décimales (défaut: 0 pour FCFA)
- size: Taille (small, normal, large, xl - défaut: normal)
- color: Couleur du texte (success, danger, warning, primary - optionnel)
- bold: Texte en gras (true/false - défaut: false)
- alignment: Alignement (left, center, right - défaut: left)
Utilisation basique:
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{facture.montantTotal}"/>
</ui:include>
Affiche: 1 250 000 FCFA
Grande taille avec couleur:
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{chantier.budget}"/>
<ui:param name="size" value="xl"/>
<ui:param name="color" value="primary"/>
<ui:param name="bold" value="true"/>
</ui:include>
Avec symbole personnalisé:
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{devis.montant}"/>
<ui:param name="currency" value="EUR"/>
<ui:param name="showSymbol" value="true"/>
<ui:param name="decimals" value="2"/>
</ui:include>
Affiche: € 1 250,50 EUR
-->
<c:set var="currencyCode" value="#{empty currency ? 'FCFA' : currency}"/>
<c:set var="displayCurrency" value="#{empty showCurrency ? true : showCurrency}"/>
<c:set var="displaySymbol" value="#{empty showSymbol ? false : showSymbol}"/>
<c:set var="decimalCount" value="#{empty decimals ? 0 : decimals}"/>
<c:set var="textSize" value="#{empty size ? 'normal' : size}"/>
<c:set var="isBold" value="#{empty bold ? false : bold}"/>
<c:set var="textAlign" value="#{empty alignment ? 'left' : alignment}"/>
<!-- Classes CSS pour la taille -->
<c:choose>
<c:when test="#{textSize eq 'small'}">
<c:set var="sizeClass" value="text-sm"/>
</c:when>
<c:when test="#{textSize eq 'large'}">
<c:set var="sizeClass" value="text-lg"/>
</c:when>
<c:when test="#{textSize eq 'xl'}">
<c:set var="sizeClass" value="text-xl"/>
</c:when>
<c:otherwise>
<c:set var="sizeClass" value="text-base"/>
</c:otherwise>
</c:choose>
<!-- Classes CSS pour la couleur -->
<c:choose>
<c:when test="#{color eq 'success'}">
<c:set var="colorClass" value="text-green-600"/>
</c:when>
<c:when test="#{color eq 'danger'}">
<c:set var="colorClass" value="text-red-600"/>
</c:when>
<c:when test="#{color eq 'warning'}">
<c:set var="colorClass" value="text-orange-600"/>
</c:when>
<c:when test="#{color eq 'primary'}">
<c:set var="colorClass" value="text-primary"/>
</c:when>
<c:otherwise>
<c:set var="colorClass" value="text-900"/>
</c:otherwise>
</c:choose>
<!-- Symboles de devise -->
<c:choose>
<c:when test="#{currencyCode eq 'EUR'}">
<c:set var="currencySymbol" value="€"/>
</c:when>
<c:when test="#{currencyCode eq 'USD'}">
<c:set var="currencySymbol" value="$"/>
</c:when>
<c:when test="#{currencyCode eq 'GBP'}">
<c:set var="currencySymbol" value="£"/>
</c:when>
<c:when test="#{currencyCode eq 'FCFA' or currencyCode eq 'XOF'}">
<c:set var="currencySymbol" value=""/>
</c:when>
<c:otherwise>
<c:set var="currencySymbol" value=""/>
</c:otherwise>
</c:choose>
<!-- Affichage du montant -->
<span class="monetary-display #{sizeClass} #{colorClass} #{isBold ? 'font-bold' : ''}"
style="text-align: #{textAlign}; display: inline-block;">
<!-- Symbole avant -->
<c:if test="#{displaySymbol and not empty currencySymbol}">
<span class="currency-symbol mr-1">#{currencySymbol}</span>
</c:if>
<!-- Montant formaté -->
<span class="amount">
<h:outputText value="#{amount}">
<f:convertNumber type="currency"
currencySymbol=""
groupingUsed="true"
minFractionDigits="#{decimalCount}"
maxFractionDigits="#{decimalCount}"/>
</h:outputText>
</span>
<!-- Code devise après -->
<c:if test="#{displayCurrency}">
<span class="currency-code ml-1 font-medium">#{currencyCode}</span>
</c:if>
</span>
</ui:composition>

View File

@@ -0,0 +1,115 @@
<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: Indicateur de progression
Principe DRY: Un seul composant pour tous les indicateurs de progression
Paramètres:
- value: Pourcentage (0-100) - requis
- label: Libellé à afficher - optionnel
- showValue: Afficher le pourcentage (true/false - défaut: true)
- mode: Mode d'affichage (determinate, indeterminate - défaut: determinate)
- color: Couleur (primary, success, info, warning, danger - défaut: auto basé sur valeur)
- height: Hauteur de la barre (défaut: 1rem)
- labelPosition: Position du label (top, inside, bottom - défaut: top)
Utilisation basique:
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
<ui:param name="value" value="#{chantier.progressionPourcentage}"/>
<ui:param name="label" value="Progression du chantier"/>
</ui:include>
Avec couleur personnalisée:
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
<ui:param name="value" value="75"/>
<ui:param name="color" value="success"/>
<ui:param name="labelPosition" value="inside"/>
</ui:include>
Mode indéterminé (chargement):
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
<ui:param name="mode" value="indeterminate"/>
<ui:param name="label" value="Chargement en cours..."/>
</ui:include>
-->
<c:set var="progressMode" value="#{empty mode ? 'determinate' : mode}"/>
<c:set var="displayValue" value="#{empty showValue ? true : showValue}"/>
<c:set var="barHeight" value="#{empty height ? '1rem' : height}"/>
<c:set var="labelPos" value="#{empty labelPosition ? 'top' : labelPosition}"/>
<!-- Déterminer la couleur automatiquement si non fournie -->
<c:choose>
<c:when test="#{not empty color}">
<c:set var="barColor" value="#{color}"/>
</c:when>
<c:when test="#{value >= 100}">
<c:set var="barColor" value="success"/>
</c:when>
<c:when test="#{value >= 75}">
<c:set var="barColor" value="info"/>
</c:when>
<c:when test="#{value >= 50}">
<c:set var="barColor" value="primary"/>
</c:when>
<c:when test="#{value >= 25}">
<c:set var="barColor" value="warning"/>
</c:when>
<c:otherwise>
<c:set var="barColor" value="danger"/>
</c:otherwise>
</c:choose>
<!-- Classes CSS pour la couleur -->
<c:choose>
<c:when test="#{barColor eq 'success'}">
<c:set var="colorClass" value="bg-green-500"/>
</c:when>
<c:when test="#{barColor eq 'info'}">
<c:set var="colorClass" value="bg-blue-500"/>
</c:when>
<c:when test="#{barColor eq 'warning'}">
<c:set var="colorClass" value="bg-orange-500"/>
</c:when>
<c:when test="#{barColor eq 'danger'}">
<c:set var="colorClass" value="bg-red-500"/>
</c:when>
<c:otherwise>
<c:set var="colorClass" value="bg-primary"/>
</c:otherwise>
</c:choose>
<div class="progress-indicator-container" style="width: 100%;">
<!-- Label en haut -->
<div class="flex align-items-center justify-content-between mb-2"
style="#{labelPos eq 'top' ? '' : 'display: none;'}">
<span class="text-900 font-medium">#{label}</span>
<span class="text-900 font-semibold" style="#{displayValue ? '' : 'display: none;'}">
#{value}%
</span>
</div>
<!-- Barre de progression -->
<p:progressBar value="#{value}"
mode="#{progressMode}"
style="height: #{barHeight}; border-radius: 0.5rem;"
styleClass="#{colorClass}"
displayValue="#{labelPos eq 'inside' and displayValue}"/>
<!-- Label en bas -->
<div class="flex align-items-center justify-content-between mt-2"
style="#{labelPos eq 'bottom' ? '' : 'display: none;'}">
<span class="text-700">#{label}</span>
<span class="text-900 font-semibold" style="#{displayValue ? '' : 'display: none;'}">
#{value}%
</span>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,121 @@
<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: Badge de statut coloré
Principe DRY: Un seul composant pour tous les badges de statut dans l'application
Write Once, Use Anywhere: Mapping automatique statut → couleur
Paramètres:
- value: Valeur du statut (requis)
- severity: Gravité explicite (success, info, warning, danger) - optionnel
- icon: Icône à afficher - optionnel
- rounded: Badge arrondi (true/false - défaut: true)
- size: Taille (normal, large - défaut: normal)
Mapping automatique des statuts métier:
SUCCESS (vert): EN_COURS, ACTIF, TERMINE, VALIDE, PAYE, LIVRE, DISPONIBLE, APPROUVE
INFO (bleu): PLANIFIE, NOUVEAU, EN_ATTENTE, BROUILLON, PENDING
WARNING (orange): RETARD, SUSPENDU, IMPAYE, ALERTE, MAINTENANCE
DANGER (rouge): ANNULE, REFUSE, EXPIRE, HORS_SERVICE, BLOQUE
Utilisation:
<ui:include src="/WEB-INF/components/status-badge.xhtml">
<ui:param name="value" value="#{chantier.statut}"/>
</ui:include>
Avec icône personnalisée:
<ui:include src="/WEB-INF/components/status-badge.xhtml">
<ui:param name="value" value="#{facture.statut}"/>
<ui:param name="icon" value="pi pi-check-circle"/>
</ui:include>
Gravité manuelle:
<ui:include src="/WEB-INF/components/status-badge.xhtml">
<ui:param name="value" value="#{custom.status}"/>
<ui:param name="severity" value="danger"/>
</ui:include>
-->
<c:set var="upperValue" value="#{value.toString().toUpperCase().replace(' ', '_')}"/>
<!-- Déterminer la gravité automatiquement si non fournie -->
<c:choose>
<!-- SUCCESS - Vert -->
<c:when test="#{not empty severity}">
<c:set var="badgeSeverity" value="#{severity}"/>
</c:when>
<c:when test="#{upperValue eq 'EN_COURS' or upperValue eq 'ACTIF' or upperValue eq 'ACTIVE' or
upperValue eq 'TERMINE' or upperValue eq 'COMPLETE' or upperValue eq 'VALIDE' or
upperValue eq 'PAYE' or upperValue eq 'PAYEE' or upperValue eq 'LIVRE' or
upperValue eq 'DISPONIBLE' or upperValue eq 'APPROUVE' or upperValue eq 'ACCEPTE' or
upperValue eq 'OPERATIONNEL'}">
<c:set var="badgeSeverity" value="success"/>
</c:when>
<!-- INFO - Bleu -->
<c:when test="#{upperValue eq 'PLANIFIE' or upperValue eq 'PLANIFIEE' or upperValue eq 'NOUVEAU' or
upperValue eq 'NOUVELLE' or upperValue eq 'EN_ATTENTE' or upperValue eq 'BROUILLON' or
upperValue eq 'PENDING' or upperValue eq 'PROGRAMME' or upperValue eq 'PREVU'}">
<c:set var="badgeSeverity" value="info"/>
</c:when>
<!-- WARNING - Orange -->
<c:when test="#{upperValue eq 'RETARD' or upperValue eq 'EN_RETARD' or upperValue eq 'SUSPENDU' or
upperValue eq 'IMPAYE' or upperValue eq 'IMPAYEE' or upperValue eq 'ALERTE' or
upperValue eq 'MAINTENANCE' or upperValue eq 'UTILISE' or upperValue eq 'OCCUPE' or
upperValue eq 'PARTIEL' or upperValue eq 'PARTIELLE'}">
<c:set var="badgeSeverity" value="warning"/>
</c:when>
<!-- DANGER - Rouge -->
<c:when test="#{upperValue eq 'ANNULE' or upperValue eq 'ANNULEE' or upperValue eq 'REFUSE' or
upperValue eq 'REFUSEE' or upperValue eq 'EXPIRE' or upperValue eq 'EXPIREE' or
upperValue eq 'HORS_SERVICE' or upperValue eq 'BLOQUE' or upperValue eq 'INACTIVE' or
upperValue eq 'INACTIF' or upperValue eq 'URGENT' or upperValue eq 'CRITIQUE'}">
<c:set var="badgeSeverity" value="danger"/>
</c:when>
<!-- Par défaut - Info (bleu) -->
<c:otherwise>
<c:set var="badgeSeverity" value="info"/>
</c:otherwise>
</c:choose>
<!-- Déterminer l'icône automatiquement -->
<c:choose>
<c:when test="#{not empty icon}">
<c:set var="badgeIcon" value="#{icon}"/>
</c:when>
<c:when test="#{badgeSeverity eq 'success'}">
<c:set var="badgeIcon" value="pi pi-check-circle"/>
</c:when>
<c:when test="#{badgeSeverity eq 'info'}">
<c:set var="badgeIcon" value="pi pi-info-circle"/>
</c:when>
<c:when test="#{badgeSeverity eq 'warning'}">
<c:set var="badgeIcon" value="pi pi-exclamation-triangle"/>
</c:when>
<c:when test="#{badgeSeverity eq 'danger'}">
<c:set var="badgeIcon" value="pi pi-times-circle"/>
</c:when>
</c:choose>
<!-- Rendu du badge -->
<p:badge value="#{value}"
severity="#{badgeSeverity}"
styleClass="#{rounded eq false ? '' : 'border-round'}
#{size eq 'large' ? 'text-lg px-3 py-2' : 'px-2'}"
style="display: inline-flex; align-items: center; gap: 0.5rem;">
<i class="#{badgeIcon}" style="font-size: 0.875rem;"/>
<span style="font-weight: 600; text-transform: capitalize;">
#{value.toString().toLowerCase().replace('_', ' ')}
</span>
</p:badge>
</ui:composition>