Files
rfcp/docs/devlog/back/RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md

309 lines
6.9 KiB
Markdown

# 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)
<button
onClick={handleUndo}
disabled={!canUndo}
title="Undo (Ctrl+Z)"
>
<UndoIcon />
</button>
<button
onClick={handleRedo}
disabled={!canRedo}
title="Redo (Ctrl+Shift+Z)"
>
<RedoIcon />
</button>
```
---
### 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** 🚀