|
|
@@ -4,21 +4,21 @@
|
|
|
<div class="container-fluid">
|
|
|
<div class="row">
|
|
|
<div class="col-12">
|
|
|
- <h2>{{ $spare_part ? 'Редактирование запчасти' : 'Создание запчасти' }}</h2>
|
|
|
-
|
|
|
- @if($spare_part && hasRole('admin'))
|
|
|
- <div class="mb-3">
|
|
|
- <label class="form-label">Загрузить изображение</label>
|
|
|
- <form action="{{ route('spare_parts.upload_image', $spare_part) }}"
|
|
|
- method="POST"
|
|
|
- enctype="multipart/form-data"
|
|
|
- id="imageUploadForm">
|
|
|
- @csrf
|
|
|
- <input type="file" name="image" class="form-control" accept="image/*">
|
|
|
- <button type="submit" class="btn btn-sm btn-primary mt-2">Загрузить</button>
|
|
|
- </form>
|
|
|
+ <div class="row mb-2">
|
|
|
+ <div class="col-6">
|
|
|
+ <h2>{{ $spare_part ? 'Редактирование запчасти' : 'Создание запчасти' }}</h2>
|
|
|
</div>
|
|
|
- @endif
|
|
|
+ <div class="col-6 text-end">
|
|
|
+ @if($spare_part && hasRole('admin'))
|
|
|
+ <button class="btn btn-sm text-success" onclick="$('#upl-image').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить изображение</button>
|
|
|
+
|
|
|
+ <form action="{{ route('spare_parts.upload_image', ['sparePart' => $spare_part]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
|
|
|
+ @csrf
|
|
|
+ <input type="file" name="image" onchange="$(this).parent().submit()" required id="upl-image" accept="image/*" />
|
|
|
+ </form>
|
|
|
+ @endif
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<form action="{{ $spare_part ? route('spare_parts.update', $spare_part) : route('spare_parts.store') }}"
|
|
|
method="POST">
|
|
|
@@ -107,48 +107,54 @@
|
|
|
|
|
|
<div class="mb-3">
|
|
|
<label for="tsn_number" class="form-label">№ по ТСН</label>
|
|
|
- <input type="text"
|
|
|
- class="form-control"
|
|
|
- id="tsn_number"
|
|
|
- name="tsn_number"
|
|
|
- value="{{ old('tsn_number', $spare_part->tsn_number ?? '') }}"
|
|
|
- {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-md-5 position-relative">
|
|
|
+ <input type="text"
|
|
|
+ class="form-control"
|
|
|
+ id="tsn_number"
|
|
|
+ name="tsn_number"
|
|
|
+ value="{{ old('tsn_number', $spare_part->tsn_number ?? '') }}"
|
|
|
+ autocomplete="off"
|
|
|
+ {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ <div class="autocomplete-dropdown" id="tsn_number_dropdown"></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-md-7">
|
|
|
+ <input type="text"
|
|
|
+ class="form-control"
|
|
|
+ id="tsn_number_description"
|
|
|
+ name="tsn_number_description"
|
|
|
+ placeholder="Расшифровка"
|
|
|
+ {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="form-text" id="tsn_number_hint"></div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="mb-3" id="tsn_description_block" style="display: none;">
|
|
|
- <label for="tsn_number_description" class="form-label">Расшифровка № по ТСН</label>
|
|
|
- <input type="text"
|
|
|
- class="form-control"
|
|
|
- id="tsn_number_description"
|
|
|
- name="tsn_number_description"
|
|
|
- placeholder="Введите расшифровку для нового номера"
|
|
|
- {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
- <div class="form-text text-muted">Расшифровка будет сохранена в справочник при сохранении запчасти</div>
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="mb-3">
|
|
|
<label for="pricing_code" class="form-label">Шифр расценки</label>
|
|
|
- <input type="text"
|
|
|
- class="form-control"
|
|
|
- id="pricing_code"
|
|
|
- name="pricing_code"
|
|
|
- value="{{ old('pricing_code', $spare_part->pricing_code ?? '') }}"
|
|
|
- {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-md-5 position-relative">
|
|
|
+ <input type="text"
|
|
|
+ class="form-control"
|
|
|
+ id="pricing_code"
|
|
|
+ name="pricing_code"
|
|
|
+ value="{{ old('pricing_code', $spare_part->pricing_code ?? '') }}"
|
|
|
+ autocomplete="off"
|
|
|
+ {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ <div class="autocomplete-dropdown" id="pricing_code_dropdown"></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-md-7">
|
|
|
+ <input type="text"
|
|
|
+ class="form-control"
|
|
|
+ id="pricing_code_description"
|
|
|
+ name="pricing_code_description"
|
|
|
+ placeholder="Расшифровка"
|
|
|
+ {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="form-text" id="pricing_code_hint"></div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="mb-3" id="pricing_code_description_block" style="display: none;">
|
|
|
- <label for="pricing_code_description" class="form-label">Расшифровка шифра расценки</label>
|
|
|
- <input type="text"
|
|
|
- class="form-control"
|
|
|
- id="pricing_code_description"
|
|
|
- name="pricing_code_description"
|
|
|
- placeholder="Введите расшифровку для нового шифра"
|
|
|
- {{ hasRole('admin') ? '' : 'readonly' }}>
|
|
|
- <div class="form-text text-muted">Расшифровка будет сохранена в справочник при сохранении запчасти</div>
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="mb-3">
|
|
|
<label for="min_stock" class="form-label">Минимальный остаток</label>
|
|
|
<input type="number"
|
|
|
@@ -206,59 +212,249 @@
|
|
|
@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: 250px;
|
|
|
+ overflow-y: auto;
|
|
|
+ display: none;
|
|
|
+ box-shadow: 0 4px 6px rgba(0,0,0,.1);
|
|
|
+}
|
|
|
+.autocomplete-dropdown .autocomplete-item {
|
|
|
+ padding: 8px 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 .code {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #495057;
|
|
|
+}
|
|
|
+.autocomplete-dropdown .autocomplete-item .description {
|
|
|
+ font-size: 0.875em;
|
|
|
+ color: #6c757d;
|
|
|
+}
|
|
|
+</style>
|
|
|
<script>
|
|
|
-$(document).ready(function() {
|
|
|
+function waitForJQuery(callback) {
|
|
|
+ if (typeof $ !== 'undefined') {
|
|
|
+ callback();
|
|
|
+ } else {
|
|
|
+ setTimeout(function() { waitForJQuery(callback); }, 50);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+waitForJQuery(function() {
|
|
|
+ $(document).ready(function() {
|
|
|
@if(hasRole('admin'))
|
|
|
- // Автозаполнение для № по ТСН
|
|
|
- $('#tsn_number').on('input', function() {
|
|
|
- const code = $(this).val().trim();
|
|
|
- if (code.length > 0) {
|
|
|
- $.get('{{ route('pricing_codes.get_description') }}', {
|
|
|
- type: 'tsn_number',
|
|
|
- code: code
|
|
|
- }, function(data) {
|
|
|
- if (data.description) {
|
|
|
- $('#tsn_number_hint').html('<i class="bi bi-info-circle text-success"></i> ' + data.description).removeClass('text-danger').addClass('text-success');
|
|
|
- $('#tsn_description_block').hide();
|
|
|
- $('#tsn_number_description').val('');
|
|
|
- } else {
|
|
|
- $('#tsn_number_hint').html('<i class="bi bi-exclamation-triangle text-warning"></i> Расшифровка не найдена').removeClass('text-success').addClass('text-warning');
|
|
|
- $('#tsn_description_block').show();
|
|
|
- }
|
|
|
+ //console.log('Autocomplete init started');
|
|
|
+
|
|
|
+ // Общая функция для инициализации автокомплита
|
|
|
+ function initAutocomplete(inputId, dropdownId, descriptionId, hintId, type) {
|
|
|
+ const $input = $('#' + inputId);
|
|
|
+ const $dropdown = $('#' + dropdownId);
|
|
|
+ const $description = $('#' + descriptionId);
|
|
|
+ const $hint = $('#' + hintId);
|
|
|
+ let currentFocus = -1;
|
|
|
+ let searchTimeout;
|
|
|
+
|
|
|
+ //console.log('Init autocomplete for:', inputId, $input.length);
|
|
|
+
|
|
|
+ // Обработка ввода
|
|
|
+ $input.on('input', function() {
|
|
|
+ const query = $(this).val().trim();
|
|
|
+ clearTimeout(searchTimeout);
|
|
|
+ //console.log('Input event:', inputId, query);
|
|
|
+
|
|
|
+ if (query.length > 0) {
|
|
|
+ searchTimeout = setTimeout(function() {
|
|
|
+ // Поиск вариантов для автокомплита
|
|
|
+ $.get('{{ route('pricing_codes.search') }}', {
|
|
|
+ type: type,
|
|
|
+ query: query
|
|
|
+ }).done(function(data) {
|
|
|
+ //console.log('Search result:', data);
|
|
|
+ showDropdown(data, query);
|
|
|
+ }).fail(function(xhr) {
|
|
|
+ console.error('Search error:', xhr.status, xhr.responseText);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Проверка точного соответствия для расшифровки
|
|
|
+ $.get('{{ route('pricing_codes.get_description') }}', {
|
|
|
+ type: type,
|
|
|
+ code: query
|
|
|
+ }).done(function(data) {
|
|
|
+ // console.log('Description result:', data);
|
|
|
+ if (data.description) {
|
|
|
+ $description.val(data.description);
|
|
|
+ $description.prop('readonly', true);
|
|
|
+ $hint.html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
|
|
|
+ } else {
|
|
|
+ $description.val('');
|
|
|
+ $description.prop('readonly', false);
|
|
|
+ $hint.html('<i class="bi bi-plus-circle text-primary"></i> Новый код - заполните расшифровку');
|
|
|
+ }
|
|
|
+ }).fail(function(xhr) {
|
|
|
+ console.error('Description error:', xhr.status, xhr.responseText);
|
|
|
+ });
|
|
|
+ }, 200);
|
|
|
+ } else {
|
|
|
+ $dropdown.hide();
|
|
|
+ $description.val('');
|
|
|
+ $description.prop('readonly', false);
|
|
|
+ $hint.text('');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Показать выпадающий список
|
|
|
+ function showDropdown(items, query) {
|
|
|
+ $dropdown.empty();
|
|
|
+ currentFocus = -1;
|
|
|
+
|
|
|
+ if (items.length === 0) {
|
|
|
+ $dropdown.hide();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ items.forEach(function(item, index) {
|
|
|
+ const $item = $('<div class="autocomplete-item"></div>');
|
|
|
+ const highlightedCode = highlightMatch(item.code, query);
|
|
|
+ const highlightedDesc = item.description ? highlightMatch(item.description, query) : '';
|
|
|
+
|
|
|
+ $item.html('<div class="code">' + highlightedCode + '</div>' +
|
|
|
+ (item.description ? '<div class="description">' + highlightedDesc + '</div>' : ''));
|
|
|
+ $item.data('code', item.code);
|
|
|
+ $item.data('description', item.description || '');
|
|
|
+
|
|
|
+ $item.on('click', function() {
|
|
|
+ selectItem($(this));
|
|
|
+ });
|
|
|
+
|
|
|
+ $dropdown.append($item);
|
|
|
});
|
|
|
- } else {
|
|
|
- $('#tsn_number_hint').text('');
|
|
|
- $('#tsn_description_block').hide();
|
|
|
+
|
|
|
+ $dropdown.show();
|
|
|
}
|
|
|
- });
|
|
|
|
|
|
- // Автозаполнение для шифра расценки
|
|
|
- $('#pricing_code').on('input', function() {
|
|
|
- const code = $(this).val().trim();
|
|
|
+ // Подсветка совпадения
|
|
|
+ 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.val($item.data('code'));
|
|
|
+ $description.val($item.data('description'));
|
|
|
+ $description.prop('readonly', true);
|
|
|
+ $hint.html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
|
|
|
+ $dropdown.hide();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Навигация клавиатурой
|
|
|
+ $input.on('keydown', function(e) {
|
|
|
+ const $items = $dropdown.find('.autocomplete-item');
|
|
|
+
|
|
|
+ if (e.keyCode === 40) { // Стрелка вниз
|
|
|
+ e.preventDefault();
|
|
|
+ currentFocus++;
|
|
|
+ if (currentFocus >= $items.length) currentFocus = 0;
|
|
|
+ setActive($items);
|
|
|
+ } else if (e.keyCode === 38) { // Стрелка вверх
|
|
|
+ e.preventDefault();
|
|
|
+ currentFocus--;
|
|
|
+ if (currentFocus < 0) currentFocus = $items.length - 1;
|
|
|
+ setActive($items);
|
|
|
+ } else if (e.keyCode === 13) { // Enter
|
|
|
+ e.preventDefault();
|
|
|
+ if (currentFocus > -1 && $items.length > 0) {
|
|
|
+ selectItem($items.eq(currentFocus));
|
|
|
+ }
|
|
|
+ } else if (e.keyCode === 27) { // Escape
|
|
|
+ $dropdown.hide();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ function setActive($items) {
|
|
|
+ $items.removeClass('active');
|
|
|
+ if (currentFocus >= 0 && currentFocus < $items.length) {
|
|
|
+ $items.eq(currentFocus).addClass('active');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Закрытие при клике вне
|
|
|
+ $(document).on('click', function(e) {
|
|
|
+ if (!$(e.target).closest('#' + inputId + ', #' + dropdownId).length) {
|
|
|
+ $dropdown.hide();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // При фокусе показать список если есть значение
|
|
|
+ $input.on('focus', function() {
|
|
|
+ const query = $(this).val().trim();
|
|
|
+ if (query.length > 0) {
|
|
|
+ $.get('{{ route('pricing_codes.search') }}', {
|
|
|
+ type: type,
|
|
|
+ query: query
|
|
|
+ }, function(data) {
|
|
|
+ showDropdown(data, query);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Инициализация автокомплитов
|
|
|
+ initAutocomplete('tsn_number', 'tsn_number_dropdown', 'tsn_number_description', 'tsn_number_hint', 'tsn_number');
|
|
|
+ initAutocomplete('pricing_code', 'pricing_code_dropdown', 'pricing_code_description', 'pricing_code_hint', 'pricing_code');
|
|
|
+
|
|
|
+ // Загрузка расшифровок при открытии страницы
|
|
|
+ function loadInitialDescription(inputId, descriptionId, hintId, type) {
|
|
|
+ const code = $('#' + inputId).val().trim();
|
|
|
+ //console.log('Load initial for:', inputId, 'code:', code);
|
|
|
if (code.length > 0) {
|
|
|
$.get('{{ route('pricing_codes.get_description') }}', {
|
|
|
- type: 'pricing_code',
|
|
|
+ type: type,
|
|
|
code: code
|
|
|
- }, function(data) {
|
|
|
+ }).done(function(data) {
|
|
|
+ //console.log('Initial description for', inputId, ':', data);
|
|
|
if (data.description) {
|
|
|
- $('#pricing_code_hint').html('<i class="bi bi-info-circle text-success"></i> ' + data.description).removeClass('text-danger').addClass('text-success');
|
|
|
- $('#pricing_code_description_block').hide();
|
|
|
- $('#pricing_code_description').val('');
|
|
|
+ $('#' + descriptionId).val(data.description);
|
|
|
+ $('#' + descriptionId).prop('readonly', true);
|
|
|
+ $('#' + hintId).html('<i class="bi bi-check-circle text-success"></i> Код найден в справочнике');
|
|
|
} else {
|
|
|
- $('#pricing_code_hint').html('<i class="bi bi-exclamation-triangle text-warning"></i> Расшифровка не найдена').removeClass('text-success').addClass('text-warning');
|
|
|
- $('#pricing_code_description_block').show();
|
|
|
+ $('#' + descriptionId).prop('readonly', false);
|
|
|
+ $('#' + hintId).html('<i class="bi bi-plus-circle text-primary"></i> Новый код - заполните расшифровку');
|
|
|
}
|
|
|
+ }).fail(function(xhr) {
|
|
|
+ console.error('Initial description error:', xhr.status, xhr.responseText);
|
|
|
});
|
|
|
- } else {
|
|
|
- $('#pricing_code_hint').text('');
|
|
|
- $('#pricing_code_description_block').hide();
|
|
|
}
|
|
|
- });
|
|
|
+ }
|
|
|
|
|
|
- // Триггер при загрузке страницы
|
|
|
- $('#tsn_number').trigger('input');
|
|
|
- $('#pricing_code').trigger('input');
|
|
|
+ loadInitialDescription('tsn_number', 'tsn_number_description', 'tsn_number_hint', 'tsn_number');
|
|
|
+ loadInitialDescription('pricing_code', 'pricing_code_description', 'pricing_code_hint', 'pricing_code');
|
|
|
+ //console.log('Autocomplete init complete');
|
|
|
@endif
|
|
|
+ });
|
|
|
});
|
|
|
</script>
|
|
|
@endpush
|