docs: Reorganize documentation by replacing Claude-specific instructions and a general plan with new development and Gemini guides, and remove temporary database files.
This commit is contained in:
2077
DEVELOPMENT.md
Normal file
2077
DEVELOPMENT.md
Normal file
File diff suppressed because it is too large
Load Diff
343
PLAN.md
343
PLAN.md
@@ -1,343 +0,0 @@
|
|||||||
# Piano: Collaborazione Real-Time nel Report Designer
|
|
||||||
|
|
||||||
## Obiettivo
|
|
||||||
|
|
||||||
Implementare la collaborazione in tempo reale nel Report Designer, simile a Google Docs o Excel Online. Quando un utente modifica un template, tutti gli altri utenti collegati allo stesso template vedono le modifiche istantaneamente.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architettura Proposta
|
|
||||||
|
|
||||||
### Concetti Chiave
|
|
||||||
|
|
||||||
1. **Room-based Collaboration**: Ogni template ha una "room" SignalR dedicata
|
|
||||||
2. **Operational Transformation Semplificata**: Invece di OT completo (complesso), usiamo un modello "last-write-wins" con sync frequente
|
|
||||||
3. **Presence Awareness**: Gli utenti vedono chi altro sta modificando il template
|
|
||||||
4. **Cursor/Selection Sharing**: Mostra quale elemento sta selezionando ogni utente
|
|
||||||
|
|
||||||
### Flusso Dati
|
|
||||||
|
|
||||||
```
|
|
||||||
User A modifica elemento
|
|
||||||
↓
|
|
||||||
Template locale aggiornato (come ora)
|
|
||||||
↓
|
|
||||||
SignalR invia delta alla room
|
|
||||||
↓
|
|
||||||
Backend riceve e ritrasmette a tutti nella room (eccetto mittente)
|
|
||||||
↓
|
|
||||||
User B/C/... ricevono delta
|
|
||||||
↓
|
|
||||||
Applicano delta al loro template locale
|
|
||||||
↓
|
|
||||||
Canvas si aggiorna automaticamente (già funziona così)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementazione Dettagliata
|
|
||||||
|
|
||||||
### FASE 1: Backend - ReportCollaborationHub
|
|
||||||
|
|
||||||
**File:** `/src/Apollinare.API/Hubs/ReportCollaborationHub.cs`
|
|
||||||
|
|
||||||
Nuovo Hub dedicato per la collaborazione sui report:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class ReportCollaborationHub : Hub
|
|
||||||
{
|
|
||||||
// Stato in-memory degli utenti per template
|
|
||||||
private static ConcurrentDictionary<string, HashSet<CollaboratorInfo>> _templateUsers = new();
|
|
||||||
|
|
||||||
// Join a template room
|
|
||||||
public async Task JoinTemplate(int templateId, string userName, string userColor)
|
|
||||||
|
|
||||||
// Leave template room
|
|
||||||
public async Task LeaveTemplate(int templateId)
|
|
||||||
|
|
||||||
// Broadcast element change to room
|
|
||||||
public async Task ElementChanged(int templateId, ElementChangeDto change)
|
|
||||||
|
|
||||||
// Broadcast element added
|
|
||||||
public async Task ElementAdded(int templateId, AprtElement element)
|
|
||||||
|
|
||||||
// Broadcast element deleted
|
|
||||||
public async Task ElementDeleted(int templateId, string elementId)
|
|
||||||
|
|
||||||
// Broadcast page changes
|
|
||||||
public async Task PageChanged(int templateId, PageChangeDto change)
|
|
||||||
|
|
||||||
// Broadcast selection change (which element user is editing)
|
|
||||||
public async Task SelectionChanged(int templateId, string? elementId)
|
|
||||||
|
|
||||||
// Broadcast cursor position (optional, for live cursors)
|
|
||||||
public async Task CursorMoved(int templateId, float x, float y)
|
|
||||||
|
|
||||||
// Request full template sync (when joining late or after reconnect)
|
|
||||||
public async Task RequestSync(int templateId)
|
|
||||||
|
|
||||||
// Send full template state (host responds to sync requests)
|
|
||||||
public async Task SendSync(int templateId, string connectionId, AprtTemplate template)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**DTOs:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public record CollaboratorInfo(string ConnectionId, string UserName, string Color, string? SelectedElementId);
|
|
||||||
|
|
||||||
public record ElementChangeDto(
|
|
||||||
string ElementId,
|
|
||||||
string ChangeType, // "position", "style", "content", "visibility", etc.
|
|
||||||
object NewValue
|
|
||||||
);
|
|
||||||
|
|
||||||
public record PageChangeDto(
|
|
||||||
string PageId,
|
|
||||||
string ChangeType, // "added", "deleted", "reordered", "renamed", "settings"
|
|
||||||
object? Data
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### FASE 2: Frontend - Servizio Collaborazione
|
|
||||||
|
|
||||||
**File:** `/frontend/src/services/reportCollaboration.ts`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class ReportCollaborationService {
|
|
||||||
private connection: HubConnection | null = null;
|
|
||||||
private templateId: number | null = null;
|
|
||||||
private listeners: Map<string, Set<Function>> = new Map();
|
|
||||||
|
|
||||||
// Connessione e gestione room
|
|
||||||
async joinTemplate(
|
|
||||||
templateId: number,
|
|
||||||
userName: string,
|
|
||||||
userColor: string,
|
|
||||||
): Promise<void>;
|
|
||||||
async leaveTemplate(): Promise<void>;
|
|
||||||
|
|
||||||
// Invio modifiche (chiamati dal ReportEditorPage)
|
|
||||||
sendElementChange(elementId: string, changeType: string, newValue: any): void;
|
|
||||||
sendElementAdded(element: AprtElement): void;
|
|
||||||
sendElementDeleted(elementId: string): void;
|
|
||||||
sendPageChange(pageId: string, changeType: string, data?: any): void;
|
|
||||||
sendSelectionChange(elementId: string | null): void;
|
|
||||||
|
|
||||||
// Sottoscrizione eventi (per ricevere modifiche da altri)
|
|
||||||
onElementChanged(callback: (change: ElementChange) => void): () => void;
|
|
||||||
onElementAdded(callback: (element: AprtElement) => void): () => void;
|
|
||||||
onElementDeleted(callback: (elementId: string) => void): () => void;
|
|
||||||
onPageChanged(callback: (change: PageChange) => void): () => void;
|
|
||||||
onCollaboratorsChanged(
|
|
||||||
callback: (collaborators: Collaborator[]) => void,
|
|
||||||
): () => void;
|
|
||||||
onSelectionChanged(
|
|
||||||
callback: (userId: string, elementId: string | null) => void,
|
|
||||||
): () => void;
|
|
||||||
onSyncRequested(callback: (requesterId: string) => void): () => void;
|
|
||||||
|
|
||||||
// Sync
|
|
||||||
requestSync(): void;
|
|
||||||
sendSync(connectionId: string, template: AprtTemplate): void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FASE 3: Frontend - Integrazione in ReportEditorPage
|
|
||||||
|
|
||||||
**Modifiche a:** `/frontend/src/pages/ReportEditorPage.tsx`
|
|
||||||
|
|
||||||
1. **Nuovo State per collaborazione:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const [collaborators, setCollaborators] = useState<Collaborator[]>([]);
|
|
||||||
const [remoteSelections, setRemoteSelections] = useState<Map<string, string>>(
|
|
||||||
new Map(),
|
|
||||||
);
|
|
||||||
const [isCollaborating, setIsCollaborating] = useState(false);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Join/Leave room al mount/unmount:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isNew && id) {
|
|
||||||
const userName = getCurrentUserName(); // Da auth context
|
|
||||||
const userColor = generateUserColor(userName);
|
|
||||||
|
|
||||||
reportCollaborationService
|
|
||||||
.joinTemplate(Number(id), userName, userColor)
|
|
||||||
.then(() => setIsCollaborating(true));
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
reportCollaborationService.leaveTemplate();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [id, isNew]);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Sottoscrizione eventi remoti:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isCollaborating) return;
|
|
||||||
|
|
||||||
const unsubscribers = [
|
|
||||||
reportCollaborationService.onElementChanged((change) => {
|
|
||||||
// Applica modifica senza creare history entry
|
|
||||||
historyActions.setWithoutHistory((prev) => {
|
|
||||||
// ... applica change.newValue a element con change.elementId
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
reportCollaborationService.onElementAdded((element) => {
|
|
||||||
historyActions.setWithoutHistory((prev) => ({
|
|
||||||
...prev,
|
|
||||||
elements: [...prev.elements, element],
|
|
||||||
}));
|
|
||||||
}),
|
|
||||||
|
|
||||||
// ... altri handler
|
|
||||||
|
|
||||||
reportCollaborationService.onCollaboratorsChanged(setCollaborators),
|
|
||||||
];
|
|
||||||
|
|
||||||
return () => unsubscribers.forEach((unsub) => unsub());
|
|
||||||
}, [isCollaborating]);
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Invio modifiche locali:**
|
|
||||||
|
|
||||||
Modificare `handleUpdateElement` per inviare anche via SignalR:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const handleUpdateElement = useCallback(
|
|
||||||
(elementId: string, updates: Partial<AprtElement>) => {
|
|
||||||
// Aggiorna stato locale (come ora)
|
|
||||||
historyActions.set((prev) => ({...}));
|
|
||||||
|
|
||||||
// Invia a collaboratori
|
|
||||||
if (isCollaborating) {
|
|
||||||
reportCollaborationService.sendElementChange(elementId, "update", updates);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[historyActions, isCollaborating],
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### FASE 4: UI Collaborazione
|
|
||||||
|
|
||||||
**File:** `/frontend/src/components/reportEditor/CollaboratorsBar.tsx`
|
|
||||||
|
|
||||||
Barra che mostra gli utenti connessi:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface CollaboratorsBarProps {
|
|
||||||
collaborators: Collaborator[];
|
|
||||||
remoteSelections: Map<string, string>; // userId -> elementId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mostra:
|
|
||||||
// - Avatar circolari colorati per ogni collaboratore
|
|
||||||
// - Tooltip con nome utente
|
|
||||||
// - Indicatore "sta modificando [elemento]"
|
|
||||||
// - Badge con conteggio totale collaboratori
|
|
||||||
```
|
|
||||||
|
|
||||||
**Modifiche a EditorCanvas:** Evidenziare elementi selezionati da altri utenti con bordo colorato.
|
|
||||||
|
|
||||||
### FASE 5: Gestione Conflitti
|
|
||||||
|
|
||||||
Per semplicità, usiamo strategia **"last-write-wins"** con alcune ottimizzazioni:
|
|
||||||
|
|
||||||
1. **Elementi diversi**: Nessun conflitto, modifiche applicate indipendentemente
|
|
||||||
2. **Stesso elemento, proprietà diverse**: Merge delle proprietà
|
|
||||||
3. **Stesso elemento, stessa proprietà**: Ultima modifica vince
|
|
||||||
4. **Lock visivo**: Quando un utente seleziona un elemento, gli altri vedono un indicatore
|
|
||||||
|
|
||||||
**Opzionale (Fase futura):** Lock pessimistico - solo un utente alla volta può modificare un elemento.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File da Creare/Modificare
|
|
||||||
|
|
||||||
### Nuovi File
|
|
||||||
|
|
||||||
| File | Descrizione |
|
|
||||||
| ----------------------------------------------------------- | ----------------------------------- |
|
|
||||||
| `src/Apollinare.API/Hubs/ReportCollaborationHub.cs` | Hub SignalR per collaborazione |
|
|
||||||
| `src/Apollinare.API/Models/CollaborationDtos.cs` | DTOs per messaggi collaborazione |
|
|
||||||
| `frontend/src/services/reportCollaboration.ts` | Client SignalR per collaborazione |
|
|
||||||
| `frontend/src/components/reportEditor/CollaboratorsBar.tsx` | UI collaboratori connessi |
|
|
||||||
| `frontend/src/types/collaboration.ts` | Types TypeScript per collaborazione |
|
|
||||||
|
|
||||||
### File da Modificare
|
|
||||||
|
|
||||||
| File | Modifiche |
|
|
||||||
| -------------------------------------------------------- | ------------------------------------------------- |
|
|
||||||
| `src/Apollinare.API/Program.cs` | Registrare nuovo hub `/hubs/report-collaboration` |
|
|
||||||
| `frontend/src/pages/ReportEditorPage.tsx` | Integrare collaborazione, stato collaboratori |
|
|
||||||
| `frontend/src/components/reportEditor/EditorCanvas.tsx` | Mostrare selezioni remote |
|
|
||||||
| `frontend/src/components/reportEditor/EditorToolbar.tsx` | Mostrare CollaboratorsBar |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Stima Complessità
|
|
||||||
|
|
||||||
| Fase | Complessità | Note |
|
|
||||||
| ---------------------- | ----------- | ---------------------------------------- |
|
|
||||||
| 1. Backend Hub | Media | SignalR groups, gestione stato in-memory |
|
|
||||||
| 2. Frontend Service | Media | Gestione connessione, eventi |
|
|
||||||
| 3. Integrazione Editor | Alta | Molti handler da modificare |
|
|
||||||
| 4. UI Collaboratori | Bassa | Componente semplice |
|
|
||||||
| 5. Gestione Conflitti | Media | Merge logic |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Considerazioni Aggiuntive
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Throttling degli aggiornamenti durante drag (ogni 50-100ms invece di ogni frame)
|
|
||||||
- Debounce per modifiche testo (300ms)
|
|
||||||
- Batch di modifiche multiple in singolo messaggio
|
|
||||||
|
|
||||||
### Scalabilità
|
|
||||||
|
|
||||||
- Per deployment multi-server: usare Redis backplane per SignalR
|
|
||||||
- Considerare Azure SignalR Service per produzione
|
|
||||||
|
|
||||||
### Autenticazione
|
|
||||||
|
|
||||||
- Aggiungere autenticazione al hub (verificare che utente abbia accesso al template)
|
|
||||||
- Usare JWT token per identificare utente
|
|
||||||
|
|
||||||
### Edge Cases
|
|
||||||
|
|
||||||
- Reconnessione dopo disconnessione: richiedere sync completo
|
|
||||||
- Template eliminato mentre utenti connessi: notificare e chiudere
|
|
||||||
- Conflitto salvataggio: merge o "force save" con conferma
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ordine di Implementazione Consigliato
|
|
||||||
|
|
||||||
1. **Backend Hub base** - Join/Leave room, broadcast semplice
|
|
||||||
2. **Frontend service base** - Connessione, invio/ricezione messaggi
|
|
||||||
3. **Integrazione minima** - Solo sync modifiche elementi (no UI collaboratori)
|
|
||||||
4. **Test funzionale** - Verificare che modifiche si propaghino
|
|
||||||
5. **UI Collaboratori** - Mostrare chi è connesso
|
|
||||||
6. **Selezioni remote** - Evidenziare elementi selezionati da altri
|
|
||||||
7. **Ottimizzazioni** - Throttling, batching, gestione conflitti
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Domande per l'Utente
|
|
||||||
|
|
||||||
Prima di procedere, confermare:
|
|
||||||
|
|
||||||
1. **Autenticazione**: Il sistema ha già autenticazione utenti? Devo usare un sistema mock per ora?
|
|
||||||
2. **Persistenza stato**: Le modifiche devono essere salvate automaticamente o solo quando l'utente clicca "Salva"?
|
|
||||||
3. **Lock elementi**: Vuoi che solo un utente alla volta possa modificare un elemento (lock pessimistico)?
|
|
||||||
4. **Cursori live**: Vuoi vedere il cursore degli altri utenti in tempo reale (come Google Docs)?
|
|
||||||
5. **Nome utente**: Da dove prendo il nome utente da mostrare? (localStorage, auth context, prompt?)
|
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user