12 KiB
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.elementscome array flat (tutti gli elementi) - AprtTemplate: Ha già un campo
sectionsma non è usato per le pagine - Gli elementi hanno già un campo
section(header/body/footer) ma nonpageIndex
Backend
- ReportGeneratorService.cs: Genera PDF con una sola pagina (
container.Page(...)) - AprtModels.cs: Definisce
AprtSectionconrepeatOnPages(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)
- Aggiornare
report.tsconAprtPagee modifiche aAprtTemplate/AprtElement - Aggiornare
AprtModels.cscon le stesse strutture - Aggiornare
defaultTemplatecon pagina di default
Fase 2: Backend PDF Multi-Pagina (30 min)
- Modificare
ReportGeneratorService.GeneratePdfAsyncper iterare sulle pagine - Aggiungere logica di migrazione per template esistenti
- Testare generazione PDF con template multi-pagina manuale
Fase 3: Frontend - PageNavigator (1 ora)
- Creare componente
PageNavigator.tsx - Integrare in
ReportEditorPage.tsx(layout con sidebar sinistra) - Implementare selezione pagina corrente
Fase 4: Frontend - Gestione Elementi per Pagina (30 min)
- Modificare
ReportEditorPage.tsxper filtrare elementi per pagina - Assegnare
pageIdagli elementi nuovi - Aggiornare
EditorCanvas.tsxper ricevere solo elementi pagina corrente
Fase 5: Frontend - CRUD Pagine (45 min)
- Implementare aggiunta pagina
- Implementare duplicazione pagina (con copia elementi)
- Implementare eliminazione pagina
- Implementare riordinamento pagine (drag-and-drop)
Fase 6: Frontend - Settings Pagina (30 min)
- Modificare
PropertiesPanel.tsxper mostrare settings pagina corrente - Supportare override formato/orientamento/margini per pagina
Fase 7: UI Polish (30 min)
- Aggiungere navigazione rapida in toolbar
- Miniature pagine nel navigator
- Indicatore pagina corrente nel canvas
Fase 8: Test e Documentazione (30 min)
- Test completo flusso multi-pagina
- Test migrazione template esistenti
- Aggiornare CLAUDE.md con documentazione
Considerazioni UX
- Default intuitivo: Template nuovo ha 1 pagina, utente può aggiungerne altre
- Copia/Incolla tra pagine: Gli elementi copiati mantengono posizione, si può incollare su altra pagina
- Keyboard shortcuts:
Page Up/Page Downper navigareCtrl+Shift+Nper nuova pagina
- Undo/Redo: Le operazioni su pagine sono incluse nella history
- 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