309 lines
6.9 KiB
Markdown
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** 🚀
|