templates/munganyo/index.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}Munganyo - Actes Exécutifs de l'Union des Comores{% endblock %}
  3. {% block body %}
  4. <!-- Hero Section Munganyo -->
  5. <section class="relative bg-gradient-to-br from-[#006633] via-[#008040] to-[#00994d] text-white overflow-hidden">
  6. <div class="absolute inset-0 opacity-20">
  7. <div class="absolute top-20 left-10 w-64 h-64 bg-[#006633] rounded-full blur-3xl"></div>
  8. <div class="absolute bottom-20 right-10 w-96 h-96 bg-[#008040] rounded-full blur-3xl"></div>
  9. </div>
  10. <div class="container mx-auto px-4 py-12 md:py-20 relative z-10">
  11. <div class="max-w-4xl mx-auto text-center">
  12. <div class="inline-flex items-center bg-white/20 backdrop-blur-sm rounded-full px-4 md:px-5 py-1.5 md:py-2 mb-4 md:mb-6 border border-white/30">
  13. <i class="fas fa-file-alt mr-1 md:mr-2 text-sm md:text-base"></i>
  14. <span class="text-xs md:text-sm font-medium text-white">Plateforme des actes exécutifs</span>
  15. </div>
  16. <h1 class="text-3xl md:text-5xl lg:text-6xl font-bold mb-4 md:mb-6 text-white">
  17. Munganyo
  18. <span class="block text-white text-xl md:text-2xl lg:text-3xl">
  19. Actes du Gouvernement
  20. </span>
  21. </h1>
  22. <p class="text-base md:text-xl text-white/90 mb-6 md:mb-8 max-w-2xl mx-auto leading-relaxed hidden sm:block">
  23. La plateforme officielle de consultation des décrets, arrêtés et circulaires
  24. en vigueur de l'Union des Comores
  25. </p>
  26. <!-- Barre de recherche -->
  27. <div class="max-w-2xl mx-auto">
  28. <div class="relative">
  29. <i class="fas fa-search absolute left-3 md:left-5 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm md:text-lg"></i>
  30. <input type="text"
  31. id="searchInput"
  32. placeholder="Rechercher un décret, un arrêté, une circulaire..."
  33. class="w-full pl-9 md:pl-14 pr-24 md:pr-36 py-3 md:py-5 text-gray-800 text-sm md:text-lg rounded-full border-none shadow-2xl focus:outline-none focus:ring-4 focus:ring-[#006633]/50 focus:shadow-xl transition-all duration-300">
  34. <button id="searchBtn" class="absolute right-1 md:right-2 top-1/2 transform -translate-y-1/2 bg-gradient-to-r from-[#006633] to-[#008040] text-white px-3 md:px-6 py-1.5 md:py-2.5 rounded-full font-semibold hover:from-[#008040] hover:to-[#00994d] transition-all duration-300 shadow-md hover:shadow-lg text-sm md:text-base">
  35. <i class="fas fa-search md:mr-1"></i>
  36. <span class="hidden md:inline">Rechercher</span>
  37. </button>
  38. </div>
  39. </div>
  40. <!-- Filtres rapides -->
  41. <div class="mt-6 md:mt-10 flex flex-wrap justify-center gap-2 md:gap-3">
  42. <button onclick="applyQuickFilter('all')" class="quick-filter px-4 md:px-5 py-2 md:py-2.5 bg-gradient-to-r from-[#006633] to-[#008040] hover:from-[#004d26] hover:to-[#006633] rounded-full text-xs md:text-sm font-semibold text-white transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-1 md:gap-2">
  43. <i class="fas fa-th-large text-xs md:text-sm"></i>
  44. Tous les actes
  45. </button>
  46. <button onclick="applyQuickFilter('decret')" class="quick-filter px-4 md:px-5 py-2 md:py-2.5 bg-gradient-to-r from-[#006633] to-[#008040] hover:from-[#004d26] hover:to-[#006633] rounded-full text-xs md:text-sm font-semibold text-white transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-1 md:gap-2">
  47. <i class="fas fa-gavel text-xs md:text-sm"></i>
  48. Décrets
  49. </button>
  50. <button onclick="applyQuickFilter('arrete')" class="quick-filter px-4 md:px-5 py-2 md:py-2.5 bg-gradient-to-r from-[#006633] to-[#008040] hover:from-[#004d26] hover:to-[#006633] rounded-full text-xs md:text-sm font-semibold text-white transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-1 md:gap-2">
  51. <i class="fas fa-clipboard-list text-xs md:text-sm"></i>
  52. Arrêtés
  53. </button>
  54. <button onclick="applyQuickFilter('circulaire')" class="quick-filter px-4 md:px-5 py-2 md:py-2.5 bg-gradient-to-r from-[#006633] to-[#008040] hover:from-[#004d26] hover:to-[#006633] rounded-full text-xs md:text-sm font-semibold text-white transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-1 md:gap-2">
  55. <i class="fas fa-envelope text-xs md:text-sm"></i>
  56. Circulaires
  57. </button>
  58. <button onclick="applyQuickFilter('instruction')" class="quick-filter px-4 md:px-5 py-2 md:py-2.5 bg-gradient-to-r from-[#006633] to-[#008040] hover:from-[#004d26] hover:to-[#006633] rounded-full text-xs md:text-sm font-semibold text-white transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-1 md:gap-2">
  59. <i class="fas fa-tasks text-xs md:text-sm"></i>
  60. Instructions
  61. </button>
  62. </div>
  63. </div>
  64. </div>
  65. <div class="absolute bottom-0 left-0 right-0">
  66. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320" class="w-full">
  67. <path fill="#f3f4f6" fill-opacity="1" d="M0,96L48,112C96,128,192,160,288,160C384,160,480,128,576,122.7C672,117,768,139,864,154.7C960,171,1056,181,1152,170.7C1248,160,1344,128,1392,112L1440,96L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path>
  68. </svg>
  69. </div>
  70. </section>
  71. <!-- Section Statistiques -->
  72. <section class="py-12 md:py-16 bg-gray-50">
  73. <div class="container mx-auto px-4">
  74. <div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
  75. <div class="bg-white rounded-2xl shadow-lg p-4 md:p-6 text-center transform hover:-translate-y-2 transition-all">
  76. <div class="w-10 h-10 md:w-14 md:h-14 bg-[#006633]/10 rounded-full flex items-center justify-center mx-auto mb-2 md:mb-4">
  77. <i class="fas fa-file-signature text-xl md:text-2xl text-[#006633]"></i>
  78. </div>
  79. <div class="text-2xl md:text-3xl font-bold text-gray-800 mb-0.5 md:mb-1" id="totalActs">0</div>
  80. <div class="text-gray-600 font-medium text-xs md:text-base">Actes exécutifs</div>
  81. <div class="text-xs text-gray-400 mt-1 hidden md:block">Décrets, arrêtés, circulaires</div>
  82. </div>
  83. <div class="bg-white rounded-2xl shadow-lg p-4 md:p-6 text-center transform hover:-translate-y-2 transition-all">
  84. <div class="w-10 h-10 md:w-14 md:h-14 bg-[#006633]/10 rounded-full flex items-center justify-center mx-auto mb-2 md:mb-4">
  85. <i class="fas fa-gavel text-xl md:text-2xl text-[#006633]"></i>
  86. </div>
  87. <div class="text-2xl md:text-3xl font-bold text-gray-800 mb-0.5 md:mb-1" id="totalDecrets">0</div>
  88. <div class="text-gray-600 font-medium text-xs md:text-base">Décrets</div>
  89. <div class="text-xs text-gray-400 mt-1 hidden md:block">En vigueur</div>
  90. </div>
  91. <div class="bg-white rounded-2xl shadow-lg p-4 md:p-6 text-center transform hover:-translate-y-2 transition-all">
  92. <div class="w-10 h-10 md:w-14 md:h-14 bg-[#006633]/10 rounded-full flex items-center justify-center mx-auto mb-2 md:mb-4">
  93. <i class="fas fa-clipboard-list text-xl md:text-2xl text-[#006633]"></i>
  94. </div>
  95. <div class="text-2xl md:text-3xl font-bold text-gray-800 mb-0.5 md:mb-1" id="totalArretes">0</div>
  96. <div class="text-gray-600 font-medium text-xs md:text-base">Arrêtés</div>
  97. <div class="text-xs text-gray-400 mt-1 hidden md:block">Interministériels et ministériels</div>
  98. </div>
  99. <div class="bg-white rounded-2xl shadow-lg p-4 md:p-6 text-center transform hover:-translate-y-2 transition-all">
  100. <div class="w-10 h-10 md:w-14 md:h-14 bg-[#006633]/10 rounded-full flex items-center justify-center mx-auto mb-2 md:mb-4">
  101. <i class="fas fa-chart-line text-xl md:text-2xl text-[#006633]"></i>
  102. </div>
  103. <div class="text-2xl md:text-3xl font-bold text-gray-800 mb-0.5 md:mb-1" id="totalVigueur">0</div>
  104. <div class="text-gray-600 font-medium text-xs md:text-base">En vigueur</div>
  105. <div class="text-xs text-gray-400 mt-1 hidden md:block">Actes actuellement valides</div>
  106. </div>
  107. </div>
  108. </div>
  109. </section>
  110. <!-- Filtres avancés -->
  111. <section class="py-6 md:py-8 bg-white border-b">
  112. <div class="container mx-auto px-4">
  113. <div class="bg-gray-50 rounded-2xl p-4 md:p-6">
  114. <div class="flex flex-col md:flex-row md:flex-wrap md:items-center justify-between gap-3 md:gap-4">
  115. <div class="flex items-center space-x-2 md:space-x-3">
  116. <i class="fas fa-filter text-gray-400 text-sm md:text-base"></i>
  117. <span class="font-medium text-gray-700 text-sm md:text-base">Filtres :</span>
  118. </div>
  119. <div class="flex flex-wrap gap-2 md:gap-3">
  120. <select id="filterType" class="px-3 md:px-4 py-1.5 md:py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#006633] focus:border-[#006633] text-sm">
  121. <option value="">Tous les types</option>
  122. <option value="decret">Décrets</option>
  123. <option value="arrete">Arrêtés</option>
  124. <option value="circulaire">Circulaires</option>
  125. <option value="instruction">Instructions</option>
  126. </select>
  127. <select id="filterInstitution" class="px-3 md:px-4 py-1.5 md:py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#006633] focus:border-[#006633] text-sm">
  128. <option value="">Toutes les institutions</option>
  129. <option value="Présidence">Présidence</option>
  130. <option value="Finances">Ministère des Finances</option>
  131. <option value="Justice">Ministère de la Justice</option>
  132. <option value="Secrétariat">Secrétariat Général</option>
  133. </select>
  134. <select id="filterYear" class="px-3 md:px-4 py-1.5 md:py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#006633] focus:border-[#006633] text-sm">
  135. <option value="">Toutes les années</option>
  136. <option value="2026">2026</option>
  137. <option value="2025">2025</option>
  138. <option value="2024">2024</option>
  139. </select>
  140. <select id="filterStatus" class="px-3 md:px-4 py-1.5 md:py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#006633] focus:border-[#006633] text-sm">
  141. <option value="">Statut juridique</option>
  142. <option value="vigueur">En vigueur</option>
  143. <option value="modifie">Modifié</option>
  144. <option value="abroge">Abrogé</option>
  145. </select>
  146. <button id="applyFiltersBtn" class="bg-gradient-to-r from-[#006633] to-[#008040] text-white px-4 md:px-6 py-1.5 md:py-2 rounded-xl hover:from-[#004d26] hover:to-[#006633] transition text-sm">
  147. <i class="fas fa-search mr-1 md:mr-2"></i>Appliquer
  148. </button>
  149. </div>
  150. </div>
  151. </div>
  152. </div>
  153. </section>
  154. <!-- Section des résultats avec ID pour l'ancrage -->
  155. <section id="resultsSection" class="py-12 md:py-16 bg-gray-50 scroll-mt-20">
  156. <div class="container mx-auto px-4">
  157. <div class="flex flex-col md:flex-row md:justify-between md:items-center mb-6 md:mb-8 gap-3">
  158. <div>
  159. <h2 class="text-2xl md:text-3xl font-bold text-gray-800">Derniers actes publiés</h2>
  160. <p class="text-gray-600 text-sm md:text-base mt-1">Les décrets et arrêtés les plus récents du Journal Officiel</p>
  161. </div>
  162. <div class="flex space-x-2">
  163. <button id="tableViewBtn" class="px-3 md:px-4 py-1.5 md:py-2 bg-gradient-to-r from-[#006633] to-[#008040] text-white rounded-lg shadow-sm text-sm">
  164. <i class="fas fa-table"></i>
  165. </button>
  166. <button id="listViewBtn" class="px-3 md:px-4 py-1.5 md:py-2 bg-white rounded-lg shadow-sm hover:bg-gray-50 transition text-sm">
  167. <i class="fas fa-list"></i>
  168. </button>
  169. </div>
  170. </div>
  171. <!-- Bandeau de résultat de filtre -->
  172. <div id="filterResultBadge" class="mb-4 hidden">
  173. <div class="bg-blue-50 border-l-4 border-blue-500 rounded-lg p-3 flex items-center justify-between">
  174. <div class="flex items-center gap-2">
  175. <i class="fas fa-filter text-blue-500"></i>
  176. <span class="text-sm text-gray-700" id="filterResultText"></span>
  177. </div>
  178. <button onclick="resetFilters()" class="text-blue-600 hover:text-blue-800 text-sm font-medium">
  179. <i class="fas fa-times mr-1"></i>Effacer les filtres
  180. </button>
  181. </div>
  182. </div>
  183. <!-- Vue Tableau -->
  184. <div id="tableView" class="bg-white rounded-2xl shadow-lg overflow-hidden">
  185. <div class="overflow-x-auto">
  186. <table class="min-w-[800px] md:min-w-full divide-y divide-gray-200">
  187. <thead class="bg-gradient-to-r from-gray-50 to-gray-100">
  188. <tr>
  189. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Type</th>
  190. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Numéro</th>
  191. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Titre</th>
  192. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Institution</th>
  193. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Date</th>
  194. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Statut</th>
  195. <th class="px-4 md:px-6 py-3 md:py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Actions</th>
  196. </tr>
  197. </thead>
  198. <tbody id="actsTableBody" class="bg-white divide-y divide-gray-100">
  199. <tr>
  200. <td colspan="7" class="px-6 py-8 text-center text-gray-500">
  201. <i class="fas fa-spinner fa-spin text-2xl mb-2"></i>
  202. <p>Chargement des données...</p>
  203. </td>
  204. </tr>
  205. </tbody>
  206. </table>
  207. </div>
  208. </div>
  209. <!-- Vue Liste -->
  210. <div id="listView" style="display: none;" class="space-y-4">
  211. <div id="actsListBody" class="space-y-4">
  212. <!-- Les données seront insérées ici -->
  213. </div>
  214. </div>
  215. <div class="mt-6 bg-gray-50 px-4 md:px-6 py-3 md:py-4 border-t rounded-b-2xl">
  216. <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
  217. <div class="text-xs md:text-sm text-gray-600" id="paginationInfo">
  218. Affichage de <span id="startCount">0</span> à <span id="endCount">0</span> sur <span id="totalCount">0</span> actes
  219. </div>
  220. <div class="flex flex-wrap gap-1 md:gap-2" id="paginationControls">
  221. </div>
  222. </div>
  223. </div>
  224. </div>
  225. </section>
  226. <!-- Modal Détails -->
  227. <div id="detailModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden items-center justify-center p-4" style="display: none;">
  228. <div class="bg-white rounded-2xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
  229. <div class="sticky top-0 bg-white border-b px-6 py-4 flex justify-between items-center">
  230. <h3 class="text-xl font-bold text-gray-800">Détails de l'acte</h3>
  231. <button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
  232. <i class="fas fa-times text-2xl"></i>
  233. </button>
  234. </div>
  235. <div class="p-6" id="modalContent">
  236. <!-- Contenu dynamique -->
  237. </div>
  238. </div>
  239. </div>
  240. <!-- Section Alertes -->
  241. <section class="py-12 md:py-16 bg-gradient-to-r from-[#006633] to-[#008040] text-white">
  242. <div class="container mx-auto px-4">
  243. <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12 items-center">
  244. <div>
  245. <i class="fas fa-bell text-3xl md:text-4xl mb-3 md:mb-4 opacity-80"></i>
  246. <h2 class="text-2xl md:text-3xl font-bold mb-3 md:mb-4">Restez informé des nouveaux actes</h2>
  247. <p class="text-white/90 mb-4 md:mb-6 leading-relaxed text-sm md:text-base">
  248. Recevez une notification par email dès qu'un nouveau décret ou arrêté est publié.
  249. </p>
  250. <ul class="space-y-2 md:space-y-3 mb-6 md:mb-8">
  251. <li class="flex items-center text-sm md:text-base">
  252. <i class="fas fa-check-circle text-emerald-300 mr-2 md:mr-3"></i>
  253. <span>Alertes personnalisables par institution</span>
  254. </li>
  255. <li class="flex items-center text-sm md:text-base">
  256. <i class="fas fa-check-circle text-emerald-300 mr-2 md:mr-3"></i>
  257. <span>Notification en temps réel des publications</span>
  258. </li>
  259. </ul>
  260. <a href="#" class="inline-flex items-center bg-white text-[#006633] px-5 md:px-6 py-2.5 md:py-3 rounded-xl font-semibold hover:bg-gray-100 transition shadow-lg text-sm md:text-base">
  261. <i class="fas fa-envelope mr-2"></i>
  262. S'abonner aux alertes
  263. </a>
  264. </div>
  265. <div class="bg-white/10 backdrop-blur-sm rounded-2xl p-5 md:p-6">
  266. <h3 class="text-lg md:text-xl font-bold mb-3 md:mb-4">Dernières alertes envoyées</h3>
  267. <div class="space-y-3 md:space-y-4">
  268. <div class="flex items-start space-x-2 md:space-x-3">
  269. <div class="w-6 h-6 md:w-8 md:h-8 bg-emerald-500 rounded-full flex items-center justify-center flex-shrink-0">
  270. <i class="fas fa-gavel text-white text-xs"></i>
  271. </div>
  272. <div>
  273. <p class="text-xs md:text-sm font-medium">Nouveau décret publié</p>
  274. <p class="text-xs text-emerald-200">Décret n°2026-045/PR - 01 mars 2026</p>
  275. </div>
  276. </div>
  277. <div class="flex items-start space-x-2 md:space-x-3">
  278. <div class="w-6 h-6 md:w-8 md:h-8 bg-teal-500 rounded-full flex items-center justify-center flex-shrink-0">
  279. <i class="fas fa-file-alt text-white text-xs"></i>
  280. </div>
  281. <div>
  282. <p class="text-xs md:text-sm font-medium">Nouvel arrêté publié</p>
  283. <p class="text-xs text-emerald-200">Arrêté n°2026-089/MF - 25 février 2026</p>
  284. </div>
  285. </div>
  286. </div>
  287. </div>
  288. </div>
  289. </div>
  290. </section>
  291. <script>
  292. // Données
  293. let allActs = {{ acts|json_encode|raw }};
  294. let filteredActs = [...allActs];
  295. let currentView = 'table';
  296. let currentPage = 1;
  297. let itemsPerPage = 5;
  298. let activeFilters = { type: '', institution: '', year: '', status: '', search: '' };
  299. // Initialisation
  300. document.addEventListener('DOMContentLoaded', function() {
  301. updateStats();
  302. renderActs();
  303. // Événements
  304. document.getElementById('searchBtn').addEventListener('click', function() {
  305. performSearch();
  306. scrollToResults();
  307. });
  308. document.getElementById('searchInput').addEventListener('keypress', function(e) {
  309. if(e.key === 'Enter') {
  310. performSearch();
  311. scrollToResults();
  312. }
  313. });
  314. document.getElementById('applyFiltersBtn').addEventListener('click', function() {
  315. applyAdvancedFilters();
  316. scrollToResults();
  317. });
  318. document.getElementById('tableViewBtn').addEventListener('click', () => setView('table'));
  319. document.getElementById('listViewBtn').addEventListener('click', () => setView('list'));
  320. });
  321. // Fonction pour scroller vers les résultats
  322. function scrollToResults() {
  323. const resultsSection = document.getElementById('resultsSection');
  324. if(resultsSection) {
  325. const offset = 80;
  326. const elementPosition = resultsSection.getBoundingClientRect().top;
  327. const offsetPosition = elementPosition + window.pageYOffset - offset;
  328. window.scrollTo({
  329. top: offsetPosition,
  330. behavior: 'smooth'
  331. });
  332. }
  333. }
  334. // Mise à jour du bandeau de filtre
  335. function updateFilterBadge() {
  336. const badge = document.getElementById('filterResultBadge');
  337. const filterText = document.getElementById('filterResultText');
  338. let filters = [];
  339. if(activeFilters.search) filters.push(`Recherche: "${activeFilters.search}"`);
  340. if(activeFilters.type) {
  341. const typeNames = { decret: 'Décrets', arrete: 'Arrêtés', circulaire: 'Circulaires', instruction: 'Instructions' };
  342. filters.push(`Type: ${typeNames[activeFilters.type] || activeFilters.type}`);
  343. }
  344. if(activeFilters.institution) filters.push(`Institution: ${activeFilters.institution}`);
  345. if(activeFilters.year) filters.push(`Année: ${activeFilters.year}`);
  346. if(activeFilters.status) {
  347. const statusNames = { vigueur: 'En vigueur', modifie: 'Modifié', abroge: 'Abrogé' };
  348. filters.push(`Statut: ${statusNames[activeFilters.status] || activeFilters.status}`);
  349. }
  350. if(filters.length > 0) {
  351. filterText.innerHTML = `<i class="fas fa-chart-line mr-1"></i> Filtres actifs : ${filters.join(' • ')} - ${filteredActs.length} résultat(s) trouvé(s)`;
  352. badge.classList.remove('hidden');
  353. } else {
  354. badge.classList.add('hidden');
  355. }
  356. }
  357. // Réinitialiser les filtres
  358. function resetFilters() {
  359. document.getElementById('filterType').value = '';
  360. document.getElementById('filterInstitution').value = '';
  361. document.getElementById('filterYear').value = '';
  362. document.getElementById('filterStatus').value = '';
  363. document.getElementById('searchInput').value = '';
  364. activeFilters = { type: '', institution: '', year: '', status: '', search: '' };
  365. filteredActs = [...allActs];
  366. currentPage = 1;
  367. renderActs();
  368. updateStats();
  369. updateFilterBadge();
  370. scrollToResults();
  371. }
  372. // Recherche
  373. function performSearch() {
  374. const searchTerm = document.getElementById('searchInput').value.toLowerCase();
  375. activeFilters.search = searchTerm;
  376. if(searchTerm === '') {
  377. filteredActs = [...allActs];
  378. } else {
  379. filteredActs = allActs.filter(act => {
  380. return act.numero.toLowerCase().includes(searchTerm) ||
  381. act.titre.toLowerCase().includes(searchTerm) ||
  382. act.description.toLowerCase().includes(searchTerm);
  383. });
  384. }
  385. // Réappliquer les autres filtres
  386. applyCurrentFilters();
  387. }
  388. // Filtre rapide
  389. function applyQuickFilter(type) {
  390. document.getElementById('filterType').value = type === 'all' ? '' : type;
  391. activeFilters.type = type === 'all' ? '' : type;
  392. applyAdvancedFilters();
  393. }
  394. // Appliquer les filtres actuels
  395. function applyCurrentFilters() {
  396. let results = [...allActs];
  397. if(activeFilters.search) {
  398. results = results.filter(act => {
  399. return act.numero.toLowerCase().includes(activeFilters.search) ||
  400. act.titre.toLowerCase().includes(activeFilters.search) ||
  401. act.description.toLowerCase().includes(activeFilters.search);
  402. });
  403. }
  404. if(activeFilters.type) {
  405. results = results.filter(act => act.type === activeFilters.type);
  406. }
  407. if(activeFilters.institution) {
  408. results = results.filter(act => act.institution.includes(activeFilters.institution));
  409. }
  410. if(activeFilters.year) {
  411. results = results.filter(act => act.date.startsWith(activeFilters.year));
  412. }
  413. if(activeFilters.status) {
  414. results = results.filter(act => act.status === activeFilters.status);
  415. }
  416. filteredActs = results;
  417. currentPage = 1;
  418. renderActs();
  419. updateStats();
  420. updateFilterBadge();
  421. }
  422. // Filtres avancés
  423. function applyAdvancedFilters() {
  424. activeFilters.type = document.getElementById('filterType').value;
  425. activeFilters.institution = document.getElementById('filterInstitution').value;
  426. activeFilters.year = document.getElementById('filterYear').value;
  427. activeFilters.status = document.getElementById('filterStatus').value;
  428. applyCurrentFilters();
  429. }
  430. // Changer l'affichage
  431. function setView(view) {
  432. currentView = view;
  433. const tableView = document.getElementById('tableView');
  434. const listView = document.getElementById('listView');
  435. const tableViewBtn = document.getElementById('tableViewBtn');
  436. const listViewBtn = document.getElementById('listViewBtn');
  437. if(view === 'table') {
  438. tableView.style.display = 'block';
  439. listView.style.display = 'none';
  440. tableViewBtn.className = 'px-3 md:px-4 py-1.5 md:py-2 bg-gradient-to-r from-[#006633] to-[#008040] text-white rounded-lg shadow-sm text-sm';
  441. listViewBtn.className = 'px-3 md:px-4 py-1.5 md:py-2 bg-white rounded-lg shadow-sm hover:bg-gray-50 transition text-sm';
  442. } else {
  443. tableView.style.display = 'none';
  444. listView.style.display = 'block';
  445. listViewBtn.className = 'px-3 md:px-4 py-1.5 md:py-2 bg-gradient-to-r from-[#006633] to-[#008040] text-white rounded-lg shadow-sm text-sm';
  446. tableViewBtn.className = 'px-3 md:px-4 py-1.5 md:py-2 bg-white rounded-lg shadow-sm hover:bg-gray-50 transition text-sm';
  447. }
  448. renderActs();
  449. }
  450. // Rendu des actes
  451. function renderActs() {
  452. const start = (currentPage - 1) * itemsPerPage;
  453. const end = start + itemsPerPage;
  454. const pageActs = filteredActs.slice(start, end);
  455. const totalPages = Math.ceil(filteredActs.length / itemsPerPage);
  456. // Rendu tableau
  457. const tableBody = document.getElementById('actsTableBody');
  458. if(pageActs.length === 0) {
  459. tableBody.innerHTML = `
  460. <tr>
  461. <td colspan="7" class="px-6 py-8 text-center text-gray-500">
  462. <i class="fas fa-search text-2xl mb-2"></i>
  463. <p>Aucun acte trouvé</p>
  464. <p class="text-sm mt-2">Essayez de modifier vos critères de recherche</p>
  465. </td>
  466. </tr>
  467. `;
  468. } else {
  469. tableBody.innerHTML = pageActs.map(act => `
  470. <tr class="hover:bg-gray-50 transition cursor-pointer">
  471. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap">
  472. <span class="px-2 md:px-3 py-0.5 md:py-1 text-xs font-semibold rounded-full bg-[#006633]/10 text-[#006633]">
  473. <i class="fas ${act.typeIcon} mr-1"></i> ${act.typeLabel}
  474. </span>
  475. </td>
  476. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap font-mono text-xs md:text-sm font-semibold text-gray-800">
  477. ${act.numero}
  478. </td>
  479. <td class="px-4 md:px-6 py-3 md:py-4">
  480. <div class="text-xs md:text-sm font-medium text-gray-900">${act.titre}</div>
  481. <div class="text-xs text-gray-500 mt-1 hidden md:block">${act.description}</div>
  482. </td>
  483. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap">
  484. <div class="text-xs md:text-sm text-gray-700">${act.institution}</div>
  485. </td>
  486. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap">
  487. <div class="text-xs md:text-sm text-gray-700">${act.dateFormatted}</div>
  488. </td>
  489. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap">
  490. <span class="px-2 py-0.5 md:py-1 text-xs font-semibold rounded-full bg-green-100 text-green-700">
  491. <i class="fas ${act.statusIcon} mr-1"></i> ${act.statusLabel}
  492. </span>
  493. </td>
  494. <td class="px-4 md:px-6 py-3 md:py-4 whitespace-nowrap">
  495. <div class="flex space-x-2">
  496. <button onclick="showDetails(${act.id})" class="text-[#006633] hover:text-[#008040]">
  497. <i class="fas fa-eye"></i>
  498. </button>
  499. <button onclick="generatePDF(${act.id})" class="text-gray-600 hover:text-gray-800">
  500. <i class="fas fa-download"></i>
  501. </button>
  502. </div>
  503. </td>
  504. </tr>
  505. `).join('');
  506. }
  507. // Rendu liste
  508. const listBody = document.getElementById('actsListBody');
  509. if(pageActs.length === 0) {
  510. listBody.innerHTML = `
  511. <div class="bg-white rounded-2xl shadow-lg p-8 text-center text-gray-500">
  512. <i class="fas fa-search text-3xl mb-3"></i>
  513. <p>Aucun acte trouvé</p>
  514. <p class="text-sm mt-2">Essayez de modifier vos critères de recherche</p>
  515. </div>
  516. `;
  517. } else {
  518. listBody.innerHTML = pageActs.map(act => `
  519. <div class="bg-white rounded-2xl shadow-lg p-6 hover:shadow-xl transition cursor-pointer">
  520. <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
  521. <div class="flex-1">
  522. <div class="flex items-center gap-2 mb-3">
  523. <span class="px-3 py-1 text-xs font-semibold rounded-full bg-[#006633]/10 text-[#006633]">
  524. <i class="fas ${act.typeIcon} mr-1"></i> ${act.typeLabel}
  525. </span>
  526. <span class="px-3 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-700">
  527. <i class="fas ${act.statusIcon} mr-1"></i> ${act.statusLabel}
  528. </span>
  529. </div>
  530. <h3 class="text-lg font-bold text-gray-800 mb-2">${act.titre}</h3>
  531. <p class="text-gray-600 text-sm mb-3">${act.description}</p>
  532. <div class="flex flex-wrap gap-4 text-sm text-gray-500">
  533. <span><i class="fas fa-hashtag mr-1"></i> ${act.numero}</span>
  534. <span><i class="fas fa-building mr-1"></i> ${act.institution}</span>
  535. <span><i class="fas fa-calendar mr-1"></i> ${act.dateFormatted}</span>
  536. </div>
  537. </div>
  538. <div class="flex gap-2">
  539. <button onclick="showDetails(${act.id})" class="px-4 py-2 bg-[#006633]/10 text-[#006633] rounded-lg hover:bg-[#006633]/20 transition">
  540. <i class="fas fa-eye mr-1"></i> Voir
  541. </button>
  542. <button onclick="generatePDF(${act.id})" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition">
  543. <i class="fas fa-download mr-1"></i> PDF
  544. </button>
  545. </div>
  546. </div>
  547. </div>
  548. `).join('');
  549. }
  550. // Pagination
  551. document.getElementById('startCount').textContent = filteredActs.length === 0 ? 0 : start + 1;
  552. document.getElementById('endCount').textContent = Math.min(end, filteredActs.length);
  553. document.getElementById('totalCount').textContent = filteredActs.length;
  554. const paginationDiv = document.getElementById('paginationControls');
  555. if(totalPages > 1) {
  556. let html = '';
  557. html += `<button onclick="changePage(${currentPage - 1})" class="px-2 md:px-3 py-1 border rounded-lg hover:bg-gray-100 transition" ${currentPage === 1 ? 'disabled style="opacity:0.5"' : ''}>Précédent</button>`;
  558. let startPage = Math.max(1, currentPage - 2);
  559. let endPage = Math.min(totalPages, startPage + 4);
  560. startPage = Math.max(1, endPage - 4);
  561. for(let i = startPage; i <= endPage; i++) {
  562. html += `<button onclick="changePage(${i})" class="px-3 md:px-4 py-1 ${currentPage === i ? 'bg-gradient-to-r from-[#006633] to-[#008040] text-white' : 'border hover:bg-gray-100'} rounded-lg text-sm">${i}</button>`;
  563. }
  564. if(endPage < totalPages) {
  565. html += `<span class="px-2">...</span>`;
  566. html += `<button onclick="changePage(${totalPages})" class="px-3 md:px-4 py-1 border rounded-lg hover:bg-gray-100 text-sm">${totalPages}</button>`;
  567. }
  568. html += `<button onclick="changePage(${currentPage + 1})" class="px-2 md:px-3 py-1 border rounded-lg hover:bg-gray-100 transition" ${currentPage === totalPages ? 'disabled style="opacity:0.5"' : ''}>Suivant</button>`;
  569. paginationDiv.innerHTML = html;
  570. } else {
  571. paginationDiv.innerHTML = '';
  572. }
  573. }
  574. // Changer de page
  575. function changePage(page) {
  576. const totalPages = Math.ceil(filteredActs.length / itemsPerPage);
  577. if(page < 1 || page > totalPages) return;
  578. currentPage = page;
  579. renderActs();
  580. }
  581. // Afficher les détails
  582. function showDetails(id) {
  583. const act = allActs.find(a => a.id === id);
  584. if(!act) return;
  585. const modalContent = document.getElementById('modalContent');
  586. modalContent.innerHTML = `
  587. <div class="space-y-6">
  588. <div class="flex items-center gap-3 pb-4 border-b">
  589. <div class="w-16 h-16 bg-[#006633]/10 rounded-2xl flex items-center justify-center">
  590. <i class="fas ${act.typeIcon} text-3xl text-[#006633]"></i>
  591. </div>
  592. <div>
  593. <div class="flex items-center gap-2 mb-2">
  594. <span class="px-3 py-1 text-xs font-semibold rounded-full bg-[#006633]/10 text-[#006633]">
  595. ${act.typeLabel}
  596. </span>
  597. <span class="px-3 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-700">
  598. ${act.statusLabel}
  599. </span>
  600. </div>
  601. <h2 class="text-2xl font-bold text-gray-800">${act.titre}</h2>
  602. </div>
  603. </div>
  604. <div class="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-gray-50 rounded-xl">
  605. <div>
  606. <p class="text-sm text-gray-500">Numéro</p>
  607. <p class="font-mono font-semibold text-gray-800">${act.numero}</p>
  608. </div>
  609. <div>
  610. <p class="text-sm text-gray-500">Date de publication</p>
  611. <p class="font-semibold text-gray-800">${act.dateFormatted}</p>
  612. </div>
  613. <div>
  614. <p class="text-sm text-gray-500">Institution</p>
  615. <p class="font-semibold text-gray-800">${act.institution}</p>
  616. <p class="text-sm text-gray-500">${act.institutionDetail}</p>
  617. </div>
  618. <div>
  619. <p class="text-sm text-gray-500">Statut juridique</p>
  620. <p class="font-semibold text-green-700">${act.statusLabel}</p>
  621. </div>
  622. </div>
  623. <div>
  624. <h4 class="font-bold text-gray-800 mb-3">Contenu de l'acte</h4>
  625. <div class="bg-gray-50 rounded-xl p-4">
  626. <pre class="whitespace-pre-wrap font-sans text-sm text-gray-700">${act.content}</pre>
  627. </div>
  628. </div>
  629. <div class="flex gap-3 pt-4 border-t">
  630. <button onclick="generatePDF(${act.id}); closeModal();" class="flex-1 px-6 py-3 bg-gradient-to-r from-[#006633] to-[#008040] text-white rounded-xl font-semibold hover:from-[#004d26] hover:to-[#006633] transition">
  631. <i class="fas fa-download mr-2"></i>Télécharger en PDF
  632. </button>
  633. <button onclick="closeModal()" class="px-6 py-3 border border-gray-300 rounded-xl hover:bg-gray-50 transition">
  634. Fermer
  635. </button>
  636. </div>
  637. </div>
  638. `;
  639. document.getElementById('detailModal').style.display = 'flex';
  640. }
  641. // Fermer le modal
  642. function closeModal() {
  643. document.getElementById('detailModal').style.display = 'none';
  644. }
  645. // Génération PDF
  646. function generatePDF(id) {
  647. const act = allActs.find(a => a.id === id);
  648. if(act) {
  649. const content = `${act.typeLabel.toUpperCase()} N°${act.numero}\n\n${act.titre}\n\n${act.content}\n\n---\nPublié le ${act.dateFormatted}\nInstitution: ${act.institution} - ${act.institutionDetail}\nStatut: ${act.statusLabel}`;
  650. const blob = new Blob([content], {type: 'application/pdf'});
  651. const url = URL.createObjectURL(blob);
  652. const a = document.createElement('a');
  653. a.href = url;
  654. a.download = `${act.typeLabel}_${act.numero}.pdf`;
  655. document.body.appendChild(a);
  656. a.click();
  657. document.body.removeChild(a);
  658. URL.revokeObjectURL(url);
  659. // Notification légère
  660. const notification = document.createElement('div');
  661. notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50';
  662. notification.innerHTML = `<i class="fas fa-check-circle mr-2"></i>Téléchargement du PDF démarré`;
  663. document.body.appendChild(notification);
  664. setTimeout(() => notification.remove(), 3000);
  665. }
  666. }
  667. // Mettre à jour les statistiques
  668. function updateStats() {
  669. const total = allActs.length;
  670. const decrets = allActs.filter(a => a.type === 'decret').length;
  671. const arretes = allActs.filter(a => a.type === 'arrete').length;
  672. const enVigueur = allActs.filter(a => a.status === 'vigueur').length;
  673. document.getElementById('totalActs').textContent = total;
  674. document.getElementById('totalDecrets').textContent = decrets;
  675. document.getElementById('totalArretes').textContent = arretes;
  676. document.getElementById('totalVigueur').textContent = enVigueur;
  677. }
  678. // Fermer le modal en cliquant en dehors
  679. document.getElementById('detailModal').addEventListener('click', function(e) {
  680. if(e.target === this) {
  681. closeModal();
  682. }
  683. });
  684. </script>
  685. {% endblock %}