Upload CSV Post-Creazione Profilo

Upload CSV Post-Creazione Profilo

🎯 Funzionalità

Permette di caricare un file CSV per profili creati manualmente (senza file iniziale) per:

  • Generare automaticamente il mapping colonne
  • Aggiungere sample data
  • Salvare file esempio
  • ---

    πŸ“‹ Caso d'Uso

    Scenario

    1. Admin crea profilo manualmente via SQL (senza CSV) 2. Profilo ha solo name, columns Γ¨ vuoto/NULL 3. Admin visualizza profilo β†’ vede form upload 4. Carica CSV β†’ Sistema analizza e aggiorna profilo

    ---

    πŸ”§ Implementazione

    1. Route

    File: routes/i40.php

    ``php Route::post('/{importProfile}/upload-csv', [ImportProfilesController::class, 'uploadCsv']) ->name('upload-csv'); `

    2. Controller Method

    File: app/Http/Controllers/Admin/I40/ImportProfilesController.php

    `php public function uploadCsv(Request $request, ImportProfile $importProfile) { $validated = $request->validate([ 'csv_file' => 'required|file|mimes:csv,txt|max:10240', ]);

    // Analizza CSV $analysis = $this->analyzer->analyze($request->file('csv_file'));

    // Elimina vecchio file se esiste if ($importProfile->sample_file_path) { Storage::disk('public')->delete($importProfile->sample_file_path); }

    // Salva nuovo file $path = $request->file('csv_file')->store('I40/import-profiles', 'public');

    // Aggiorna profilo $importProfile->update([ 'delimiter' => $analysis['delimiter'], 'encoding' => $analysis['encoding'], 'has_header' => true, 'columns' => $analysis['suggested_mapping'], 'sample_data' => $analysis['sample_data'], 'sample_file_name' => $request->file('csv_file')->getClientOriginalName(), 'sample_file_path' => $path, ]);

    return response()->json([ 'success' => true, 'message' => 'CSV caricato e analizzato con successo!', 'data' => [ 'columns' => $importProfile->columns, 'sample_file_name' => $importProfile->sample_file_name, // ... altri dati ] ]); } `

    3. UI Modal - Conditional Render

    File: resources/views/admin/i40/import-profiles/index.blade.php

    `javascript // Se c'Γ¨ file β†’ Mostra download ${data.sample_file_name && data.sample_file_name !== 'N/D' ? <i class="bi bi-file-earmark-text"></i> <strong>${data.sample_file_name}</strong> <a href="${data.sample_file_url}" download>Scarica</a> : // Se NON c'Γ¨ file β†’ Mostra form upload <form id="uploadCsvForm"> <input type="file" id="csvFileUpload" accept=".csv,.txt"> <button onclick="uploadCsv(${data.id})">Carica e Analizza</button> </form> } `

    </p><p><h3><strong>4. JavaScript Upload Function</strong></h3></p><p></code>`<code>javascript
    function uploadCsv(id) {
        const fileInput = document.getElementById(&#039;csvFileUpload&#039;);
        const file = fileInput.files[0];
        
        if (!file) {
            alert(&#039;Seleziona un file CSV&#039;);
            return;
        }
        
        const formData = new FormData();
        formData.append(&#039;csv_file&#039;, file);
        
        // Mostra loading
        uploadForm.innerHTML = &#039;&lt;div class=&quot;spinner-border&quot;&gt;Analisi...&lt;/div&gt;&#039;;
        
        fetch(</code>${apiUrl}/${id}/upload-csv<code>, {
            method: &#039;POST&#039;,
            body: formData,
            headers: {&#039;X-CSRF-TOKEN&#039;: csrfToken}
        })
        .then(r =&gt; r.json())
        .then(response =&gt; {
            if (response.success) {
                // Chiudi modal e ricarica
                location.reload();
            } else {
                alert(&#039;Errore: &#039; + response.message);
            }
        });
    }
    </code>`<code></p><p>---</p><p><h2>🎨 UX Flow</h2></p><p><h3><strong>Con File Esistente</strong></h3>
    </code>`<code>
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ File Esempio                    β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚ πŸ“„ produzione_juki.csv          β”‚
    β”‚ [⬇️ Scarica]                    β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    `
    </p><p><h3><strong>Senza File (profilo manuale)</strong></h3>
    </code>`<code>
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ File Esempio                    β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚ ❌ Nessun file CSV associato    β”‚
    β”‚                                 β”‚
    β”‚ Carica CSV per analisi:         β”‚
    β”‚ [Scegli file...]                β”‚
    β”‚ [⬆️ Carica e Analizza]          β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    `
    </p><p><h3><strong>Durante Upload</strong></h3>
    </code>`<code>
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ File Esempio                    β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚ ⏳ Analisi CSV in corso...      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    `
    </p><p><h3><strong>Dopo Upload (reload)</strong></h3>
    </code>`<code>
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ File Esempio                    β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚ πŸ“„ nuovo_file.csv               β”‚
    β”‚ [⬇️ Scarica]                    β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜</p><p>βœ… Toast: &quot;CSV caricato e analizzato con successo!&quot;
    Mapping aggiornato con 5 colonne rilevate
    `

    ---

    βœ… Vantaggi

    1. Completa profili manuali - Profili creati via SQL possono essere completati 2. Analisi automatica - Mapping generato dal sistema 3. UX coerente - Form inline nel modal 4. Feedback immediato - Loading state e notifiche 5. Non invasivo - Appare solo se manca file

    ---

    πŸ”„ Workflow Completo

    Scenario A: Creazione Standard

    ` Upload CSV β†’ Analisi β†’ Salva profilo βœ… Profilo completo con mapping `
    </p><p><h3><strong>Scenario B: Creazione Manuale + Upload Post</strong></h3>
    </code>`<code>
    SQL INSERT β†’ Profilo senza CSV
    ↓
    Visualizza profilo β†’ Form upload visibile
    ↓
    Upload CSV β†’ Analisi β†’ Aggiorna profilo
    βœ… Profilo completato con mapping
    `

    Scenario C: Sostituzione File

    ` Profilo con CSV vecchio ↓ Upload nuovo CSV ↓ Analisi β†’ Mapping aggiornato βœ… File sostituito, mapping rigenerato `
    </p><p>---</p><p><h2>⚠️ Considerazioni</h2></p><p><h3><strong>Immutabilità vs Upload</strong></h3>
    <li>βœ… <strong>Upload Γ¨ permesso</strong>: Non modifica logica business, solo aggiunge dati tecnici</li>
    <li>βœ… <strong>Mapping rigenerato</strong>: Basato sul nuovo CSV</li>
    <li>⚠️ <strong>Se profilo usato</strong>: Potrebbe creare inconsistenza</li></ul></p><p><h3><strong>Possibile Protezione Aggiuntiva (opzionale)</strong></h3>
    `php // Nel controller uploadCsv() if ($importProfile->hasImports()) { return response()->json([ 'success' => false, 'message' => 'Profilo giΓ  utilizzato. Duplicalo prima di modificarlo.' ], 422); } ``

    ---

    πŸ“š Best Practice

    1. Usa sempre analisi automatica (non mapping manuale) 2. Valida CSV prima di salvare 3. Elimina vecchio file quando sostituisci 4. Notifica utente con toast 5. Reload pagina per aggiornare tabella

    ---

    Documento creato: 17 Ottobre 2025 Feature: Upload CSV post-creazione profilo

Analisi Codice

Blocco 1 php
Route::post('/{importProfile}/upload-csv', [ImportProfilesController::class, 'uploadCsv'])
    ->name('upload-csv');
Blocco 2 php
public function uploadCsv(Request $request, ImportProfile $importProfile)
{
    $validated = $request->validate([
        'csv_file' => 'required|file|mimes:csv,txt|max:10240',
    ]);

    // Analizza CSV
    $analysis = $this->analyzer->analyze($request->file('csv_file'));

    // Elimina vecchio file se esiste
    if ($importProfile->sample_file_path) {
        Storage::disk('public')->delete($importProfile->sample_file_path);
    }

    // Salva nuovo file
    $path = $request->file('csv_file')->store('I40/import-profiles', 'public');

    // Aggiorna profilo
    $importProfile->update([
        'delimiter' => $analysis['delimiter'],
        'encoding' => $analysis['encoding'],
        'has_header' => true,
        'columns' => $analysis['suggested_mapping'],
        'sample_data' => $analysis['sample_data'],
        'sample_file_name' => $request->file('csv_file')->getClientOriginalName(),
        'sample_file_path' => $path,
    ]);

    return response()->json([
        'success' => true,
        'message' => 'CSV caricato e analizzato con successo!',
        'data' => [
            'columns' => $importProfile->columns,
            'sample_file_name' => $importProfile->sample_file_name,
            // ... altri dati
        ]
    ]);
}
Blocco 3 javascript
// Se c'Γ¨ file β†’ Mostra download
${data.sample_file_name && data.sample_file_name !== 'N/D' ? `
    <i class="bi bi-file-earmark-text"></i>
    <strong>${data.sample_file_name}</strong>
    <a href="${data.sample_file_url}" download>Scarica</a>
` : `
    // Se NON c'Γ¨ file β†’ Mostra form upload
    <form id="uploadCsvForm">
        <input type="file" id="csvFileUpload" accept=".csv,.txt">
        <button onclick="uploadCsv(${data.id})">Carica e Analizza</button>
    </form>
`}
Blocco 4 javascript
function uploadCsv(id) {
    const fileInput = document.getElementById('csvFileUpload');
    const file = fileInput.files[0];
    
    if (!file) {
        alert('Seleziona un file CSV');
        return;
    }
    
    const formData = new FormData();
    formData.append('csv_file', file);
    
    // Mostra loading
    uploadForm.innerHTML = '<div class="spinner-border">Analisi...</div>';
    
    fetch(`${apiUrl}/${id}/upload-csv`, {
        method: 'POST',
        body: formData,
        headers: {'X-CSRF-TOKEN': csrfToken}
    })
    .then(r => r.json())
    .then(response => {
        if (response.success) {
            // Chiudi modal e ricarica
            location.reload();
        } else {
            alert('Errore: ' + response.message);
        }
    });
}
Blocco 5
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ File Esempio                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ πŸ“„ produzione_juki.csv          β”‚
β”‚ [⬇️ Scarica]                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Blocco 6
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ File Esempio                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ❌ Nessun file CSV associato    β”‚
β”‚                                 β”‚
β”‚ Carica CSV per analisi:         β”‚
β”‚ [Scegli file...]                β”‚
β”‚ [⬆️ Carica e Analizza]          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Blocco 7
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ File Esempio                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ⏳ Analisi CSV in corso...      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Blocco 8
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ File Esempio                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ πŸ“„ nuovo_file.csv               β”‚
β”‚ [⬇️ Scarica]                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

βœ… Toast: "CSV caricato e analizzato con successo!"
Mapping aggiornato con 5 colonne rilevate
Blocco 9
Upload CSV β†’ Analisi β†’ Salva profilo
βœ… Profilo completo con mapping
Blocco 10
SQL INSERT β†’ Profilo senza CSV
↓
Visualizza profilo β†’ Form upload visibile
↓
Upload CSV β†’ Analisi β†’ Aggiorna profilo
βœ… Profilo completato con mapping
Blocco 11
Profilo con CSV vecchio
↓
Upload nuovo CSV
↓
Analisi β†’ Mapping aggiornato
βœ… File sostituito, mapping rigenerato
Blocco 12 php
// Nel controller uploadCsv()
if ($importProfile->hasImports()) {
    return response()->json([
        'success' => false,
        'message' => 'Profilo giΓ  utilizzato. Duplicalo prima di modificarlo.'
    ], 422);
}