Files
zentral/.claude/plan-multi-page.md
2025-11-28 10:59:10 +01:00

12 KiB

Piano Implementazione Multi-Pagina per Report Designer

Obiettivo

Aggiungere la gestione di template multi-pagina al report designer, permettendo di creare documenti PDF con più pagine, ognuna con i propri elementi.


Analisi dello Stato Attuale

Frontend

  • EditorCanvas.tsx: Renderizza un singolo canvas Fabric.js che rappresenta una pagina
  • ReportEditorPage.tsx: Gestisce template.elements come array flat (tutti gli elementi)
  • AprtTemplate: Ha già un campo sections ma non è usato per le pagine
  • Gli elementi hanno già un campo section (header/body/footer) ma non pageIndex

Backend

  • ReportGeneratorService.cs: Genera PDF con una sola pagina (container.Page(...))
  • AprtModels.cs: Definisce AprtSection con repeatOnPages (pensato per header/footer ripetuti)

Metalinguaggio APRT

  • Attualmente non supporta concetto di "pagine multiple"
  • Gli elementi hanno posizione assoluta relativa alla pagina

Approccio Proposto

Opzione Scelta: Array di Pagine nel Template

Modificare la struttura del template per includere un array esplicito di pagine:

interface AprtTemplate {
  // ... existing fields
  pages: AprtPage[];  // NUOVO
  elements: AprtElement[];  // Rimane per backward compatibility
}

interface AprtPage {
  id: string;
  name: string;
  pageSize?: PageSize;      // Override opzionale (default: dal meta)
  orientation?: PageOrientation;  // Override opzionale
  margins?: AprtMargins;    // Override opzionale
  backgroundColor?: string;
  elements: string[];       // Array di ID elementi che appartengono a questa pagina
}

interface AprtElement {
  // ... existing fields
  pageId?: string;  // NUOVO - ID della pagina di appartenenza
}

Modifiche Richieste

1. Frontend - Types (report.ts)

// Aggiungere:
export interface AprtPage {
  id: string;
  name: string;
  pageSize?: PageSize;
  orientation?: PageOrientation;
  margins?: AprtMargins;
  backgroundColor?: string;
}

// Modificare AprtTemplate:
export interface AprtTemplate {
  // ... existing
  pages: AprtPage[];  // Aggiungere
}

// Modificare AprtElement:
export interface AprtElement {
  // ... existing
  pageId?: string;  // Aggiungere
}

// Aggiornare defaultTemplate:
export const defaultTemplate: AprtTemplate = {
  // ... existing
  pages: [{
    id: 'page-1',
    name: 'Pagina 1'
  }],
};

2. Frontend - Nuovo Componente PageNavigator.tsx

Barra laterale sinistra o tabs per navigare tra le pagine:

┌─────────────────────────────────────────────────┐
│ [+ Aggiungi Pagina]                             │
├─────────────────────────────────────────────────┤
│ ┌─────────┐  Pagina 1 (A4 Portrait)      [⋮]   │
│ │ thumb   │  5 elementi                         │
│ └─────────┘                                     │
├─────────────────────────────────────────────────┤
│ ┌─────────┐  Pagina 2 (A4 Portrait)      [⋮]   │
│ │ thumb   │  3 elementi                    ●    │ ← Selezionata
│ └─────────┘                                     │
└─────────────────────────────────────────────────┘

Funzionalità:

  • Lista pagine con miniatura
  • Aggiunta nuova pagina
  • Duplicazione pagina
  • Eliminazione pagina (con conferma)
  • Riordinamento drag-and-drop
  • Indicatore pagina attiva

3. Frontend - Modifiche a ReportEditorPage.tsx

// Nuovo state:
const [currentPageId, setCurrentPageId] = useState<string>('page-1');

// Computed:
const currentPage = template.pages.find(p => p.id === currentPageId);
const currentPageElements = template.elements.filter(e => e.pageId === currentPageId);

// Handlers:
const handleAddPage = () => { /* ... */ };
const handleDeletePage = (pageId: string) => { /* ... */ };
const handleDuplicatePage = (pageId: string) => { /* ... */ };
const handleReorderPages = (fromIndex: number, toIndex: number) => { /* ... */ };
const handleUpdatePageSettings = (pageId: string, updates: Partial<AprtPage>) => { /* ... */ };

// Quando si aggiunge un elemento, assegnare pageId corrente:
const handleAddElement = (type: ElementType) => {
  const newElement = {
    // ... existing
    pageId: currentPageId,  // NUOVO
  };
};

4. Frontend - Modifiche a EditorCanvas.tsx

  • Ricevere solo gli elementi della pagina corrente
  • Opzionalmente mostrare indicatore numero pagina
interface EditorCanvasProps {
  // ... existing
  pageIndex?: number;      // Numero pagina per visualizzazione
  totalPages?: number;     // Totale pagine
}

5. Frontend - Modifiche a PropertiesPanel.tsx

Quando nessun elemento è selezionato, mostrare le impostazioni della pagina corrente:

  • Formato pagina (override rispetto al default)
  • Orientamento (override)
  • Margini (override)
  • Colore sfondo

6. Frontend - Modifiche a EditorToolbar.tsx

Aggiungere pulsanti per navigazione veloce:

  • [◀ Pagina Prec] / [Pagina Succ ▶]
  • Indicatore Pagina 1 di 3

7. Backend - Modifiche a AprtModels.cs

public class AprtTemplate
{
    // ... existing
    
    [JsonPropertyName("pages")]
    public List<AprtPage> Pages { get; set; } = new();
}

public class AprtPage
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = string.Empty;
    
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;
    
    [JsonPropertyName("pageSize")]
    public string? PageSize { get; set; }
    
    [JsonPropertyName("orientation")]
    public string? Orientation { get; set; }
    
    [JsonPropertyName("margins")]
    public AprtMargins? Margins { get; set; }
    
    [JsonPropertyName("backgroundColor")]
    public string? BackgroundColor { get; set; }
}

public class AprtElement
{
    // ... existing
    
    [JsonPropertyName("pageId")]
    public string? PageId { get; set; }
}

8. Backend - Modifiche a ReportGeneratorService.cs

public async Task<byte[]> GeneratePdfAsync(int templateId, Dictionary<string, object> dataContext)
{
    // ... load template
    
    var document = Document.Create(container =>
    {
        // Raggruppa elementi per pagina
        var pages = aprt.Pages.Any() 
            ? aprt.Pages 
            : new List<AprtPage> { new AprtPage { Id = "default" } };
        
        foreach (var page in pages)
        {
            // Determina dimensioni pagina (override o default)
            var pageSize = page.PageSize ?? aprt.Meta.PageSize;
            var orientation = page.Orientation ?? aprt.Meta.Orientation;
            var margins = page.Margins ?? aprt.Meta.Margins;
            
            // Filtra elementi per questa pagina
            var pageElements = aprt.Elements
                .Where(e => e.Visible && (e.PageId == page.Id || 
                    (string.IsNullOrEmpty(e.PageId) && page.Id == pages[0].Id)))
                .ToList();
            
            container.Page(p =>
            {
                var dims = GetPageDimensionsMm(pageSize, orientation);
                p.Size(dims.Width, dims.Height, Unit.Millimetre);
                p.Margin(0);
                
                // Render elementi della pagina
                var pageImageBytes = RenderContentToBitmap(
                    pageElements, dims.Width, dims.Height, 
                    dataContext, resources, 300f);
                
                p.Content().Image(pageImageBytes).FitArea();
            });
        }
    });
    
    return document.GeneratePdf();
}

Migrazione Template Esistenti

Per backward compatibility, i template senza pages funzionano come prima:

// Nel backend:
if (!aprt.Pages.Any())
{
    // Crea pagina di default con tutti gli elementi
    aprt.Pages.Add(new AprtPage { Id = "page-1", Name = "Pagina 1" });
    foreach (var element in aprt.Elements.Where(e => string.IsNullOrEmpty(e.PageId)))
    {
        element.PageId = "page-1";
    }
}
// Nel frontend (ReportEditorPage.tsx):
useEffect(() => {
  if (existingTemplate) {
    const parsed = JSON.parse(existingTemplate.templateJson);
    
    // Migrazione: se manca pages, creane una di default
    if (!parsed.pages || parsed.pages.length === 0) {
      parsed.pages = [{ id: 'page-1', name: 'Pagina 1' }];
      // Assegna tutti gli elementi esistenti alla prima pagina
      parsed.elements?.forEach(el => {
        if (!el.pageId) el.pageId = 'page-1';
      });
    }
    
    // ... rest of loading
  }
}, [existingTemplate]);

Ordine di Implementazione

Fase 1: Struttura Dati (30 min)

  1. Aggiornare report.ts con AprtPage e modifiche a AprtTemplate/AprtElement
  2. Aggiornare AprtModels.cs con le stesse strutture
  3. Aggiornare defaultTemplate con pagina di default

Fase 2: Backend PDF Multi-Pagina (30 min)

  1. Modificare ReportGeneratorService.GeneratePdfAsync per iterare sulle pagine
  2. Aggiungere logica di migrazione per template esistenti
  3. Testare generazione PDF con template multi-pagina manuale

Fase 3: Frontend - PageNavigator (1 ora)

  1. Creare componente PageNavigator.tsx
  2. Integrare in ReportEditorPage.tsx (layout con sidebar sinistra)
  3. Implementare selezione pagina corrente

Fase 4: Frontend - Gestione Elementi per Pagina (30 min)

  1. Modificare ReportEditorPage.tsx per filtrare elementi per pagina
  2. Assegnare pageId agli elementi nuovi
  3. Aggiornare EditorCanvas.tsx per ricevere solo elementi pagina corrente

Fase 5: Frontend - CRUD Pagine (45 min)

  1. Implementare aggiunta pagina
  2. Implementare duplicazione pagina (con copia elementi)
  3. Implementare eliminazione pagina
  4. Implementare riordinamento pagine (drag-and-drop)

Fase 6: Frontend - Settings Pagina (30 min)

  1. Modificare PropertiesPanel.tsx per mostrare settings pagina corrente
  2. Supportare override formato/orientamento/margini per pagina

Fase 7: UI Polish (30 min)

  1. Aggiungere navigazione rapida in toolbar
  2. Miniature pagine nel navigator
  3. Indicatore pagina corrente nel canvas

Fase 8: Test e Documentazione (30 min)

  1. Test completo flusso multi-pagina
  2. Test migrazione template esistenti
  3. Aggiornare CLAUDE.md con documentazione

Considerazioni UX

  1. Default intuitivo: Template nuovo ha 1 pagina, utente può aggiungerne altre
  2. Copia/Incolla tra pagine: Gli elementi copiati mantengono posizione, si può incollare su altra pagina
  3. Keyboard shortcuts:
    • Page Up / Page Down per navigare
    • Ctrl+Shift+N per nuova pagina
  4. Undo/Redo: Le operazioni su pagine sono incluse nella history
  5. Preview: Mostra tutte le pagine del PDF generato

File da Modificare

File Tipo Modifica
frontend/src/types/report.ts Aggiungere AprtPage, modificare AprtTemplate, AprtElement
frontend/src/pages/ReportEditorPage.tsx Gestione stato pagina corrente, filtro elementi
frontend/src/components/reportEditor/PageNavigator.tsx NUOVO - Navigatore pagine
frontend/src/components/reportEditor/EditorCanvas.tsx Ricevere elementi filtrati
frontend/src/components/reportEditor/PropertiesPanel.tsx Settings pagina corrente
frontend/src/components/reportEditor/EditorToolbar.tsx Navigazione rapida
src/Apollinare.API/Services/Reports/AprtModels.cs Aggiungere AprtPage
src/Apollinare.API/Services/Reports/ReportGeneratorService.cs Loop pagine per PDF
CLAUDE.md Documentazione feature

Stima Tempo Totale

~4 ore per implementazione completa