Files
htt-locator/resources/views/admin/articoli/index.blade.php
2026-01-18 12:23:37 +01:00

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>