2
0

cms.proweb-dossiers.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <?php
  2. $dateData = get::jsonDateDataExcelProwebDossiers();
  3. $date = ($dateData != NULL) ? " (au " . core::convertDate($dateData, FALSE) . ")" : "";
  4. $jsonTarget = "/json.php?file=dossiers-proweb";
  5. if (debug::isFile("debug")) {
  6. debug::log(debug::getBadge($jsonTarget, "OUVRIR LE JSON : " . $jsonTarget), "JSON chargé en arrière plan");
  7. }
  8. ?>
  9. <header class="d-flex flex-column flex-md-row align-items-md-center p-3 bg-light ">
  10. <h2 class="bd-title" id="content">
  11. <span>Proweb : Liste des dossiers<?php echo $date ?></span>
  12. </h2>
  13. <?php if (access::ifAccesss("proweb-dossiers-upload")) { ?>
  14. <div class="fix-container-button-nav">
  15. <a href="proweb-dossiers-upload.html"><button type="submit" class="btn btn-outline-success btn-sm"><?php icon::getFont(["icon" => "bi bi-file-earmark-plus"]) ?> Importer un fichier Excel</button></a>
  16. <a href="<?= prowebDossiers::getLinkProweb() ?>" target="_blank"><button type="submit" class="btn btn-outline-secondary btn-sm"><?php icon::getFont(["icon" => "bi bi-link-45deg"]) ?> Exporter tous les dossiers depuis Proweb</button></a>
  17. </div>
  18. <?php } ?>
  19. </header>
  20. <?php
  21. echo core::filAriane(array(
  22. "current" => "Liste des dossiers",
  23. "arbo" => array(
  24. "Proweb" => NULL,
  25. "Liste des dossiers" => "proweb-dossiers.html"
  26. ),
  27. "refresh-json" => "dossiers-proweb"
  28. ));
  29. ?>
  30. <!-- Panel de filtres avancés avec checkboxes multi-sélection -->
  31. <div class="card mb-3" id="advancedFiltersPanel">
  32. <div class="card-header d-flex justify-content-between align-items-center" style="cursor: pointer;" onclick="toggleAdvancedFilters()">
  33. <span><i class="bi bi-funnel"></i> Filtres avancés (sélection multiple)</span>
  34. <i class="bi bi-chevron-up" id="advancedFiltersIcon"></i>
  35. </div>
  36. <div class="card-body" id="advancedFiltersBody">
  37. <div class="row">
  38. <div class="col-lg-2 col-md-4 mb-2">
  39. <label class="form-label fw-bold">Année</label>
  40. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-exe_ref" placeholder="Rechercher..." oninput="filterCheckboxList('exe_ref')">
  41. <div class="filter-checkbox-container" id="filter-exe_ref"><small class="text-muted">Chargement...</small></div>
  42. </div>
  43. <div class="col-lg-2 col-md-4 mb-2">
  44. <label class="form-label fw-bold">Catégorie</label>
  45. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-catprest_lib" placeholder="Rechercher..." oninput="filterCheckboxList('catprest_lib')">
  46. <div class="filter-checkbox-container" id="filter-catprest_lib"><small class="text-muted">Chargement...</small></div>
  47. </div>
  48. <div class="col-lg-2 col-md-4 mb-2">
  49. <label class="form-label fw-bold">Prestation</label>
  50. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_lib" placeholder="Rechercher..." oninput="filterCheckboxList('prest_lib')">
  51. <div class="filter-checkbox-container" id="filter-prest_lib"><small class="text-muted">Chargement...</small></div>
  52. </div>
  53. <div class="col-lg-2 col-md-4 mb-2">
  54. <label class="form-label fw-bold">Etat</label>
  55. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-etdoss_lib" placeholder="Rechercher..." oninput="filterCheckboxList('etdoss_lib')">
  56. <div class="filter-checkbox-container" id="filter-etdoss_lib"><small class="text-muted">Chargement...</small></div>
  57. </div>
  58. <div class="col-lg-2 col-md-4 mb-2">
  59. <label class="form-label fw-bold">Commission</label>
  60. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-comm_ref" placeholder="Rechercher..." oninput="filterCheckboxList('comm_ref')">
  61. <div class="filter-checkbox-container" id="filter-comm_ref"><small class="text-muted">Chargement...</small></div>
  62. </div>
  63. <div class="col-lg-2 col-md-4 mb-2">
  64. <label class="form-label fw-bold">Clôturé</label>
  65. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_closed" placeholder="Rechercher..." oninput="filterCheckboxList('prest_closed')">
  66. <div class="filter-checkbox-container" id="filter-prest_closed"><small class="text-muted">Chargement...</small></div>
  67. </div>
  68. </div>
  69. <div class="row">
  70. <div class="col-lg-2 col-md-4 mb-2">
  71. <label class="form-label fw-bold">Complément</label>
  72. <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_lib2" placeholder="Rechercher..." oninput="filterCheckboxList('prest_lib2')">
  73. <div class="filter-checkbox-container" id="filter-prest_lib2"><small class="text-muted">Chargement...</small></div>
  74. </div>
  75. </div>
  76. <div class="mt-2">
  77. <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearAllAdvancedFilters()">
  78. <i class="bi bi-x-circle"></i> Réinitialiser les filtres
  79. </button>
  80. <span class="ms-3 text-muted" id="filterInfo"></span>
  81. </div>
  82. </div>
  83. </div>
  84. <style>
  85. .filter-checkbox-container {
  86. max-height: 150px;
  87. overflow-y: auto;
  88. border: 1px solid #dee2e6;
  89. border-radius: 4px;
  90. padding: 8px;
  91. background: #fff;
  92. }
  93. .filter-checkbox-container .form-check {
  94. margin-bottom: 2px;
  95. }
  96. .filter-checkbox-container .form-check-label {
  97. font-size: 0.85rem;
  98. cursor: pointer;
  99. }
  100. .filter-checkbox-container .form-check-input {
  101. cursor: pointer;
  102. }
  103. .filter-checkbox-container .filter-option-disabled {
  104. opacity: 0.5;
  105. text-decoration: line-through;
  106. }
  107. .filter-checkbox-container .filter-option-count {
  108. font-size: 0.75rem;
  109. color: #6c757d;
  110. margin-left: 4px;
  111. }
  112. #advancedFiltersPanel .card-header:hover {
  113. background-color: #f8f9fa;
  114. }
  115. .filter-search-input {
  116. font-size: 0.8rem;
  117. }
  118. .filter-search-input::placeholder {
  119. font-style: italic;
  120. color: #adb5bd;
  121. }
  122. .filter-checkbox-hidden {
  123. display: none !important;
  124. }
  125. </style>
  126. <div>
  127. <table
  128. id="ProWebDossiers"
  129. class="table-striped table-hover table-sm"
  130. data-sort-name="hismo_date_crea"
  131. data-sort-order="desc"
  132. data-page-size="50"
  133. data-show-footer="true"
  134. data-toggle="table"
  135. data-buttons-align="left"
  136. data-pagination="true"
  137. data-filter-control="true"
  138. data-flat="true"
  139. data-show-export="true"
  140. data-show-columns-toggle-all="true"
  141. data-page-list="[50, 100, 250, 500, 750, 1000]"
  142. data-url="<?php echo $jsonTarget ?>">
  143. <thead>
  144. <tr>
  145. <th data-sortable="true" data-field="exe_ref" data-filter-control="select">Année</th>
  146. <th data-sortable="true" data-field="hismo_date_crea" data-filter-control="select">Création</th>
  147. <th data-sortable="true" data-field="prest_lib" data-filter-control="input">Prestation</th>
  148. <th data-sortable="true" data-field="prest_lib2" data-filter-control="select">Complément</th>
  149. <th data-sortable="true" data-field="catprest_lib" data-filter-control="select">Catégorie</th>
  150. <th data-sortable="true" data-field="comm_ref" data-filter-control="select">Commission</th>
  151. <th data-sortable="true" data-field="prest_closed" data-filter-control="select" data-formatter="clotureFormatter">Clôturé</th>
  152. <th data-sortable="true" data-field="etdoss_lib" data-filter-control="select">Etat</th>
  153. <th data-sortable="true" data-field="od_matricule" data-filter-control="input">Matricule</th>
  154. <th data-sortable="true" data-field="nomComplet" data-filter-control="input" data-formatter="concatNomPrenom">Prénom Nom</th>
  155. <th data-sortable="true" data-field="doss_nb_inscrit" data-filter-control="select" data-footer-formatter="totalInscrits">Nb. Inscrits</th>
  156. <th data-sortable="true" data-formatter="dataFormatter" data-field="doss_partce" data-filter-control="input" data-footer-formatter="prixCSE">Part CSE</th>
  157. <th data-sortable="true" data-formatter="dataFormatter" data-field="doss_partod" data-filter-control="input" data-footer-formatter="prixSalarie">Part Salarié</th>
  158. <th data-sortable="true" data-formatter="dataFormatter" data-field="doss_montant_total" data-filter-control="input" data-footer-formatter="prixTotal">Total</th>
  159. </tr>
  160. </thead>
  161. </table>
  162. </div>
  163. <script>
  164. let euro = Intl.NumberFormat('de-DE', {
  165. style: 'currency',
  166. currency: 'EUR',
  167. });
  168. // Données originales et filtrées
  169. let originalData = [];
  170. let currentFilters = {};
  171. // Champs pour les filtres avancés avec checkboxes
  172. const advancedFilterFields = ['exe_ref', 'catprest_lib', 'prest_lib', 'etdoss_lib', 'comm_ref', 'prest_closed', 'prest_lib2'];
  173. function dataFormatter(value) {
  174. return euro.format(value);
  175. }
  176. function totalInscrits(data) {
  177. var total = 0;
  178. data.forEach(function(row) {
  179. var nb = parseFloat(row.doss_nb_inscrit);
  180. if (!isNaN(nb)) {
  181. total += nb;
  182. }
  183. });
  184. return total;
  185. }
  186. function prixCSE(data) {
  187. var total = 0;
  188. data.forEach(function(row) {
  189. total += parseFloat(row.doss_partce);
  190. });
  191. return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
  192. }
  193. function prixSalarie(data) {
  194. var total = 0;
  195. data.forEach(function(row) {
  196. total += parseFloat(row.doss_partod);
  197. });
  198. return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
  199. }
  200. function prixTotal(data) {
  201. var total = 0;
  202. data.forEach(function(row) {
  203. total += parseFloat(row.doss_montant_total);
  204. });
  205. return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
  206. }
  207. function concatNomPrenom(value, row, index) {
  208. return row.od_prenom + ' ' + row.od_nom;
  209. }
  210. function clotureFormatter(value) {
  211. return value == 1 ? "Clôturé" : "Ouvert";
  212. }
  213. function subventionFormatter(value) {
  214. switch (value) {
  215. case "O":
  216. return "Subventionné"
  217. break;
  218. case "N":
  219. return "Non-subventionné"
  220. break;
  221. default:
  222. return "-"
  223. break;
  224. }
  225. }
  226. // Toggle du panneau de filtres avancés
  227. function toggleAdvancedFilters() {
  228. const body = document.getElementById('advancedFiltersBody');
  229. const icon = document.getElementById('advancedFiltersIcon');
  230. if (body.style.display === 'none') {
  231. body.style.display = 'block';
  232. icon.className = 'bi bi-chevron-up';
  233. } else {
  234. body.style.display = 'none';
  235. icon.className = 'bi bi-chevron-down';
  236. }
  237. }
  238. // Filtrer la liste des checkboxes par recherche textuelle
  239. function filterCheckboxList(field) {
  240. const searchInput = document.getElementById(`search-${field}`);
  241. const container = document.getElementById(`filter-${field}`);
  242. if (!searchInput || !container) return;
  243. const searchText = searchInput.value.toLowerCase().trim();
  244. const checkboxes = container.querySelectorAll('.form-check');
  245. checkboxes.forEach(checkDiv => {
  246. const label = checkDiv.querySelector('.form-check-label');
  247. if (label) {
  248. // Exclure le compteur entre parenthèses de la recherche
  249. const countSpan = label.querySelector('.filter-option-count');
  250. let labelText = label.textContent.toLowerCase();
  251. if (countSpan) {
  252. labelText = labelText.replace(countSpan.textContent.toLowerCase(), '').trim();
  253. }
  254. if (searchText === '' || labelText.includes(searchText)) {
  255. checkDiv.classList.remove('filter-checkbox-hidden');
  256. } else {
  257. checkDiv.classList.add('filter-checkbox-hidden');
  258. }
  259. }
  260. });
  261. }
  262. // Réinitialiser les barres de recherche
  263. function clearSearchInputs() {
  264. advancedFilterFields.forEach(field => {
  265. const searchInput = document.getElementById(`search-${field}`);
  266. if (searchInput) {
  267. searchInput.value = '';
  268. }
  269. });
  270. }
  271. // Formater la valeur pour l'affichage
  272. function formatFilterValue(field, value) {
  273. if (field === 'prest_closed') {
  274. return value == 1 ? 'Clôturé' : 'Ouvert';
  275. }
  276. return value || '(vide)';
  277. }
  278. // Obtenir les valeurs uniques pour un champ dans les données filtrées
  279. function getUniqueValues(data, field) {
  280. const values = new Map();
  281. data.forEach(row => {
  282. const val = row[field];
  283. const key = val === null || val === undefined || val === '' ? '__empty__' : String(val);
  284. values.set(key, (values.get(key) || 0) + 1);
  285. });
  286. return values;
  287. }
  288. // Obtenir les données filtrées selon les filtres actuels (sauf un champ exclu)
  289. function getFilteredData(excludeField = null) {
  290. return originalData.filter(row => {
  291. for (const [field, selectedValues] of Object.entries(currentFilters)) {
  292. if (field === excludeField) continue;
  293. if (selectedValues.length === 0) continue;
  294. const rowValue = row[field];
  295. const rowKey = rowValue === null || rowValue === undefined || rowValue === '' ? '__empty__' : String(rowValue);
  296. if (!selectedValues.includes(rowKey)) {
  297. return false;
  298. }
  299. }
  300. return true;
  301. });
  302. }
  303. // Mettre à jour les checkboxes d'un filtre
  304. function updateFilterCheckboxes(field) {
  305. const container = document.getElementById(`filter-${field}`);
  306. if (!container) return;
  307. // Obtenir les données filtrées en excluant ce champ
  308. const filteredData = getFilteredData(field);
  309. const availableValues = getUniqueValues(filteredData, field);
  310. const allValues = getUniqueValues(originalData, field);
  311. // Sauvegarder les valeurs actuellement sélectionnées
  312. const currentSelected = currentFilters[field] || [];
  313. container.innerHTML = '';
  314. // Trier les valeurs
  315. const sortedKeys = Array.from(allValues.keys()).sort((a, b) => {
  316. if (a === '__empty__') return 1;
  317. if (b === '__empty__') return -1;
  318. return String(a).localeCompare(String(b));
  319. });
  320. sortedKeys.forEach(key => {
  321. const count = availableValues.get(key) || 0;
  322. const totalCount = allValues.get(key);
  323. const isDisabled = count === 0;
  324. const isChecked = currentSelected.includes(key);
  325. const div = document.createElement('div');
  326. div.className = 'form-check';
  327. const input = document.createElement('input');
  328. input.type = 'checkbox';
  329. input.className = 'form-check-input';
  330. input.id = `chk-${field}-${key}`;
  331. input.value = key;
  332. input.checked = isChecked;
  333. input.dataset.field = field;
  334. input.addEventListener('change', onFilterCheckboxChange);
  335. const label = document.createElement('label');
  336. label.className = 'form-check-label' + (isDisabled && !isChecked ? ' filter-option-disabled' : '');
  337. label.htmlFor = input.id;
  338. label.innerHTML = `${formatFilterValue(field, key === '__empty__' ? '' : key)} <span class="filter-option-count">(${count}/${totalCount})</span>`;
  339. div.appendChild(input);
  340. div.appendChild(label);
  341. container.appendChild(div);
  342. });
  343. }
  344. // Mettre à jour tous les filtres
  345. function updateAllFilterCheckboxes() {
  346. advancedFilterFields.forEach(field => {
  347. updateFilterCheckboxes(field);
  348. });
  349. updateFilterInfo();
  350. }
  351. // Quand une checkbox change
  352. function onFilterCheckboxChange(event) {
  353. const field = event.target.dataset.field;
  354. const value = event.target.value;
  355. const isChecked = event.target.checked;
  356. if (!currentFilters[field]) {
  357. currentFilters[field] = [];
  358. }
  359. if (isChecked) {
  360. if (!currentFilters[field].includes(value)) {
  361. currentFilters[field].push(value);
  362. }
  363. } else {
  364. currentFilters[field] = currentFilters[field].filter(v => v !== value);
  365. }
  366. // Mettre à jour les autres filtres (cascade)
  367. advancedFilterFields.forEach(f => {
  368. if (f !== field) {
  369. updateFilterCheckboxes(f);
  370. }
  371. });
  372. // Appliquer le filtre à la table
  373. applyAdvancedFilters();
  374. updateFilterInfo();
  375. }
  376. // Appliquer les filtres avancés à la table
  377. function applyAdvancedFilters() {
  378. const $table = $('#ProWebDossiers');
  379. // Filtrer les données
  380. const filteredData = getFilteredData();
  381. // Recharger la table avec les données filtrées
  382. $table.bootstrapTable('load', filteredData);
  383. }
  384. // Réinitialiser tous les filtres avancés
  385. function clearAllAdvancedFilters() {
  386. currentFilters = {};
  387. advancedFilterFields.forEach(field => {
  388. currentFilters[field] = [];
  389. });
  390. // Réinitialiser les barres de recherche
  391. clearSearchInputs();
  392. // Recharger les données originales
  393. $('#ProWebDossiers').bootstrapTable('load', originalData);
  394. // Mettre à jour les checkboxes
  395. updateAllFilterCheckboxes();
  396. // Réinitialiser aussi les filtres de la table
  397. $('#ProWebDossiers').bootstrapTable('clearFilterControl');
  398. }
  399. // Mettre à jour l'info des filtres actifs
  400. function updateFilterInfo() {
  401. const activeFilters = [];
  402. for (const [field, values] of Object.entries(currentFilters)) {
  403. if (values.length > 0) {
  404. const fieldNames = {
  405. 'exe_ref': 'Année',
  406. 'catprest_lib': 'Catégorie',
  407. 'prest_lib': 'Prestation',
  408. 'etdoss_lib': 'Etat',
  409. 'comm_ref': 'Commission',
  410. 'prest_closed': 'Clôturé',
  411. 'prest_lib2': 'Complément'
  412. };
  413. activeFilters.push(`${fieldNames[field]}: ${values.length} sélection(s)`);
  414. }
  415. }
  416. const infoEl = document.getElementById('filterInfo');
  417. if (activeFilters.length > 0) {
  418. const filteredCount = getFilteredData().length;
  419. infoEl.innerHTML = `<strong>${filteredCount}</strong> dossier(s) affiché(s) | Filtres actifs: ${activeFilters.join(', ')}`;
  420. } else {
  421. infoEl.innerHTML = `<strong>${originalData.length}</strong> dossier(s) au total`;
  422. }
  423. }
  424. // Initialisation
  425. $(document).ready(function() {
  426. const $table = $('#ProWebDossiers');
  427. // Attendre que les données soient chargées
  428. $table.on('load-success.bs.table', function(e, data) {
  429. // Sauvegarder les données originales
  430. originalData = data;
  431. // Initialiser les filtres
  432. advancedFilterFields.forEach(field => {
  433. currentFilters[field] = [];
  434. });
  435. // Construire les checkboxes des filtres
  436. updateAllFilterCheckboxes();
  437. });
  438. // Synchroniser avec les filtres de la table Bootstrap
  439. $table.on('column-search.bs.table', function(e, field, text) {
  440. // Quand un filtre de colonne change, mettre à jour les compteurs
  441. setTimeout(updateFilterInfo, 100);
  442. });
  443. });
  444. </script>