|
|
@@ -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
|