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 soloname, 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('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(</code>${apiUrl}/${id}/upload-csv<code>, {
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);
}
});
}
</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: "CSV caricato e analizzato con successo!" 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);
}