408 lines
20 KiB
PHP
408 lines
20 KiB
PHP
<x-app-layout>
|
|
<x-slot name="header">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
|
{{ __('Gestione Articoli') }}
|
|
</h2>
|
|
<div class="flex items-center space-x-4">
|
|
<button type="button" id="btn-stampa-qr" onclick="stampaQrSelezionati()"
|
|
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded hidden">
|
|
Stampa QR CODE (<span id="count-selezionati">0</span>)
|
|
</button>
|
|
<button type="button" onclick="openImportModal()"
|
|
class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
|
Importa Excel
|
|
</button>
|
|
<a href="{{ route('admin.articoli.create') }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
|
+ Nuovo Articolo
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
|
|
<div class="py-12">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
@if (session('success'))
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
|
{{ session('success') }}
|
|
</div>
|
|
@endif
|
|
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6">
|
|
<!-- Search Form -->
|
|
<form method="GET" action="{{ route('admin.articoli.index') }}" class="mb-6">
|
|
<div class="flex gap-4">
|
|
<input type="text" name="search" value="{{ request('search') }}"
|
|
placeholder="Cerca per codice, descrizione o ciclo..."
|
|
class="flex-1 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
|
<button type="submit" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
|
|
Cerca
|
|
</button>
|
|
@if(request('search'))
|
|
<a href="{{ route('admin.articoli.index') }}" class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
|
|
Reset
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Table -->
|
|
<table class="w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-10">
|
|
<input type="checkbox" id="select-all" onclick="toggleSelectAll()"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
|
</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Codice</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ciclo</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Descrizione</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Posizione</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Quantita</th>
|
|
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@forelse ($articoli as $articolo)
|
|
<tr class="{{ $loop->even ? 'bg-gray-100' : 'bg-white' }} hover:bg-blue-50 cursor-pointer" onclick="goToDetail(event, '{{ route('admin.articoli.show', $articolo) }}')">
|
|
<td class="px-3 py-2 whitespace-nowrap" onclick="event.stopPropagation()">
|
|
<input type="checkbox" class="articolo-checkbox rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
value="{{ $articolo->id }}" onchange="updateSelection()">
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
{{ $articolo->codice_articolo }}
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $articolo->ciclo }}
|
|
</td>
|
|
<td class="px-3 py-2 text-sm text-gray-500 truncate max-w-xs">
|
|
{{ $articolo->descrizione }}
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $articolo->posizione }}
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $articolo->quantita }}
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm font-medium space-x-2" onclick="event.stopPropagation()">
|
|
<a href="{{ route('admin.articoli.show', $articolo) }}" class="text-blue-600 hover:text-blue-900">Dettagli</a>
|
|
<a href="{{ route('admin.articoli.edit', $articolo) }}" class="text-indigo-600 hover:text-indigo-900">Modifica</a>
|
|
<form action="{{ route('admin.articoli.destroy', $articolo) }}" method="POST" class="inline" onsubmit="return confirm('Sei sicuro di voler eliminare questo articolo?');">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="text-red-600 hover:text-red-900">Elimina</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" class="px-3 py-4 text-center text-gray-500">
|
|
Nessun articolo trovato.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
<div class="mt-4">
|
|
{{ $articoli->withQueryString()->links() }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import Modal Overlay -->
|
|
<div id="import-modal" class="fixed inset-0 z-50" style="display: none;">
|
|
<!-- Backdrop -->
|
|
<div class="fixed inset-0" style="background-color: rgba(0, 0, 0, 0.5);" onclick="closeImportModal()"></div>
|
|
<!-- Modal Content -->
|
|
<div class="fixed inset-0 flex items-center justify-center p-4">
|
|
<div class="relative bg-white rounded-lg shadow-xl w-half max-w-md p-6">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-medium text-gray-900">Importa Articoli da Excel</h3>
|
|
<button onclick="closeImportModal()" class="text-gray-400 hover:text-gray-600">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<form id="import-form" enctype="multipart/form-data">
|
|
@csrf
|
|
<!-- Drop Zone -->
|
|
<div id="drop-zone" class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer hover:border-blue-500 transition-colors">
|
|
<svg style="width: 48px; height: 48px;" class="mx-auto text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
|
</svg>
|
|
<p class="mt-2 text-sm text-gray-600">
|
|
<span class="font-semibold text-blue-600">Clicca per selezionare</span> o trascina un file Excel
|
|
</p>
|
|
<p class="mt-1 text-xs text-gray-500">Solo file .xlsx o .xls</p>
|
|
<input type="file" id="file-input" name="file" accept=".xlsx,.xls" class="hidden">
|
|
</div>
|
|
|
|
<!-- Selected File -->
|
|
<div id="selected-file" class="hidden mt-4 p-3 bg-gray-50 rounded-lg flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<span id="file-name" class="ml-2 text-sm text-gray-700"></span>
|
|
</div>
|
|
<button type="button" onclick="clearFile()" class="text-red-500 hover:text-red-700">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Clean Import Checkbox -->
|
|
<div class="mt-4">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="clean-import" name="clean_import" value="1"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
|
<span class="ml-2 text-sm text-gray-700">Importazione pulita</span>
|
|
</label>
|
|
<p class="mt-1 ml-6 text-xs text-gray-500">
|
|
Cancella tutti gli articoli esistenti prima dell'importazione
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div id="progress-container" class="hidden mt-4">
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
|
<div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
<p id="progress-text" class="text-sm text-gray-600 mt-2 text-center">Caricamento in corso...</p>
|
|
</div>
|
|
|
|
<!-- Result Message -->
|
|
<div id="result-message" class="hidden mt-4 p-3 rounded-lg"></div>
|
|
|
|
<!-- Buttons -->
|
|
<div class="mt-6 flex justify-end space-x-4">
|
|
<button type="button" onclick="closeImportModal()"
|
|
style="padding: 8px 16px; background-color: #d1d5db; color: #374151; border-radius: 6px; border: none; cursor: pointer;">
|
|
Annulla
|
|
</button>
|
|
<button type="submit" id="import-btn"
|
|
style="padding: 8px 16px; background-color: #9ca3af; color: white; border-radius: 6px; border: none; cursor: not-allowed;">
|
|
Importa
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function goToDetail(event, url) {
|
|
window.location.href = url;
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
const selectAll = document.getElementById('select-all');
|
|
const checkboxes = document.querySelectorAll('.articolo-checkbox');
|
|
checkboxes.forEach(cb => cb.checked = selectAll.checked);
|
|
updateSelection();
|
|
}
|
|
|
|
function updateSelection() {
|
|
const checkboxes = document.querySelectorAll('.articolo-checkbox:checked');
|
|
const count = checkboxes.length;
|
|
const btn = document.getElementById('btn-stampa-qr');
|
|
const countSpan = document.getElementById('count-selezionati');
|
|
|
|
countSpan.textContent = count;
|
|
|
|
if (count > 0) {
|
|
btn.classList.remove('hidden');
|
|
} else {
|
|
btn.classList.add('hidden');
|
|
}
|
|
|
|
// Update select-all checkbox state
|
|
const allCheckboxes = document.querySelectorAll('.articolo-checkbox');
|
|
const selectAll = document.getElementById('select-all');
|
|
selectAll.checked = allCheckboxes.length > 0 && count === allCheckboxes.length;
|
|
selectAll.indeterminate = count > 0 && count < allCheckboxes.length;
|
|
}
|
|
|
|
function stampaQrSelezionati() {
|
|
const checkboxes = document.querySelectorAll('.articolo-checkbox:checked');
|
|
const ids = Array.from(checkboxes).map(cb => cb.value);
|
|
|
|
if (ids.length === 0) {
|
|
alert('Seleziona almeno un articolo');
|
|
return;
|
|
}
|
|
|
|
// Open PDF in new window
|
|
const url = '{{ route('admin.articoli.print-qrcodes') }}?ids=' + ids.join(',');
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
// Import Modal Functions
|
|
const dropZone = document.getElementById('drop-zone');
|
|
const fileInput = document.getElementById('file-input');
|
|
const importForm = document.getElementById('import-form');
|
|
const importBtn = document.getElementById('import-btn');
|
|
const selectedFile = document.getElementById('selected-file');
|
|
const fileName = document.getElementById('file-name');
|
|
const progressContainer = document.getElementById('progress-container');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const progressText = document.getElementById('progress-text');
|
|
const resultMessage = document.getElementById('result-message');
|
|
|
|
function openImportModal() {
|
|
document.getElementById('import-modal').style.display = 'flex';
|
|
resetImportForm();
|
|
}
|
|
|
|
function closeImportModal() {
|
|
document.getElementById('import-modal').style.display = 'none';
|
|
resetImportForm();
|
|
}
|
|
|
|
function resetImportForm() {
|
|
importForm.reset();
|
|
fileInput.value = '';
|
|
selectedFile.classList.add('hidden');
|
|
dropZone.classList.remove('hidden');
|
|
progressContainer.classList.add('hidden');
|
|
resultMessage.classList.add('hidden');
|
|
importBtn.disabled = true;
|
|
progressBar.style.width = '0%';
|
|
}
|
|
|
|
function clearFile() {
|
|
fileInput.value = '';
|
|
selectedFile.classList.add('hidden');
|
|
dropZone.classList.remove('hidden');
|
|
importBtn.disabled = true;
|
|
importBtn.style.backgroundColor = '#9ca3af';
|
|
importBtn.style.cursor = 'not-allowed';
|
|
}
|
|
|
|
function handleFileSelect(file) {
|
|
if (file && (file.name.endsWith('.xlsx') || file.name.endsWith('.xls'))) {
|
|
const dataTransfer = new DataTransfer();
|
|
dataTransfer.items.add(file);
|
|
fileInput.files = dataTransfer.files;
|
|
|
|
fileName.textContent = file.name;
|
|
selectedFile.classList.remove('hidden');
|
|
dropZone.classList.add('hidden');
|
|
importBtn.disabled = false;
|
|
importBtn.style.backgroundColor = '#2563eb';
|
|
importBtn.style.cursor = 'pointer';
|
|
resultMessage.classList.add('hidden');
|
|
} else {
|
|
alert('Per favore seleziona un file Excel (.xlsx o .xls)');
|
|
}
|
|
}
|
|
|
|
// Click to upload
|
|
dropZone.addEventListener('click', () => fileInput.click());
|
|
|
|
// File input change
|
|
fileInput.addEventListener('change', (e) => {
|
|
if (e.target.files.length > 0) {
|
|
handleFileSelect(e.target.files[0]);
|
|
}
|
|
});
|
|
|
|
// Drag and drop
|
|
dropZone.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.add('border-blue-500', 'bg-blue-50');
|
|
});
|
|
|
|
dropZone.addEventListener('dragleave', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.remove('border-blue-500', 'bg-blue-50');
|
|
});
|
|
|
|
dropZone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.remove('border-blue-500', 'bg-blue-50');
|
|
|
|
if (e.dataTransfer.files.length > 0) {
|
|
handleFileSelect(e.dataTransfer.files[0]);
|
|
}
|
|
});
|
|
|
|
// Form submit
|
|
importForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(importForm);
|
|
formData.append('clean_import', document.getElementById('clean-import').checked ? '1' : '0');
|
|
|
|
// Show progress
|
|
progressContainer.classList.remove('hidden');
|
|
importBtn.disabled = true;
|
|
progressBar.style.width = '30%';
|
|
progressText.textContent = 'Caricamento file...';
|
|
|
|
try {
|
|
const response = await fetch('{{ route('admin.articoli.import') }}', {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
}
|
|
});
|
|
|
|
progressBar.style.width = '70%';
|
|
progressText.textContent = 'Elaborazione in corso...';
|
|
|
|
const data = await response.json();
|
|
|
|
progressBar.style.width = '100%';
|
|
|
|
if (data.success) {
|
|
resultMessage.className = 'mt-4 p-3 rounded-lg bg-green-100 text-green-700';
|
|
resultMessage.textContent = data.message;
|
|
progressText.textContent = 'Completato!';
|
|
|
|
// Reload page after 2 seconds
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 2000);
|
|
} else {
|
|
resultMessage.className = 'mt-4 p-3 rounded-lg bg-red-100 text-red-700';
|
|
resultMessage.textContent = data.message;
|
|
progressContainer.classList.add('hidden');
|
|
importBtn.disabled = false;
|
|
}
|
|
|
|
resultMessage.classList.remove('hidden');
|
|
} catch (error) {
|
|
progressContainer.classList.add('hidden');
|
|
resultMessage.className = 'mt-4 p-3 rounded-lg bg-red-100 text-red-700';
|
|
resultMessage.textContent = 'Errore durante l\'importazione: ' + error.message;
|
|
resultMessage.classList.remove('hidden');
|
|
importBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Close modal on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
closeImportModal();
|
|
}
|
|
});
|
|
|
|
// Close modal on outside click
|
|
document.getElementById('import-modal').addEventListener('click', (e) => {
|
|
if (e.target === document.getElementById('import-modal')) {
|
|
closeImportModal();
|
|
}
|
|
});
|
|
</script>
|
|
</x-app-layout>
|