426 lines
14 KiB
Markdown
426 lines
14 KiB
Markdown
# 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)
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```csharp
|
|
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
|
|
1. Creare entità `ReportTemplate`, `ReportFont`, `ReportImage`
|
|
2. Aggiornare DbContext e migrare database
|
|
3. Creare `ReportTemplatesController` con CRUD base
|
|
4. Creare `ReportResourcesController` per upload font/immagini
|
|
5. Installare e configurare QuestPDF
|
|
6. Creare `ReportGeneratorService` base
|
|
|
|
#### Fase 2: Metalinguaggio Parser
|
|
1. Definire classi C# per il metalinguaggio APRT
|
|
2. Implementare parser JSON → oggetti
|
|
3. Implementare renderer elementi → QuestPDF
|
|
4. Gestire binding dati con espressioni {{campo}}
|
|
5. Implementare paginazione e sezioni ripetute
|
|
|
|
#### Fase 3: Frontend Editor Base
|
|
1. Installare Fabric.js (`fabric`)
|
|
2. Creare pagina `ReportEditorPage`
|
|
3. Implementare `Canvas` con Fabric.js
|
|
4. Implementare `Toolbar` per aggiungere elementi
|
|
5. Implementare `PropertiesPanel` per editing proprietà
|
|
6. Implementare serializzazione canvas → APRT
|
|
|
|
#### Fase 4: Data Binding
|
|
1. Creare `DataBindingPanel` con schema dati disponibili
|
|
2. Implementare drag-drop campi su elementi
|
|
3. Supportare espressioni {{campo.sottocampo}}
|
|
4. Implementare formattazione (currency, date, number)
|
|
5. Supportare espressioni condizionali
|
|
|
|
#### Fase 5: Tabelle e Repeater
|
|
1. Implementare `TableElement` con colonne configurabili
|
|
2. Supportare data source array per righe ripetute
|
|
3. Implementare auto-height per tabelle
|
|
4. Gestire page break automatici
|
|
|
|
#### Fase 6: Risorse e Upload
|
|
1. Implementare upload font custom
|
|
2. Implementare upload immagini
|
|
3. Creare libreria risorse condivise
|
|
4. Preview font e immagini
|
|
|
|
#### Fase 7: Preview e Generazione
|
|
1. Implementare preview real-time (canvas → PNG)
|
|
2. Implementare generazione PDF finale
|
|
3. Download PDF
|
|
4. Stampa diretta
|
|
|
|
#### Fase 8: Import/Export
|
|
1. Implementare export .aprt (JSON + risorse embedded base64)
|
|
2. Implementare import .aprt
|
|
3. 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):**
|
|
```xml
|
|
<PackageReference Include="QuestPDF" Version="2024.12.0" />
|
|
```
|
|
|
|
**Frontend (npm):**
|
|
```json
|
|
{
|
|
"fabric": "^6.0.0",
|
|
"file-saver": "^2.0.5",
|
|
"@types/fabric": "^5.3.0"
|
|
}
|
|
```
|
|
|
|
### 7. Routes Frontend
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **QuestPDF** invece di iTextSharp (licenza più permissiva, API moderna)
|
|
2. **Fabric.js** invece di Konva (più features per editing)
|
|
3. **JSON** come metalinguaggio (leggibile, facile da parsare)
|
|
4. **Embedded resources** negli export (portabilità completa)
|
|
5. **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
|