@mytec: iter1.1.1 start
This commit is contained in:
308
RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md
Normal file
308
RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# 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** 🚀
|
||||
Reference in New Issue
Block a user