templates/partials/_etablissements_cards.html.twig line 1

  1. {% for etablissement in etablissements %}
  2. {# ============================================================
  3.    CARTE ÉTABLISSEMENT UNIQUE — Design premium centré
  4.    Ancien code de la grille multiple conservé ci-dessous en commentaire.
  5.    ANCIEN CODE (carte petite pour grille 3 colonnes) :
  6.    <div class="group bg-white rounded-xl border border-slate-200 p-5 ...">
  7.        ... (voir git history ou RESUME_MODIFICATIONS.txt)
  8.    </div>
  9.    ============================================================ #}
  10. <div class="w-full h-full flex flex-col etablissement-card">
  11.     {# --- En-tête blanc --- #}
  12.     <div class="bg-white border border-indigo-100 rounded-t-2xl px-4 py-4 text-center shadow-sm">
  13.         <div class="w-14 h-14 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-3">
  14.             <i class="fas fa-school text-2xl text-indigo-600"></i>
  15.         </div>
  16.         <h3 class="text-xl font-bold text-slate-900 leading-tight">{{ etablissement.nom }}</h3>
  17.         {% if etablissement.localite %}
  18.             <p class="text-xs text-slate-500 mt-2">
  19.                 <i class="fas fa-map-marker-alt mr-1 text-indigo-400"></i>
  20.                 {{ etablissement.localite.nom }}
  21.                 {% if etablissement.localite.directionRegionale %}
  22.                     — {{ etablissement.localite.directionRegionale.nom }}
  23.                 {% endif %}
  24.             </p>
  25.         {% endif %}
  26.     </div>
  27.     {# --- Corps principal --- #}
  28.     <div class="bg-white rounded-b-2xl border border-t-0 border-indigo-100 shadow-lg px-4 py-4 flex-grow flex flex-col">
  29.         {# --- Stats résumées --- #}
  30.         {% set totalPlaces = 0 %}
  31.         {% for em in etablissement.etablissementMetiers %}
  32.             {% set totalPlaces = totalPlaces + (em.nbrplace ?? 0) %}
  33.         {% endfor %}
  34.         <div class="flex justify-center gap-4 mb-4 py-2 bg-indigo-50 rounded-xl">
  35.             <div class="text-center">
  36.                 <div class="text-2xl font-bold text-indigo-600">{{ etablissement.etablissementMetiers|length }}</div>
  37.                 <div class="text-[10px] text-slate-500 mt-0.5 uppercase font-semibold">Métier(s)</div>
  38.             </div>
  39.             <div class="w-px bg-indigo-200"></div>
  40.             <div class="text-center">
  41.                 <div class="text-2xl font-bold text-green-600">{{ totalPlaces }}</div>
  42.                 <div class="text-[10px] text-slate-500 mt-0.5 uppercase font-semibold">Place(s)</div>
  43.             </div>
  44.         </div>
  45.         {# --- Liste des métiers avec bouton "Voir les options" --- #}
  46.         {% if etablissement.etablissementMetiers|length > 0 %}
  47.         <div class="mb-5 flex-grow">
  48.             <h4 class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">
  49.                 <i class="fas fa-briefcase mr-2 text-indigo-400"></i>Métiers disponibles
  50.             </h4>
  51.             <div class="space-y-2 h-32 overflow-y-auto pr-1 custom-scrollbar">
  52.                 {% for em in etablissement.etablissementMetiers %}
  53.                     {% if em.metier %}
  54.                     <div class="flex flex-col sm:flex-row sm:items-center justify-between w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl gap-2">
  55.                         <div class="flex items-center gap-3">
  56.                             <div class="w-7 h-7 bg-indigo-100 rounded-lg flex items-center justify-center flex-shrink-0">
  57.                                 <i class="fas fa-hard-hat text-indigo-600 text-[10px]"></i>
  58.                             </div>
  59.                             <div class="text-left leading-tight">
  60.                                 <span class="text-sm font-semibold text-slate-800 block">
  61.                                     {{ em.metier.nom }}
  62.                                 </span>
  63.                                 {% if em.nbrplace is defined and em.nbrplace %}
  64.                                 <span class="text-[10px] text-slate-500">{{ em.nbrplace }} place(s)</span>
  65.                                 {% endif %}
  66.                             </div>
  67.                         </div>
  68.                         {# Bouton "Voir les options" — ouvre le modal #}
  69.                         <button type="button"
  70.                                 onclick="ouvrirOptionsMetier('{{ em.metier.nom|e('html_attr') }}')"
  71.                                 class="text-[10px] font-semibold text-indigo-600 bg-indigo-100 hover:bg-indigo-600 hover:text-white px-2.5 py-1.5 rounded-full transition-all flex items-center gap-1 self-start sm:self-auto flex-shrink-0">
  72.                             <i class="fas fa-eye text-[9px]"></i> Voir les options
  73.                         </button>
  74.                     </div>
  75.                     {% endif %}
  76.                 {% endfor %}
  77.             </div>
  78.         </div>
  79.         {% else %}
  80.         <div class="mb-5 flex-grow">
  81.             <div class="h-32 flex items-center justify-center border-2 border-dashed border-slate-200 rounded-xl">
  82.                 <p class="text-xs text-slate-400">Aucun métier disponible</p>
  83.             </div>
  84.         </div>
  85.         {% endif %}
  86.         {# --- CTA principal --- #}
  87.         <div class="mt-auto pt-2">
  88.             <a href="{{ path('app_register') }}?etablissement={{ etablissement.id }}"
  89.                class="block w-full text-center bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white font-bold py-3 rounded-xl transition-all transform hover:-translate-y-0.5 hover:shadow-lg text-sm">
  90.                 <i class="fas fa-paper-plane mr-2"></i>
  91.                 Candidater ici
  92.             </a>
  93.         </div>
  94.     </div>
  95. </div>
  96. {% else %}
  97. <div class="col-span-full text-center py-12">
  98.     <div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4">
  99.         <i class="fas fa-school text-2xl text-slate-400"></i>
  100.     </div>
  101.     <p class="text-lg text-white font-semibold">Aucun établissement trouvé</p>
  102.     <p class="text-sm text-white/70">Vérifiez la configuration de l'ID établissement</p>
  103. </div>
  104. {% endfor %}
  105. {# ============================================================
  106.    MODAL "VOIR LES OPTIONS" — Affiché au clic sur un métier
  107.    Contenu extrait des documents officiels (images fournies).
  108.    ============================================================ #}
  109. <div id="modal-options-metier"
  110.      class="fixed inset-0 z-50 hidden overflow-y-auto"
  111.      role="dialog" aria-modal="true">
  112.     {# Overlay #}
  113.     <div class="fixed inset-0 bg-slate-900/70 backdrop-blur-sm"
  114.          onclick="fermerOptionsMetier()"></div>
  115.     {# Panneau #}
  116.     <div class="relative min-h-screen flex items-start justify-center pt-8 pb-12 px-4">
  117.         <div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-2xl overflow-hidden">
  118.             {# En-tête du modal #}
  119.             <div class="bg-gradient-to-r from-indigo-600 to-purple-600 px-6 py-4 flex items-center justify-between">
  120.                 <div>
  121.                     <p class="text-xs text-white/70 font-medium uppercase tracking-wider mb-0.5">Options du métier</p>
  122.                     <h3 id="modal-metier-titre" class="text-lg font-bold text-white"></h3>
  123.                 </div>
  124.                 <button onclick="fermerOptionsMetier()"
  125.                         class="w-8 h-8 bg-white/20 hover:bg-white/30 rounded-full flex items-center justify-center transition">
  126.                     <i class="fas fa-times text-white text-sm"></i>
  127.                 </button>
  128.             </div>
  129.             {# Bandeau info consultatif #}
  130.             <div class="bg-amber-50 border-b border-amber-100 px-6 py-2 flex items-center gap-2">
  131.                 <i class="fas fa-info-circle text-amber-500 text-sm"></i>
  132.                 <p class="text-xs text-amber-700">Ces informations sont fournies à titre consultatif.</p>
  133.             </div>
  134.             {# Corps : tableau des options #}
  135.             <div class="px-6 py-5 max-h-[70vh] overflow-y-auto">
  136.                 <div id="modal-options-contenu"></div>
  137.                 {# Message si métier non trouvé #}
  138.                 <div id="modal-options-vide" class="hidden text-center py-10">
  139.                     <i class="fas fa-search text-3xl text-slate-300 mb-3"></i>
  140.                     <p class="text-slate-500 text-sm">Aucune option détaillée disponible pour ce métier.</p>
  141.                 </div>
  142.             </div>
  143.             {# Pied du modal #}
  144.             <div class="px-6 py-4 border-t border-slate-100 flex justify-between items-center bg-slate-50">
  145.                 <p class="text-xs text-slate-400">
  146.                     <i class="fas fa-file-alt mr-1"></i>
  147.                     Certificat de Formation Qualifiante — DAIP 2026
  148.                 </p>
  149.                 <button onclick="fermerOptionsMetier()"
  150.                         class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold rounded-lg transition">
  151.                     Fermer
  152.                 </button>
  153.             </div>
  154.         </div>
  155.     </div>
  156. </div>
  157. {# ============================================================
  158.    DONNÉES DES OPTIONS — Extraites des documents officiels
  159.    Clé = nom du métier (doit correspondre au nom en base).
  160.    ============================================================ #}
  161. <script>
  162. const METIERS_OPTIONS = {
  163.     "COUTURE INDUSTRIELLE": {
  164.         description: "Certificat de Formation Qualifiante",
  165.         options: [
  166.             { nom: "CHEMISE / TUNIQUE CLASSIQUE HOMME/DAME", niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 20 },
  167.             { nom: "JEANS ET TENUE DE TRAVAIL",              niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 20 },
  168.             { nom: "JUPE, ROBE ET BUSTIER",                  niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 20 },
  169.             { nom: "PANTALON CLASSIQUE HOMME/DAME",          niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 20 },
  170.             { nom: "VESTE HOMME/DAME",                       niveau: "CEPE (20 ans - 40 ans)", duree: "4 mois", places: 20 },
  171.             { nom: "CHAPELIER MODISTE",                      niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 10 },
  172.             { nom: "T-SHIRT ET POLO",                        niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 20 },
  173.             { nom: "SAC ET PORTEFEUILLE",                    niveau: "CEPE (18 ans - 40 ans)", duree: "4 mois", places: 10 },
  174.         ]
  175.     },
  176.     "MODELISME": {
  177.         description: "Certificat de Formation Qualifiante",
  178.         options: [
  179.             { nom: "COUPE HOMME",             niveau: "BEPC (25 ans - 40 ans)", duree: "4 mois", places: 6 },
  180.             { nom: "COUPE DAME",              niveau: "BEPC (25 ans - 40 ans)", duree: "4 mois", places: 6 },
  181.             { nom: "COUPE VESTE HOMME/DAME",  niveau: "BEPC (25 ans - 40 ans)", duree: "4 mois", places: 6 },
  182.             { nom: "COUPE INDUSTRIELLE",      niveau: "BEPC (25 ans - 40 ans)", duree: "4 mois", places: 6 },
  183.         ]
  184.     },
  185.     // Support du pluriel
  186.     "METIERS DE LA MODE": {
  187.         description: "Certificat de Formation Qualifiante",
  188.         options: [
  189.             { nom: "AGENT DE NETTOYAGE ET DE MARQUAGE",       niveau: "CEPE (20 ans - 40 ans)", duree: "4 mois", places: 6 },
  190.             { nom: "AGENT CONTROLEUR QUALITE ET FINITION",    niveau: "CEPE (20 ans - 40 ans)", duree: "4 mois", places: 6 },
  191.         ]
  192.     },
  193.     // Support du singulier
  194.     "METIER DE LA MODE": {
  195.         description: "Certificat de Formation Qualifiante",
  196.         options: [
  197.             { nom: "AGENT DE NETTOYAGE ET DE MARQUAGE",       niveau: "CEPE (20 ans - 40 ans)", duree: "4 mois", places: 6 },
  198.             { nom: "AGENT CONTROLEUR QUALITE ET FINITION",    niveau: "CEPE (20 ans - 40 ans)", duree: "4 mois", places: 6 },
  199.         ]
  200.     },
  201.     "BRODERIE": {
  202.         description: "Certificat de Formation Qualifiante",
  203.         options: [
  204.             { nom: "BRODERIE SUR MACHINE AUTOMATISEE", niveau: "CEPE (18 ans - 40 ans)",                                        duree: "4 mois", places: 10 },
  205.             { nom: "GRAPHISTE BRODERIE",               niveau: "BEPC avec bonne notion en outil informatique (20 ans - 40 ans)", duree: "4 mois", places: 4  },
  206.         ]
  207.     },
  208.     "SERIGRAPHIE": {
  209.         description: "Certificat de Formation Qualifiante",
  210.         options: [
  211.             { nom: "SERIGRAPHE",                      niveau: "CEPE (18 ans - 40 ans)",                                        duree: "4 mois", places: 6 },
  212.             { nom: "GRAPHISTE IMPRESSION NUMERIQUE",  niveau: "BEPC avec bonne notion en outil informatique (20 ans - 40 ans)", duree: "4 mois", places: 4 },
  213.         ]
  214.     }
  215. };
  216. // ---- Recherche floue : tolère les majuscules/minuscules, espaces et accents ----
  217. function trouverMetier(nomBrut) {
  218.     // Supprimer les accents (ex: É -> E) et mettre en majuscules
  219.     const nomNorm = nomBrut.normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().toUpperCase();
  220.     
  221.     // Correspondance exacte
  222.     if (METIERS_OPTIONS[nomNorm]) return METIERS_OPTIONS[nomNorm];
  223.     
  224.     // Correspondance partielle
  225.     const cle = Object.keys(METIERS_OPTIONS).find(k => nomNorm.includes(k) || k.includes(nomNorm));
  226.     return cle ? METIERS_OPTIONS[cle] : null;
  227. }
  228. function ouvrirOptionsMetier(nomMetier) {
  229.     const modal   = document.getElementById('modal-options-metier');
  230.     const titre   = document.getElementById('modal-metier-titre');
  231.     const contenu = document.getElementById('modal-options-contenu');
  232.     const vide    = document.getElementById('modal-options-vide');
  233.     titre.textContent = nomMetier;
  234.     const data = trouverMetier(nomMetier);
  235.     if (data && data.options && data.options.length > 0) {
  236.         vide.classList.add('hidden');
  237.         contenu.classList.remove('hidden');
  238.         let html = `
  239.         <p class="text-xs text-slate-500 mb-4 font-medium">
  240.             <i class="fas fa-certificate mr-1 text-indigo-400"></i>${data.description}
  241.         </p>
  242.         <div class="overflow-x-auto rounded-xl border border-slate-200">
  243.             <table class="w-full text-sm">
  244.                 <thead>
  245.                     <tr class="bg-indigo-50 text-indigo-700 text-xs uppercase tracking-wider">
  246.                         <th class="px-4 py-3 text-left font-semibold">Option</th>
  247.                         <th class="px-4 py-3 text-left font-semibold">Niveau requis</th>
  248.                         <th class="px-3 py-3 text-center font-semibold">Durée</th>
  249.                         <th class="px-3 py-3 text-center font-semibold">Places</th>
  250.                     </tr>
  251.                 </thead>
  252.                 <tbody class="divide-y divide-slate-100">`;
  253.         data.options.forEach((opt, i) => {
  254.             html += `
  255.                     <tr class="${i % 2 === 0 ? 'bg-white' : 'bg-slate-50'} hover:bg-indigo-50/40 transition-colors">
  256.                         <td class="px-4 py-3 font-semibold text-slate-800">${opt.nom}</td>
  257.                         <td class="px-4 py-3 text-slate-600 text-xs">${opt.niveau}</td>
  258.                         <td class="px-3 py-3 text-center">
  259.                             <span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700">
  260.                                 <i class="fas fa-clock mr-1 text-[9px]"></i>${opt.duree}
  261.                             </span>
  262.                         </td>
  263.                         <td class="px-3 py-3 text-center">
  264.                             <span class="inline-flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold bg-green-100 text-green-700">${opt.places}</span>
  265.                         </td>
  266.                     </tr>`;
  267.         });
  268.         html += `</tbody></table></div>`;
  269.         contenu.innerHTML = html;
  270.     } else {
  271.         contenu.innerHTML = '';
  272.         vide.classList.remove('hidden');
  273.     }
  274.     modal.classList.remove('hidden');
  275.     document.body.style.overflow = 'hidden';
  276. }
  277. function fermerOptionsMetier() {
  278.     document.getElementById('modal-options-metier').classList.add('hidden');
  279.     document.body.style.overflow = '';
  280. }
  281. // Fermer avec Échap
  282. document.addEventListener('keydown', e => {
  283.     if (e.key === 'Escape') fermerOptionsMetier();
  284. });
  285. </script>