Files
zentral/PLAN.md
2025-11-28 10:59:10 +01:00

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

  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):

<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

  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