Archive Tree View - Storico CSV
Archive Tree View - Storico CSV
Data Implementazione: 20 Ottobre 2025 Versione: 1.0 Modulo: Industria 4.0 - Archivio CSV
---
π Panoramica
Sistema di navigazione gerarchica per esplorare lo storico dei CSV elaborati, organizzato come albero espandibile:
``
π Archivio CSV
ββ π₯οΈ Taglio Laser GL840P (3 anni)
β ββ π
2025 (12 mesi)
β β ββ π Ottobre (5 CSV)
β β β ββ π CutData_2025-10_20251020_135627.csv
β β β β πΎ 2.5 MB | π 20/10/2025 13:56 | π 295 operazioni | π
01/10 - 15/10
β β β β [β¬ Download] [ποΈ Elimina]
β β β ββ π CutData_2025-10_20251015_092341.csv
β β β ββ ...
β β ββ π Settembre (8 CSV)
β β ββ ...
β ββ π
2024 (12 mesi)
β ββ ...
ββ π₯οΈ Piegatrice Horizon (2 anni)
ββ ...
`
---
ποΈ Architettura
Struttura Dati (Tree)
`php
// Output di buildArchiveTree()
[
[
'type' => 'machine',
'id' => 5,
'label' => 'Taglio Laser GL840P',
'path' => 'connettore_I40/archive/machine_5',
'children' => [
[
'type' => 'year',
'label' => '2025',
'path' => 'connettore_I40/archive/machine_5/2025',
'children' => [
[
'type' => 'month',
'label' => 'Ottobre',
'path' => 'connettore_I40/archive/machine_5/2025/10',
'children' => [
[
'type' => 'file',
'label' => 'CutData_2025-10_20251020_135627.csv',
'path' => 'connettore_I40/archive/machine_5/2025/10/CutData_2025-10_20251020_135627.csv',
'size' => 2621440,
'size_formatted' => '2.50 MB',
'modified_at' => '20/10/2025 13:56',
'timestamp' => 1729426597,
'csv_import_id' => 17,
'operations_count' => 295,
'date_range' => '01/10 - 15/10'
]
]
]
]
]
]
]
]
`
File System Structure
`
storage/app/
βββ connettore_I40/
βββ archive/
βββ machine_5/ # Taglio Laser
β βββ 2025/
β β βββ 10/ # Ottobre
β β β βββ CutData_2025-10_20251020_135627.csv
β β β βββ CutData_2025-10_20251015_092341.csv
β β β βββ ...
β β βββ 09/ # Settembre
β β βββ ...
β βββ 2024/
β βββ ...
βββ machine_7/ # Piegatrice
βββ failed/ # CSV falliti (opzionale)
βββ machine_5/
βββ 2025/
βββ 10/
`
---
π§ Componenti Implementati
1. Backend Service
File: app/Services/I40/CsvArchiveService.php
Metodo: buildArchiveTree(?int $machineId = null): array
Costruisce l'albero gerarchico completo:
`php
public function buildArchiveTree(?int $machineId = null): array
{
$tree = [];
if ($machineId) {
// Singola macchina
$machine = Machine::find($machineId);
$tree[] = $this->buildMachineNode($machine);
} else {
// Tutte le macchine
$machines = Machine::orderBy('name')->get();
foreach ($machines as $machine) {
$node = $this->buildMachineNode($machine);
if (!empty($node['children'])) {
$tree[] = $node;
}
}
}
return $tree;
}
`
Metodo: buildMachineNode(Machine $machine): array
Costruisce ricorsivamente: Macchina β Anni β Mesi β CSV
Logica:
1. Scandisce directory machine_{id}/
2. Per ogni anno (2024, 2025, etc.)
3. Per ogni mese (01-12)
4. Per ogni CSV nel mese
5. Aggiunge metadata da tabella csv_imports (operations_count, date_range)
6. Ordina: Anni DESC, Mesi DESC, CSV per timestamp DESC
Metodo: getMonthName(string $monthNum): string
Converte numero mese (01-12) in nome italiano (Gennaio-Dicembre)
---
2. Controller
File: app/Http/Controllers/Admin/I40/ArchiveController.php
`php
public function index(Request $request)
{
$machines = Machine::orderBy('name')->get();
$selectedMachineId = $request->input('machine_id');
$selectedMachine = $selectedMachineId ? Machine::find($selectedMachineId) : null;
// β
Costruisci albero gerarchico
$archiveTree = $this->archiveService->buildArchiveTree($selectedMachineId);
// Statistiche
$stats = $selectedMachine
? $this->archiveService->getArchiveStats($selectedMachine->id)
: null;
return view('admin.i40.archive.index', compact(
'machines',
'selectedMachine',
'archiveTree',
'stats'
));
}
`
---
3. Frontend View
File: resources/views/admin/i40/archive/index.blade.php
Struttura
`blade
<!-- Header + Filtro Macchina -->
<div class="row">
<div class="col-md-4">
<select onchange="switchMachine(this.value)">
<option>-- Tutte le macchine --</option>
@foreach($machines as $machine)...
</select>
</div>
<!-- Stats Cards (se macchina selezionata) -->
<div class="col-md-8">
<div class="row">
<div class="col-md-3">File Totali</div>
<div class="col-md-3">Completati</div>
<div class="col-md-3">Falliti</div>
<div class="col-md-3">Dimensione</div>
</div>
</div>
</div>
<!-- Archive Tree (UL ricorsivo) -->
<div class="card">
<div class="card-header">
Albero Archivio
<button onclick="expandAll()">Espandi Tutto</button>
</div>
<div class="card-body">
<ul class="archive-tree">
@foreach($archiveTree as $machineNode)
@include('admin.i40.archive.partials.tree-node', ['node' => $machineNode])
@endforeach
</ul>
</div>
</div>
`
---
4. Partial Ricorsivo
File: resources/views/admin/i40/archive/partials/tree-node.blade.php
Renderizza ricorsivamente ogni nodo in base al type:
Nodo Macchina
`blade
<div class="tree-node" onclick="toggleNode(this)">
<span class="tree-toggle">βΆ</span>
<span class="tree-icon">π₯οΈ</span>
<span class="tree-label">{{ $node['label'] }}</span>
<span class="tree-meta">({{ count($node['children']) }} anni)</span>
</div>
<ul class="tree-children">
@foreach($node['children'] as $child)
@include('...tree-node', ['node' => $child])
@endforeach
</ul>
`Nodo Anno
`blade
<span class="tree-icon">π
</span>
<span class="tree-label">2025</span>
<span class="tree-meta">(12 mesi)</span>
`Nodo Mese
`blade
<span class="tree-icon">π</span>
<span class="tree-label">Ottobre</span>
<span class="tree-meta">(5 CSV)</span>
`Nodo File (Foglia)
`blade
<div class="tree-file-item">
<div class="file-info">
<div class="file-name">
<i class="bi bi-file-earmark-csv"></i> CutData_2025-10_...csv
</div>
<div class="file-details">
πΎ 2.5 MB | π 20/10/2025 13:56 | π 295 operazioni | π
01/10 - 15/10
</div>
</div>
<div class="file-actions">
<button onclick="downloadFile(...)">β¬ Download</button>
<button onclick="deleteFile(...)">ποΈ Elimina</button>
</div>
</div>
`---
π¨ CSS Tree View
Stili Principali
`css
.archive-tree ul {
padding-left: 28px; / Indentazione figli /
}.tree-node {
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
}
.tree-node:hover {
background: #f8f9fa;
}
.tree-toggle {
width: 16px;
transition: transform 0.2s;
}
.tree-toggle.expanded {
transform: rotate(90deg); / Ruota da βΆ a βΌ /
}
.tree-children {
display: none;
}
.tree-children.show {
display: block;
}
`Stili File Item
`css
.tree-file-item {
display: flex;
justify-content: space-between;
padding: 10px 14px;
border-left: 3px solid #0d6efd;
background: #fff;
}.tree-file-item:hover {
background: #f8f9fa;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
`---
βοΈ JavaScript Functions
1.
toggleNode(element)Espande/contrae un nodo dell'albero:
`javascript
function toggleNode(element) {
const toggle = element.querySelector('.tree-toggle');
const children = element.nextElementSibling;
if (toggle && children && children.classList.contains('tree-children')) {
toggle.classList.toggle('expanded'); // Ruota icona
children.classList.toggle('show'); // Mostra/nascondi figli
}
}
`Funzionamento:
- Click su nodo β
toggleNode(this) chiamato
Trova .tree-toggle (icona βΆ) e .tree-children (UL figli)
Aggiunge/rimuove classe expanded e show2.
expandAll()Espande tutto l'albero:
`javascript
function expandAll() {
document.querySelectorAll('.tree-children').forEach(el => {
el.classList.add('show');
});
document.querySelectorAll('.tree-toggle').forEach(el => {
el.classList.add('expanded');
});
}
`3.
downloadFile(filePath, filename)Scarica CSV:
`javascript
function downloadFile(filePath, filename) {
document.getElementById('downloadFilePath').value = filePath;
document.getElementById('downloadForm').submit();
}
`Backend:
ArchiveController::download()`php
public function download(Request $request)
{
$filePath = $request->input('file');
if (!Storage::exists($filePath)) {
return back()->withErrors(['error' => 'File non trovato']);
}
return Storage::download($filePath, basename($filePath));
}
`4.
deleteFile(filePath, filename)Elimina CSV (con conferma):
`javascript
function deleteFile(filePath, filename) {
if (confirm(Sei sicuro di voler eliminare:\n\n${filename}\n\nAzione irreversibile!)) {
document.getElementById('deleteFilePath').value = filePath;
document.getElementById('deleteForm').submit();
}
}
`Backend:
ArchiveController::delete()`php
public function delete(Request $request)
{
$filePath = $request->input('file');
try {
Storage::delete($filePath);
return back()->with('success', 'File eliminato: ' . basename($filePath));
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore: ' . $e->getMessage()]);
}
}
`---
π Flusso Utente
Scenario: Navigazione Archivio
`
1. UTENTE: Accede a /admin/i40/archive
β
2. VIEW: Mostra select macchine + alert "Seleziona macchina"
β
3. UTENTE: Seleziona "Taglio Laser GL840P"
β
4. FRONTEND: switchMachine(5) β window.location = "?machine_id=5"
β
5. BACKEND: ArchiveController::index()
β’ $archiveTree = buildArchiveTree(5)
β’ Scandisce storage/app/connettore_I40/archive/machine_5/
β’ Costruisce albero: 1 macchina β 3 anni β N mesi β M CSV
β
6. VIEW: Renderizza albero
β’ Partial ricorsivo tree-node.blade.php
β’ Livelli nested: <ul> β <li> β <ul> β <li> β ...
β
7. UTENTE: Click su "π
2025"
β
8. JS: toggleNode() β Espande nodo
β’ Icona βΆ ruota a βΌ (classe .expanded)
β’ <ul class="tree-children"> diventa visible (classe .show)
β’ Mostra 12 mesi (Gennaio, Febbraio, ..., Dicembre)
β
9. UTENTE: Click su "π Ottobre"
β
10. JS: toggleNode() β Espande mese
β’ Mostra 5 file CSV con dettagli completi
β
11. UTENTE: Click "β¬ Download" su CSV
β
12. JS: downloadFile(path, filename)
β’ Imposta hidden input form
β’ Submit GET /archive/download?file=connettore_I40/archive/...
β
13. BACKEND: Storage::download(path)
β’ Browser scarica file CSV
`---
π Metadata CSV Visualizzati
Per ogni file CSV, vengono mostrati:
Dati da Filesystem
Nome file: CutData_2025-10_20251020_135627.csv
Dimensione: 2.50 MB (formatBytes)
Data modifica: 20/10/2025 13:56 (Storage::lastModified)Dati da Database (csv_imports)
Operazioni count: 295 operazioni (records_count)
Range date: π
01/10 - 15/10 (operations_date_min β operations_date_max)Query:
`php
$csvImport = CsvImport::where('file_path', $csvPath)->first();$dateRange = $csvImport
? Carbon::parse($csvImport->operations_date_min)->format('d/m') . ' - ' .
Carbon::parse($csvImport->operations_date_max)->format('d/m')
: null;
`---
π― Features
β
Implementate
1. Gerarchia 4 Livelli:
- Macchina β Anno β Mese β CSV
2. Espansione/Contrazione:
- Click su qualsiasi nodo (escluso file)
- Icona animata βΆ β βΌ
3. Expand All:
- Pulsante per espandere tutto l'albero
4. Metadata Ricchi:
- Dimensione file
- Data elaborazione
- NΒ° operazioni
- Range date operazioni
5. Azioni su File:
- Download immediato
- Eliminazione con conferma
6. Stats Cards:
- File totali
- Completati vs Falliti
- Dimensione totale
7. Cleanup Automatico:
- Modal per eliminare file > N giorni
- Valore default da
i40_settingsπ Possibili Estensioni Future
1. Ricerca Fulltext:
- Input search per filtrare CSV per nome/data
2. Multi-Select + Bulk Actions:
- Checkbox su file
- Download multipli come ZIP
- Eliminazione multipla
3. Preview CSV:
- Modal con prime 10 righe del CSV
- Senza scaricare il file completo
4. Export Report:
- Esporta lista archivio come Excel/PDF
5. Filtri Avanzati:
- Solo CSV con errori
- Solo CSV > 1000 operazioni
- Range date operazioni
---
π Troubleshooting
Problema: "Nessun CSV archiviato"
Causa: Directory archive non esiste o vuota
Diagnosi:
`bash
Verifica directory
ls -la storage/app/connettore_I40/archive/machine_5/Verifica database
SELECT * FROM csv_imports WHERE machine_id=5 AND is_archived=true;
`Soluzione:
Elabora almeno 1 CSV per la macchina
Verifica che archive_enabled sia true in i40_settings
Verifica permessi folder storage/app/Problema: "Tree non si espande"
Causa: JavaScript
toggleNode() non funzionaDiagnosi:
`javascript
// Console browser
console.log(document.querySelector('.tree-toggle')); // Deve esistere
console.log(document.querySelector('.tree-children')); // Deve esistere
`Soluzione:
Verifica che Bootstrap sia caricato
Verifica console per errori JavaScript
Controlla che onclick="toggleNode(this)" sia sul div correttoProblema: "Metadata non visualizzati"
Causa: Campo
csv_imports.file_path non corrisponde al path attualeDiagnosi:
`sql
SELECT id, filename, file_path, is_archived
FROM csv_imports
WHERE machine_id=5
ORDER BY created_at DESC
LIMIT 10;
`Soluzione:
Verifica che file_path sia aggiornato dopo archiviazione
Controlla query in buildMachineNode():
`php
$csvImport = CsvImport::where('file_path', $csvPath)->first();
`---
β‘ Performance
Ottimizzazioni Implementate
1. Lazy Loading Figli:
-
tree-children ha display: none di default
- Solo quando espanso carica visibilitΓ (CSS, nessun AJAX)
2. Ordinamento Intelligente:
- Anni DESC (2025 prima di 2024)
- Mesi DESC (Dicembre prima di Gennaio)
- CSV DESC (piΓΉ recente prima)
3. Contatori Dinamici:
- "(3 anni)", "(12 mesi)", "(5 CSV)" calcolati runtime
4. Max Height + Scroll:
- Card body max-height: 70vh per evitare pagine infinite
- Scrollbar solo sull'albero, header/stats fissiScenario Worst-Case
Dati:
10 macchine
3 anni per macchina
12 mesi per anno
10 CSV per mese Totale: 10 Γ 3 Γ 12 Γ 10 = 3.600 CSV
Performance:
Generazione albero (backend): ~500ms
Rendering HTML: ~200ms
DOM nodes: ~3.600 <li> + metadata
Totale caricamento: < 1 secondo Se troppo lento (> 10.000 CSV):
Implementare lazy loading AJAX per anni/mesi
Virtualizzazione tree view (solo nodi visibili renderizzati) ---
π Helper Function
i40_setting(string $key, $default = null)File:
app/helpers.phpUso:
`php
// Nei Controller
$days = i40_setting('archive_retention_days', 90);// Nelle Blade Views
{{ i40_setting('archive_enabled', true) }}
`Caratteristiche:
Auto-cast in base al tipo del default (bool, int, float, string)
Gestione errori graceful (ritorna default se eccezione)
Non usa cache (a differenza di I40Setting::get())---
β
Checklist Verifica
Pre-Test
[ ] Tabella i40_settings popolata con script SQL
[ ] Helper i40_setting() caricato in AppServiceProvider
[ ] Almeno 1 CSV elaborato e archiviato FunzionalitΓ
[ ] Select macchina funziona (cambia URL)
[ ] Stats cards visualizzate correttamente
[ ] Albero renderizzato (Macchina β Anno β Mese β CSV)
[ ] Click su nodo espande/contrae (icona ruota βΆ β βΌ)
[ ] Click "Espandi Tutto" apre tutti i nodi
[ ] Metadata CSV visualizzati (dimensione, data, operazioni, range)
[ ] Pulsante Download funziona
[ ] Pulsante Elimina chiede conferma ed elimina file
[ ] Modal Cleanup si apre con valore default da settings Edge Cases
[ ] Macchina senza CSV β Alert "Nessun CSV archiviato"
[ ] Anno senza mesi β Non visualizzato
[ ] Mese senza CSV β Non visualizzato
[ ] CSV senza metadata in DB β Mostra solo dati filesystem ---
π Note Implementazione
PerchΓ© Partial Ricorsivo?
L'albero ha profonditΓ variabile (3-4 livelli) e struttura identica per ogni nodo (toggle + label + children). Il partial ricorsivo:
β
Evita duplicazione codice (stesso template per macchina/anno/mese)
β
Scalabile (funziona con qualsiasi profonditΓ )
β
Manutenibile (modifica in 1 solo file)
β
Leggibile (logica separata dal layout principale)
PerchΓ© UL/LI invece di DIV?
Semantica: <ul>/<li> Γ¨ semanticamente corretto per liste gerarchiche
AccessibilitΓ : Screen reader riconoscono la struttura
CSS semplice: padding-left per indentazione nativa
Performance: Browser ottimizza rendering liste Differenza da Bootstrap Table
Prima (Breadcrumb + Table):
Navigazione lineare (entra in cartella, vedi lista, torna indietro)
2-3 click per raggiungere un CSV
Non vedi struttura globale Ora (Tree View):
Navigazione espandibile (overview completo)
1 click per espandere, 1 click per scaricare
Vedi subito quanti anni/mesi/CSV per macchina ---
π Documento Salvato In:
/Users/nscapati/Dropbox/SFTP/sartUP/MD/i40/ARCHIVE_TREE_VIEW.md`Ultima Revisione: 20 Ottobre 2025 Status: β Production Ready
Analisi Codice
Blocco 1
π Archivio CSV
ββ π₯οΈ Taglio Laser GL840P (3 anni)
β ββ π
2025 (12 mesi)
β β ββ π Ottobre (5 CSV)
β β β ββ π CutData_2025-10_20251020_135627.csv
β β β β πΎ 2.5 MB | π 20/10/2025 13:56 | π 295 operazioni | π
01/10 - 15/10
β β β β [β¬ Download] [ποΈ Elimina]
β β β ββ π CutData_2025-10_20251015_092341.csv
β β β ββ ...
β β ββ π Settembre (8 CSV)
β β ββ ...
β ββ π
2024 (12 mesi)
β ββ ...
ββ π₯οΈ Piegatrice Horizon (2 anni)
ββ ...
Blocco 2 php
// Output di buildArchiveTree()
[
[
'type' => 'machine',
'id' => 5,
'label' => 'Taglio Laser GL840P',
'path' => 'connettore_I40/archive/machine_5',
'children' => [
[
'type' => 'year',
'label' => '2025',
'path' => 'connettore_I40/archive/machine_5/2025',
'children' => [
[
'type' => 'month',
'label' => 'Ottobre',
'path' => 'connettore_I40/archive/machine_5/2025/10',
'children' => [
[
'type' => 'file',
'label' => 'CutData_2025-10_20251020_135627.csv',
'path' => 'connettore_I40/archive/machine_5/2025/10/CutData_2025-10_20251020_135627.csv',
'size' => 2621440,
'size_formatted' => '2.50 MB',
'modified_at' => '20/10/2025 13:56',
'timestamp' => 1729426597,
'csv_import_id' => 17,
'operations_count' => 295,
'date_range' => '01/10 - 15/10'
]
]
]
]
]
]
]
]
Blocco 3
storage/app/
βββ connettore_I40/
βββ archive/
βββ machine_5/ # Taglio Laser
β βββ 2025/
β β βββ 10/ # Ottobre
β β β βββ CutData_2025-10_20251020_135627.csv
β β β βββ CutData_2025-10_20251015_092341.csv
β β β βββ ...
β β βββ 09/ # Settembre
β β βββ ...
β βββ 2024/
β βββ ...
βββ machine_7/ # Piegatrice
βββ failed/ # CSV falliti (opzionale)
βββ machine_5/
βββ 2025/
βββ 10/
Blocco 4 php
public function buildArchiveTree(?int $machineId = null): array
{
$tree = [];
if ($machineId) {
// Singola macchina
$machine = Machine::find($machineId);
$tree[] = $this->buildMachineNode($machine);
} else {
// Tutte le macchine
$machines = Machine::orderBy('name')->get();
foreach ($machines as $machine) {
$node = $this->buildMachineNode($machine);
if (!empty($node['children'])) {
$tree[] = $node;
}
}
}
return $tree;
}
Blocco 5 php
public function index(Request $request)
{
$machines = Machine::orderBy('name')->get();
$selectedMachineId = $request->input('machine_id');
$selectedMachine = $selectedMachineId ? Machine::find($selectedMachineId) : null;
// β
Costruisci albero gerarchico
$archiveTree = $this->archiveService->buildArchiveTree($selectedMachineId);
// Statistiche
$stats = $selectedMachine
? $this->archiveService->getArchiveStats($selectedMachine->id)
: null;
return view('admin.i40.archive.index', compact(
'machines',
'selectedMachine',
'archiveTree',
'stats'
));
}
Blocco 6 blade
<!-- Header + Filtro Macchina -->
<div class="row">
<div class="col-md-4">
<select onchange="switchMachine(this.value)">
<option>-- Tutte le macchine --</option>
@foreach($machines as $machine)...
</select>
</div>
<!-- Stats Cards (se macchina selezionata) -->
<div class="col-md-8">
<div class="row">
<div class="col-md-3">File Totali</div>
<div class="col-md-3">Completati</div>
<div class="col-md-3">Falliti</div>
<div class="col-md-3">Dimensione</div>
</div>
</div>
</div>
<!-- Archive Tree (UL ricorsivo) -->
<div class="card">
<div class="card-header">
Albero Archivio
<button onclick="expandAll()">Espandi Tutto</button>
</div>
<div class="card-body">
<ul class="archive-tree">
@foreach($archiveTree as $machineNode)
@include('admin.i40.archive.partials.tree-node', ['node' => $machineNode])
@endforeach
</ul>
</div>
</div>
Blocco 7 blade
<div class="tree-node" onclick="toggleNode(this)">
<span class="tree-toggle">βΆ</span>
<span class="tree-icon">π₯οΈ</span>
<span class="tree-label">{{ $node['label'] }}</span>
<span class="tree-meta">({{ count($node['children']) }} anni)</span>
</div>
<ul class="tree-children">
@foreach($node['children'] as $child)
@include('...tree-node', ['node' => $child])
@endforeach
</ul>
Blocco 8 blade
<span class="tree-icon">π
</span>
<span class="tree-label">2025</span>
<span class="tree-meta">(12 mesi)</span>
Blocco 9 blade
<span class="tree-icon">π</span>
<span class="tree-label">Ottobre</span>
<span class="tree-meta">(5 CSV)</span>
Blocco 10 blade
<div class="tree-file-item">
<div class="file-info">
<div class="file-name">
<i class="bi bi-file-earmark-csv"></i> CutData_2025-10_...csv
</div>
<div class="file-details">
πΎ 2.5 MB | π 20/10/2025 13:56 | π 295 operazioni | π
01/10 - 15/10
</div>
</div>
<div class="file-actions">
<button onclick="downloadFile(...)">β¬ Download</button>
<button onclick="deleteFile(...)">ποΈ Elimina</button>
</div>
</div>
Blocco 11 css
.archive-tree ul {
padding-left: 28px; /* Indentazione figli */
}
.tree-node {
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
}
.tree-node:hover {
background: #f8f9fa;
}
.tree-toggle {
width: 16px;
transition: transform 0.2s;
}
.tree-toggle.expanded {
transform: rotate(90deg); /* Ruota da βΆ a βΌ */
}
.tree-children {
display: none;
}
.tree-children.show {
display: block;
}
Blocco 12 css
.tree-file-item {
display: flex;
justify-content: space-between;
padding: 10px 14px;
border-left: 3px solid #0d6efd;
background: #fff;
}
.tree-file-item:hover {
background: #f8f9fa;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
Blocco 13 javascript
function toggleNode(element) {
const toggle = element.querySelector('.tree-toggle');
const children = element.nextElementSibling;
if (toggle && children && children.classList.contains('tree-children')) {
toggle.classList.toggle('expanded'); // Ruota icona
children.classList.toggle('show'); // Mostra/nascondi figli
}
}
Blocco 14 javascript
function expandAll() {
document.querySelectorAll('.tree-children').forEach(el => {
el.classList.add('show');
});
document.querySelectorAll('.tree-toggle').forEach(el => {
el.classList.add('expanded');
});
}
Blocco 15 javascript
function downloadFile(filePath, filename) {
document.getElementById('downloadFilePath').value = filePath;
document.getElementById('downloadForm').submit();
}
Blocco 16 php
public function download(Request $request)
{
$filePath = $request->input('file');
if (!Storage::exists($filePath)) {
return back()->withErrors(['error' => 'File non trovato']);
}
return Storage::download($filePath, basename($filePath));
}
Blocco 17 javascript
function deleteFile(filePath, filename) {
if (confirm(`Sei sicuro di voler eliminare:\n\n${filename}\n\nAzione irreversibile!`)) {
document.getElementById('deleteFilePath').value = filePath;
document.getElementById('deleteForm').submit();
}
}
Blocco 18 php
public function delete(Request $request)
{
$filePath = $request->input('file');
try {
Storage::delete($filePath);
return back()->with('success', 'File eliminato: ' . basename($filePath));
} catch (\Exception $e) {
return back()->withErrors(['error' => 'Errore: ' . $e->getMessage()]);
}
}
Blocco 19
1. UTENTE: Accede a /admin/i40/archive
β
2. VIEW: Mostra select macchine + alert "Seleziona macchina"
β
3. UTENTE: Seleziona "Taglio Laser GL840P"
β
4. FRONTEND: switchMachine(5) β window.location = "?machine_id=5"
β
5. BACKEND: ArchiveController::index()
β’ $archiveTree = buildArchiveTree(5)
β’ Scandisce storage/app/connettore_I40/archive/machine_5/
β’ Costruisce albero: 1 macchina β 3 anni β N mesi β M CSV
β
6. VIEW: Renderizza albero
β’ Partial ricorsivo tree-node.blade.php
β’ Livelli nested: <ul> β <li> β <ul> β <li> β ...
β
7. UTENTE: Click su "π
2025"
β
8. JS: toggleNode() β Espande nodo
β’ Icona βΆ ruota a βΌ (classe .expanded)
β’ <ul class="tree-children"> diventa visible (classe .show)
β’ Mostra 12 mesi (Gennaio, Febbraio, ..., Dicembre)
β
9. UTENTE: Click su "π Ottobre"
β
10. JS: toggleNode() β Espande mese
β’ Mostra 5 file CSV con dettagli completi
β
11. UTENTE: Click "β¬ Download" su CSV
β
12. JS: downloadFile(path, filename)
β’ Imposta hidden input form
β’ Submit GET /archive/download?file=connettore_I40/archive/...
β
13. BACKEND: Storage::download(path)
β’ Browser scarica file CSV
Blocco 20 php
$csvImport = CsvImport::where('file_path', $csvPath)->first();
$dateRange = $csvImport
? Carbon::parse($csvImport->operations_date_min)->format('d/m') . ' - ' .
Carbon::parse($csvImport->operations_date_max)->format('d/m')
: null;
Blocco 21 bash
# Verifica directory
ls -la storage/app/connettore_I40/archive/machine_5/
# Verifica database
SELECT * FROM csv_imports WHERE machine_id=5 AND is_archived=true;
Blocco 22 javascript
// Console browser
console.log(document.querySelector('.tree-toggle')); // Deve esistere
console.log(document.querySelector('.tree-children')); // Deve esistere
Blocco 23 sql
SELECT id, filename, file_path, is_archived
FROM csv_imports
WHERE machine_id=5
ORDER BY created_at DESC
LIMIT 10;
Blocco 24 php
$csvImport = CsvImport::where('file_path', $csvPath)->first();
Blocco 25 php
// Nei Controller
$days = i40_setting('archive_retention_days', 90);
// Nelle Blade Views
{{ i40_setting('archive_enabled', true) }}