Selaa lähdekoodia

added search for add spare part order

Alexander Musikhin 1 päivä sitten
vanhempi
sitoutus
8a2e6a08be

+ 0 - 2
app/Http/Controllers/SparePartOrderController.php

@@ -118,14 +118,12 @@ class SparePartOrderController extends Controller
             'movements.user',
             'reservations.reclamation',
         ]);
-        $this->data['spare_parts'] = SparePart::orderBy('article')->get();
         return view('spare_part_orders.edit', $this->data);
     }
 
     public function create()
     {
         $this->data['spare_part_order'] = null;
-        $this->data['spare_parts'] = SparePart::orderBy('article')->get();
         return view('spare_part_orders.edit', $this->data);
     }
 

+ 237 - 11
resources/views/spare_part_orders/edit.blade.php

@@ -22,18 +22,21 @@
                 @endif
 
                 <div class="mb-3">
-                    <label for="spare_part_id" class="form-label">Артикул <span class="text-danger">*</span></label>
-                    <select class="form-select" id="spare_part_id" name="spare_part_id" required {{ $spare_part_order ? 'disabled' : '' }}>
-                        <option value="">Выберите...</option>
-                        @foreach($spare_parts as $sp)
-                            <option value="{{ $sp->id }}"
-                                    {{ old('spare_part_id', $spare_part_order->spare_part_id ?? '') == $sp->id ? 'selected' : '' }}>
-                                {{ $sp->article }} - {{ $sp->used_in_maf }}
-                            </option>
-                        @endforeach
-                    </select>
+                    <label for="spare_part_search" class="form-label">Артикул <span class="text-danger">*</span></label>
                     @if($spare_part_order)
-                        <input type="hidden" name="spare_part_id" value="{{ $spare_part_order->spare_part_id }}">
+                        <input type="text" class="form-control" id="spare_part_search"
+                               value="{{ $spare_part_order->sparePart->article }} - {{ $spare_part_order->sparePart->used_in_maf }}"
+                               readonly>
+                        <input type="hidden" name="spare_part_id" id="spare_part_id" value="{{ $spare_part_order->spare_part_id }}">
+                    @else
+                        <div class="position-relative">
+                            <input type="text" class="form-control" id="spare_part_search"
+                                   placeholder="Введите артикул или наименование..."
+                                   autocomplete="off" required>
+                            <input type="hidden" name="spare_part_id" id="spare_part_id" value="{{ old('spare_part_id', '') }}" required>
+                            <div class="autocomplete-dropdown" id="spare_part_dropdown"></div>
+                        </div>
+                        <div class="form-text" id="spare_part_hint"></div>
                     @endif
                 </div>
 
@@ -311,4 +314,227 @@
         </div>
     </div>
 @endif
+
+@push('scripts')
+<style>
+.autocomplete-dropdown {
+    position: absolute;
+    top: 100%;
+    left: 0;
+    right: 0;
+    z-index: 1050;
+    background: white;
+    border: 1px solid #dee2e6;
+    border-top: none;
+    border-radius: 0 0 .375rem .375rem;
+    max-height: 300px;
+    overflow-y: auto;
+    display: none;
+    box-shadow: 0 4px 6px rgba(0,0,0,.1);
+}
+.autocomplete-dropdown .autocomplete-item {
+    padding: 10px 12px;
+    cursor: pointer;
+    border-bottom: 1px solid #f0f0f0;
+}
+.autocomplete-dropdown .autocomplete-item:last-child {
+    border-bottom: none;
+}
+.autocomplete-dropdown .autocomplete-item:hover,
+.autocomplete-dropdown .autocomplete-item.active {
+    background-color: #e9ecef;
+}
+.autocomplete-dropdown .autocomplete-item .article {
+    font-weight: 600;
+    color: #495057;
+}
+.autocomplete-dropdown .autocomplete-item .used-in-maf {
+    font-size: 0.875em;
+    color: #6c757d;
+}
+</style>
+<script>
+document.addEventListener('DOMContentLoaded', function() {
+    const $input = document.getElementById('spare_part_search');
+    const $dropdown = document.getElementById('spare_part_dropdown');
+    const $hiddenInput = document.getElementById('spare_part_id');
+    const $hint = document.getElementById('spare_part_hint');
+
+    // Если это форма редактирования (поле readonly), не инициализируем autocomplete
+    if (!$dropdown || $input.readOnly) {
+        return;
+    }
+
+    let currentFocus = -1;
+    let searchTimeout;
+
+    // Обработка ввода
+    $input.addEventListener('input', function() {
+        const query = this.value.trim();
+        clearTimeout(searchTimeout);
+
+        // Сбрасываем скрытое поле при изменении текста
+        $hiddenInput.value = '';
+        if ($hint) {
+            $hint.innerHTML = '';
+        }
+
+        if (query.length >= 1) {
+            searchTimeout = setTimeout(function() {
+                fetch('{{ route('spare_parts.search') }}?query=' + encodeURIComponent(query))
+                    .then(response => response.json())
+                    .then(data => showDropdown(data, query))
+                    .catch(error => console.error('Search error:', error));
+            }, 200);
+        } else {
+            hideDropdown();
+        }
+    });
+
+    // Показать выпадающий список
+    function showDropdown(items, query) {
+        $dropdown.innerHTML = '';
+        currentFocus = -1;
+
+        if (items.length === 0) {
+            $dropdown.innerHTML = '<div class="autocomplete-item text-muted">Ничего не найдено</div>';
+            $dropdown.style.display = 'block';
+            return;
+        }
+
+        items.forEach(function(item, index) {
+            const div = document.createElement('div');
+            div.className = 'autocomplete-item';
+
+            const highlightedArticle = highlightMatch(item.article, query);
+            const highlightedUsedIn = item.used_in_maf ? highlightMatch(item.used_in_maf, query) : '';
+
+            div.innerHTML = '<div class="article">' + highlightedArticle + '</div>' +
+                           (item.used_in_maf ? '<div class="used-in-maf">' + highlightedUsedIn + '</div>' : '');
+
+            div.dataset.id = item.id;
+            div.dataset.article = item.article;
+            div.dataset.usedInMaf = item.used_in_maf || '';
+
+            div.addEventListener('click', function() {
+                selectItem(this);
+            });
+
+            $dropdown.appendChild(div);
+        });
+
+        $dropdown.style.display = 'block';
+    }
+
+    function hideDropdown() {
+        $dropdown.style.display = 'none';
+    }
+
+    // Подсветка совпадения
+    function highlightMatch(text, query) {
+        if (!text || !query) return text || '';
+        const regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
+        return text.replace(regex, '<strong>$1</strong>');
+    }
+
+    function escapeRegex(str) {
+        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+    }
+
+    // Выбор элемента
+    function selectItem(item) {
+        $input.value = item.dataset.article + ' - ' + item.dataset.usedInMaf;
+        $hiddenInput.value = item.dataset.id;
+        if ($hint) {
+            $hint.innerHTML = '<i class="bi bi-check-circle text-success"></i> Выбрано: ' + item.dataset.article;
+        }
+        hideDropdown();
+    }
+
+    // Навигация клавиатурой
+    $input.addEventListener('keydown', function(e) {
+        const items = $dropdown.querySelectorAll('.autocomplete-item:not(.text-muted)');
+
+        if (e.key === 'ArrowDown') {
+            e.preventDefault();
+            currentFocus++;
+            if (currentFocus >= items.length) currentFocus = 0;
+            setActive(items);
+        } else if (e.key === 'ArrowUp') {
+            e.preventDefault();
+            currentFocus--;
+            if (currentFocus < 0) currentFocus = items.length - 1;
+            setActive(items);
+        } else if (e.key === 'Enter') {
+            e.preventDefault();
+            if (currentFocus > -1 && items.length > 0) {
+                selectItem(items[currentFocus]);
+            }
+        } else if (e.key === 'Escape') {
+            hideDropdown();
+        }
+    });
+
+    function setActive(items) {
+        items.forEach(item => item.classList.remove('active'));
+        if (currentFocus >= 0 && currentFocus < items.length) {
+            items[currentFocus].classList.add('active');
+            items[currentFocus].scrollIntoView({ block: 'nearest' });
+        }
+    }
+
+    // Закрытие при клике вне
+    document.addEventListener('click', function(e) {
+        if (!e.target.closest('#spare_part_search') && !e.target.closest('#spare_part_dropdown')) {
+            hideDropdown();
+        }
+    });
+
+    // При фокусе показать список если есть значение
+    $input.addEventListener('focus', function() {
+        const query = this.value.trim();
+        if (query.length >= 1 && !$hiddenInput.value) {
+            fetch('{{ route('spare_parts.search') }}?query=' + encodeURIComponent(query))
+                .then(response => response.json())
+                .then(data => showDropdown(data, query))
+                .catch(error => console.error('Search error:', error));
+        }
+    });
+
+    // Валидация формы - проверяем что выбрана запчасть
+    const form = $input.closest('form');
+    if (form) {
+        form.addEventListener('submit', function(e) {
+            if (!$hiddenInput.value) {
+                e.preventDefault();
+                $input.classList.add('is-invalid');
+                if ($hint) {
+                    $hint.innerHTML = '<span class="text-danger">Выберите запчасть из списка</span>';
+                }
+                $input.focus();
+            }
+        });
+
+        $input.addEventListener('input', function() {
+            $input.classList.remove('is-invalid');
+        });
+    }
+
+    // Восстановление значения при ошибке валидации
+    @if(old('spare_part_id'))
+    fetch('{{ route('spare_parts.search') }}?query=' + encodeURIComponent(''))
+        .then(response => response.json())
+        .then(data => {
+            const sparePart = data.find(sp => sp.id == '{{ old('spare_part_id') }}');
+            if (sparePart) {
+                $input.value = sparePart.article + ' - ' + (sparePart.used_in_maf || '');
+                if ($hint) {
+                    $hint.innerHTML = '<i class="bi bi-check-circle text-success"></i> Выбрано: ' + sparePart.article;
+                }
+            }
+        });
+    @endif
+});
+</script>
+@endpush
 @endsection