🎨 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_atcsv_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-3g-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.php

    Metodo: 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.php

    Sezioni 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 sessionStorage
  • E. 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
  • CSS Custom: Inline nel blade per evitare problemi di caching
  • JavaScript: jQuery + vanilla JS per compatibilità
  • Responsive: Layout adattivo con breakpoint Bootstrap 5

---

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); }
}