Initial commit
This commit is contained in:
375
.claude/plan-multi-page.md
Normal file
375
.claude/plan-multi-page.md
Normal file
@@ -0,0 +1,375 @@
|
||||
# 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:
|
||||
|
||||
```typescript
|
||||
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`)
|
||||
|
||||
```typescript
|
||||
// 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`
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
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`
|
||||
|
||||
```csharp
|
||||
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`
|
||||
|
||||
```csharp
|
||||
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:
|
||||
|
||||
```csharp
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// 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)
|
||||
4. [ ] Modificare `ReportGeneratorService.GeneratePdfAsync` per iterare sulle pagine
|
||||
5. [ ] Aggiungere logica di migrazione per template esistenti
|
||||
6. [ ] Testare generazione PDF con template multi-pagina manuale
|
||||
|
||||
### Fase 3: Frontend - PageNavigator (1 ora)
|
||||
7. [ ] Creare componente `PageNavigator.tsx`
|
||||
8. [ ] Integrare in `ReportEditorPage.tsx` (layout con sidebar sinistra)
|
||||
9. [ ] Implementare selezione pagina corrente
|
||||
|
||||
### Fase 4: Frontend - Gestione Elementi per Pagina (30 min)
|
||||
10. [ ] Modificare `ReportEditorPage.tsx` per filtrare elementi per pagina
|
||||
11. [ ] Assegnare `pageId` agli elementi nuovi
|
||||
12. [ ] Aggiornare `EditorCanvas.tsx` per ricevere solo elementi pagina corrente
|
||||
|
||||
### Fase 5: Frontend - CRUD Pagine (45 min)
|
||||
13. [ ] Implementare aggiunta pagina
|
||||
14. [ ] Implementare duplicazione pagina (con copia elementi)
|
||||
15. [ ] Implementare eliminazione pagina
|
||||
16. [ ] Implementare riordinamento pagine (drag-and-drop)
|
||||
|
||||
### Fase 6: Frontend - Settings Pagina (30 min)
|
||||
17. [ ] Modificare `PropertiesPanel.tsx` per mostrare settings pagina corrente
|
||||
18. [ ] Supportare override formato/orientamento/margini per pagina
|
||||
|
||||
### Fase 7: UI Polish (30 min)
|
||||
19. [ ] Aggiungere navigazione rapida in toolbar
|
||||
20. [ ] Miniature pagine nel navigator
|
||||
21. [ ] Indicatore pagina corrente nel canvas
|
||||
|
||||
### Fase 8: Test e Documentazione (30 min)
|
||||
22. [ ] Test completo flusso multi-pagina
|
||||
23. [ ] Test migrazione template esistenti
|
||||
24. [ ] 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
|
||||
Reference in New Issue
Block a user