edit.blade.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. @extends('layouts.app')
  2. @section('content')
  3. <div class="container-fluid">
  4. <div class="row">
  5. <div class="col-12">
  6. <div class="row mb-2">
  7. <div class="col-6">
  8. <h2>{{ $spare_part ? 'Редактирование запчасти' : 'Создание запчасти' }}</h2>
  9. </div>
  10. <div class="col-6 text-end">
  11. @if($spare_part && hasRole('admin'))
  12. <button class="btn btn-sm text-success" onclick="$('#upl-image').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить изображение</button>
  13. <form action="{{ route('spare_parts.upload_image', ['sparePart' => $spare_part]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
  14. @csrf
  15. <input type="file" name="image" onchange="$(this).parent().submit()" required id="upl-image" accept="image/*" />
  16. </form>
  17. @endif
  18. </div>
  19. </div>
  20. <form action="{{ $spare_part ? route('spare_parts.update', $spare_part) : route('spare_parts.store') }}"
  21. method="POST">
  22. @csrf
  23. @if($spare_part)
  24. @method('PUT')
  25. @endif
  26. <input type="hidden" name="previous_url" value="{{ $previous_url ?? url()->previous() }}">
  27. <div class="row">
  28. {{-- Левая колонка --}}
  29. <div class="col-md-6">
  30. @if($spare_part && $spare_part->image)
  31. <div class="mb-3">
  32. <img src="{{ $spare_part->image }}" alt="{{ $spare_part->article }}" class="img-fluid" style="max-width: 200px;">
  33. </div>
  34. @endif
  35. <div class="mb-3">
  36. <label for="article" class="form-label">Артикул <span class="text-danger">*</span></label>
  37. <input type="text"
  38. class="form-control"
  39. id="article"
  40. name="article"
  41. value="{{ old('article', $spare_part->article ?? '') }}"
  42. {{ hasRole('admin') ? '' : 'readonly' }}
  43. required>
  44. </div>
  45. <div class="mb-3">
  46. <label for="used_in_maf" class="form-label">Где используется</label>
  47. <input type="text"
  48. class="form-control"
  49. id="used_in_maf"
  50. name="used_in_maf"
  51. value="{{ old('used_in_maf', $spare_part->used_in_maf ?? '') }}"
  52. {{ hasRole('admin') ? '' : 'readonly' }}>
  53. </div>
  54. <div class="mb-3">
  55. <label for="note" class="form-label">Примечание</label>
  56. <textarea class="form-control"
  57. id="note"
  58. name="note"
  59. rows="3"
  60. {{ hasRole('admin') ? '' : 'readonly' }}>{{ old('note', $spare_part->note ?? '') }}</textarea>
  61. </div>
  62. </div>
  63. {{-- Правая колонка --}}
  64. <div class="col-md-6">
  65. @if(hasRole('admin'))
  66. <div class="mb-3">
  67. <label for="purchase_price" class="form-label">Цена закупки (руб.)</label>
  68. <input type="number"
  69. class="form-control"
  70. id="purchase_price"
  71. name="purchase_price"
  72. step="0.01"
  73. value="{{ old('purchase_price', $spare_part->purchase_price ?? '') }}">
  74. </div>
  75. @endif
  76. <div class="mb-3">
  77. <label for="customer_price" class="form-label">Цена для заказчика (руб.)</label>
  78. <input type="number"
  79. class="form-control"
  80. id="customer_price"
  81. name="customer_price"
  82. step="0.01"
  83. value="{{ old('customer_price', $spare_part->customer_price ?? '') }}"
  84. {{ hasRole('admin') ? '' : 'readonly' }}>
  85. </div>
  86. <div class="mb-3">
  87. <label for="expertise_price" class="form-label">Цена экспертизы (руб.)</label>
  88. <input type="number"
  89. class="form-control"
  90. id="expertise_price"
  91. name="expertise_price"
  92. step="0.01"
  93. value="{{ old('expertise_price', $spare_part->expertise_price ?? '') }}"
  94. {{ hasRole('admin') ? '' : 'readonly' }}>
  95. </div>
  96. <div class="mb-3">
  97. <label for="tsn_number" class="form-label">№ по ТСН</label>
  98. <div class="row">
  99. <div class="col-md-5 position-relative">
  100. <input type="text"
  101. class="form-control"
  102. id="tsn_number"
  103. name="tsn_number"
  104. value="{{ old('tsn_number', $spare_part->tsn_number ?? '') }}"
  105. autocomplete="off"
  106. {{ hasRole('admin') ? '' : 'readonly' }}>
  107. <div class="autocomplete-dropdown" id="tsn_number_dropdown"></div>
  108. </div>
  109. <div class="col-md-7">
  110. <input type="text"
  111. class="form-control"
  112. id="tsn_number_description"
  113. name="tsn_number_description"
  114. placeholder="Расшифровка"
  115. {{ hasRole('admin') ? '' : 'readonly' }}>
  116. </div>
  117. </div>
  118. <div class="form-text" id="tsn_number_hint"></div>
  119. </div>
  120. <div class="mb-3">
  121. <label for="pricing_code" class="form-label">Шифр расценки</label>
  122. <div class="row">
  123. <div class="col-md-5 position-relative">
  124. <input type="text"
  125. class="form-control"
  126. id="pricing_code"
  127. name="pricing_code"
  128. value="{{ old('pricing_code', $spare_part->pricing_code ?? '') }}"
  129. autocomplete="off"
  130. {{ hasRole('admin') ? '' : 'readonly' }}>
  131. <div class="autocomplete-dropdown" id="pricing_code_dropdown"></div>
  132. </div>
  133. <div class="col-md-7">
  134. <input type="text"
  135. class="form-control"
  136. id="pricing_code_description"
  137. name="pricing_code_description"
  138. placeholder="Расшифровка"
  139. {{ hasRole('admin') ? '' : 'readonly' }}>
  140. </div>
  141. </div>
  142. <div class="form-text" id="pricing_code_hint"></div>
  143. </div>
  144. <div class="mb-3">
  145. <label for="min_stock" class="form-label">Минимальный остаток</label>
  146. <input type="number"
  147. class="form-control"
  148. id="min_stock"
  149. name="min_stock"
  150. value="{{ old('min_stock', $spare_part->min_stock ?? 0) }}"
  151. {{ hasRole('admin') ? '' : 'readonly' }}>
  152. </div>
  153. </div>
  154. </div>
  155. <div class="row">
  156. <div class="col-12">
  157. @if(hasRole('admin'))
  158. <button type="submit" class="btn btn-success">Сохранить</button>
  159. @endif
  160. <a href="{{ $previous_url ?? route('spare_parts.index') }}" class="btn btn-secondary">Назад</a>
  161. @if($spare_part && hasRole('admin'))
  162. <button type="button" class="btn btn-danger float-end" data-bs-toggle="modal" data-bs-target="#deleteModal">
  163. Удалить
  164. </button>
  165. @endif
  166. </div>
  167. </div>
  168. </form>
  169. </div>
  170. </div>
  171. </div>
  172. @if($spare_part && hasRole('admin'))
  173. {{-- Модальное окно удаления --}}
  174. <div class="modal fade" id="deleteModal" tabindex="-1">
  175. <div class="modal-dialog">
  176. <div class="modal-content">
  177. <div class="modal-header">
  178. <h5 class="modal-title">Подтверждение удаления</h5>
  179. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  180. </div>
  181. <div class="modal-body">
  182. Вы действительно хотите удалить запчасть "{{ $spare_part->article }}"?
  183. </div>
  184. <div class="modal-footer">
  185. <form action="{{ route('spare_parts.destroy', $spare_part) }}" method="POST">
  186. @csrf
  187. @method('DELETE')
  188. <button type="submit" class="btn btn-danger">Удалить</button>
  189. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
  190. </form>
  191. </div>
  192. </div>
  193. </div>
  194. </div>
  195. @endif
  196. @push('scripts')
  197. <style>
  198. .autocomplete-dropdown {
  199. position: absolute;
  200. top: 100%;
  201. left: 0;
  202. right: 0;
  203. z-index: 1050;
  204. background: white;
  205. border: 1px solid #dee2e6;
  206. border-top: none;
  207. border-radius: 0 0 .375rem .375rem;
  208. max-height: 250px;
  209. overflow-y: auto;
  210. display: none;
  211. box-shadow: 0 4px 6px rgba(0,0,0,.1);
  212. }
  213. .autocomplete-dropdown .autocomplete-item {
  214. padding: 8px 12px;
  215. cursor: pointer;
  216. border-bottom: 1px solid #f0f0f0;
  217. }
  218. .autocomplete-dropdown .autocomplete-item:last-child {
  219. border-bottom: none;
  220. }
  221. .autocomplete-dropdown .autocomplete-item:hover,
  222. .autocomplete-dropdown .autocomplete-item.active {
  223. background-color: #e9ecef;
  224. }
  225. .autocomplete-dropdown .autocomplete-item .code {
  226. font-weight: 600;
  227. color: #495057;
  228. }
  229. .autocomplete-dropdown .autocomplete-item .description {
  230. font-size: 0.875em;
  231. color: #6c757d;
  232. }
  233. </style>
  234. <script>
  235. function waitForJQuery(callback) {
  236. if (typeof $ !== 'undefined') {
  237. callback();
  238. } else {
  239. setTimeout(function() { waitForJQuery(callback); }, 50);
  240. }
  241. }
  242. waitForJQuery(function() {
  243. $(document).ready(function() {
  244. @if(hasRole('admin'))
  245. //console.log('Autocomplete init started');
  246. // Общая функция для инициализации автокомплита
  247. function initAutocomplete(inputId, dropdownId, descriptionId, hintId, type) {
  248. const $input = $('#' + inputId);
  249. const $dropdown = $('#' + dropdownId);
  250. const $description = $('#' + descriptionId);
  251. const $hint = $('#' + hintId);
  252. let currentFocus = -1;
  253. let searchTimeout;
  254. //console.log('Init autocomplete for:', inputId, $input.length);
  255. // Обработка ввода
  256. $input.on('input', function() {
  257. const query = $(this).val().trim();
  258. clearTimeout(searchTimeout);
  259. //console.log('Input event:', inputId, query);
  260. if (query.length > 0) {
  261. searchTimeout = setTimeout(function() {
  262. // Поиск вариантов для автокомплита
  263. $.get('{{ route('pricing_codes.search') }}', {
  264. type: type,
  265. query: query
  266. }).done(function(data) {
  267. //console.log('Search result:', data);
  268. showDropdown(data, query);
  269. }).fail(function(xhr) {
  270. console.error('Search error:', xhr.status, xhr.responseText);
  271. });
  272. // Проверка точного соответствия для расшифровки
  273. $.get('{{ route('pricing_codes.get_description') }}', {
  274. type: type,
  275. code: query
  276. }).done(function(data) {
  277. // console.log('Description result:', data);
  278. if (data.description) {
  279. $description.val(data.description);
  280. $description.prop('readonly', true);
  281. $hint.html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
  282. } else {
  283. $description.val('');
  284. $description.prop('readonly', false);
  285. $hint.html('<i class="bi bi-plus-circle text-primary"></i> Новый код - заполните расшифровку');
  286. }
  287. }).fail(function(xhr) {
  288. console.error('Description error:', xhr.status, xhr.responseText);
  289. });
  290. }, 200);
  291. } else {
  292. $dropdown.hide();
  293. $description.val('');
  294. $description.prop('readonly', false);
  295. $hint.text('');
  296. }
  297. });
  298. // Показать выпадающий список
  299. function showDropdown(items, query) {
  300. $dropdown.empty();
  301. currentFocus = -1;
  302. if (items.length === 0) {
  303. $dropdown.hide();
  304. return;
  305. }
  306. items.forEach(function(item, index) {
  307. const $item = $('<div class="autocomplete-item"></div>');
  308. const highlightedCode = highlightMatch(item.code, query);
  309. const highlightedDesc = item.description ? highlightMatch(item.description, query) : '';
  310. $item.html('<div class="code">' + highlightedCode + '</div>' +
  311. (item.description ? '<div class="description">' + highlightedDesc + '</div>' : ''));
  312. $item.data('code', item.code);
  313. $item.data('description', item.description || '');
  314. $item.on('click', function() {
  315. selectItem($(this));
  316. });
  317. $dropdown.append($item);
  318. });
  319. $dropdown.show();
  320. }
  321. // Подсветка совпадения
  322. function highlightMatch(text, query) {
  323. if (!text || !query) return text || '';
  324. const regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
  325. return text.replace(regex, '<strong>$1</strong>');
  326. }
  327. function escapeRegex(str) {
  328. return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  329. }
  330. // Выбор элемента
  331. function selectItem($item) {
  332. $input.val($item.data('code'));
  333. $description.val($item.data('description'));
  334. $description.prop('readonly', true);
  335. $hint.html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
  336. $dropdown.hide();
  337. }
  338. // Навигация клавиатурой
  339. $input.on('keydown', function(e) {
  340. const $items = $dropdown.find('.autocomplete-item');
  341. if (e.keyCode === 40) { // Стрелка вниз
  342. e.preventDefault();
  343. currentFocus++;
  344. if (currentFocus >= $items.length) currentFocus = 0;
  345. setActive($items);
  346. } else if (e.keyCode === 38) { // Стрелка вверх
  347. e.preventDefault();
  348. currentFocus--;
  349. if (currentFocus < 0) currentFocus = $items.length - 1;
  350. setActive($items);
  351. } else if (e.keyCode === 13) { // Enter
  352. e.preventDefault();
  353. if (currentFocus > -1 && $items.length > 0) {
  354. selectItem($items.eq(currentFocus));
  355. }
  356. } else if (e.keyCode === 27) { // Escape
  357. $dropdown.hide();
  358. }
  359. });
  360. function setActive($items) {
  361. $items.removeClass('active');
  362. if (currentFocus >= 0 && currentFocus < $items.length) {
  363. $items.eq(currentFocus).addClass('active');
  364. }
  365. }
  366. // Закрытие при клике вне
  367. $(document).on('click', function(e) {
  368. if (!$(e.target).closest('#' + inputId + ', #' + dropdownId).length) {
  369. $dropdown.hide();
  370. }
  371. });
  372. // При фокусе показать список если есть значение
  373. $input.on('focus', function() {
  374. const query = $(this).val().trim();
  375. if (query.length > 0) {
  376. $.get('{{ route('pricing_codes.search') }}', {
  377. type: type,
  378. query: query
  379. }, function(data) {
  380. showDropdown(data, query);
  381. });
  382. }
  383. });
  384. }
  385. // Инициализация автокомплитов
  386. initAutocomplete('tsn_number', 'tsn_number_dropdown', 'tsn_number_description', 'tsn_number_hint', 'tsn_number');
  387. initAutocomplete('pricing_code', 'pricing_code_dropdown', 'pricing_code_description', 'pricing_code_hint', 'pricing_code');
  388. // Загрузка расшифровок при открытии страницы
  389. function loadInitialDescription(inputId, descriptionId, hintId, type) {
  390. const code = $('#' + inputId).val().trim();
  391. //console.log('Load initial for:', inputId, 'code:', code);
  392. if (code.length > 0) {
  393. $.get('{{ route('pricing_codes.get_description') }}', {
  394. type: type,
  395. code: code
  396. }).done(function(data) {
  397. //console.log('Initial description for', inputId, ':', data);
  398. if (data.description) {
  399. $('#' + descriptionId).val(data.description);
  400. $('#' + descriptionId).prop('readonly', true);
  401. $('#' + hintId).html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
  402. } else {
  403. $('#' + descriptionId).prop('readonly', false);
  404. $('#' + hintId).html('<i class="bi bi-plus-circle text-primary"></i> Новый код - заполните расшифровку');
  405. }
  406. }).fail(function(xhr) {
  407. console.error('Initial description error:', xhr.status, xhr.responseText);
  408. });
  409. }
  410. }
  411. loadInitialDescription('tsn_number', 'tsn_number_description', 'tsn_number_hint', 'tsn_number');
  412. loadInitialDescription('pricing_code', 'pricing_code_description', 'pricing_code_hint', 'pricing_code');
  413. //console.log('Autocomplete init complete');
  414. @endif
  415. });
  416. });
  417. </script>
  418. @endpush
  419. @endsection