edit.blade.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. @extends('layouts.app')
  2. @section('content')
  3. <div class="px-3">
  4. <form class="row" action="{{ route('responsible.update', ['responsible' => $responsible, 'nav' => $nav ?? null]) }}" method="post">
  5. <div class="col-xxl-6">
  6. <h4>Ответственный</h4>
  7. @csrf
  8. <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
  9. <div class="row mb-2">
  10. <label for="area_search" class="col-form-label small col-md-4 text-md-end">
  11. Район
  12. <sup>*</sup>
  13. </label>
  14. <div class="col-md-8">
  15. <div class="position-relative">
  16. <input type="text"
  17. class="form-control form-control-sm"
  18. id="area_search"
  19. placeholder="Введите район..."
  20. autocomplete="off"
  21. value="{{ old('area_name', $responsible->area?->name ?? '') }}"
  22. required>
  23. <input type="hidden"
  24. name="area_id"
  25. id="area_id"
  26. value="{{ old('area_id', $responsible->area?->id ?? '') }}"
  27. required>
  28. <div class="autocomplete-dropdown autocomplete-dropdown--order" id="area_dropdown"></div>
  29. </div>
  30. <div class="form-text" id="area_hint"></div>
  31. </div>
  32. </div>
  33. @include('partials.input', ['name' => 'name', 'title' => 'ФИО', 'required' => true, 'value' => $responsible->name])
  34. @include('partials.input', ['name' => 'phone', 'title' => 'Телефон', 'required' => true, 'value' => $responsible->phone])
  35. @include('partials.input', ['name' => 'post', 'title' => 'Должность', 'value' => $responsible->post])
  36. @include('partials.submit', ['name' => 'Сохранить', 'delete' => ['form_id' => 'destroy', 'title' => 'Удалить'], 'back_url' => $back_url ?? route('responsible.index', session('gp_responsibles', []))])
  37. </div>
  38. </form>
  39. </div>
  40. <div class="visually-hidden d-none">
  41. <form action="{{ route('responsible.destroy', ['responsible' => $responsible, 'nav' => $nav ?? null]) }}" id="destroy" method="post">
  42. @csrf
  43. @method('DELETE')
  44. </form>
  45. </div>
  46. @endsection
  47. @push('scripts')
  48. <script>
  49. waitForJQuery(function () {
  50. const $form = $('form.row[action*="responsible"]');
  51. const $input = $('#area_search');
  52. const $dropdown = $('#area_dropdown');
  53. const $hiddenInput = $('#area_id');
  54. const $hint = $('#area_hint');
  55. let currentFocus = -1;
  56. const areas = @json($areasForSearch ?? []);
  57. function escapeHtml(text) {
  58. return String(text)
  59. .replace(/&/g, '&amp;')
  60. .replace(/</g, '&lt;')
  61. .replace(/>/g, '&gt;')
  62. .replace(/"/g, '&quot;')
  63. .replace(/'/g, '&#039;');
  64. }
  65. function escapeRegex(str) {
  66. return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  67. }
  68. function highlightMatch(text, query) {
  69. if (!text || !query) {
  70. return escapeHtml(text || '');
  71. }
  72. const safeText = escapeHtml(text);
  73. const regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
  74. return safeText.replace(regex, '<strong>$1</strong>');
  75. }
  76. function showDropdown(items, query) {
  77. $dropdown.empty();
  78. currentFocus = -1;
  79. if (!items.length) {
  80. $dropdown.html('<div class="autocomplete-item text-muted">Ничего не найдено</div>');
  81. $dropdown.show();
  82. return;
  83. }
  84. items.forEach(function (item) {
  85. const html = '<div class="autocomplete-item" data-id="' + item.id + '" data-name="' + escapeHtml(item.name) + '">' +
  86. '<div class="article">' + highlightMatch(item.name, query) + '</div>' +
  87. '</div>';
  88. $dropdown.append(html);
  89. });
  90. $dropdown.show();
  91. }
  92. function hideDropdown() {
  93. $dropdown.hide();
  94. }
  95. function selectItem($item) {
  96. const id = String($item.data('id') || '');
  97. const name = String($item.data('name') || '');
  98. $input.val(name);
  99. $hiddenInput.val(id);
  100. $hint.html('<i class="bi bi-check-circle text-success"></i> Выбрано: ' + name);
  101. $input.removeClass('is-invalid');
  102. hideDropdown();
  103. }
  104. function setActive($items) {
  105. $items.removeClass('active');
  106. if (currentFocus >= 0 && currentFocus < $items.length) {
  107. const $active = $items.eq(currentFocus);
  108. $active.addClass('active');
  109. $active[0].scrollIntoView({block: 'nearest'});
  110. }
  111. }
  112. function searchAreas(query) {
  113. const normalized = String(query || '').trim().toLowerCase();
  114. if (normalized.length < 1) {
  115. hideDropdown();
  116. return;
  117. }
  118. const items = areas.filter(function (item) {
  119. return String(item.name).toLowerCase().includes(normalized);
  120. });
  121. showDropdown(items.slice(0, 50), normalized);
  122. }
  123. $input.on('input', function () {
  124. $hiddenInput.val('');
  125. $hint.html('');
  126. $input.removeClass('is-invalid');
  127. searchAreas($(this).val());
  128. });
  129. $input.on('focus', function () {
  130. if (!$hiddenInput.val()) {
  131. searchAreas($(this).val());
  132. }
  133. });
  134. $input.on('keydown', function (e) {
  135. const $items = $dropdown.find('.autocomplete-item:not(.text-muted)');
  136. if (e.key === 'ArrowDown') {
  137. e.preventDefault();
  138. currentFocus++;
  139. if (currentFocus >= $items.length) {
  140. currentFocus = 0;
  141. }
  142. setActive($items);
  143. } else if (e.key === 'ArrowUp') {
  144. e.preventDefault();
  145. currentFocus--;
  146. if (currentFocus < 0) {
  147. currentFocus = $items.length - 1;
  148. }
  149. setActive($items);
  150. } else if (e.key === 'Enter') {
  151. if ($items.length > 0 && currentFocus > -1) {
  152. e.preventDefault();
  153. selectItem($items.eq(currentFocus));
  154. }
  155. } else if (e.key === 'Escape') {
  156. hideDropdown();
  157. }
  158. });
  159. $dropdown.on('click', '.autocomplete-item:not(.text-muted)', function () {
  160. selectItem($(this));
  161. });
  162. $(document).on('click', function (e) {
  163. if (!$(e.target).closest('#area_search, #area_dropdown').length) {
  164. hideDropdown();
  165. }
  166. });
  167. $form.on('submit', function (e) {
  168. if (!$hiddenInput.val()) {
  169. e.preventDefault();
  170. $input.addClass('is-invalid').focus();
  171. $hint.html('<span class="text-danger">Выберите район из списка</span>');
  172. }
  173. });
  174. const initialAreaId = String($hiddenInput.val() || '');
  175. const initialArea = areas.find(function (item) {
  176. return String(item.id) === initialAreaId;
  177. });
  178. if (initialArea) {
  179. $input.val(initialArea.name);
  180. $hint.html('<i class="bi bi-check-circle text-success"></i> Выбрано: ' + initialArea.name);
  181. }
  182. });
  183. </script>
  184. @endpush