리도,언도기능

This commit is contained in:
kjs 2026-01-08 12:28:48 +09:00
parent a146667615
commit 980c929d83
1 changed files with 93 additions and 2 deletions

View File

@ -55,8 +55,91 @@ export const EditableSpreadsheet: React.FC<EditableSpreadsheetProps> = ({
// 복사된 범위 (점선 애니메이션 표시용) // 복사된 범위 (점선 애니메이션 표시용)
const [copiedRange, setCopiedRange] = useState<CellRange | null>(null); const [copiedRange, setCopiedRange] = useState<CellRange | null>(null);
// Undo/Redo 히스토리
interface HistoryState {
columns: string[];
data: Record<string, any>[];
}
const [history, setHistory] = useState<HistoryState[]>([]);
const [historyIndex, setHistoryIndex] = useState(-1);
const [isUndoRedo, setIsUndoRedo] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const tableRef = useRef<HTMLDivElement>(null); const tableRef = useRef<HTMLDivElement>(null);
// 히스토리에 현재 상태 저장
const saveToHistory = useCallback(() => {
if (isUndoRedo) return;
const newState: HistoryState = {
columns: [...columns],
data: data.map(row => ({ ...row })),
};
setHistory(prev => {
// 현재 인덱스 이후의 히스토리는 삭제 (새로운 분기)
const newHistory = prev.slice(0, historyIndex + 1);
newHistory.push(newState);
// 최대 50개까지만 유지
if (newHistory.length > 50) {
newHistory.shift();
return newHistory;
}
return newHistory;
});
setHistoryIndex(prev => Math.min(prev + 1, 49));
}, [columns, data, historyIndex, isUndoRedo]);
// 초기 상태 저장
useEffect(() => {
if (history.length === 0 && (columns.length > 0 || data.length > 0)) {
setHistory([{ columns: [...columns], data: data.map(row => ({ ...row })) }]);
setHistoryIndex(0);
}
}, []);
// 데이터 변경 시 히스토리 저장 (Undo/Redo가 아닌 경우)
useEffect(() => {
if (!isUndoRedo && historyIndex >= 0) {
const currentState = history[historyIndex];
if (currentState) {
const columnsChanged = JSON.stringify(columns) !== JSON.stringify(currentState.columns);
const dataChanged = JSON.stringify(data) !== JSON.stringify(currentState.data);
if (columnsChanged || dataChanged) {
saveToHistory();
}
}
}
setIsUndoRedo(false);
}, [columns, data]);
// Undo 실행
const handleUndo = useCallback(() => {
if (historyIndex <= 0) return;
const prevIndex = historyIndex - 1;
const prevState = history[prevIndex];
if (prevState) {
setIsUndoRedo(true);
setHistoryIndex(prevIndex);
onColumnsChange([...prevState.columns]);
onDataChange(prevState.data.map(row => ({ ...row })));
}
}, [history, historyIndex, onColumnsChange, onDataChange]);
// Redo 실행
const handleRedo = useCallback(() => {
if (historyIndex >= history.length - 1) return;
const nextIndex = historyIndex + 1;
const nextState = history[nextIndex];
if (nextState) {
setIsUndoRedo(true);
setHistoryIndex(nextIndex);
onColumnsChange([...nextState.columns]);
onDataChange(nextState.data.map(row => ({ ...row })));
}
}, [history, historyIndex, onColumnsChange, onDataChange]);
// 범위 정규화 (시작이 끝보다 크면 교환) // 범위 정규화 (시작이 끝보다 크면 교환)
const normalizeRange = (range: CellRange): CellRange => { const normalizeRange = (range: CellRange): CellRange => {
@ -518,7 +601,15 @@ export const EditableSpreadsheet: React.FC<EditableSpreadsheetProps> = ({
return; return;
} }
if ((e.ctrlKey || e.metaKey) && e.key === "c") { if ((e.ctrlKey || e.metaKey) && e.key === "z") {
// Ctrl+Z: Undo
e.preventDefault();
handleUndo();
} else if ((e.ctrlKey || e.metaKey) && e.key === "y") {
// Ctrl+Y: Redo
e.preventDefault();
handleRedo();
} else if ((e.ctrlKey || e.metaKey) && e.key === "c") {
e.preventDefault(); e.preventDefault();
handleCopy(); handleCopy();
} else if ((e.ctrlKey || e.metaKey) && e.key === "v") { } else if ((e.ctrlKey || e.metaKey) && e.key === "v") {
@ -555,7 +646,7 @@ export const EditableSpreadsheet: React.FC<EditableSpreadsheetProps> = ({
document.addEventListener("keydown", handleGlobalKeyDown); document.addEventListener("keydown", handleGlobalKeyDown);
return () => document.removeEventListener("keydown", handleGlobalKeyDown); return () => document.removeEventListener("keydown", handleGlobalKeyDown);
}, [editingCell, selection, handleCopy, handlePaste, handleDelete]); }, [editingCell, selection, handleCopy, handlePaste, handleDelete, handleUndo, handleRedo]);
// 행 삭제 // 행 삭제
const handleDeleteRow = (rowIndex: number) => { const handleDeleteRow = (rowIndex: number) => {