newFilterElement.blade.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <div class="dropdown-menu filter-menu-wide" aria-labelledby="{{$id}}">
  2. <div class="px-1">
  3. <div class="d-flex mb-2 {{ $isSort ? '' : 'd-none' }}">
  4. <div class="me-3">Сортировка</div>
  5. <div class="d-flex filter-sort-controls">
  6. <button type="button" class="btn btn-outline-secondary btn-sm w-100" id="sort-by-asc-{{$id}}"><i class="bi bi-arrow-up"></i>ASC</button>
  7. <button type="button" class="btn btn-outline-secondary btn-sm w-100" id="sort-by-desc-{{$id}}"><i class="bi bi-arrow-down"></i>DESC</button>
  8. </div>
  9. </div>
  10. @if($type === 'ranges')
  11. @php
  12. $inputType = $type === 'dates' ? 'date' : 'number';
  13. $fromKey = $id . '_from';
  14. $toKey = $id . '_to';
  15. @endphp
  16. <form class="dropdown-filter_{{$id}} mb-2">
  17. <div class="mb-2">
  18. <label class="form-label mb-0 small">От:</label>
  19. <input type="{{ $inputType }}" class="form-control form-control-sm" id="range_from_{{$id}}"
  20. value="{{ request()->filters[$fromKey] ?? '' }}"
  21. @if($data) min="{{ $data['min'] }}" max="{{ $data['max'] }}" @endif>
  22. </div>
  23. <div class="mb-2">
  24. <label class="form-label mb-0 small">До:</label>
  25. <input type="{{ $inputType }}" class="form-control form-control-sm" id="range_to_{{$id}}"
  26. value="{{ request()->filters[$toKey] ?? '' }}"
  27. @if($data) min="{{ $data['min'] }}" max="{{ $data['max'] }}" @endif>
  28. </div>
  29. </form>
  30. <div class="modal-footer d-flex justify-content-between" id="modal-footer_{{$id}}">
  31. <button type="button" class="btn btn-outline-secondary btn-sm w-50 border-0" id="reset-filters_{{$id}}">Отмена</button>
  32. <button type="button" class="btn btn-primary btn-sm w-50" id="accept-filter_{{$id}}">Применить</button>
  33. </div>
  34. @else
  35. <form class="dropdown-filter_{{$id}} mb-2">
  36. <div class="d-flex align-items-center mb-2">
  37. <input class="form-control me-2" id="search_{{$id}}" placeholder="Поиск">
  38. <button type="button" class="btn btn-outline-secondary btn-sm" id="sort-filter-{{$id}}" title="А-Я / Я-А">
  39. <i class="bi bi-sort-alpha-down" id="sort-filter-icon-{{$id}}"></i>
  40. </button>
  41. </div>
  42. <div class="d-flex gap-2 mb-2" id="bulk-toggle_{{$id}}">
  43. <button type="button" class="btn btn-outline-secondary btn-sm w-50" id="check-all_{{$id}}">Выбрать все</button>
  44. <button type="button" class="btn btn-outline-secondary btn-sm w-50" id="uncheck-all_{{$id}}">Снять все</button>
  45. </div>
  46. <div class="ps-1 filter-list-scroll" id="filter-list-{{$id}}">
  47. </div>
  48. </form>
  49. <div class="modal-footer d-flex justify-content-between" id="modal-footer_{{$id}}">
  50. <button type="button" class="btn btn-outline-secondary reset-filters_{{$id}} btn-sm w-50 border-0" id="reset-filters_{{$id}}">Отмена</button>
  51. <button type="button" class="btn btn-primary accept-filter_{{$id}} btn-sm w-50" id="accept-filter_{{$id}}">Применить</button>
  52. </div>
  53. @endif
  54. </div>
  55. </div>
  56. @push('scripts')
  57. <script type="module">
  58. function escapeHtml(str) {
  59. return str.toString()
  60. .replace(/&/g, "&amp;")
  61. .replace(/"/g, "&quot;")
  62. .replace(/</g, "&lt;")
  63. .replace(/>/g, "&gt;");
  64. }
  65. // Ждём загрузки jQuery через Vite
  66. function waitForJQuery(callback) {
  67. if (typeof window.$ !== 'undefined') {
  68. callback();
  69. } else {
  70. setTimeout(() => waitForJQuery(callback), 50);
  71. }
  72. }
  73. waitForJQuery(async function () {
  74. @if($type === 'ranges')
  75. $("#accept-filter_{{$id}}").on("click", function () {
  76. let currentUrl = new URL(document.location.href);
  77. const fromVal = $("#range_from_{{$id}}").val();
  78. const toVal = $("#range_to_{{$id}}").val();
  79. if (fromVal) {
  80. currentUrl.searchParams.set('filters[{{$id}}_from]', fromVal);
  81. } else {
  82. currentUrl.searchParams.delete('filters[{{$id}}_from]');
  83. }
  84. if (toVal) {
  85. currentUrl.searchParams.set('filters[{{$id}}_to]', toVal);
  86. } else {
  87. currentUrl.searchParams.delete('filters[{{$id}}_to]');
  88. }
  89. currentUrl.searchParams.delete('page');
  90. document.location.href = currentUrl.href;
  91. });
  92. $("#reset-filters_{{$id}}").on("click", function () {
  93. let currentUrl = new URL(document.location.href);
  94. currentUrl.searchParams.delete('filters[{{$id}}_from]');
  95. currentUrl.searchParams.delete('filters[{{$id}}_to]');
  96. currentUrl.searchParams.delete('page');
  97. document.location.href = currentUrl.href;
  98. });
  99. @else
  100. const $container = $("#filter-list-{{$id}}");
  101. const $searchInput = $("#search_{{$id}}");
  102. const $sortBtn = $("#sort-filter-{{$id}}");
  103. const $sortIcon = $("#sort-filter-icon-{{$id}}");
  104. const $bulkToggle = $("#bulk-toggle_{{$id}}");
  105. const $checkAllBtn = $("#check-all_{{$id}}");
  106. const $uncheckAllBtn = $("#uncheck-all_{{$id}}");
  107. const urlParams = new URL(document.location.href);
  108. const existingFilter = urlParams.searchParams.get(`filters[{{$id}}]`);
  109. const selectedValues = existingFilter ? existingFilter.split("||") : null;
  110. let filterData = @json(isset($data['values']) ? array_values(array_keys($data['values'])) : []);
  111. let sortAsc = true;
  112. function renderFilterList(data) {
  113. const html = data.map(item => `
  114. <div class="form-check">
  115. <input class="form-check-input" type="checkbox" value="${escapeHtml(item)}" id="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
  116. <label class="form-check-label" for="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
  117. ${escapeHtml(item)}
  118. </label>
  119. </div>
  120. `).join('');
  121. $container.html(html);
  122. if (selectedValues === null) {
  123. $container.find(".form-check-input").prop("checked", true);
  124. } else {
  125. selectedValues.forEach(value => {
  126. $container.find(`.form-check-input[value="${$.escapeSelector(value)}"]`).prop("checked", true);
  127. });
  128. }
  129. }
  130. function sortData(asc) {
  131. const collator = new Intl.Collator('ru', { sensitivity: 'base', numeric: true });
  132. return [...filterData].sort((a, b) => {
  133. if (a === '-пусто-') return -1;
  134. if (b === '-пусто-') return 1;
  135. return asc ? collator.compare(a, b) : collator.compare(b, a);
  136. });
  137. }
  138. if (filterData.length) {
  139. renderFilterList(sortData(sortAsc));
  140. } else {
  141. try {
  142. const response = await fetch(`{!! route('getFilters', ['column' => $id, 'table' => $table]) !!}`);
  143. const data = await response.json();
  144. if (Array.isArray(data) && data.length) {
  145. if(data[0] === null) data[0] = '-пусто-';
  146. filterData = data;
  147. const sortedData = sortData(sortAsc);
  148. renderFilterList(sortedData);
  149. } else {
  150. $("#search_{{$id}}").parent().hide();
  151. $bulkToggle.hide();
  152. $("#modal-footer_{{$id}}").hide();
  153. $container.html('<div class="text-muted">Нет данных</div>');
  154. }
  155. } catch (error) {
  156. console.error("Ошибка при загрузке фильтров:", error);
  157. $container.html('<div class="text-danger">Ошибка загрузки</div>');
  158. }
  159. }
  160. $sortBtn.on("click", function (e) {
  161. e.preventDefault();
  162. sortAsc = !sortAsc;
  163. $sortIcon.removeClass("bi-sort-alpha-down bi-sort-alpha-up")
  164. .addClass(sortAsc ? "bi-sort-alpha-down" : "bi-sort-alpha-up");
  165. const sortedData = sortData(sortAsc);
  166. renderFilterList(sortedData);
  167. });
  168. $checkAllBtn.on("click", function () {
  169. $container.find(".form-check-input").prop("checked", true);
  170. });
  171. $uncheckAllBtn.on("click", function () {
  172. $container.find(".form-check-input").prop("checked", false);
  173. });
  174. $("#accept-filter_{{$id}}").on("click", function () {
  175. const checkedValues = $container.find(".form-check-input:checked")
  176. .map(function () {
  177. return $(this).val();
  178. })
  179. .get();
  180. let currentUrl = new URL(document.location.href);
  181. if (!checkedValues.join('||') || checkedValues.length === filterData.length) {
  182. currentUrl.searchParams.delete('filters[{{$id}}]');
  183. } else {
  184. currentUrl.searchParams.set('filters[{{$id}}]', checkedValues.join('||'));
  185. }
  186. currentUrl.searchParams.delete('page');
  187. document.location.href = currentUrl.href;
  188. });
  189. $("#reset-filters_{{$id}}").on("click", function () {
  190. let currentUrl = new URL(document.location.href);
  191. currentUrl.searchParams.delete('filters[{{$id}}]');
  192. document.location.href = currentUrl.href;
  193. });
  194. $searchInput.on("input", function () {
  195. const query = $(this).val().toLowerCase();
  196. $container.find(".form-check").each(function () {
  197. const labelText = $(this).find("label").text().toLowerCase();
  198. if (labelText.includes(query)) {
  199. $(this).show();
  200. } else {
  201. $(this).hide();
  202. $(this).find(".form-check-input").prop("checked", false);
  203. }
  204. });
  205. });
  206. @endif
  207. $('#sort-by-asc-{{$id}}').on('click', function () {
  208. let columnName = '{{$id}}';
  209. let currentUrl = new URL(document.location.href);
  210. currentUrl.searchParams.set('sortBy', columnName);
  211. if(currentUrl.searchParams.get('order') === 'desc') {
  212. currentUrl.searchParams.delete('order');
  213. }
  214. document.location.href = currentUrl.href;
  215. });
  216. $('#sort-by-desc-{{$id}}').on('click', function () {
  217. let columnName = '{{$id}}';
  218. let currentUrl = new URL(document.location.href);
  219. currentUrl.searchParams.set('sortBy', columnName);
  220. currentUrl.searchParams.set('order', 'desc');
  221. document.location.href = currentUrl.href;
  222. });
  223. });
  224. </script>
  225. @endpush