diff --git a/CLAUDE.md b/CLAUDE.md index ab54395..6c2910f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,34 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve **Lavoro completato nell'ultima sessione:** +- **NUOVA FEATURE: Scorciatoie da Tastiera Complete per Report Designer** - COMPLETATO + - **Problema:** Le scorciatoie da tastiera (Ctrl+X, Ctrl+C, etc.) venivano intercettate dal browser invece che dalla pagina + - **Soluzione:** Riscritto completamente l'handler delle scorciatoie con: + - Controllo se il focus è su campi input/textarea/contenteditable + - Nuovo metodo `isTextEditing()` esposto da EditorCanvasRef per verificare editing testo nel canvas + - `e.preventDefault()` per bloccare comportamento browser + - **Scorciatoie implementate:** + - `Ctrl+Z` - Annulla + - `Ctrl+Y` - Ripeti + - `Ctrl+S` - Salva + - `Ctrl+X` - Taglia + - `Ctrl+C` - Copia + - `Ctrl+V` - Incolla + - `Ctrl+D` - Duplica + - `Ctrl+A` - Seleziona tutto + - `Ctrl+L` - Blocca/Sblocca elemento + - `Ctrl+G` - Raggruppa + - `Ctrl+Shift+G` - Separa + - `Ctrl+]` - Porta avanti + - `Ctrl+Shift+]` - Porta in primo piano + - `Ctrl+[` - Porta indietro + - `Ctrl+Shift+[` - Porta in fondo + - `Delete/Backspace` - Elimina elemento + - `Escape` - Deseleziona + - **File modificati:** + - `EditorCanvas.tsx` - Aggiunto `isTextEditing()` a `EditorCanvasRef` + - `ReportEditorPage.tsx` - Importato `EditorCanvasRef`, aggiunto `canvasRef`, riscritto `useEffect` delle scorciatoie + - **FIX: Rimossa Toolbar Contestuale che causava Layout Shift** - COMPLETATO - **Problema:** Quando si selezionava un oggetto nel canvas, appariva una toolbar aggiuntiva sotto quella principale che causava uno scroll della pagina - **Soluzione:** Rimossa completamente la toolbar contestuale - le proprietà degli oggetti vengono gestite solo dal `PropertiesPanel` sulla destra @@ -233,6 +261,11 @@ cd src/Apollinare.API && dotnet run cd frontend && npm run dev ``` +**IMPORTANTE:** Dopo modifiche al codice, i servizi backend e frontend vanno **sempre riavviati** per vedere le modifiche: + +- Backend: fermare con `Ctrl+C` e rilanciare `dotnet run` +- Frontend: in dev mode (`npm run dev`) il hot-reload è automatico per la maggior parte delle modifiche, ma per modifiche strutturali (nuovi file, cambi a tipi, etc.) potrebbe essere necessario riavviare + --- ## Project Overview @@ -1202,6 +1235,16 @@ frontend/src/ - Import inutilizzati: `TextField`, `ToggleButton`, `ToggleButtonGroup`, icone formattazione - **File:** `EditorToolbar.tsx`, `ReportEditorPage.tsx` +28. **Scorciatoie da Tastiera Intercettate dal Browser (FIX 29/11/2025):** + - **Problema:** Le scorciatoie da tastiera (Ctrl+S, Ctrl+C, Ctrl+V, etc.) nel report designer venivano catturate dal browser invece che dalla pagina - ad esempio Ctrl+S apriva il dialog di salvataggio del browser invece di salvare il template + - **Causa:** L'handler delle scorciatoie non chiamava `e.preventDefault()` in modo consistente e non verificava se l'utente stava modificando testo in un input field o nel canvas + - **Soluzione:** Riscritto completamente l'handler delle scorciatoie: + - Verifica se il target è un campo input (`INPUT`, `TEXTAREA`, `contentEditable`) + - Nuovo metodo `isTextEditing()` esposto da `EditorCanvasRef` per verificare se un elemento testo è in editing nel canvas Fabric.js + - `e.preventDefault()` chiamato subito dopo il riconoscimento della scorciatoia + - Implementate tutte le scorciatoie dichiarate nel context menu (Ctrl+X/C/V/D/A/L/G, Ctrl+[/], Delete, Escape) + - **File:** `EditorCanvas.tsx` (aggiunto `isTextEditing()` a `EditorCanvasRef`), `ReportEditorPage.tsx` (riscritto `useEffect` delle scorciatoie, aggiunto `canvasRef`) + ### Schema Database Report System Le tabelle sono già nel DbContext (`AppollinareDbContext.cs`): diff --git a/frontend/src/components/reportEditor/EditorCanvas.tsx b/frontend/src/components/reportEditor/EditorCanvas.tsx index e681d47..3c9d29e 100644 --- a/frontend/src/components/reportEditor/EditorCanvas.tsx +++ b/frontend/src/components/reportEditor/EditorCanvas.tsx @@ -68,6 +68,8 @@ interface EditorCanvasProps { export interface EditorCanvasRef { getCanvas: () => fabric.Canvas | null; addElement: (element: AprtElement) => void; + /** Check if a text element is currently being edited */ + isTextEditing: () => boolean; } // Snap configuration @@ -155,6 +157,12 @@ const EditorCanvas = forwardRef( fabricRef.current.renderAll(); } }, + isTextEditing: () => { + if (!fabricRef.current) return false; + const activeObj = fabricRef.current.getActiveObject(); + if (!activeObj) return false; + return !!(activeObj as fabric.Textbox).isEditing; + }, })); // Clear guide lines diff --git a/frontend/src/pages/ReportEditorPage.tsx b/frontend/src/pages/ReportEditorPage.tsx index 5569fe5..947c890 100644 --- a/frontend/src/pages/ReportEditorPage.tsx +++ b/frontend/src/pages/ReportEditorPage.tsx @@ -46,6 +46,7 @@ import { } from "@mui/icons-material"; import EditorCanvas, { type ContextMenuEvent, + type EditorCanvasRef, } from "../components/reportEditor/EditorCanvas"; import EditorToolbar, { type SnapOptions, @@ -179,6 +180,9 @@ export default function ReportEditorPage() { // Template version for collaboration sync (increments on each save) const templateVersionRef = useRef(0); + // Ref to EditorCanvas for checking text editing state + const canvasRef = useRef(null); + // ============ COLLABORATION (using global context) ============ // Room key format: "report-template:{id}" const roomKey = id ? `report-template:${id}` : null; @@ -803,6 +807,28 @@ export default function ReportEditorPage() { setSelectedElementIds([]); // Clear selection when switching pages }, []); + // Navigate to previous page + const handlePrevPage = useCallback(() => { + const currentIndex = template.pages.findIndex( + (p) => p.id === currentPageId, + ); + if (currentIndex > 0) { + setCurrentPageId(template.pages[currentIndex - 1].id); + setSelectedElementIds([]); + } + }, [template.pages, currentPageId]); + + // Navigate to next page + const handleNextPage = useCallback(() => { + const currentIndex = template.pages.findIndex( + (p) => p.id === currentPageId, + ); + if (currentIndex < template.pages.length - 1) { + setCurrentPageId(template.pages[currentIndex + 1].id); + setSelectedElementIds([]); + } + }, [template.pages, currentPageId]); + // ============ END PAGE MANAGEMENT HANDLERS ============ // Add new element @@ -1487,24 +1513,161 @@ export default function ReportEditorPage() { // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - if (e.ctrlKey || e.metaKey) { - switch (e.key) { + // Don't handle shortcuts when typing in input fields + const target = e.target as HTMLElement; + const isInputField = + target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable; + + // Don't handle shortcuts when editing text in canvas + const isCanvasTextEditing = canvasRef.current?.isTextEditing() ?? false; + + // Allow browser defaults for input fields and text editing + if (isInputField || isCanvasTextEditing) { + return; + } + + const hasCtrl = e.ctrlKey || e.metaKey; + const hasShift = e.shiftKey; + + // Handle Ctrl/Cmd shortcuts + if (hasCtrl) { + switch (e.key.toLowerCase()) { + // Undo: Ctrl+Z case "z": - e.preventDefault(); - handleUndo(); + if (!hasShift) { + e.preventDefault(); + handleUndo(); + } break; + + // Redo: Ctrl+Y or Ctrl+Shift+Z case "y": e.preventDefault(); handleRedo(); break; + + // Save: Ctrl+S case "s": e.preventDefault(); handleSave(); break; + + // Cut: Ctrl+X + case "x": + if (selectedElementId) { + e.preventDefault(); + handleCut(); + } + break; + + // Copy: Ctrl+C + case "c": + if (selectedElementId) { + e.preventDefault(); + handleCopy(); + } + break; + + // Paste: Ctrl+V + case "v": + if (clipboard) { + e.preventDefault(); + handlePaste(); + } + break; + + // Duplicate: Ctrl+D + case "d": + if (selectedElementId) { + e.preventDefault(); + handleDuplicate(); + } + break; + + // Select All: Ctrl+A + case "a": + e.preventDefault(); + handleSelectAll(); + break; + + // Lock/Unlock: Ctrl+L + case "l": + if (selectedElementId) { + e.preventDefault(); + handleToggleLock(); + } + break; + + // Group: Ctrl+G / Ungroup: Ctrl+Shift+G + case "g": + if (selectedElementId) { + e.preventDefault(); + if (hasShift) { + handleUngroup(); + } else { + handleGroup(); + } + } + break; + + // Layer ordering with brackets + // Bring Forward: Ctrl+] / Bring to Front: Ctrl+Shift+] + case "]": + if (selectedElementId) { + e.preventDefault(); + if (hasShift) { + handleBringToFront(); + } else { + handleBringForward(); + } + } + break; + + // Send Backward: Ctrl+[ / Send to Back: Ctrl+Shift+[ + case "[": + if (selectedElementId) { + e.preventDefault(); + if (hasShift) { + handleSendToBack(); + } else { + handleSendBackward(); + } + } + break; } } - if (e.key === "Delete" && selectedElementId) { - handleDeleteElement(); + + // Non-Ctrl shortcuts + if (!hasCtrl) { + switch (e.key) { + // Delete element + case "Delete": + case "Backspace": + if (selectedElementId && !selectedElement?.locked) { + e.preventDefault(); + handleDeleteElement(); + } + break; + + // Escape to deselect + case "Escape": + e.preventDefault(); + handleDeselectAll(); + break; + + // Page navigation + case "PageUp": + e.preventDefault(); + handlePrevPage(); + break; + + case "PageDown": + e.preventDefault(); + handleNextPage(); + break; + } } }; @@ -1514,8 +1677,25 @@ export default function ReportEditorPage() { handleUndo, handleRedo, handleSave, + handleCut, + handleCopy, + handlePaste, + handleDuplicate, + handleSelectAll, + handleDeselectAll, + handleToggleLock, + handleGroup, + handleUngroup, + handleBringToFront, + handleBringForward, + handleSendToBack, + handleSendBackward, handleDeleteElement, + handlePrevPage, + handleNextPage, selectedElementId, + selectedElement, + clipboard, ]); // Auto-save: simple debounced save on every template change @@ -1701,18 +1881,8 @@ export default function ReportEditorPage() { currentPageIndex={currentPageIndex} totalPages={template.pages.length} currentPageName={currentPage?.name || "Pagina 1"} - onPrevPage={() => { - if (currentPageIndex > 0) { - setCurrentPageId(template.pages[currentPageIndex - 1].id); - setSelectedElementIds([]); - } - }} - onNextPage={() => { - if (currentPageIndex < template.pages.length - 1) { - setCurrentPageId(template.pages[currentPageIndex + 1].id); - setSelectedElementIds([]); - } - }} + onPrevPage={handlePrevPage} + onNextPage={handleNextPage} hasUnsavedChanges={hasUnsavedChanges} // Auto-save props autoSaveEnabled={autoSaveEnabled} @@ -1747,6 +1917,7 @@ export default function ReportEditorPage() { {/* Canvas - show only elements for current page */}