templates/candidature/form.html.twig line 1

  1. {% extends 'base.html.twig' %}
  2. {% block title %}Nouvelle candidature | DAIP{% endblock %}
  3. {# Définition de la classe CSS réutilisable #}
  4. {% set input_class = 'w-full px-4 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition' %}
  5. {% set is_edit_page = app.request.attributes.get('_route') == 'app_candidature_edit' %}
  6. {% block body %}
  7. {# Flash messages #}
  8. {% for label, messages in app.flashes %}
  9.     {% for message in messages %}
  10.         <div class="mb-4 p-4 rounded-lg border
  11.             {% if label == 'error' %}
  12.                 bg-red-50 border-red-200 text-red-800
  13.             {% elseif label == 'success' %}
  14.                 bg-green-50 border-green-200 text-green-800
  15.             {% else %}
  16.                 bg-blue-50 border-blue-200 text-blue-800
  17.             {% endif %}
  18.         ">
  19.             {{ message|raw }}
  20.         </div>
  21.     {% endfor %}
  22. {% endfor %}
  23. <div class="bg-gradient-to-r from-indigo-600 to-purple-600 p-8 mb-8 text-white">
  24.     <div class="flex items-center gap-4 mb-4">
  25.         <div class="w-16 h-16 bg-white/20 rounded-full flex items-center justify-center">
  26.             <i class="fas fa-file-alt text-3xl"></i>
  27.         </div>
  28.         <div>
  29.             <h1 class="text-2xl font-bold">
  30.                 {% if is_edit_page %}Modification de candidature{% else %}Nouvelle candidature{% endif %}
  31.             </h1>
  32.             <p class="text-white/80">
  33.                 {% if is_edit_page %}Mettez a jour les informations de la candidature de facon claire et complete.{% else %}Vous etes sur le point de postuler a une formation{% endif %}
  34.             </p>
  35.         </div>
  36.     </div>
  37.     
  38.     <!-- Informations du candidat -->
  39.     {% if app.user %}
  40.         {% if is_granted('ROLE_ENT') and is_edit_page and candidature is defined %}
  41.             <div class="mt-4 grid grid-cols-1 lg:grid-cols-2 gap-4">
  42.                 <div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 border border-white/20">
  43.                     <p class="text-sm font-semibold text-white/80 mb-3 flex items-center">
  44.                         <i class="fas fa-user-graduate mr-2"></i>
  45.                         Informations du candidat
  46.                     </p>
  47.                     <div class="space-y-3">
  48.                         <div class="bg-white/10 rounded-lg px-3 py-2">
  49.                             <p class="text-xs uppercase tracking-wide text-white/70">Nom du candidat</p>
  50.                             <p class="text-sm font-semibold text-white mt-1">{{ candidature.user ? (candidature.user.nom ~ ' ' ~ candidature.user.prenoms)|trim : 'Non renseigne' }}</p>
  51.                         </div>
  52.                         <div class="bg-white/10 rounded-lg px-3 py-2">
  53.                             <p class="text-xs uppercase tracking-wide text-white/70">Numero de candidature</p>
  54.                             <p class="text-sm font-semibold text-white mt-1">{{ candidature.numero|default('Non attribue') }}</p>
  55.                         </div>
  56.                     </div>
  57.                 </div>
  58.                 <div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 border border-white/20">
  59.                     <p class="text-sm font-semibold text-white/80 mb-3 flex items-center">
  60.                         <i class="fas fa-file-signature mr-2"></i>
  61.                         Informations de la candidature
  62.                     </p>
  63.                     <div class="space-y-3">
  64.                         <div class="bg-white/10 rounded-lg px-3 py-2">
  65.                             <p class="text-xs uppercase tracking-wide text-white/70">Etablissement choisi</p>
  66.                             <p class="text-sm font-semibold text-white mt-1">{{ candidature.etablissement ? candidature.etablissement.nom : 'Non renseigne' }}</p>
  67.                         </div>
  68.                         <div class="bg-white/10 rounded-lg px-3 py-2">
  69.                             <p class="text-xs uppercase tracking-wide text-white/70">Metier choisi</p>
  70.                             <p class="text-sm font-semibold text-white mt-1">{{ candidature.metier ? candidature.metier.nom : 'Non renseigne' }}</p>
  71.                         </div>
  72.                     </div>
  73.                 </div>
  74.             </div>
  75.         {% else %}
  76.             <div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 mt-4">
  77.                 <div class="flex items-center gap-3">
  78.                     <div class="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
  79.                         <i class="fas fa-user"></i>
  80.                     </div>
  81.                     <div>
  82.                         <p class="text-sm text-white/80">Candidat</p>
  83.                         <p class="font-semibold">{{ app.user.nom }} {{ app.user.prenoms }}</p>
  84.                     </div>
  85.                     {# Calcul et affichage de l'âge #}
  86.                     {% if app.user.datenaissance %}
  87.                         {% set age = date().diff(date(app.user.datenaissance)).y %}
  88.                         <div class="ml-4 px-3 py-1 bg-white/20 rounded-full">
  89.                             <span class="text-sm">
  90.                                 <i class="fas fa-birthday-cake mr-1"></i>
  91.                                 {{ age }} ans
  92.                             </span>
  93.                         </div>
  94.                     {% endif %}
  95.                     <div class="ml-auto">
  96.                         <span class="px-3 py-1 bg-white/20 rounded-full text-sm">{{ app.user.email }}</span>
  97.                         <span class="ml-2 px-3 py-1 bg-indigo-500 rounded-full text-xs">
  98.                             {% if is_granted('ROLE_ADMIN') %}ADMIN
  99.                             {% elseif is_granted('ROLE_ENT') %}ENT
  100.                             {% elseif is_granted('ROLE_JURY') %}JURY
  101.                             {% elseif is_granted('ROLE_CANDIDAT') %}CANDIDAT
  102.                             {% endif %}
  103.                         </span>
  104.                     </div>
  105.                 </div>
  106.             </div>
  107.         {% endif %}
  108.     {% endif %}
  109.     
  110.     {# Vérification d'âge #}
  111.     {% if app.user and app.user.datenaissance %}
  112.         {% set age = date().diff(date(app.user.datenaissance)).y %}
  113.         {% if age < 16 or age > 40 %}
  114.             <div class="mt-4 bg-red-500/20 backdrop-blur-sm border border-red-500/30 rounded-xl p-4">
  115.                 <div class="flex items-center gap-3">
  116.                     <div class="w-10 h-10 bg-red-500/30 rounded-full flex items-center justify-center">
  117.                         <i class="fas fa-exclamation-triangle text-red-300"></i>
  118.                     </div>
  119.                     <div>
  120.                         <p class="font-semibold text-white">Âge non conforme</p>
  121.                         <p class="text-sm text-white/80">
  122.                             {% if age < 16 %}
  123.                                 Vous avez {{ age }} ans. L'âge minimum requis pour candidater est de 16 ans.
  124.                             {% elseif age > 40 %}
  125.                                 Vous avez {{ age }} ans. L'âge maximum autorisé pour candidater est de 40 ans.
  126.                             {% endif %}
  127.                         </p>
  128.                     </div>
  129.                 </div>
  130.             </div>
  131.         {% endif %}
  132.     {% endif %}
  133. </div>
  134. <div class="max-w-7xl mx-auto px-4 pb-16 {{ is_edit_page ? 'pt-2' : '' }}">
  135.     {% include 'partials/_flash_messages.html.twig' %}
  136.     {% if is_edit_page and candidature is defined and candidature.id %}
  137.         {# ===== PARTIE 1 : MISE À JOUR DES INFORMATIONS ===== #}
  138.         <div class="bg-white rounded-2xl shadow-xl border border-slate-200 overflow-hidden mb-8">
  139.             <div class="p-6 md:p-8">
  140.                 <div class="rounded-xl border border-indigo-200 bg-indigo-50 p-4 md:p-5 mb-8">
  141.                     <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2">
  142.                         <div>
  143.                             <p class="text-sm font-semibold text-indigo-800 flex items-center">
  144.                                 <i class="fas fa-edit mr-2"></i> Partie 1 — Mise à jour des informations
  145.                             </p>
  146.                             <p class="text-sm text-indigo-700 mt-1">Modifiez les informations du candidat, le métier et les documents.</p>
  147.                         </div>
  148.                         <span class="inline-flex items-center px-3 py-1 rounded-full bg-white text-indigo-700 text-sm font-semibold border border-indigo-200">
  149.                             N° {{ candidature.numero|default('Non attribué') }}
  150.                         </span>
  151.                     </div>
  152.                 </div>
  153.                 <form id="form-update-info" action="{{ path('app_candidature_update_info', {id: candidature.id}) }}" method="POST" enctype="multipart/form-data" class="space-y-10">
  154.                     <input type="hidden" name="_token" value="{{ csrf_token('candidature_update_info_' ~ candidature.id) }}">
  155.                     {# Identité du candidat (ENT/ADMIN) #}
  156.                     {% if is_granted('ROLE_ENT') or is_granted('ROLE_ADMIN') %}
  157.                         <div class="rounded-xl border border-orange-200 bg-orange-50 p-5">
  158.                             <h3 class="text-base font-semibold text-orange-800 mb-4 flex items-center">
  159.                                 <div class="w-7 h-7 bg-orange-200 rounded-lg flex items-center justify-center mr-2">
  160.                                     <i class="fas fa-user-edit text-orange-700 text-sm"></i>
  161.                                 </div>
  162.                                 Identité du candidat
  163.                             </h3>
  164.                             <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  165.                                 <div>
  166.                                     <label class="block text-sm font-medium text-slate-700 mb-1">Nom <span class="text-red-500">*</span></label>
  167.                                     <input type="text" name="nom" value="{{ candidature.user ? candidature.user.nom : '' }}" class="{{ input_class }}" placeholder="Nom de famille">
  168.                                 </div>
  169.                                 <div>
  170.                                     <label class="block text-sm font-medium text-slate-700 mb-1">Prénom(s) <span class="text-red-500">*</span></label>
  171.                                     <input type="text" name="prenoms" value="{{ candidature.user ? candidature.user.prenoms : '' }}" class="{{ input_class }}" placeholder="Prénom(s)">
  172.                                 </div>
  173.                                 <div class="md:col-span-2">
  174.                                     <label class="block text-sm font-medium text-slate-700 mb-1">Contact (téléphone)</label>
  175.                                     <input type="text" name="contact" value="{{ candidature.user ? candidature.user.contact : '' }}" class="{{ input_class }}" placeholder="Ex: 0102030405">
  176.                                 </div>
  177.                             </div>
  178.                         </div>
  179.                     {% endif %}
  180.                     {# Formation souhaitée #}
  181.                     <h3 class="text-lg font-semibold text-indigo-800 flex items-center border-b pb-3 mb-5">
  182.                         <div class="w-8 h-8 bg-indigo-200 rounded-lg flex items-center justify-center mr-3">
  183.                             <i class="fas fa-graduation-cap text-indigo-700"></i>
  184.                         </div>
  185.                         Formation souhaitée
  186.                     </h3>
  187.                     <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  188.                         <div>
  189.                             <label class="block text-sm font-medium text-slate-700 mb-1">Établissement <span class="text-red-500">*</span></label>
  190.                             <div class="{{ input_class }} bg-slate-100 text-slate-600 cursor-default flex items-center gap-2">
  191.                                 <i class="fas fa-lock text-slate-400 text-xs"></i>
  192.                                 {{ candidature.etablissement ? candidature.etablissement.nom : 'Non renseigné' }}
  193.                             </div>
  194.                             <input type="hidden" id="candidature_etablissement" value="{{ candidature.etablissement ? candidature.etablissement.id : '' }}">
  195.                         </div>
  196.                         <div>
  197.                             <label class="block text-sm font-medium text-slate-700 mb-1">Métier <span class="text-red-500">*</span></label>
  198.                             <select name="metier_id" id="candidature_metier" required class="{{ input_class }}">
  199.                                 {% if candidature.metier %}
  200.                                     <option value="{{ candidature.metier.id }}" selected>{{ candidature.metier.nom }}</option>
  201.                                 {% else %}
  202.                                     <option value="">-- Sélectionnez un métier --</option>
  203.                                 {% endif %}
  204.                             </select>
  205.                             <div id="metier-loading" class="hidden text-sm text-indigo-600 mt-1">
  206.                                 <i class="fas fa-spinner fa-spin mr-1"></i> Chargement...
  207.                             </div>
  208.                         </div>
  209.                     </div>
  210.                     {# Informations détaillées du métier #}
  211.                     <div id="metier-info" class="hidden mt-6">
  212.                         <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
  213.                             <div class="flex items-center gap-3 p-2 bg-indigo-100 rounded-lg">
  214.                                 <i class="fas fa-building text-indigo-600"></i>
  215.                                 <div>
  216.                                     <p class="text-xs text-indigo-600">Secteur</p>
  217.                                     <p id="metier-secteur" class="text-sm font-medium">-</p>
  218.                                 </div>
  219.                             </div>
  220.                             <div class="flex items-center gap-3 p-2 bg-purple-100 rounded-lg">
  221.                                 <i class="fas fa-users text-purple-600"></i>
  222.                                 <div>
  223.                                     <p class="text-xs text-purple-600">Places</p>
  224.                                     <p id="metier-nbrplace" class="text-sm font-medium">-</p>
  225.                                 </div>
  226.                             </div>
  227.                             <div class="flex items-center gap-3 p-2 bg-green-100 rounded-lg">
  228.                                 <i class="fas fa-graduation-cap text-green-600"></i>
  229.                                 <div>
  230.                                     <p class="text-xs text-green-600">Niveau</p>
  231.                                     <p id="metier-niveau" class="text-sm font-medium">-</p>
  232.                                 </div>
  233.                             </div>
  234.                             <div class="flex items-center gap-3 p-2 bg-amber-100 rounded-lg">
  235.                                 <i class="fas fa-clock text-amber-600"></i>
  236.                                 <div>
  237.                                     <p class="text-xs text-amber-600">Durée</p>
  238.                                     <p id="metier-duree" class="text-sm font-medium">-</p>
  239.                                 </div>
  240.                             </div>
  241.                         </div>
  242.                     </div>
  243.                     {# Documents #}
  244.                     <h3 class="text-lg font-semibold text-purple-800 mb-5 flex items-center border-b pb-3">
  245.                         <div class="w-8 h-8 bg-purple-200 rounded-lg flex items-center justify-center mr-3">
  246.                             <i class="fas fa-cloud-upload-alt text-purple-700"></i>
  247.                         </div>
  248.                         Documents à télécharger <span class="text-red-500"> *</span>
  249.                     </h3>
  250.                     {% include 'partials/_document_upload_plain.html.twig' with {
  251.                         'candidature': candidature,
  252.                         'document_labels': document_labels,
  253.                         'input_class': input_class
  254.                     } %}
  255.                     {# Bouton Mise à jour #}
  256.                     <div class="flex flex-col md:flex-row justify-between items-center gap-4 pt-6 border-t border-slate-200">
  257.                         <div class="text-sm text-slate-500">
  258.                             <i class="fas fa-info-circle mr-1"></i>
  259.                             Les champs marqués d'une <span class="text-red-500">*</span> sont obligatoires
  260.                         </div>
  261.                         <button type="submit" class="px-8 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg flex items-center">
  262.                             <i class="fas fa-save mr-2"></i>
  263.                             Mise à jour des informations
  264.                         </button>
  265.                     </div>
  266.                 </form>
  267.             </div>
  268.         </div>
  269.         {# ===== PARTIE 2 : ANALYSE DU DOSSIER ===== #}
  270.         <div class="bg-white rounded-2xl shadow-xl border border-slate-200 overflow-hidden">
  271.             <div class="p-6 md:p-8">
  272.                 {{ form_start(form, {'attr': {'class': 'space-y-10', 'id': 'candidature-form'}}) }}
  273.                     <div class="rounded-xl border border-amber-200 bg-amber-50 p-4 md:p-5 mb-4">
  274.                         <p class="text-sm font-semibold text-amber-800 flex items-center">
  275.                             <i class="fas fa-clipboard-list mr-2"></i>
  276.                             Partie 2 — Analyse du dossier
  277.                         </p>
  278.                         <p class="text-sm text-amber-700 mt-1">Étudiez le dossier, évaluez le candidat et enregistrez le résultat.</p>
  279.                     </div>
  280.                     {% include 'partials/_candidature_evaluation_sections.html.twig' %}
  281.                     <div class="flex flex-col md:flex-row justify-between items-center gap-4 pt-6 border-t border-slate-200">
  282.                         <div class="text-sm text-slate-500">
  283.                             <i class="fas fa-info-circle mr-1"></i>
  284.                             Les champs marqués d'une <span class="text-red-500">*</span> sont obligatoires
  285.                         </div>
  286.                         <button type="submit" class="px-8 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg flex items-center">
  287.                             <i class="fas fa-paper-plane mr-2"></i>
  288.                             Enregistrer l'évaluation
  289.                         </button>
  290.                     </div>
  291.                 {{ form_end(form) }}
  292.             </div>
  293.         </div>
  294.     {% else %}
  295.     <div class="bg-white rounded-2xl shadow-xl border border-slate-200 overflow-hidden">
  296.         <div class="p-6 md:p-8">
  297.             {{ form_start(form, {'attr': {'class': 'space-y-10', 'enctype': 'multipart/form-data', 'id': 'candidature-form'}}) }}
  298.             {% if is_edit_page and candidature is defined %}
  299.                 <div class="rounded-xl border border-indigo-200 bg-indigo-50 p-4 md:p-5">
  300.                     <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2">
  301.                         <div>
  302.                             <p class="text-sm font-semibold text-indigo-800">Edition de candidature</p>
  303.                             <p class="text-sm text-indigo-700 mt-1">Verifiez les informations puis mettez a jour les documents si necessaire.</p>
  304.                         </div>
  305.                         <span class="inline-flex items-center px-3 py-1 rounded-full bg-white text-indigo-700 text-sm font-semibold border border-indigo-200">
  306.                             N° {{ candidature.numero|default('Non attribue') }}
  307.                         </span>
  308.                     </div>
  309.                 </div>
  310.             {% endif %}
  311.             {# Section : Identité du candidat (visible ENT / ADMIN en mode édition) #}
  312.             {% if is_edit_page and (is_granted('ROLE_ENT') or is_granted('ROLE_ADMIN')) and form.nom is defined %}
  313.                 <div class="rounded-xl border border-orange-200 bg-orange-50 p-5">
  314.                     <h3 class="text-base font-semibold text-orange-800 mb-4 flex items-center">
  315.                         <div class="w-7 h-7 bg-orange-200 rounded-lg flex items-center justify-center mr-2">
  316.                             <i class="fas fa-user-edit text-orange-700 text-sm"></i>
  317.                         </div>
  318.                         Identité du candidat
  319.                     </h3>
  320.                     <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  321.                         <div>
  322.                             <label class="block text-sm font-medium text-slate-700 mb-1">
  323.                                 Nom <span class="text-red-500">*</span>
  324.                             </label>
  325.                             {{ form_widget(form.nom, {'attr': {'class': input_class, 'placeholder': 'Nom de famille'}}) }}
  326.                         </div>
  327.                         <div>
  328.                             <label class="block text-sm font-medium text-slate-700 mb-1">
  329.                                 Prénom(s) <span class="text-red-500">*</span>
  330.                             </label>
  331.                             {{ form_widget(form.prenoms, {'attr': {'class': input_class, 'placeholder': 'Prénom(s)'}}) }}
  332.                         </div>
  333.                     </div>
  334.                 </div>
  335.             {% endif %}
  336.             <!-- SECTION 1: TOUS LES UTILISATEURS - Formation souhaitée -->
  337.             <h3 class="text-lg font-semibold text-indigo-800 flex items-center border-b pb-3 mb-5">
  338.                 <div class="w-8 h-8 bg-indigo-200 rounded-lg flex items-center justify-center mr-3">
  339.                     <i class="fas fa-graduation-cap text-indigo-700"></i>
  340.                 </div>
  341.                 Formation souhaitée
  342.             </h3>
  343.             
  344.             <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  345.                 <div>
  346.                     <label class="block text-sm font-medium text-slate-700 mb-1">
  347.                         Établissement <span class="text-red-500">*</span>
  348.                     </label>
  349.                     {% if is_edit_page and candidature is defined and candidature.etablissement %}
  350.                         {# En mode édition : établissement fixe (lecture seule) #}
  351.                         <div class="{{ input_class }} bg-slate-100 text-slate-600 cursor-default flex items-center gap-2">
  352.                             <i class="fas fa-lock text-slate-400 text-xs"></i>
  353.                             {{ candidature.etablissement.nom }}
  354.                         </div>
  355.                         {# Champ caché pour maintenir la valeur lors de la soumission #}
  356.                         {{ form_widget(form.etablissement, {'attr': {'class': 'hidden', 'id': 'candidature_etablissement'}}) }}
  357.                     {% else %}
  358.                         {{ form_widget(form.etablissement, {'attr': {'class': input_class, 'id': 'candidature_etablissement'}}) }}
  359.                     {% endif %}
  360.                     <div id="etablissement-loading" class="hidden text-sm text-indigo-600 mt-1">
  361.                         <i class="fas fa-spinner fa-spin mr-1"></i> Chargement...
  362.                     </div>
  363.                 </div>
  364.                 
  365.                 <div>
  366.                     <label class="block text-sm font-medium text-slate-700 mb-1">
  367.                         Métier <span class="text-red-500">*</span>
  368.                     </label>
  369.                     {{ form_widget(form.metier, {'attr': {'class': input_class, 'id': 'candidature_metier'}}) }}
  370.                     <div id="metier-loading" class="hidden text-sm text-indigo-600 mt-1">
  371.                         <i class="fas fa-spinner fa-spin mr-1"></i> Chargement...
  372.                     </div>
  373.                 </div>
  374.             </div>
  375.             <!-- Informations détaillées du métier -->
  376.             <div id="metier-info" class="hidden mt-6">
  377.                 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
  378.                     <div class="flex items-center gap-3 p-2 bg-indigo-100 rounded-lg">
  379.                         <i class="fas fa-building text-indigo-600"></i>
  380.                         <div>
  381.                             <p class="text-xs text-indigo-600">Secteur</p>
  382.                             <p id="metier-secteur" class="text-sm font-medium">-</p>
  383.                         </div>
  384.                     </div>
  385.                     <div class="flex items-center gap-3 p-2 bg-purple-100 rounded-lg">
  386.                         <i class="fas fa-users text-purple-600"></i>
  387.                         <div>
  388.                             <p class="text-xs text-purple-600">Places</p>
  389.                             <p id="metier-nbrplace" class="text-sm font-medium">-</p>
  390.                         </div>
  391.                     </div>
  392.                     <div class="flex items-center gap-3 p-2 bg-green-100 rounded-lg">
  393.                         <i class="fas fa-graduation-cap text-green-600"></i>
  394.                         <div>
  395.                             <p class="text-xs text-green-600">Niveau</p>
  396.                             <p id="metier-niveau" class="text-sm font-medium">-</p>
  397.                         </div>
  398.                     </div>
  399.                     <div class="flex items-center gap-3 p-2 bg-amber-100 rounded-lg">
  400.                         <i class="fas fa-clock text-amber-600"></i>
  401.                         <div>
  402.                             <p class="text-xs text-amber-600">Durée</p>
  403.                             <p id="metier-duree" class="text-sm font-medium">-</p>
  404.                         </div>
  405.                     </div>
  406.                 </div>
  407.             </div>
  408.             <!-- SECTION 2: DOCUMENTS -->
  409.             <h3 class="text-lg font-semibold text-purple-800 mb-5 flex items-center border-b pb-3">
  410.                 <div class="w-8 h-8 bg-purple-200 rounded-lg flex items-center justify-center mr-3">
  411.                     <i class="fas fa-cloud-upload-alt text-purple-700"></i>
  412.                 </div>
  413.                 Documents à télécharger <span class="text-red-500"> *</span>
  414.             </h3>
  415.             
  416.             {% include 'partials/_document_upload.html.twig' with {
  417.                 'form': form,
  418.                 'document_labels': document_labels,
  419.                 'input_class': input_class
  420.             } %}
  421.             <!-- SECTION 3: POUR ENT ET ADMIN - Étude de dossier -->
  422.             {% if is_granted('ROLE_ENT') or is_granted('ROLE_ADMIN') %}
  423.                 <h3 class="text-lg font-semibold text-amber-800 mb-5 flex items-center border-b pb-3">
  424.                     <div class="w-8 h-8 bg-amber-200 rounded-lg flex items-center justify-center mr-3">
  425.                         <i class="fas fa-clipboard-check text-amber-700"></i>
  426.                     </div>
  427.                     Étude de dossier (ENT)
  428.                 </h3>
  429.                 
  430.                 <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  431.                     <div>
  432.                         <label class="block text-sm font-medium text-slate-700 mb-1">Statut dossier</label>
  433.                         {{ form_widget(form.etustatut, {'attr': {'class': input_class}}) }}
  434.                     </div>
  435.                     <div>
  436.                         <label class="block text-sm font-medium text-slate-700 mb-1">Commentaire</label>
  437.                         {{ form_widget(form.etucom, {'attr': {'class': input_class, 'placeholder': 'Commentaire sur le dossier...'}}) }}
  438.                     </div>
  439.                 </div>
  440.             {% endif %}
  441.             <!-- SECTION 4: POUR JURY, ENT ET ADMIN - Évaluation entretien -->
  442.             {% if is_granted('ROLE_JURY') or is_granted('ROLE_ADMIN') or is_granted('ROLE_ENT') %}
  443.                 <h3 class="text-lg font-semibold text-blue-800 mb-5 flex items-center border-b pb-3">
  444.                     <div class="w-8 h-8 bg-blue-200 rounded-lg flex items-center justify-center mr-3">
  445.                         <i class="fas fa-microphone-alt text-blue-700"></i>
  446.                     </div>
  447.                     Évaluation entretien
  448.                 </h3>
  449.                 
  450.                 <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
  451.                     <div>
  452.                         <label class="block text-sm font-medium text-slate-700 mb-1">Date entretien</label>
  453.                         {{ form_widget(form.entdate, {'attr': {'class': input_class}}) }}
  454.                     </div>
  455.                     <div>
  456.                         <label class="block text-sm font-medium text-slate-700 mb-1">Lieu entretien</label>
  457.                         {{ form_widget(form.entlieu, {'attr': {'class': input_class, 'placeholder': 'Ex: Salle 101'}}) }}
  458.                     </div>
  459.                 </div>
  460.                 <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
  461.                     <div>
  462.                         <label class="block text-sm font-medium text-slate-700 mb-1">Numéro du jury</label>
  463.                         {{ form_widget(form.jury, {'attr': {'class': input_class, 'min': 1, 'max': 20}}) }}
  464.                     </div>
  465.                     <div>
  466.                         <label class="block text-sm font-medium text-slate-700 mb-1">Vague</label>
  467.                         {{ form_widget(form.vague, {'attr': {'class': input_class, 'min': 1}}) }}
  468.                     </div>
  469.                     <div>
  470.                         <label class="block text-sm font-medium text-slate-700 mb-1">Décision du jury</label>
  471.                         {{ form_widget(form.entstatut, {'attr': {'class': input_class}}) }}
  472.                     </div>
  473.                 </div>
  474.                 {% if form.note1 is defined %}
  475.                 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
  476.                     <div>
  477.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 1</label>
  478.                         {{ form_widget(form.note1, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  479.                     </div>
  480.                     <div>
  481.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 2</label>
  482.                         {{ form_widget(form.note2, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  483.                     </div>
  484.                     <div>
  485.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 3</label>
  486.                         {{ form_widget(form.note3, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  487.                     </div>
  488.                     <div>
  489.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 4</label>
  490.                         {{ form_widget(form.note4, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  491.                     </div>
  492.                     <div>
  493.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 5</label>
  494.                         {{ form_widget(form.note5, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  495.                     </div>
  496.                     <div>
  497.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 6</label>
  498.                         {{ form_widget(form.note6, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  499.                     </div>
  500.                     <div>
  501.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 7</label>
  502.                         {{ form_widget(form.note7, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  503.                     </div>
  504.                     <div>
  505.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 8</label>
  506.                         {{ form_widget(form.note8, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  507.                     </div>
  508.                     <div>
  509.                         <label class="block text-sm font-medium text-slate-700 mb-1">Note 9</label>
  510.                         {{ form_widget(form.note9, {'attr': {'class': input_class, 'min': 0, 'max': 20, 'step': 0.5}}) }}
  511.                     </div>
  512.                 </div>
  513.                 {% endif %}
  514.                 <div class="mt-4 mb-6">
  515.                     <label class="block text-sm font-medium text-slate-700 mb-1">Commentaire entretien</label>
  516.                     {{ form_widget(form.entcom, {'attr': {'class': input_class, 'rows': 3, 'placeholder': "Observations sur l'entretien..."}}) }}
  517.                 </div>
  518.             {% endif %}
  519.             <!-- SECTION 4bis: GRILLE D'ÉVALUATION DYNAMIQUE -->
  520.             {% if (is_granted('ROLE_ENT') or is_granted('ROLE_ADMIN') or is_granted('ROLE_JURY')) and candidature is defined and candidature.id %}
  521.                 <div id="evaluation-section">
  522.                     <h3 class="text-lg font-semibold text-indigo-800 mb-5 flex items-center border-b pb-3">
  523.                         <div class="w-8 h-8 bg-indigo-200 rounded-lg flex items-center justify-center mr-3">
  524.                             <i class="fas fa-clipboard-check text-indigo-700"></i>
  525.                         </div>
  526.                         Grille d'évaluation dynamique
  527.                     </h3>
  528.                     <!-- Évaluations existantes -->
  529.                     {% if evaluations is defined and evaluations|length > 0 %}
  530.                     <div class="mb-6 space-y-3" id="existing-evaluations">
  531.                         <p class="text-sm font-medium text-slate-600 flex items-center mb-2">
  532.                             <i class="fas fa-history text-indigo-500 mr-2"></i>
  533.                             Évaluations précédentes
  534.                         </p>
  535.                         {% for eval in evaluations %}
  536.                         <div class="bg-slate-50 rounded-xl p-4 border border-slate-200">
  537.                             <div class="flex items-center justify-between mb-2">
  538.                                 <div class="flex items-center gap-3">
  539.                                     <span class="font-medium text-slate-800">{{ eval.grille.nom }}</span>
  540.                                     <span class="text-xs text-slate-500">{{ eval.createdAt|date('d/m/Y H:i') }}</span>
  541.                                 </div>
  542.                                 <div class="flex items-center gap-2">
  543.                                     {% set percentage = eval.noteMaxTotale > 0 ? (eval.scoreTotal / eval.noteMaxTotale * 100) : 0 %}
  544.                                     <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-bold
  545.                                         {% if percentage >= 70 %}bg-green-100 text-green-700
  546.                                         {% elseif percentage >= 40 %}bg-yellow-100 text-yellow-700
  547.                                         {% else %}bg-red-100 text-red-700{% endif %}">
  548.                                         {{ eval.scoreTotal }} / {{ eval.noteMaxTotale }}
  549.                                     </span>
  550.                                 </div>
  551.                             </div>
  552.                             <!-- Progress bar -->
  553.                             <div class="w-full bg-slate-200 rounded-full h-2.5 mb-2">
  554.                                 <div class="h-2.5 rounded-full transition-all duration-500
  555.                                     {% if percentage >= 70 %}bg-green-500
  556.                                     {% elseif percentage >= 40 %}bg-yellow-500
  557.                                     {% else %}bg-red-500{% endif %}" 
  558.                                     style="width: {{ percentage }}%"></div>
  559.                             </div>
  560.                             {% if eval.commentaire %}
  561.                             <p class="text-xs text-slate-500 mt-1"><i class="fas fa-comment mr-1"></i> {{ eval.commentaire }}</p>
  562.                             {% endif %}
  563.                         </div>
  564.                         {% endfor %}
  565.                     </div>
  566.                     {% endif %}
  567.                     {#
  568.                     <!-- Sélection de la grille -->
  569.                     <div class="bg-white rounded-xl border border-slate-200 p-5 mb-4">
  570.                         <label class="block text-sm font-medium text-slate-700 mb-2">
  571.                             <i class="fas fa-th-list text-indigo-500 mr-1"></i>
  572.                             Sélectionner une grille d'évaluation
  573.                         </label>
  574.                         <select id="grille-select" class="{{ input_class }}">
  575.                             <option value="">-- Choisir une grille --</option>
  576.                             {% if grilles is defined %}
  577.                                 {% for grille in grilles %}
  578.                                     <option value="{{ grille.id }}" data-note-max="{{ grille.noteMaxTotale }}" data-nb-criteres="{{ grille.criteres|length }}">
  579.                                         {{ grille.nom }} ({{ grille.criteres|length }} critères — max {{ grille.noteMaxTotale }})
  580.                                     </option>
  581.                                 {% endfor %}
  582.                             {% endif %}
  583.                         </select>
  584.                     </div>
  585.                     #}
  586.                     <!-- Zone dynamique des critères -->
  587.                     <div id="criteres-container" class="hidden">
  588.                         <div class="bg-white rounded-xl border border-slate-200 overflow-hidden mb-4">
  589.                             <div class="px-5 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
  590.                                 <span class="text-sm font-semibold text-slate-700">
  591.                                     <i class="fas fa-star text-yellow-500 mr-1"></i>
  592.                                     Critères d'évaluation
  593.                                 </span>
  594.                                 <span id="grille-name" class="text-xs text-indigo-600 font-medium"></span>
  595.                             </div>
  596.                             <div class="overflow-x-auto">
  597.                                 <table class="w-full">
  598.                                     <thead class="bg-slate-50/50">
  599.                                         <tr>
  600.                                             <th class="px-5 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Critère</th>
  601.                                             <th class="px-5 py-3 text-center text-xs font-semibold text-slate-600 uppercase tracking-wider w-28">Note max</th>
  602.                                             <th class="px-5 py-3 text-center text-xs font-semibold text-slate-600 uppercase tracking-wider w-36">Note attribuée</th>
  603.                                         </tr>
  604.                                     </thead>
  605.                                     <tbody id="criteres-body" class="divide-y divide-slate-100">
  606.                                         <!-- Rempli dynamiquement par JS -->
  607.                                     </tbody>
  608.                                 </table>
  609.                             </div>
  610.                         </div>
  611.                         <!-- Score total + progress bar -->
  612.                         <div class="bg-white rounded-xl border border-slate-200 p-5 mb-4">
  613.                             <div class="flex items-center justify-between mb-3">
  614.                                 <span class="text-sm font-semibold text-slate-700">
  615.                                     <i class="fas fa-calculator text-indigo-500 mr-1"></i>
  616.                                     Score total
  617.                                 </span>
  618.                                 <span id="score-display" class="text-lg font-bold text-slate-800">0 / 0</span>
  619.                             </div>
  620.                             <div class="w-full bg-slate-200 rounded-full h-3">
  621.                                 <div id="score-bar" class="h-3 rounded-full transition-all duration-300 bg-slate-400" style="width: 0%"></div>
  622.                             </div>
  623.                             <p id="score-label" class="text-xs text-slate-500 mt-2 text-center"></p>
  624.                         </div>
  625.                         <!-- Commentaire -->
  626.                         <div class="bg-white rounded-xl border border-slate-200 p-5 mb-4">
  627.                             <label class="block text-sm font-medium text-slate-700 mb-2">
  628.                                 <i class="fas fa-comment-alt text-indigo-500 mr-1"></i>
  629.                                 Observation du jury
  630.                             </label>
  631.                             <textarea id="evaluation-commentaire" rows="3" 
  632.                                 class="{{ input_class }}" 
  633.                                 placeholder="Saisissez les observations du jury..."></textarea>
  634.                         </div>
  635.                         <!-- Bouton enregistrer -->
  636.                         <div class="flex justify-end">
  637.                             <button type="button" id="save-evaluation-btn" 
  638.                                 class="px-6 py-2.5 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg inline-flex items-center">
  639.                                 <i class="fas fa-save mr-2"></i>
  640.                                 Enregistrer l'évaluation
  641.                             </button>
  642.                         </div>
  643.                         <!-- Message de succès -->
  644.                         <div id="evaluation-success" class="hidden mt-4 p-4 rounded-xl bg-green-50 text-green-800 border border-green-200">
  645.                             <div class="flex items-center">
  646.                                 <i class="fas fa-check-circle text-green-500 mr-3 text-lg"></i>
  647.                                 <span id="evaluation-success-msg">Évaluation enregistrée avec succès.</span>
  648.                             </div>
  649.                         </div>
  650.                         <!-- Message d'erreur -->
  651.                         <div id="evaluation-error" class="hidden mt-4 p-4 rounded-xl bg-red-50 text-red-800 border border-red-200">
  652.                             <div class="flex items-center">
  653.                                 <i class="fas fa-exclamation-circle text-red-500 mr-3 text-lg"></i>
  654.                                 <span id="evaluation-error-msg">Erreur lors de l'enregistrement.</span>
  655.                             </div>
  656.                         </div>
  657.                     </div>
  658.                 </div>
  659.             {% endif %}
  660.             <!-- SECTION 5: Visite médicale et résultat final -->
  661.             {% if is_granted('ROLE_ENT') or is_granted('ROLE_JURY') or is_granted('ROLE_ADMIN') %}
  662.                 <div class="bg-emerald-50 rounded-2xl border border-emerald-200 p-6">
  663.                     <h3 class="text-lg font-semibold text-emerald-800 mb-5 flex items-center border-b border-emerald-200 pb-3">
  664.                         <div class="w-8 h-8 bg-emerald-200 rounded-lg flex items-center justify-center mr-3">
  665.                             <i class="fas fa-hospital text-emerald-700"></i>
  666.                         </div>
  667.                         VISITE MÉDICALE & Résultat final
  668.                     </h3>
  669.                     
  670.                     <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
  671.                         <div>
  672.                             <label class="block text-sm font-medium text-slate-700 mb-1">Date visite</label>
  673.                             {{ form_widget(form.visdate, {'attr': {'class': input_class}}) }}
  674.                         </div>
  675.                         <div>
  676.                             <label class="block text-sm font-medium text-slate-700 mb-1">Lieu visite</label>
  677.                             {{ form_widget(form.vislieu, {'attr': {'class': input_class, 'placeholder': 'Ex: Centre médical'}}) }}
  678.                         </div>
  679.                     </div>
  680.                     <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
  681.                         <div>
  682.                             <label class="block text-sm font-medium text-slate-700 mb-1">Statut visite</label>
  683.                             {{ form_widget(form.visstatut, {'attr': {'class': input_class}}) }}
  684.                         </div>
  685.                         <div>
  686.                             <label class="block text-sm font-medium text-slate-700 mb-1">Résultat final</label>
  687.                             {{ form_widget(form.resultat, {'attr': {'class': input_class}}) }}
  688.                         </div>
  689.                         <div>
  690.                             <label class="block text-sm font-medium text-slate-700 mb-1">Commentaire</label>
  691.                             {{ form_widget(form.viscom, {'attr': {'class': input_class, 'placeholder': 'Commentaire visite...'}}) }}
  692.                         </div>
  693.                     </div>
  694.                 </div>
  695.             {% endif %}
  696.             <!-- Boutons d'action -->
  697.             <div class="flex flex-col md:flex-row justify-between items-center gap-4 pt-6 border-t border-slate-200">
  698.                 <div class="text-sm text-slate-500">
  699.                     <i class="fas fa-info-circle mr-1"></i>
  700.                     Les champs marqués d'une <span class="text-red-500">*</span> sont obligatoires
  701.                 </div>
  702.                 
  703.                 <div class="flex gap-3">
  704.                     {% if app.user %}
  705.                         {# Vérification de l'âge pour afficher/masquer le bouton de soumission #}
  706.                         {% set age = app.user.datenaissance ? date().diff(date(app.user.datenaissance)).y : null %}
  707.                         
  708.                         {% if is_granted('ROLE_ADMIN') or is_granted('ROLE_ENT') or is_granted('ROLE_JURY') %}
  709.                             {# Les rôles admin/ent/jury peuvent toujours soumettre #}
  710.                             <button type="submit" class="px-8 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg flex items-center">
  711.                                 <i class="fas fa-paper-plane mr-2"></i>
  712.                                 Enregistrer l'évaluation
  713.                             </button>
  714.                         {% elseif age >= 16 and age <= 40 %}
  715.                             {# Candidat avec âge valide #}
  716.                             <button type="submit" class="px-8 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg flex items-center">
  717.                                 <i class="fas fa-paper-plane mr-2"></i>
  718.                                 Soumettre ma candidature
  719.                             </button>
  720.                         {% else %}
  721.                             {# Candidat avec âge invalide - bouton désactivé #}
  722.                             <button type="button" disabled class="px-8 py-2 bg-gray-400 text-white font-semibold rounded-lg cursor-not-allowed opacity-50 flex items-center" title="Vous ne remplissez pas les conditions d'âge">
  723.                                 <i class="fas fa-ban mr-2"></i>
  724.                                 Soumission impossible
  725.                             </button>
  726.                         {% endif %}
  727.                     {% else %}
  728.                         {% set redirectUrl = path('app_candidature_new', {
  729.                             'etablissement': app.request.get('etablissement'),
  730.                             'metier': app.request.get('metier')
  731.                         }) %}
  732.                         
  733.                         <a href="{{ path('app_login', {'redirect': redirectUrl}) }}" 
  734.                            class="px-8 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-700 hover:to-purple-700 transition transform hover:scale-105 shadow-lg flex items-center">
  735.                             <i class="fas fa-sign-in-alt mr-2"></i>
  736.                             Se connecter pour postuler
  737.                         </a>
  738.                     {% endif %}
  739.                 </div>
  740.             </div>
  741.             {{ form_end(form) }}
  742.         </div>
  743.     </div>
  744.     {% endif %}
  745. </div>
  746. <!-- JavaScript pour la gestion dynamique établissement/métier -->
  747. <script>
  748. document.addEventListener('DOMContentLoaded', function() {
  749.     const etablissementSelect = document.getElementById('candidature_etablissement');
  750.     const metierSelect = document.getElementById('candidature_metier');
  751.     const metierInfo = document.getElementById('metier-info');
  752.     const metierSecteur = document.getElementById('metier-secteur');
  753.     const metierNiveau = document.getElementById('metier-niveau');
  754.     const metierNbrPlace = document.getElementById('metier-nbrplace');
  755.     const metierDuree = document.getElementById('metier-duree');
  756.     const etablissementLoading = document.getElementById('etablissement-loading');
  757.     const metierLoading = document.getElementById('metier-loading');
  758.     
  759.     const currentMetierValue = metierSelect.value;
  760.     function loadMetiers(etablissementId, callback) {
  761.         if (!etablissementId) {
  762.             metierSelect.innerHTML = '<option value="">-- Choisissez d\'abord un établissement --</option>';
  763.             metierSelect.disabled = true;
  764.             metierInfo.classList.add('hidden');
  765.             if (callback) callback();
  766.             return;
  767.         }
  768.         metierLoading.classList.remove('hidden');
  769.         metierSelect.disabled = true;
  770.         fetch('/candidature/etablissement/' + etablissementId + '/metiers')
  771.             .then(response => response.json())
  772.             .then(data => {
  773.                 metierSelect.innerHTML = '<option value="">-- Sélectionnez un métier --</option>';
  774.                 
  775.                 const grouped = {};
  776.                 data.forEach(metier => {
  777.                     const secteur = metier.secteur_nom || 'Autres';
  778.                     if (!grouped[secteur]) {
  779.                         grouped[secteur] = [];
  780.                     }
  781.                     grouped[secteur].push(metier);
  782.                 });
  783.                 
  784.                 for (const [secteur, metiers] of Object.entries(grouped)) {
  785.                     const optgroup = document.createElement('optgroup');
  786.                     optgroup.label = secteur;
  787.                     
  788.                     metiers.sort((a, b) => a.nom.localeCompare(b.nom));
  789.                     metiers.forEach(metier => {
  790.                         const option = document.createElement('option');
  791.                         option.value = metier.id;
  792.                         option.dataset.secteur = metier.secteur_nom || '';
  793.                         option.dataset.niveau = metier.niveau || '';
  794.                         option.dataset.duree = metier.duree || '';
  795.                         option.dataset.nbrplace = metier.nbrplace || '';
  796.                         let label = metier.nom;
  797.                         if (metier.nbrplace) {
  798.                             label += ' (' + metier.nbrplace + ' places)';
  799.                         }
  800.                         option.textContent = label;
  801.                         optgroup.appendChild(option);
  802.                     });
  803.                     
  804.                     metierSelect.appendChild(optgroup);
  805.                 }
  806.                 
  807.                 const metierToSelect = currentMetierValue || '{{ app.request.get('metier')|default('') }}';
  808.                 
  809.                 if (metierToSelect) {
  810.                     const options = metierSelect.options;
  811.                     for (let i = 0; i < options.length; i++) {
  812.                         if (options[i].value == metierToSelect) {
  813.                             options[i].selected = true;
  814.                             updateMetierInfo(options[i]);
  815.                             break;
  816.                         }
  817.                     }
  818.                 }
  819.                 
  820.                 metierSelect.disabled = false;
  821.                 metierLoading.classList.add('hidden');
  822.                 
  823.                 if (callback) callback();
  824.             })
  825.             .catch(error => {
  826.                 console.error('Erreur:', error);
  827.                 metierSelect.disabled = false;
  828.                 metierLoading.classList.add('hidden');
  829.                 if (callback) callback();
  830.             });
  831.     }
  832.     function updateMetierInfo(selectedOption) {
  833.         if (!selectedOption || !selectedOption.value) {
  834.             metierInfo.classList.add('hidden');
  835.             return;
  836.         }
  837.         
  838.         metierSecteur.textContent = selectedOption.dataset.secteur || 'Non spécifié';
  839.         metierNiveau.textContent = selectedOption.dataset.niveau || 'Non spécifié';
  840.         metierDuree.textContent = selectedOption.dataset.duree || 'Non spécifié';
  841.         metierNbrPlace.textContent = selectedOption.dataset.nbrplace || 'Non spécifié';
  842.         metierInfo.classList.remove('hidden');
  843.     }
  844.     {% if app.request.get('etablissement') %}
  845.         const etablissementId = '{{ app.request.get('etablissement') }}';
  846.         if (etablissementId) {
  847.             setTimeout(function() {
  848.                 const options = etablissementSelect.options;
  849.                 for (let i = 0; i < options.length; i++) {
  850.                     if (options[i].value == etablissementId) {
  851.                         options[i].selected = true;
  852.                         break;
  853.                     }
  854.                 }
  855.                 loadMetiers(etablissementId);
  856.             }, 100);
  857.         }
  858.     {% else %}
  859.         if (etablissementSelect.value) {
  860.             loadMetiers(etablissementSelect.value);
  861.         } else {
  862.             metierSelect.innerHTML = '<option value="">-- Choisissez d\'abord un établissement --</option>';
  863.             metierSelect.disabled = true;
  864.         }
  865.     {% endif %}
  866.     etablissementSelect.addEventListener('change', function() {
  867.         loadMetiers(this.value);
  868.         metierInfo.classList.add('hidden');
  869.     });
  870.     metierSelect.addEventListener('change', function() {
  871.         const selectedOption = this.options[this.selectedIndex];
  872.         updateMetierInfo(selectedOption);
  873.     });
  874.     const form = document.getElementById('candidature-form');
  875.     form.addEventListener('submit', function(e) {
  876.         {% if not app.user %}
  877.         e.preventDefault();
  878.         alert('Vous devez être connecté pour soumettre une candidature.');
  879.         const redirectUrl = '/candidature/new?etablissement=' + etablissementSelect.value + '&metier=' + metierSelect.value;
  880.         window.location.href = '/login?redirect=' + encodeURIComponent(redirectUrl);
  881.         return false;
  882.         {% endif %}
  883.         {# Vérification supplémentaire en JavaScript #}
  884.         {% if app.user and app.user.datenaissance %}
  885.             {% set age = date().diff(date(app.user.datenaissance)).y %}
  886.             {% if age < 16 or age > 40 %}
  887.                 e.preventDefault();
  888.                 alert('Vous ne remplissez pas les conditions d\'âge pour candidater (16 à 40 ans).');
  889.                 return false;
  890.             {% endif %}
  891.         {% endif %}
  892.         if (!etablissementSelect.value) {
  893.             e.preventDefault();
  894.             alert('Veuillez sélectionner un établissement.');
  895.             return false;
  896.         }
  897.         
  898.         if (!metierSelect.value) {
  899.             e.preventDefault();
  900.             alert('Veuillez sélectionner un métier.');
  901.             return false;
  902.         }
  903.     });
  904. });
  905. </script>
  906. <!-- JavaScript pour la grille d'évaluation dynamique -->
  907. {% if (is_granted('ROLE_ENT') or is_granted('ROLE_ADMIN') or is_granted('ROLE_JURY')) and candidature is defined and candidature.id %}
  908. <script>
  909. document.addEventListener('DOMContentLoaded', function() {
  910.     const grilleSelect = document.getElementById('grille-select');
  911.     const criteresContainer = document.getElementById('criteres-container');
  912.     const criteresBody = document.getElementById('criteres-body');
  913.     const grilleName = document.getElementById('grille-name');
  914.     const scoreDisplay = document.getElementById('score-display');
  915.     const scoreBar = document.getElementById('score-bar');
  916.     const scoreLabel = document.getElementById('score-label');
  917.     const saveBtn = document.getElementById('save-evaluation-btn');
  918.     const commentaireField = document.getElementById('evaluation-commentaire');
  919.     const successDiv = document.getElementById('evaluation-success');
  920.     const errorDiv = document.getElementById('evaluation-error');
  921.     const successMsg = document.getElementById('evaluation-success-msg');
  922.     const errorMsg = document.getElementById('evaluation-error-msg');
  923.     if (!grilleSelect) return;
  924.     const candidatureId = {{ candidature.id }};
  925.     let currentGrilleId = null;
  926.     let noteMaxTotale = 0;
  927.     // Charger les critères quand on sélectionne une grille
  928.     grilleSelect.addEventListener('change', function() {
  929.         const grilleId = this.value;
  930.         if (!grilleId) {
  931.             criteresContainer.classList.add('hidden');
  932.             currentGrilleId = null;
  933.             return;
  934.         }
  935.         currentGrilleId = grilleId;
  936.         loadCriteres(grilleId);
  937.     });
  938.     function loadCriteres(grilleId) {
  939.         fetch('/evaluation/grille/' + grilleId + '/criteres')
  940.             .then(r => r.json())
  941.             .then(data => {
  942.                 criteresBody.innerHTML = '';
  943.                 noteMaxTotale = data.grille.noteMaxTotale;
  944.                 grilleName.textContent = data.grille.nom;
  945.                 data.criteres.forEach(critere => {
  946.                     const tr = document.createElement('tr');
  947.                     tr.className = 'hover:bg-slate-50/50 transition';
  948.                     tr.innerHTML = `
  949.                         <td class="px-5 py-3">
  950.                             <div>
  951.                                 <span class="font-medium text-slate-800 text-sm">${critere.nom}</span>
  952.                                 ${critere.description ? '<p class="text-xs text-slate-500 mt-0.5">' + critere.description + '</p>' : ''}
  953.                             </div>
  954.                         </td>
  955.                         <td class="px-5 py-3 text-center">
  956.                             <span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-purple-100 text-purple-800">${critere.noteMax}</span>
  957.                         </td>
  958.                         <td class="px-5 py-3 text-center">
  959.                             <input type="number" 
  960.                                 class="w-24 mx-auto px-3 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition text-center text-sm font-medium note-input"
  961.                                 data-critere-id="${critere.id}" 
  962.                                 data-note-max="${critere.noteMax}"
  963.                                 min="0" 
  964.                                 max="${critere.noteMax}" 
  965.                                 step="0.5" 
  966.                                 placeholder="..."
  967.                             >
  968.                         </td>
  969.                     `;
  970.                     criteresBody.appendChild(tr);
  971.                 });
  972.                 criteresContainer.classList.remove('hidden');
  973.                 updateScore();
  974.                 // Charger les notes existantes
  975.                 loadExistingEvaluation(grilleId);
  976.                 // Ajouter les écouteurs sur les champs de notes
  977.                 document.querySelectorAll('.note-input').forEach(input => {
  978.                     input.addEventListener('input', function() {
  979.                         const max = parseFloat(this.dataset.noteMax);
  980.                         let val = parseFloat(this.value);
  981.                         if (val > max) { this.value = max; }
  982.                         if (val < 0) { this.value = 0; }
  983.                         updateScore();
  984.                     });
  985.                 });
  986.             })
  987.             .catch(err => {
  988.                 console.error('Erreur chargement critères:', err);
  989.             });
  990.     }
  991.     function loadExistingEvaluation(grilleId) {
  992.         fetch('/evaluation/candidature/' + candidatureId + '/evaluation')
  993.             .then(r => r.json())
  994.             .then(evaluations => {
  995.                 const existing = evaluations.find(e => e.grilleId == grilleId);
  996.                 if (existing) {
  997.                     existing.notations.forEach(n => {
  998.                         const input = document.querySelector(`.note-input[data-critere-id="${n.critereId}"]`);
  999.                         if (input && n.note !== null) {
  1000.                             input.value = n.note;
  1001.                         }
  1002.                     });
  1003.                     if (existing.commentaire) {
  1004.                         commentaireField.value = existing.commentaire;
  1005.                     }
  1006.                     updateScore();
  1007.                 }
  1008.             })
  1009.             .catch(err => console.error('Erreur chargement évaluation:', err));
  1010.     }
  1011.     function updateScore() {
  1012.         let total = 0;
  1013.         document.querySelectorAll('.note-input').forEach(input => {
  1014.             const val = parseFloat(input.value);
  1015.             if (!isNaN(val)) total += val;
  1016.         });
  1017.         scoreDisplay.textContent = total + ' / ' + noteMaxTotale;
  1018.         const percentage = noteMaxTotale > 0 ? (total / noteMaxTotale * 100) : 0;
  1019.         scoreBar.style.width = percentage + '%';
  1020.         // Couleur de la barre de progression
  1021.         scoreBar.classList.remove('bg-green-500', 'bg-yellow-500', 'bg-red-500', 'bg-slate-400');
  1022.         if (percentage >= 70) {
  1023.             scoreBar.classList.add('bg-green-500');
  1024.             scoreLabel.textContent = 'Excellent';
  1025.             scoreLabel.className = 'text-xs mt-2 text-center text-green-600 font-medium';
  1026.         } else if (percentage >= 40) {
  1027.             scoreBar.classList.add('bg-yellow-500');
  1028.             scoreLabel.textContent = 'Moyen';
  1029.             scoreLabel.className = 'text-xs mt-2 text-center text-yellow-600 font-medium';
  1030.         } else if (total > 0) {
  1031.             scoreBar.classList.add('bg-red-500');
  1032.             scoreLabel.textContent = 'Faible';
  1033.             scoreLabel.className = 'text-xs mt-2 text-center text-red-600 font-medium';
  1034.         } else {
  1035.             scoreBar.classList.add('bg-slate-400');
  1036.             scoreLabel.textContent = '';
  1037.         }
  1038.     }
  1039.     // Enregistrer l'évaluation
  1040.     saveBtn.addEventListener('click', function() {
  1041.         if (!currentGrilleId) return;
  1042.         successDiv.classList.add('hidden');
  1043.         errorDiv.classList.add('hidden');
  1044.         const notations = [];
  1045.         document.querySelectorAll('.note-input').forEach(input => {
  1046.             notations.push({
  1047.                 critereId: parseInt(input.dataset.critereId),
  1048.                 note: input.value !== '' ? parseFloat(input.value) : null,
  1049.             });
  1050.         });
  1051.         const payload = {
  1052.             grilleId: parseInt(currentGrilleId),
  1053.             notations: notations,
  1054.             commentaire: commentaireField.value || null,
  1055.         };
  1056.         saveBtn.disabled = true;
  1057.         saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Enregistrement...';
  1058.         fetch('/evaluation/candidature/' + candidatureId + '/evaluer', {
  1059.             method: 'POST',
  1060.             headers: { 'Content-Type': 'application/json' },
  1061.             body: JSON.stringify(payload),
  1062.         })
  1063.         .then(r => r.json())
  1064.         .then(data => {
  1065.             if (data.success) {
  1066.                 successMsg.textContent = data.message;
  1067.                 successDiv.classList.remove('hidden');
  1068.                 errorDiv.classList.add('hidden');
  1069.                 // Mettre à jour l'affichage du score
  1070.                 scoreDisplay.textContent = data.scoreTotal + ' / ' + data.noteMaxTotale;
  1071.             } else {
  1072.                 errorMsg.textContent = data.error || 'Erreur inconnue.';
  1073.                 errorDiv.classList.remove('hidden');
  1074.                 successDiv.classList.add('hidden');
  1075.             }
  1076.         })
  1077.         .catch(err => {
  1078.             console.error('Erreur:', err);
  1079.             errorMsg.textContent = 'Erreur réseau lors de l\'enregistrement.';
  1080.             errorDiv.classList.remove('hidden');
  1081.             successDiv.classList.add('hidden');
  1082.         })
  1083.         .finally(() => {
  1084.             saveBtn.disabled = false;
  1085.             saveBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Enregistrer l\'évaluation';
  1086.         });
  1087.     });
  1088. });
  1089. </script>
  1090. {% endif %}
  1091. {% endblock %}