122 lines
3.3 KiB
JavaScript
122 lines
3.3 KiB
JavaScript
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
|
|
export const useCellLocks = (userId, lockTimeout = 30000) => {
|
|
const [lockedCells, setLockedCells] = useState(new Map());
|
|
const lockTimersRef = useRef(new Map());
|
|
|
|
const getCellKey = (rowId, columnId) => `${rowId}_${columnId}`;
|
|
|
|
const lockCell = useCallback((rowId, columnId, lockingUserId) => {
|
|
const cellKey = getCellKey(rowId, columnId);
|
|
|
|
setLockedCells(prev => {
|
|
const newLocks = new Map(prev);
|
|
const existingLock = newLocks.get(cellKey);
|
|
|
|
if (existingLock && existingLock.userId !== lockingUserId) {
|
|
return prev; // Cell already locked by another user
|
|
}
|
|
|
|
newLocks.set(cellKey, {
|
|
userId: lockingUserId,
|
|
lockedAt: Date.now(),
|
|
rowId,
|
|
columnId
|
|
});
|
|
|
|
return newLocks;
|
|
});
|
|
|
|
return true;
|
|
}, []);
|
|
|
|
const unlockCell = useCallback((rowId, columnId, unlockingUserId) => {
|
|
const cellKey = getCellKey(rowId, columnId);
|
|
|
|
setLockedCells(prev => {
|
|
const newLocks = new Map(prev);
|
|
const lock = newLocks.get(cellKey);
|
|
|
|
if (lock && lock.userId === unlockingUserId) {
|
|
newLocks.delete(cellKey);
|
|
}
|
|
|
|
return newLocks;
|
|
});
|
|
|
|
// Clear timer if exists
|
|
if (lockTimersRef.current.has(cellKey)) {
|
|
clearTimeout(lockTimersRef.current.get(cellKey));
|
|
lockTimersRef.current.delete(cellKey);
|
|
}
|
|
}, []);
|
|
|
|
const tryLockCell = useCallback((rowId, columnId, currentUserId) => {
|
|
const cellKey = getCellKey(rowId, columnId);
|
|
const existingLock = lockedCells.get(cellKey);
|
|
|
|
if (existingLock && existingLock.userId !== currentUserId) {
|
|
return false; // Cell is locked by another user
|
|
}
|
|
|
|
if (!existingLock) {
|
|
lockCell(rowId, columnId, currentUserId);
|
|
|
|
// Auto-unlock after timeout
|
|
const timer = setTimeout(() => {
|
|
unlockCell(rowId, columnId, currentUserId);
|
|
}, lockTimeout);
|
|
|
|
lockTimersRef.current.set(cellKey, timer);
|
|
}
|
|
|
|
return true;
|
|
}, [lockedCells, lockCell, unlockCell, lockTimeout]);
|
|
|
|
const releaseAllLocks = useCallback((userId) => {
|
|
setLockedCells(prev => {
|
|
const newLocks = new Map(prev);
|
|
for (const [key, lock] of newLocks.entries()) {
|
|
if (lock.userId === userId) {
|
|
newLocks.delete(key);
|
|
if (lockTimersRef.current.has(key)) {
|
|
clearTimeout(lockTimersRef.current.get(key));
|
|
lockTimersRef.current.delete(key);
|
|
}
|
|
}
|
|
}
|
|
return newLocks;
|
|
});
|
|
}, []);
|
|
|
|
const isCellLocked = useCallback((rowId, columnId, currentUserId) => {
|
|
const cellKey = getCellKey(rowId, columnId);
|
|
const lock = lockedCells.get(cellKey);
|
|
return lock && lock.userId !== currentUserId;
|
|
}, [lockedCells]);
|
|
|
|
const getCellLockInfo = useCallback((rowId, columnId) => {
|
|
const cellKey = getCellKey(rowId, columnId);
|
|
return lockedCells.get(cellKey);
|
|
}, [lockedCells]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
// Clear all timers on unmount
|
|
for (const timer of lockTimersRef.current.values()) {
|
|
clearTimeout(timer);
|
|
}
|
|
lockTimersRef.current.clear();
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
lockedCells,
|
|
lockCell,
|
|
unlockCell,
|
|
tryLockCell,
|
|
isCellLocked,
|
|
getCellLockInfo,
|
|
releaseAllLocks
|
|
};
|
|
}; |