Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
@@ -0,0 +1,759 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{membreInscriptionBean}"/>
|
||||
<ui:define name="title">Inscription Membre - UnionFlow</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-user-plus text-primary" />
|
||||
<ui:param name="title" value="Inscription Nouveau Membre" />
|
||||
<ui:param name="description" value="Formulaire complet d'inscription avec photo et documents" />
|
||||
<ui:define name="actions">
|
||||
<div>
|
||||
<div class="text-900 font-medium">Numéro: #{membreInscriptionBean.numeroGenere}</div>
|
||||
<small class="text-600">Généré automatiquement</small>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<h:form>
|
||||
<p:messages id="messages" showDetail="true" closable="true" globalOnly="false" />
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Informations personnelles" />
|
||||
<ui:param name="fluid" value="true" />
|
||||
<ui:define name="content">
|
||||
<!-- Section photo intégrée -->
|
||||
<div class="text-center mb-4 pb-3" style="border-bottom: 1px solid var(--surface-border);">
|
||||
<div class="mb-3 relative">
|
||||
<div id="photoContainer" style="width: 120px; height: 120px; margin: 0 auto; position: relative; overflow: hidden; border-radius: 50%; border: 3px solid var(--surface-border); background: #f8f9fa;">
|
||||
<img id="photoPreview"
|
||||
alt="Photo du membre"
|
||||
style="width: auto; height: 120px; min-width: 120px; object-fit: cover; display: none; position: absolute; cursor: move; user-select: none;"
|
||||
draggable="false" />
|
||||
<div id="photoPlaceholder"
|
||||
style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; position: absolute; top: 0; left: 0;">
|
||||
<i class="pi pi-camera" style="font-size: 2rem; color: #6c757d;"></i>
|
||||
</div>
|
||||
<div id="photoOverlay"
|
||||
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.1); display: none; pointer-events: none; border-radius: 50%;">
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 0.75rem; text-align: center;">
|
||||
<i class="pi pi-arrows-alt"></i><br/>
|
||||
<small>Glisser pour positionner<br/>Molette pour zoomer</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center gap-2">
|
||||
<input type="file"
|
||||
id="photoInput"
|
||||
accept="image/*"
|
||||
style="position: absolute; left: -9999px; opacity: 0;" />
|
||||
<h:inputHidden id="photoCadree" value="#{membreInscriptionBean.photoBase64}" />
|
||||
<label for="photoInput"
|
||||
class="ui-button ui-button-outlined ui-button-secondary"
|
||||
style="cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; min-height: 2.5rem; padding: 0.75rem 1rem;">
|
||||
<i class="pi pi-camera"></i>
|
||||
<span>Choisir une photo</span>
|
||||
</label>
|
||||
<button type="button"
|
||||
id="removePhotoBtn"
|
||||
class="ui-button ui-button-outlined ui-button-danger"
|
||||
onclick="removePhoto()"
|
||||
style="display: none; min-height: 2.5rem; padding: 0.75rem 1rem;">
|
||||
<i class="pi pi-trash"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-600">JPG, PNG ou GIF - Maximum 2MB</small>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Champs d'informations personnelles -->
|
||||
<div class="field">
|
||||
<p:outputLabel for="prenom" value="Prénom" />
|
||||
<p:inputText id="prenom" value="#{membreInscriptionBean.prenom}" required="true"
|
||||
requiredMessage="Le prénom est obligatoire" styleClass="w-full" />
|
||||
<p:message for="prenom" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="nom" value="Nom" />
|
||||
<p:inputText id="nom" value="#{membreInscriptionBean.nom}" required="true"
|
||||
requiredMessage="Le nom est obligatoire" styleClass="w-full" />
|
||||
<p:message for="nom" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateNaissance" value="Date de naissance" />
|
||||
<p:calendar id="dateNaissance" value="#{membreInscriptionBean.dateNaissance}" required="true"
|
||||
pattern="dd/MM/yyyy" showIcon="true" yearNavigator="true" yearRange="1920:2030"
|
||||
monthNavigator="true" requiredMessage="La date de naissance est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="dateNaissance" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="lieuNaissance" value="Lieu de naissance" />
|
||||
<p:inputText id="lieuNaissance" value="#{membreInscriptionBean.lieuNaissance}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="sexe" value="Sexe" />
|
||||
<p:selectOneMenu id="sexe" value="#{membreInscriptionBean.sexe}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.sexeOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="nationalite" value="Nationalité" />
|
||||
<p:inputText id="nationalite" value="#{membreInscriptionBean.nationalite}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="situationMatrimoniale" value="Situation matrimoniale" />
|
||||
<p:selectOneMenu id="situationMatrimoniale" value="#{membreInscriptionBean.situationMatrimoniale}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.situationMatrimonialeOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="profession" value="Profession" />
|
||||
<p:inputText id="profession" value="#{membreInscriptionBean.profession}" styleClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="employeur" value="Employeur / Entreprise" />
|
||||
<p:inputText id="employeur" value="#{membreInscriptionBean.employeur}" styleClass="w-full" />
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Contact d'urgence" />
|
||||
<ui:param name="fluid" value="true" />
|
||||
<ui:define name="content">
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceNom" value="Nom complet" />
|
||||
<p:inputText id="contactUrgenceNom" value="#{membreInscriptionBean.contactUrgenceNom}"
|
||||
required="true" requiredMessage="Le nom du contact d'urgence est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="contactUrgenceNom" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceTelephone" value="Téléphone" />
|
||||
<p:inputText id="contactUrgenceTelephone" value="#{membreInscriptionBean.contactUrgenceTelephone}"
|
||||
required="true" requiredMessage="Le téléphone du contact d'urgence est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="contactUrgenceTelephone" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceLien" value="Lien de parenté" />
|
||||
<p:selectOneMenu id="contactUrgenceLien" value="#{membreInscriptionBean.contactUrgenceLien}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.contactUrgenceLienOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Documents justificatifs" />
|
||||
<ui:define name="content">
|
||||
<p:fileUpload listener="#{membreInscriptionBean.handleFileUpload}"
|
||||
mode="advanced"
|
||||
dragDropSupport="true"
|
||||
multiple="true"
|
||||
update="messages documentsListPanel"
|
||||
sizeLimit="5000000"
|
||||
fileLimit="5"
|
||||
allowTypes="/(\.|\/)(pdf|doc|docx|jpg|jpeg|png)$/"
|
||||
uploadLabel="Télécharger"
|
||||
cancelLabel="Annuler"
|
||||
chooseLabel="Sélectionner les fichiers"
|
||||
invalidFileMessage="Type de fichier non supporté"
|
||||
fileLimitMessage="Nombre maximum de fichiers dépassé"
|
||||
invalidSizeMessage="Taille de fichier trop importante"
|
||||
style="width:100%" />
|
||||
|
||||
<h:panelGroup id="documentsListPanel" layout="block" styleClass="mt-3">
|
||||
<h6 class="mb-2" rendered="#{not empty membreInscriptionBean.documentsJoints}">Fichiers ajoutés:</h6>
|
||||
<ui:repeat value="#{membreInscriptionBean.documentsJoints}" var="document">
|
||||
<div class="flex align-items-center justify-content-between p-2 border-round mb-2"
|
||||
style="background: var(--surface-50);">
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-file text-blue-500 mr-2"></i>
|
||||
<span class="text-900">#{document}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.supprimerDocument(document)}" />
|
||||
<ui:param name="update" value="documentsListPanel" />
|
||||
<ui:param name="title" value="Supprimer le fichier" />
|
||||
<ui:param name="severity" value="danger" />
|
||||
<ui:param name="styleClass" value="ui-button-sm" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</ui:repeat>
|
||||
</h:panelGroup>
|
||||
|
||||
<small class="text-600">Formats acceptés: PDF, DOC, DOCX, JPG, PNG - Maximum 5 fichiers de 5MB chacun</small>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Coordonnées" />
|
||||
<ui:param name="fluid" value="true" />
|
||||
<ui:define name="content">
|
||||
<div class="field">
|
||||
<p:outputLabel for="adresse" value="Adresse complète" />
|
||||
<p:inputTextarea id="adresse" value="#{membreInscriptionBean.adresse}" rows="4"
|
||||
required="true" requiredMessage="L'adresse est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="adresse" />
|
||||
</div>
|
||||
|
||||
<div class="formgrid grid">
|
||||
<div class="field col">
|
||||
<p:outputLabel for="ville" value="Ville" />
|
||||
<p:inputText id="ville" value="#{membreInscriptionBean.ville}" required="true"
|
||||
requiredMessage="La ville est obligatoire" styleClass="w-full" />
|
||||
<p:message for="ville" />
|
||||
</div>
|
||||
<div class="field col">
|
||||
<p:outputLabel for="codePostal" value="Code postal" />
|
||||
<p:inputText id="codePostal" value="#{membreInscriptionBean.codePostal}" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="pays" value="Pays" />
|
||||
<p:inputText id="pays" value="#{membreInscriptionBean.pays}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="email" value="Email" />
|
||||
<p:inputText id="email" value="#{membreInscriptionBean.email}" required="true" styleClass="w-full">
|
||||
<f:validateRegex pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
<div class="formgrid grid">
|
||||
<div class="field col">
|
||||
<p:outputLabel for="telephone" value="Téléphone fixe" />
|
||||
<p:inputText id="telephone" value="#{membreInscriptionBean.telephone}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col">
|
||||
<p:outputLabel for="telephoneMobile" value="Téléphone mobile" />
|
||||
<p:inputText id="telephoneMobile" value="#{membreInscriptionBean.telephoneMobile}"
|
||||
required="true" requiredMessage="Le téléphone mobile est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="telephoneMobile" />
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Adhésion" />
|
||||
<ui:param name="fluid" value="true" />
|
||||
<ui:define name="content">
|
||||
<div class="field">
|
||||
<p:outputLabel for="organisationId" value="Organisation *" styleClass="font-bold text-primary" />
|
||||
<p:selectOneMenu id="organisationId" value="#{membreInscriptionBean.organisationId}" required="true" requiredMessage="Vous devez sélectionner une organisation" styleClass="w-full">
|
||||
<f:selectItem itemLabel="--- Sélectionner une organisation ---" itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.organisationsDisponibles}"
|
||||
var="org"
|
||||
itemLabel="#{org.nom} (#{org.ville})"
|
||||
itemValue="#{org.id}" />
|
||||
</p:selectOneMenu>
|
||||
<p:message for="organisationId" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="typeAdhesion" value="Type d'adhésion" />
|
||||
<p:selectOneMenu id="typeAdhesion" value="#{membreInscriptionBean.typeAdhesion}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.typeAdhesionOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="numeroParrain" value="N° Membre parrain" />
|
||||
<div class="ui-inputgroup">
|
||||
<p:inputText id="numeroParrain" value="#{membreInscriptionBean.numeroParrain}" styleClass="w-full" />
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-search" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.rechercherParrain}" />
|
||||
<ui:param name="severity" value="info" />
|
||||
<ui:param name="rounded" value="false" />
|
||||
<ui:param name="text" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="nomParrain" value="Nom du parrain" />
|
||||
<p:inputText id="nomParrain" value="#{membreInscriptionBean.nomParrain}" readonly="true" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="motifAdhesion" value="Motif d'adhésion" />
|
||||
<p:inputTextarea id="motifAdhesion" value="#{membreInscriptionBean.motifAdhesion}"
|
||||
rows="3" styleClass="w-full" />
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Informations complémentaires" />
|
||||
<ui:define name="content">
|
||||
<div class="ui-fluid formgrid grid">
|
||||
<div class="field col-12 md:col-4">
|
||||
<p:outputLabel for="nomBanque" value="Nom de la banque" />
|
||||
<p:inputText id="nomBanque" value="#{membreInscriptionBean.nomBanque}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col-12 md:col-4">
|
||||
<p:outputLabel for="numeroBanque" value="Numéro de compte" />
|
||||
<p:inputText id="numeroBanque" value="#{membreInscriptionBean.numeroBanque}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col-12 md:col-4">
|
||||
<p:outputLabel for="ribIban" value="RIB / IBAN" />
|
||||
<p:inputText id="ribIban" value="#{membreInscriptionBean.ribIban}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col-12 md:col-6">
|
||||
<p:outputLabel for="competencesSpeciales" value="Compétences spéciales" />
|
||||
<p:inputTextarea id="competencesSpeciales" value="#{membreInscriptionBean.competencesSpeciales}"
|
||||
rows="3" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col-12 md:col-6">
|
||||
<p:outputLabel for="centresInteret" value="Centres d'intérêt" />
|
||||
<p:inputTextarea id="centresInteret" value="#{membreInscriptionBean.centresInteret}"
|
||||
rows="3" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field col-12">
|
||||
<p:outputLabel for="commentaires" value="Commentaires" />
|
||||
<p:inputTextarea id="commentaires" value="#{membreInscriptionBean.commentaires}"
|
||||
rows="3" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Autorisations</h5>
|
||||
<div class="formgroup-inline">
|
||||
<div class="field-checkbox">
|
||||
<p:selectBooleanCheckbox id="accepteReglement" value="#{membreInscriptionBean.accepteReglement}" />
|
||||
<p:outputLabel for="accepteReglement" value="J'accepte le règlement intérieur" />
|
||||
</div>
|
||||
<div class="field-checkbox">
|
||||
<p:selectBooleanCheckbox id="acceptePrelevement" value="#{membreInscriptionBean.acceptePrelevement}" />
|
||||
<p:outputLabel for="acceptePrelevement" value="J'autorise le prélèvement automatique" />
|
||||
</div>
|
||||
<div class="field-checkbox">
|
||||
<p:selectBooleanCheckbox id="autorisationMarketing" value="#{membreInscriptionBean.autorisationMarketing}" />
|
||||
<p:outputLabel for="autorisationMarketing" value="J'accepte de recevoir des communications" />
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions finales -->
|
||||
<div class="card">
|
||||
<h5>Finaliser l'inscription</h5>
|
||||
<div class="surface-50 p-4 border-round mb-4">
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-info-circle text-blue-500 mr-3"></i>
|
||||
<div>
|
||||
<div class="font-medium text-900">Vérifiez toutes les informations</div>
|
||||
<div class="text-600">Assurez-vous que tous les champs requis sont remplis correctement</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="🎯 Inscrire le membre" />
|
||||
<ui:param name="icon" value="pi pi-user-plus" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.inscrire}" />
|
||||
<ui:param name="update" value="messages" />
|
||||
<ui:param name="onclick" value="PF('statusDialog').show();" />
|
||||
<ui:param name="oncomplete" value="PF('statusDialog').hide();" />
|
||||
<ui:param name="title" value="Soumettre l'inscription" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="💾 Enregistrer brouillon" />
|
||||
<ui:param name="icon" value="pi pi-save" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.enregistrerBrouillon}" />
|
||||
<ui:param name="update" value="messages" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/buttons/button-warning.xhtml">
|
||||
<ui:param name="value" value="🔄 Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="onclick" value="removePhoto(); return confirm('Êtes-vous sûr de vouloir réinitialiser le formulaire ?');" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="❌ Annuler" />
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.annuler}" />
|
||||
<ui:param name="onclick" value="return confirm('Êtes-vous sûr de vouloir annuler l\'inscription ?');" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-600">
|
||||
<small>
|
||||
<i class="pi pi-shield mr-1"></i>
|
||||
Toutes les données sont chiffrées et sécurisées selon les standards RGPD
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
<ui:define name="head">
|
||||
<script type="text/javascript">
|
||||
// Configuration de la previsualisation photo
|
||||
window.addEventListener('load', function() {
|
||||
var photoInput = document.getElementById('photoInput');
|
||||
if (photoInput) {
|
||||
photoInput.addEventListener('change', function(event) {
|
||||
previewPhoto(event.target);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function previewPhoto(input) {
|
||||
if (input.files && input.files.length > 0) {
|
||||
var file = input.files[0];
|
||||
|
||||
// Verifier que c'est une image
|
||||
if (!file.type.startsWith('image/')) {
|
||||
alert('Veuillez selectionner un fichier image');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Verifier la taille (2MB max)
|
||||
if (file.size > 2097152) {
|
||||
alert('La taille du fichier depasse 2MB');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var preview = document.getElementById('photoPreview');
|
||||
var placeholder = document.getElementById('photoPlaceholder');
|
||||
var removeBtn = document.getElementById('removePhotoBtn');
|
||||
|
||||
if (preview && placeholder && removeBtn) {
|
||||
preview.src = e.target.result;
|
||||
preview.style.display = 'block';
|
||||
preview.style.left = '0px';
|
||||
preview.style.top = '0px';
|
||||
placeholder.style.display = 'none';
|
||||
removeBtn.style.display = 'inline-block';
|
||||
|
||||
// Stocker l'image originale pour le recadrage
|
||||
preview.originalSrc = e.target.result;
|
||||
|
||||
// Activer le positionnement interactif
|
||||
enablePhotoPositioning(preview);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function() {
|
||||
alert('Erreur lors de la lecture du fichier');
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
function enablePhotoPositioning(photoElement) {
|
||||
var isDragging = false;
|
||||
var startX, startY, initialLeft, initialTop;
|
||||
var overlay = document.getElementById('photoOverlay');
|
||||
var currentScale = 1;
|
||||
var minScale = 0.5;
|
||||
var maxScale = 3;
|
||||
|
||||
// Fonctionnalité de zoom avec la molette
|
||||
var container = document.getElementById('photoContainer');
|
||||
container.addEventListener('wheel', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var delta = e.deltaY > 0 ? -0.1 : 0.1;
|
||||
var newScale = Math.max(minScale, Math.min(maxScale, currentScale + delta));
|
||||
|
||||
if (newScale !== currentScale) {
|
||||
currentScale = newScale;
|
||||
photoElement.style.transform = 'scale(' + currentScale + ')';
|
||||
|
||||
// Ajuster la position si l'image sort des limites après zoom
|
||||
var currentLeft = parseInt(photoElement.style.left) || 0;
|
||||
var currentTop = parseInt(photoElement.style.top) || 0;
|
||||
|
||||
var containerWidth = container.offsetWidth;
|
||||
var containerHeight = container.offsetHeight;
|
||||
var photoWidth = photoElement.offsetWidth * currentScale;
|
||||
var photoHeight = photoElement.offsetHeight * currentScale;
|
||||
|
||||
var maxLeft = photoWidth > containerWidth ? 0 : (containerWidth - photoWidth) / 2;
|
||||
var minLeft = photoWidth > containerWidth ? -(photoWidth - containerWidth) : (containerWidth - photoWidth) / 2;
|
||||
var maxTop = photoHeight > containerHeight ? 0 : (containerHeight - photoHeight) / 2;
|
||||
var minTop = photoHeight > containerHeight ? -(photoHeight - containerHeight) : (containerHeight - photoHeight) / 2;
|
||||
|
||||
var adjustedLeft = Math.max(minLeft, Math.min(maxLeft, currentLeft));
|
||||
var adjustedTop = Math.max(minTop, Math.min(maxTop, currentTop));
|
||||
|
||||
photoElement.style.left = adjustedLeft + 'px';
|
||||
photoElement.style.top = adjustedTop + 'px';
|
||||
|
||||
// Afficher temporairement l'overlay avec le niveau de zoom
|
||||
if (overlay) {
|
||||
overlay.style.display = 'block';
|
||||
overlay.innerHTML = '<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 0.8rem; text-align: center;"><i class="pi pi-search-plus"></i><br/><small>Zoom: ' + Math.round(currentScale * 100) + '%</small></div>';
|
||||
|
||||
// Masquer l'overlay après 1 seconde
|
||||
setTimeout(function() {
|
||||
if (overlay && !isDragging) {
|
||||
overlay.style.display = 'none';
|
||||
overlay.innerHTML = '<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 0.75rem; text-align: center;"><i class="pi pi-arrows-alt"></i><br/><small>Glisser pour positionner<br/>Molette pour zoomer</small></div>';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
photoElement.addEventListener('mousedown', function(e) {
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
initialLeft = parseInt(photoElement.style.left) || 0;
|
||||
initialTop = parseInt(photoElement.style.top) || 0;
|
||||
|
||||
if (overlay) overlay.style.display = 'block';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
var deltaX = e.clientX - startX;
|
||||
var deltaY = e.clientY - startY;
|
||||
|
||||
var newLeft = initialLeft + deltaX;
|
||||
var newTop = initialTop + deltaY;
|
||||
|
||||
// Limiter le déplacement en tenant compte du zoom
|
||||
var container = document.getElementById('photoContainer');
|
||||
var containerWidth = container.offsetWidth;
|
||||
var containerHeight = container.offsetHeight;
|
||||
var photoWidth = photoElement.offsetWidth * currentScale;
|
||||
var photoHeight = photoElement.offsetHeight * currentScale;
|
||||
|
||||
// Calculer les limites en fonction du zoom
|
||||
var maxLeft = photoWidth > containerWidth ? 0 : (containerWidth - photoWidth) / 2;
|
||||
var minLeft = photoWidth > containerWidth ? -(photoWidth - containerWidth) : (containerWidth - photoWidth) / 2;
|
||||
var maxTop = photoHeight > containerHeight ? 0 : (containerHeight - photoHeight) / 2;
|
||||
var minTop = photoHeight > containerHeight ? -(photoHeight - containerHeight) : (containerHeight - photoHeight) / 2;
|
||||
|
||||
newLeft = Math.max(minLeft, Math.min(maxLeft, newLeft));
|
||||
newTop = Math.max(minTop, Math.min(maxTop, newTop));
|
||||
|
||||
photoElement.style.left = newLeft + 'px';
|
||||
photoElement.style.top = newTop + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function() {
|
||||
isDragging = false;
|
||||
if (overlay) overlay.style.display = 'none';
|
||||
});
|
||||
|
||||
// Support tactile pour mobile
|
||||
photoElement.addEventListener('touchstart', function(e) {
|
||||
var touch = e.touches[0];
|
||||
isDragging = true;
|
||||
startX = touch.clientX;
|
||||
startY = touch.clientY;
|
||||
initialLeft = parseInt(photoElement.style.left) || 0;
|
||||
initialTop = parseInt(photoElement.style.top) || 0;
|
||||
|
||||
if (overlay) overlay.style.display = 'block';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('touchmove', function(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
var touch = e.touches[0];
|
||||
var deltaX = touch.clientX - startX;
|
||||
var deltaY = touch.clientY - startY;
|
||||
|
||||
var newLeft = initialLeft + deltaX;
|
||||
var newTop = initialTop + deltaY;
|
||||
|
||||
// Limiter le déplacement en tenant compte du zoom pour mobile
|
||||
var container = document.getElementById('photoContainer');
|
||||
var containerWidth = container.offsetWidth;
|
||||
var containerHeight = container.offsetHeight;
|
||||
var photoWidth = photoElement.offsetWidth * currentScale;
|
||||
var photoHeight = photoElement.offsetHeight * currentScale;
|
||||
|
||||
// Calculer les limites en fonction du zoom
|
||||
var maxLeft = photoWidth > containerWidth ? 0 : (containerWidth - photoWidth) / 2;
|
||||
var minLeft = photoWidth > containerWidth ? -(photoWidth - containerWidth) : (containerWidth - photoWidth) / 2;
|
||||
var maxTop = photoHeight > containerHeight ? 0 : (containerHeight - photoHeight) / 2;
|
||||
var minTop = photoHeight > containerHeight ? -(photoHeight - containerHeight) : (containerHeight - photoHeight) / 2;
|
||||
|
||||
newLeft = Math.max(minLeft, Math.min(maxLeft, newLeft));
|
||||
newTop = Math.max(minTop, Math.min(maxTop, newTop));
|
||||
|
||||
photoElement.style.left = newLeft + 'px';
|
||||
photoElement.style.top = newTop + 'px';
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('touchend', function() {
|
||||
isDragging = false;
|
||||
if (overlay) overlay.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function capturePhoto() {
|
||||
var preview = document.getElementById('photoPreview');
|
||||
var container = document.getElementById('photoContainer');
|
||||
|
||||
if (!preview || !preview.originalSrc || preview.style.display === 'none') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Taille du cercle de sortie (120px)
|
||||
var outputSize = 120;
|
||||
canvas.width = outputSize;
|
||||
canvas.height = outputSize;
|
||||
|
||||
// Obtenir les paramètres actuels
|
||||
var currentScale = parseFloat(preview.style.transform.replace(/[^\d.]/g, '')) || 1;
|
||||
var currentLeft = parseInt(preview.style.left) || 0;
|
||||
var currentTop = parseInt(preview.style.top) || 0;
|
||||
|
||||
// Calculer les dimensions de l'image affichée
|
||||
var displayWidth = preview.offsetWidth;
|
||||
var displayHeight = preview.offsetHeight;
|
||||
|
||||
// Calculer le ratio entre l'image originale et l'affichage
|
||||
var ratioX = img.width / displayWidth;
|
||||
var ratioY = img.height / displayHeight;
|
||||
|
||||
// Calculer la zone source dans l'image originale
|
||||
var sourceX = Math.abs(currentLeft) * ratioX;
|
||||
var sourceY = Math.abs(currentTop) * ratioY;
|
||||
var sourceWidth = (outputSize / currentScale) * ratioX;
|
||||
var sourceHeight = (outputSize / currentScale) * ratioY;
|
||||
|
||||
// S'assurer que la zone source reste dans les limites de l'image
|
||||
sourceX = Math.max(0, Math.min(img.width - sourceWidth, sourceX));
|
||||
sourceY = Math.max(0, Math.min(img.height - sourceHeight, sourceY));
|
||||
sourceWidth = Math.min(sourceWidth, img.width - sourceX);
|
||||
sourceHeight = Math.min(sourceHeight, img.height - sourceY);
|
||||
|
||||
// Créer un masque circulaire
|
||||
ctx.beginPath();
|
||||
ctx.arc(outputSize/2, outputSize/2, outputSize/2, 0, 2 * Math.PI);
|
||||
ctx.clip();
|
||||
|
||||
// Dessiner la portion cadrée de l'image
|
||||
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, outputSize, outputSize);
|
||||
|
||||
// Convertir en blob
|
||||
canvas.toBlob(function(blob) {
|
||||
resolve(blob);
|
||||
}, 'image/jpeg', 0.9);
|
||||
};
|
||||
img.src = preview.originalSrc;
|
||||
});
|
||||
}
|
||||
|
||||
function removePhoto() {
|
||||
var photoInput = document.getElementById('photoInput');
|
||||
var preview = document.getElementById('photoPreview');
|
||||
var placeholder = document.getElementById('photoPlaceholder');
|
||||
var removeBtn = document.getElementById('removePhotoBtn');
|
||||
|
||||
if (photoInput) photoInput.value = '';
|
||||
if (preview) {
|
||||
preview.style.display = 'none';
|
||||
preview.style.transform = 'scale(1)';
|
||||
preview.style.left = '0px';
|
||||
preview.style.top = '0px';
|
||||
preview.originalSrc = null;
|
||||
}
|
||||
if (placeholder) placeholder.style.display = 'flex';
|
||||
if (removeBtn) removeBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
function preparePhotoForSubmission(button) {
|
||||
var preview = document.getElementById('photoPreview');
|
||||
if (preview && preview.style.display !== 'none') {
|
||||
// Empêcher la soumission immédiate
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="pi pi-spin pi-spinner"></i> <span>Traitement...</span>';
|
||||
|
||||
capturePhoto().then(function(blob) {
|
||||
if (blob) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var base64Data = e.target.result.split(',')[1];
|
||||
var hiddenInput = document.getElementById('photoCadree');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = base64Data;
|
||||
}
|
||||
|
||||
// Maintenant soumettre le formulaire
|
||||
button.click();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
} else {
|
||||
// Pas de photo, soumettre directement
|
||||
button.click();
|
||||
}
|
||||
});
|
||||
return false; // Empêcher la soumission immédiate
|
||||
}
|
||||
return true; // Permettre la soumission si pas de photo
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Dialogue de chargement -->
|
||||
<p:dialog id="statusDialog" widgetVar="statusDialog" modal="true" closable="false"
|
||||
showHeader="false" styleClass="no-border" resizable="false">
|
||||
<div class="flex flex-column align-items-center p-4">
|
||||
<i class="pi pi-spin pi-spinner text-4xl text-primary mb-3"></i>
|
||||
<div class="text-xl font-medium text-900">Traitement en cours...</div>
|
||||
<div class="text-600">Veuillez patienter pendant l'enregistrement</div>
|
||||
</div>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
Reference in New Issue
Block a user