14 KiB
14 KiB
Piano: Sistema di Report PDF con Editor Visuale
Obiettivo
Creare un sistema completo di generazione report PDF con:
- Editor grafico drag-and-drop (stile Canva)
- Potenza di JasperReports (data binding, paginazione, formule)
- Metalinguaggio esportabile/importabile (tipo LaTeX)
- Salvataggio template riutilizzabili
- Supporto immagini e font personalizzati
Architettura Proposta
1. Metalinguaggio Template (APRT - Apollinare Report Template)
{
"version": "1.0",
"meta": {
"name": "Scheda Evento",
"description": "Template per stampa evento",
"author": "admin",
"createdAt": "2025-01-15",
"pageSize": "A4",
"orientation": "portrait",
"margins": { "top": 20, "right": 15, "bottom": 20, "left": 15 }
},
"resources": {
"fonts": [
{ "id": "font1", "name": "Roboto", "url": "/fonts/roboto.ttf" }
],
"images": [
{ "id": "logo", "name": "Logo Aziendale", "url": "/images/logo.png" }
]
},
"dataSources": {
"evento": { "type": "object", "schema": "Evento" },
"ospiti": { "type": "array", "schema": "EventoDettaglioOspiti" },
"costi": { "type": "array", "schema": "EventoAltroCosto" }
},
"sections": [
{
"type": "header",
"height": 80,
"repeatOnPages": true,
"elements": [...]
},
{
"type": "body",
"elements": [...]
},
{
"type": "detail",
"dataSource": "ospiti",
"elements": [...]
},
{
"type": "footer",
"height": 40,
"repeatOnPages": true,
"elements": [...]
}
],
"elements": [
{
"id": "elem1",
"type": "text",
"position": { "x": 10, "y": 10, "width": 200, "height": 30 },
"style": {
"fontFamily": "font1",
"fontSize": 24,
"fontWeight": "bold",
"color": "#333333",
"textAlign": "left"
},
"content": {
"type": "static",
"value": "SCHEDA EVENTO"
}
},
{
"id": "elem2",
"type": "text",
"position": { "x": 10, "y": 50, "width": 150, "height": 20 },
"content": {
"type": "binding",
"expression": "{{evento.codice}}"
}
},
{
"id": "elem3",
"type": "image",
"position": { "x": 450, "y": 10, "width": 100, "height": 60 },
"content": {
"type": "resource",
"resourceId": "logo"
}
},
{
"id": "elem4",
"type": "table",
"position": { "x": 10, "y": 200, "width": 550, "height": "auto" },
"dataSource": "ospiti",
"columns": [
{ "field": "tipoOspite.descrizione", "header": "Tipo", "width": 150 },
{ "field": "numero", "header": "Quantità", "width": 100 },
{ "field": "costoUnitario", "header": "Costo Unit.", "width": 100, "format": "currency" },
{ "field": "costoTotale", "header": "Totale", "width": 100, "format": "currency" }
]
},
{
"id": "elem5",
"type": "shape",
"position": { "x": 10, "y": 180, "width": 550, "height": 2 },
"style": {
"backgroundColor": "#000000"
}
},
{
"id": "pageNum",
"type": "text",
"section": "footer",
"position": { "x": 250, "y": 10, "width": 100, "height": 20 },
"content": {
"type": "expression",
"value": "Pagina {{$pageNumber}} di {{$totalPages}}"
}
}
]
}
2. Struttura Backend
Nuove Entità
ReportTemplate
├── Id
├── Nome
├── Descrizione
├── Categoria (Evento, Cliente, Articoli, etc.)
├── TemplateJson (il metalinguaggio APRT)
├── Thumbnail (preview del template)
├── Attivo
├── CreatedAt/By, UpdatedAt/By
ReportFont
├── Id
├── Nome
├── FontFamily
├── FontData (BLOB - file TTF/OTF)
├── MimeType
ReportImage
├── Id
├── Nome
├── Categoria
├── ImageData (BLOB)
├── MimeType
├── Width, Height
Nuovi Controller
ReportTemplatesController
├── GET /api/report-templates # Lista template
├── GET /api/report-templates/{id} # Dettaglio
├── POST /api/report-templates # Crea
├── PUT /api/report-templates/{id} # Aggiorna
├── DELETE /api/report-templates/{id} # Elimina
├── POST /api/report-templates/{id}/clone # Duplica
├── GET /api/report-templates/{id}/export # Esporta .aprt
├── POST /api/report-templates/import # Importa .aprt
ReportResourcesController
├── GET /api/report-resources/fonts # Lista font
├── POST /api/report-resources/fonts # Upload font
├── DELETE /api/report-resources/fonts/{id}
├── GET /api/report-resources/images # Lista immagini
├── POST /api/report-resources/images # Upload immagine
├── DELETE /api/report-resources/images/{id}
ReportGeneratorController
├── POST /api/reports/generate # Genera PDF
│ Body: { templateId, dataContext: { eventoId, ... } }
├── POST /api/reports/preview # Anteprima (PNG/HTML)
Servizio Generazione PDF
Useremo QuestPDF per la generazione:
- Supporto nativo .NET
- API fluent per layout complessi
- Font personalizzati
- Immagini
- Paginazione automatica
- Performance eccellenti
public class ReportGeneratorService
{
public byte[] GeneratePdf(ReportTemplate template, object dataContext)
{
var parsed = ParseTemplate(template.TemplateJson);
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(parsed.PageSize);
page.Margin(parsed.Margins);
if (parsed.Header != null)
page.Header().Element(c => RenderSection(c, parsed.Header, dataContext));
page.Content().Element(c => RenderContent(c, parsed, dataContext));
if (parsed.Footer != null)
page.Footer().Element(c => RenderSection(c, parsed.Footer, dataContext));
});
});
return document.GeneratePdf();
}
}
3. Frontend - Editor Visuale
Componenti Principali
frontend/src/
├── pages/
│ ├── ReportEditorPage.tsx # Editor principale
│ └── ReportTemplatesPage.tsx # Lista template
├── components/
│ └── reportEditor/
│ ├── ReportEditor.tsx # Container principale
│ ├── Canvas.tsx # Area di disegno (Fabric.js o Konva)
│ ├── Toolbar.tsx # Barra strumenti (text, image, shape, table)
│ ├── PropertiesPanel.tsx # Pannello proprietà elemento selezionato
│ ├── DataBindingPanel.tsx # Pannello per mappare dati
│ ├── LayersPanel.tsx # Gestione livelli/elementi
│ ├── ResourcesPanel.tsx # Font e immagini disponibili
│ ├── PageSettings.tsx # Impostazioni pagina
│ ├── PreviewModal.tsx # Anteprima PDF
│ └── elements/
│ ├── TextElement.tsx
│ ├── ImageElement.tsx
│ ├── ShapeElement.tsx
│ ├── TableElement.tsx
│ └── BarcodeElement.tsx
├── services/
│ └── reportService.ts
└── types/
└── report.ts # Tipi TypeScript per APRT
Libreria Canvas
Fabric.js è la scelta migliore:
- Drag & drop nativo
- Selezione multipla
- Ridimensionamento con handle
- Rotazione elementi
- Serializzazione JSON
- Supporto testo, immagini, forme
- Griglia e snap
- Undo/redo
Flusso Editor
┌─────────────────────────────────────────────────────────────────┐
│ Toolbar: [Text] [Image] [Shape] [Table] [Line] [Barcode] │
├─────────────┬───────────────────────────────────┬───────────────┤
│ │ │ │
│ Layers │ CANVAS │ Properties │
│ Panel │ ┌─────────────────┐ │ Panel │
│ │ │ HEADER │ │ │
│ □ Logo │ │ [Logo] [Titolo]│ │ Position │
│ □ Titolo │ ├─────────────────┤ │ x: 10 y: 10 │
│ □ Data │ │ │ │ w: 200 h: 30 │
│ □ Tabella │ │ BODY │ │ │
│ □ Footer │ │ │ │ Style │
│ │ │ [Data Evento] │ │ Font: Roboto │
│ │ │ [Cliente] │ │ Size: 24 │
│ │ │ [Tabella] │ │ Color: #333 │
│ │ │ │ │ │
│ │ ├─────────────────┤ │ Data Binding │
│ │ │ FOOTER │ │ {{evento. │
│ │ │ [Pag X di Y] │ │ codice}} │
│ │ └─────────────────┘ │ │
│ │ │ │
├─────────────┴───────────────────────────────────┴───────────────┤
│ Data Sources: [evento] [ospiti] [costi] [risorse] │
│ Available Fields: codice, dataEvento, cliente.ragioneSociale...│
└─────────────────────────────────────────────────────────────────┘
4. Implementazione Step-by-Step
Fase 1: Backend Foundation
- Creare entità
ReportTemplate,ReportFont,ReportImage - Aggiornare DbContext e migrare database
- Creare
ReportTemplatesControllercon CRUD base - Creare
ReportResourcesControllerper upload font/immagini - Installare e configurare QuestPDF
- Creare
ReportGeneratorServicebase
Fase 2: Metalinguaggio Parser
- Definire classi C# per il metalinguaggio APRT
- Implementare parser JSON → oggetti
- Implementare renderer elementi → QuestPDF
- Gestire binding dati con espressioni {{campo}}
- Implementare paginazione e sezioni ripetute
Fase 3: Frontend Editor Base
- Installare Fabric.js (
fabric) - Creare pagina
ReportEditorPage - Implementare
Canvascon Fabric.js - Implementare
Toolbarper aggiungere elementi - Implementare
PropertiesPanelper editing proprietà - Implementare serializzazione canvas → APRT
Fase 4: Data Binding
- Creare
DataBindingPanelcon schema dati disponibili - Implementare drag-drop campi su elementi
- Supportare espressioni {{campo.sottocampo}}
- Implementare formattazione (currency, date, number)
- Supportare espressioni condizionali
Fase 5: Tabelle e Repeater
- Implementare
TableElementcon colonne configurabili - Supportare data source array per righe ripetute
- Implementare auto-height per tabelle
- Gestire page break automatici
Fase 6: Risorse e Upload
- Implementare upload font custom
- Implementare upload immagini
- Creare libreria risorse condivise
- Preview font e immagini
Fase 7: Preview e Generazione
- Implementare preview real-time (canvas → PNG)
- Implementare generazione PDF finale
- Download PDF
- Stampa diretta
Fase 8: Import/Export
- Implementare export .aprt (JSON + risorse embedded base64)
- Implementare import .aprt
- Validazione template importati
5. Template Esempio: Scheda Evento
Creeremo un template predefinito per la stampa eventi con:
- Header con logo aziendale e titolo
- Dati evento (codice, data, cliente, location)
- Tabella ospiti con subtotali
- Tabella costi aggiuntivi
- Riepilogo totali
- Note
- Footer con paginazione
6. Dipendenze da Aggiungere
Backend (NuGet):
<PackageReference Include="QuestPDF" Version="2024.12.0" />
Frontend (npm):
{
"fabric": "^6.0.0",
"file-saver": "^2.0.5",
"@types/fabric": "^5.3.0"
}
7. Routes Frontend
// App.tsx - nuove routes
<Route path="/report-templates" element={<ReportTemplatesPage />} />
<Route path="/report-editor/:id?" element={<ReportEditorPage />} />
<Route path="/report-preview/:templateId/:entityId" element={<ReportPreviewPage />} />
8. Stima Componenti
| Componente | File | Complessità |
|---|---|---|
| Entità + DbContext | 3 file | Bassa |
| Controllers | 3 file | Media |
| ReportGeneratorService | 1 file | Alta |
| APRT Parser | 1 file | Media |
| ReportEditorPage | 1 file | Alta |
| Canvas (Fabric.js) | 1 file | Alta |
| Toolbar | 1 file | Bassa |
| PropertiesPanel | 1 file | Media |
| DataBindingPanel | 1 file | Media |
| LayersPanel | 1 file | Bassa |
| Elementi (5 tipi) | 5 file | Media |
| Services frontend | 1 file | Bassa |
| Types | 1 file | Bassa |
Totale: ~20 file, complessità alta
Decisioni Architetturali
- QuestPDF invece di iTextSharp (licenza più permissiva, API moderna)
- Fabric.js invece di Konva (più features per editing)
- JSON come metalinguaggio (leggibile, facile da parsare)
- Embedded resources negli export (portabilità completa)
- Real-time preview via canvas (no round-trip server)
Note Implementative
- Il canvas Fabric.js lavora in pixel, convertiremo in mm per la stampa
- I font custom vanno registrati in QuestPDF all'avvio
- Le immagini BLOB vanno convertite in base64 per Fabric.js
- La paginazione è gestita lato server da QuestPDF
- L'editor salva solo il JSON, la generazione PDF è on-demand