🎨 CSV Imports - Miglioramenti UI e UX
🎨 CSV Imports - Miglioramenti UI e UX
Data: 21 Ottobre 2025
Componente: csv-imports/index.blade.php
Controller: CsvImportsController.php
---
📋 Riepilogo Modifiche
Questo documento descrive i miglioramenti apportati all'interfaccia della coda di importazione CSV, con focus su usabilità, design e funzionalità.
---
🔧 1. Correzione Errori Server-Side (500)
Problema
Errore 500 quando si applicavano i filtri a causa di ambiguità nei nomi delle colonne nelle query con JOIN.Soluzione
``php
// ❌ PRIMA (ambiguo)
$query->where('machine_id', $request->machine_id);
$query->where('status', $request->status);// ✅ DOPO (specifico)
$query->where('csv_imports.machine_id', $request->machine_id);
$query->where('csv_imports.status', $request->status);
$query->where('csv_imports.source_channel', $request->source_channel);
`Altri Fix
- Aggiunto prefisso tabella
csv_imports. a tutti i filtri
Corretto campo ordinamento default: arrived_at → csv_imports.arrived_at
Rimosso ->latest('arrived_at') che causava conflitti con ordinamento dinamico
Aggiunto try-catch con logging per debugging futuro File modificato:
app/Http/Controllers/Admin/I40/CsvImportsController.php---
🔍 2. Rimozione Toolbar Bootstrap Table + Ricerca Personalizzata
Problema
La toolbar di Bootstrap Table occupava spazio e non era integrata con i filtri esistenti.Soluzione
A. Disabilitazione Toolbar
`javascript
$('#csvTable').bootstrapTable({
search: false, // Disabilita ricerca integrata
showSearch: false, // Nasconde toolbar ricerca
toolbar: false, // Disabilita completamente toolbar
showColumns: false, // Nasconde selezione colonne
showRefresh: false, // Nasconde refresh
showToggle: false, // Nasconde toggle
showFullscreen: false, // Nasconde fullscreen
showExport: false // Nasconde export
});
`B. CSS Forzato
`css
.fixed-table-toolbar {
display: none !important;
}.bootstrap-table .search,
.bootstrap-table .search input {
display: none !important;
}
`C. Campo Ricerca Personalizzato
Aggiunto nella barra filtri:
`html
<div class="col-md-4">
<label class="form-label small mb-1">🔍 Ricerca</label>
<div class="input-group input-group-sm">
<input type="text" id="filter_search"
placeholder="Cerca..."
onkeyup="handleSearchInput(event)">
<button onclick="clearSearch()">
<i class="bi bi-x"></i>
</button>
</div>
</div>
`D. Funzionalità JavaScript
`javascript
// Debounce 500ms per evitare troppe richieste
let searchTimeout;
function handleSearchInput(event) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch();
}, 500);
}// Esegue la ricerca
function performSearch() {
const searchValue = document.getElementById('filter_search').value;
saveFilters(); // Salva in sessionStorage
$('#csvTable').bootstrapTable('refresh'); // Ricarica tabella
}
// Pulisci solo la ricerca
function clearSearch() {
document.getElementById('filter_search').value = '';
performSearch();
}
`E. Integrazione Server-Side
`javascript
queryParams: function(params) {
return {
limit: params.limit,
offset: params.offset,
search: document.getElementById('filter_search').value, // Campo personalizzato
sort: params.sort,
order: params.order,
machine_id: urlParams.get('machine_id'),
status: urlParams.get('status'),
source_channel: urlParams.get('source_channel')
};
}
`Ricerca globale nel controller su:
filename, machine.name, profile.name, source_channel, status.---
📐 3. Compattazione Barra Filtri
Prima (Layout Disordinato)
4 colonne variabili
Spazi non ottimizzati
Testo lungo ("Tutte le macchine", "Upload Web") Dopo (Layout Compatto)
`html
<form class="row g-2 align-items-end">
<div class="col-md-3">Macchina</div> <!-- 25% -->
<div class="col-md-2">Stato</div> <!-- 16.6% -->
<div class="col-md-2">Canale</div> <!-- 16.6% -->
<div class="col-md-4">🔍 Ricerca</div> <!-- 33.3% -->
<div class="col-md-1">Reset</div> <!-- 8.3% -->
</form>
`Miglioramenti
✅ Tutto in una riga su schermi medi/grandi
✅ Più spazio per ricerca (33% invece di 25%)
✅ Testi abbreviati: "Tutte le macchine" → "Tutte"
✅ Icona ricerca 🔍 per riconoscibilità immediata
✅ Bottone Reset rosso con testo chiaro
✅ Spacing ridotto: g-3 → g-2, mb-1 per label---
🎨 4. Redesign Card Statistiche
Design Moderno con Gradienti
Prima (Colori Solidi)
`html
<div class="card bg-success text-white">
<h3>{{ $stats['validated'] }}</h3>
<small>Validati</small>
</div>
`Dopo (Gradienti + Icone)
`html
<div class="card border-0 shadow-sm stat-card stat-card-success">
<div class="card-body text-center py-2">
<div class="stat-icon mb-1">
<i class="bi bi-check-circle fs-5"></i>
</div>
<h4 class="mb-0 fw-bold">{{ $stats['validated'] }}</h4>
<small class="text-uppercase fw-semibold">Validati</small>
</div>
</div>
`---
🎭 Icone Contestuali
| Stato | Icona | Descrizione |
|-------|-------|-------------|
| In Attesa |
bi-clock-history | Orologio con cronologia |
| Validati | bi-check-circle | Cerchio con spunta |
| In Pausa | bi-pause-circle | Cerchio con pausa |
| Falliti | bi-exclamation-circle | Cerchio con esclamativo |
| Oggi | bi-calendar-check | Calendario con spunta |
| Totali | bi-list-check | Lista con spunta |---
🎨 Palette Colori con Gradienti
`css
/ Success (Verde) /
background: linear-gradient(135deg, #d1f4e0 0%, #e8f8f0 100%);
color: #0d6832;
icon-color: #198754;/ Warning (Giallo) /
background: linear-gradient(135deg, #fff3cd 0%, #fff8e1 100%);
color: #856404;
icon-color: #fd7e14;
/ Danger (Rosso) /
background: linear-gradient(135deg, #f8d7da 0%, #fde8e9 100%);
color: #721c24;
icon-color: #dc3545;
/ Primary (Blu) /
background: linear-gradient(135deg, #cfe2ff 0%, #e7f1ff 100%);
color: #084298;
icon-color: #0d6efd;
/ Info (Cyan) /
background: linear-gradient(135deg, #d1ecf1 0%, #e8f4f8 100%);
color: #055160;
icon-color: #0dcaf0;
/ Default (Grigio) /
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
color: inherit;
icon-color: #6c757d;
`---
✨ Effetti Hover Eleganti
`css
.stat-card {
transition: all 0.3s ease;
cursor: pointer;
}.stat-card:hover {
transform: translateY(-5px); / Sollevamento 3D /
box-shadow: 0 8px 16px rgba(0,0,0,0.15) !important; / Ombra dinamica /
animation: pulse-glow 2s infinite; / Pulse continuo /
}
.stat-card:hover .stat-icon {
transform: scale(1.1); / Zoom icone +10% /
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.1); }
50% { box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15); }
}
`---
📏 Dimensioni Ottimizzate (Bilanciamento)
Versione Iniziale (Troppo Grande)
Icone: fs-3 (1.75rem)
Numeri: <h2> (2rem)
Padding: py-3 (1rem)Versione Finale (Equilibrata)
Icone: fs-5 (1.25rem) → -28%
Numeri: <h4> (1.5rem) → -25%
Padding: py-2 (0.5rem) → -50%
Label: 0.7rem, letter-spacing: 0.3px---
📂 File Modificati
1. Controller
File: app/Http/Controllers/Admin/I40/CsvImportsController.phpMetodo:
getCsvImportsData(Request $request)Modifiche:
✅ Prefisso csv_imports. su tutti i filtri
✅ Ordinamento corretto con prefissi tabella
✅ Try-catch per gestione errori
✅ Logging dettagliato per debug ---
2. View
File: resources/views/admin/i40/csv-imports/index.blade.phpSezioni Modificate:
A. CSS (Linee 6-111)
Nasconde toolbar Bootstrap Table
Stili card statistiche con gradienti
Effetti hover e animazioni
Dimensioni ottimizzate B. Card Statistiche (Linee 193-261)
6 card con icone contestuali
Gradienti di sfondo per ogni stato
Dimensioni h4 + fs-5 + py-2
Effetti hover eleganti C. Barra Filtri (Linee 263-289)
Layout compatto 3-2-2-4-1
Campo ricerca personalizzato con debounce
Testi abbreviati
Bottone Reset rosso D. JavaScript Filtri (Linee 693-760)
handleSearchInput() - Debounce 500ms
performSearch() - Ricarica tabella
clearSearch() - Pulisce solo ricerca
saveFilters() - Include campo search
loadFilters() - Carica search da sessionStorageE. Bootstrap Table Config (Linee 629-667)
Disabilitata toolbar completamente
queryParams usa campo personalizzato
Ordinamento e paginazione server-side ---
✅ Risultati Finali
Performance
✅ Errore 500 risolto completamente
✅ Query ottimizzate con prefissi corretti
✅ Filtri persistenti tra azioni UX/UI
✅ Ricerca integrata nella barra filtri (no toolbar separata)
✅ Layout compatto e professionale
✅ Card statistiche eleganti con hover effects
✅ Icone contestuali per riconoscimento rapido
✅ Gradienti moderni e armoniosi Funzionalità
✅ Ricerca globale server-side
✅ Debounce per ottimizzazione richieste
✅ Filtri salvati in sessionStorage
✅ Reset granulare (solo ricerca o tutti filtri)
✅ Ordinamento colonne funzionante
✅ Paginazione AJAX server-side ---
🚀 Prossimi Passi Suggeriti
1. Click su Card: Rendere le card statistiche cliccabili per applicare il filtro stato corrispondente
2. Indicatori Visivi: Badge o badge-pill per evidenziare filtri attivi
3. Export CSV: Aggiungere bottone export per i risultati filtrati
4. Quick Actions: Bulk actions (valida tutti, elabora tutti) per CSV selezionati
5. Grafici: Mini-grafici sparkline nelle card per trend temporali
---
📝 Note Tecniche
Bootstrap Table: Versione server-side con AJAX pagination
Bootstrap Icons: bi-*` per tutte le icone
---
Fine Documento
Analisi Codice
Blocco 1 php
// ❌ PRIMA (ambiguo)
$query->where('machine_id', $request->machine_id);
$query->where('status', $request->status);
// ✅ DOPO (specifico)
$query->where('csv_imports.machine_id', $request->machine_id);
$query->where('csv_imports.status', $request->status);
$query->where('csv_imports.source_channel', $request->source_channel);
Blocco 2 javascript
$('#csvTable').bootstrapTable({
search: false, // Disabilita ricerca integrata
showSearch: false, // Nasconde toolbar ricerca
toolbar: false, // Disabilita completamente toolbar
showColumns: false, // Nasconde selezione colonne
showRefresh: false, // Nasconde refresh
showToggle: false, // Nasconde toggle
showFullscreen: false, // Nasconde fullscreen
showExport: false // Nasconde export
});
Blocco 3 css
.fixed-table-toolbar {
display: none !important;
}
.bootstrap-table .search,
.bootstrap-table .search input {
display: none !important;
}
Blocco 4 html
<div class="col-md-4">
<label class="form-label small mb-1">🔍 Ricerca</label>
<div class="input-group input-group-sm">
<input type="text" id="filter_search"
placeholder="Cerca..."
onkeyup="handleSearchInput(event)">
<button onclick="clearSearch()">
<i class="bi bi-x"></i>
</button>
</div>
</div>
Blocco 5 javascript
// Debounce 500ms per evitare troppe richieste
let searchTimeout;
function handleSearchInput(event) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch();
}, 500);
}
// Esegue la ricerca
function performSearch() {
const searchValue = document.getElementById('filter_search').value;
saveFilters(); // Salva in sessionStorage
$('#csvTable').bootstrapTable('refresh'); // Ricarica tabella
}
// Pulisci solo la ricerca
function clearSearch() {
document.getElementById('filter_search').value = '';
performSearch();
}
Blocco 6 javascript
queryParams: function(params) {
return {
limit: params.limit,
offset: params.offset,
search: document.getElementById('filter_search').value, // Campo personalizzato
sort: params.sort,
order: params.order,
machine_id: urlParams.get('machine_id'),
status: urlParams.get('status'),
source_channel: urlParams.get('source_channel')
};
}
Blocco 7 html
<form class="row g-2 align-items-end">
<div class="col-md-3">Macchina</div> <!-- 25% -->
<div class="col-md-2">Stato</div> <!-- 16.6% -->
<div class="col-md-2">Canale</div> <!-- 16.6% -->
<div class="col-md-4">🔍 Ricerca</div> <!-- 33.3% -->
<div class="col-md-1">Reset</div> <!-- 8.3% -->
</form>
Blocco 8 html
<div class="card bg-success text-white">
<h3>{{ $stats['validated'] }}</h3>
<small>Validati</small>
</div>
Blocco 9 html
<div class="card border-0 shadow-sm stat-card stat-card-success">
<div class="card-body text-center py-2">
<div class="stat-icon mb-1">
<i class="bi bi-check-circle fs-5"></i>
</div>
<h4 class="mb-0 fw-bold">{{ $stats['validated'] }}</h4>
<small class="text-uppercase fw-semibold">Validati</small>
</div>
</div>
Blocco 10 css
/* Success (Verde) */
background: linear-gradient(135deg, #d1f4e0 0%, #e8f8f0 100%);
color: #0d6832;
icon-color: #198754;
/* Warning (Giallo) */
background: linear-gradient(135deg, #fff3cd 0%, #fff8e1 100%);
color: #856404;
icon-color: #fd7e14;
/* Danger (Rosso) */
background: linear-gradient(135deg, #f8d7da 0%, #fde8e9 100%);
color: #721c24;
icon-color: #dc3545;
/* Primary (Blu) */
background: linear-gradient(135deg, #cfe2ff 0%, #e7f1ff 100%);
color: #084298;
icon-color: #0d6efd;
/* Info (Cyan) */
background: linear-gradient(135deg, #d1ecf1 0%, #e8f4f8 100%);
color: #055160;
icon-color: #0dcaf0;
/* Default (Grigio) */
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
color: inherit;
icon-color: #6c757d;
Blocco 11 css
.stat-card {
transition: all 0.3s ease;
cursor: pointer;
}
.stat-card:hover {
transform: translateY(-5px); /* Sollevamento 3D */
box-shadow: 0 8px 16px rgba(0,0,0,0.15) !important; /* Ombra dinamica */
animation: pulse-glow 2s infinite; /* Pulse continuo */
}
.stat-card:hover .stat-icon {
transform: scale(1.1); /* Zoom icone +10% */
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.1); }
50% { box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15); }
}