# RFCP Frontend - Iteration 1.1.1: UX Safety & Undo/Redo **Date:** January 30, 2025 **Type:** Frontend Enhancement **Estimated:** 2-3 hours **Location:** `/opt/rfcp/frontend/` --- ## 🎯 Goal Add safety confirmations for destructive actions and implement full Undo/Redo system. --- ## 📋 Pre-reading 1. Review current state management in `src/store/` (Zustand) 2. Check existing toast implementation for delete actions --- ## 📊 Current State - Toast exists for delete actions (basic) - No unsaved changes detection - No confirmation dialogs - No undo/redo system --- ## ✅ Tasks ### 1. Unsaved Changes Detection **Add dirty state tracking to store:** ```typescript // src/store/projectStore.ts (or similar) interface ProjectState { // ... existing isDirty: boolean; lastSavedState: string | null; // JSON snapshot markDirty: () => void; markClean: () => void; checkDirty: () => boolean; } // On any change to sites/settings: markDirty() // On save: markClean() lastSavedState = JSON.stringify(currentState) ``` **Browser beforeunload warning:** ```typescript // src/App.tsx or dedicated hook useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (store.isDirty) { e.preventDefault(); e.returnValue = ''; // Required for Chrome } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, []); ``` --- ### 2. Confirmation Dialogs **Create reusable ConfirmDialog component:** ```typescript // src/components/ui/ConfirmDialog.tsx interface ConfirmDialogProps { isOpen: boolean; title: string; message: string; confirmText?: string; // default: "Confirm" cancelText?: string; // default: "Cancel" variant?: 'danger' | 'warning' | 'info'; onConfirm: () => void; onCancel: () => void; } // Styling: // - danger: red confirm button (for delete) // - warning: yellow/orange (for discard changes) // - info: blue (for info confirmations) ``` **Apply to actions:** | Action | Dialog | |--------|--------| | Load project (when dirty) | "Є незбережені зміни. Завантажити інший проект?" | | Delete project | "Видалити проект '{name}'? Цю дію не можна скасувати." | | Delete site | "Видалити станцію '{name}'?" | | Delete sector | "Видалити сектор '{name}'?" | | New project (when dirty) | "Є незбережені зміни. Створити новий проект?" | | Page refresh (handled by beforeunload) | Browser native dialog | --- ### 3. Undo/Redo System **Create history store:** ```typescript // src/store/historyStore.ts interface HistoryState { past: ProjectSnapshot[]; future: ProjectSnapshot[]; maxHistory: number; // default: 50 // Actions push: (snapshot: ProjectSnapshot) => void; undo: () => ProjectSnapshot | null; redo: () => ProjectSnapshot | null; clear: () => void; // Selectors canUndo: boolean; canRedo: boolean; } type ProjectSnapshot = { sites: Site[]; settings: CoverageSettings; timestamp: number; action: string; // "add site", "delete sector", "move site", etc. }; ``` **Integration points — push snapshot BEFORE these actions:** - Add site - Delete site - Update site (position, params) - Add sector - Delete sector - Update sector - Update coverage settings - Import project - Clear all sites **Keyboard shortcuts:** ```typescript // src/hooks/useKeyboardShortcuts.ts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Undo: Ctrl+Z (Windows/Linux) or Cmd+Z (Mac) if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) { e.preventDefault(); handleUndo(); } // Redo: Ctrl+Shift+Z or Ctrl+Y if ((e.ctrlKey || e.metaKey) && ( (e.key === 'z' && e.shiftKey) || e.key === 'y' )) { e.preventDefault(); handleRedo(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); ``` **UI indicators:** ```typescript // Toolbar buttons (disabled when can't undo/redo) ``` --- ### 4. Enhanced Toast Notifications **Extend existing toast for all actions:** ```typescript // Success toasts "Проект збережено" "Станцію додано" "Станцію видалено" "Зміни скасовано" (undo) "Зміни повернено" (redo) // Error toasts "Помилка збереження" "Помилка завантаження" // Info toasts "Проект завантажено" ``` **Toast with undo action (optional enhancement):** ```typescript // When deleting, show toast with undo button toast({ message: "Станцію видалено", action: { label: "Скасувати", onClick: () => handleUndo() }, duration: 5000 }); ``` --- ## 📁 Files to Create/Modify ``` src/ ├── components/ui/ │ └── ConfirmDialog.tsx # NEW ├── store/ │ ├── historyStore.ts # NEW │ └── projectStore.ts # MODIFY (add isDirty) ├── hooks/ │ ├── useUnsavedChanges.ts # NEW │ └── useKeyboardShortcuts.ts # NEW or MODIFY └── App.tsx # MODIFY (add beforeunload) ``` --- ## ✅ Success Criteria - [ ] Refresh page with unsaved changes → browser warning - [ ] Load project with unsaved changes → confirmation dialog - [ ] Delete project → confirmation dialog (danger style) - [ ] Delete site/sector → confirmation dialog - [ ] Ctrl+Z undoes last action - [ ] Ctrl+Shift+Z redoes - [ ] Undo/Redo buttons in toolbar (with disabled states) - [ ] Toast shows on save/delete/undo/redo - [ ] History limited to 50 states (memory management) --- ## 🧪 Test Scenarios 1. **Unsaved changes:** - Add site → refresh → browser warning appears - Add site → save → refresh → no warning 2. **Confirmations:** - Add site → click Load → dialog appears → Cancel → site still there - Add site → click Load → dialog appears → Confirm → new project loads 3. **Undo/Redo:** - Add 3 sites → Ctrl+Z → 2 sites remain - Ctrl+Z again → 1 site - Ctrl+Shift+Z → 2 sites back - Add new site after undo → redo history cleared 4. **Edge cases:** - Undo when nothing to undo → button disabled, no action - 51 actions → oldest dropped from history --- ## 📝 Notes - Keep snapshots lightweight (only sites + settings, not UI state) - Debounce position changes (don't snapshot every pixel of drag) - Consider grouping rapid changes (e.g., typing in input) - Ukrainian UI text for all dialogs/toasts --- **Ready for Claude Code** 🚀