diff --git a/client/src/components/TaskRegistry.tsx b/client/src/components/TaskRegistry.tsx index 4a6b06f..2d2dd73 100644 --- a/client/src/components/TaskRegistry.tsx +++ b/client/src/components/TaskRegistry.tsx @@ -114,9 +114,11 @@ interface TaskRegistryProps { // Bulk selection isAdmin?: boolean; selectedTaskIds?: Set; + anchorTaskId?: number | null; onToggleSelected?: (taskId: number) => void; onToggleSelectAll?: () => void; onSelectRange?: (taskIds: number[]) => void; + onSetAnchorTaskId?: (taskId: number | null) => void; } // Отображает значение поля с учётом типа @@ -254,13 +256,11 @@ const REGISTRY_INLINE_TYPES = new Set([ // Типы полей, которые переключаются по одиночному клику const REGISTRY_QUICK_TOGGLE_TYPES = new Set(['checkbox', 'toggle']); -const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users = [], roles = [], formId, onFilteredCountChange, onTaskClick, filters, sort, setFilter, clearFilter, clearAll, setSort, clearSort, hasActiveFilters, searches, onAddSearch, onRemoveSearch, isAdmin, selectedTaskIds, onToggleSelected, onToggleSelectAll, onSelectRange }: TaskRegistryProps) => { +const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users = [], roles = [], formId, onFilteredCountChange, onTaskClick, filters, sort, setFilter, clearFilter, clearAll, setSort, clearSort, hasActiveFilters, searches, onAddSearch, onRemoveSearch, isAdmin, selectedTaskIds, anchorTaskId, onToggleSelected, onToggleSelectAll, onSelectRange, onSetAnchorTaskId }: TaskRegistryProps) => { const { toast } = useToast(); // Текущая редактируемая ячейка: { taskId, columnKey } const [editingCell, setEditingCell] = useState<{ taskId: number; columnKey: string } | null>(null); - // Последняя выделенная задача — якорь для Shift+клика - const [lastSelectedTaskId, setLastSelectedTaskId] = useState(null); // Функция для получения имени пользователя по ID const getUserName = (userId: number): string => { @@ -635,23 +635,22 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users = const isCtrl = e.ctrlKey || e.metaKey || e.altKey; const isShift = e.shiftKey; - if (isShift && lastSelectedTaskId != null) { + if (isShift && anchorTaskId != null) { e.preventDefault(); const ids = filteredTasks.map(t => t.id); - const start = ids.indexOf(lastSelectedTaskId); + const start = ids.indexOf(anchorTaskId); const end = ids.indexOf(task.id); if (start !== -1 && end !== -1) { const range = ids.slice(Math.min(start, end), Math.max(start, end) + 1); onSelectRange?.(range); } - setLastSelectedTaskId(task.id); return; } if (isCtrl) { e.preventDefault(); onToggleSelected(task.id); - setLastSelectedTaskId(task.id); + onSetAnchorTaskId?.(task.id); return; } diff --git a/client/src/pages/TaskView.tsx b/client/src/pages/TaskView.tsx index 854113b..b85159b 100644 --- a/client/src/pages/TaskView.tsx +++ b/client/src/pages/TaskView.tsx @@ -75,6 +75,7 @@ const TaskView = () => { const [filteredTasksCount, setFilteredTasksCount] = useState(0); const [createTaskDialogOpen, setCreateTaskDialogOpen] = useState(false); const [selectedTaskIds, setSelectedTaskIds] = useState>(new Set()); + const [anchorTaskId, setAnchorTaskId] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteProgress, setDeleteProgress] = useState<{ processed: number; total: number } | null>(null); @@ -273,6 +274,7 @@ const TaskView = () => { setDeleteProgress(null); queryClient.invalidateQueries({ queryKey: ['/api/forms', formId, 'tasks', 'with-fields'] }); setSelectedTaskIds(new Set()); + setAnchorTaskId(null); setDeleteDialogOpen(false); const desc = data.errors > 0 ? `Удалено задач: ${data.deleted}, ошибок: ${data.errors}` @@ -286,12 +288,23 @@ const TaskView = () => { }); const toggleTask = (taskId: number) => { + let willBeSelected = false; setSelectedTaskIds(prev => { const next = new Set(prev); - if (next.has(taskId)) next.delete(taskId); - else next.add(taskId); + if (next.has(taskId)) { + next.delete(taskId); + willBeSelected = false; + } else { + next.add(taskId); + willBeSelected = true; + } return next; }); + if (willBeSelected) { + setAnchorTaskId(taskId); + } else if (anchorTaskId === taskId) { + setAnchorTaskId(null); + } }; const selectAllVisible = () => { @@ -310,9 +323,13 @@ const TaskView = () => { return next; }); } + setAnchorTaskId(null); }; - const clearSelection = () => setSelectedTaskIds(new Set()); + const clearSelection = () => { + setSelectedTaskIds(new Set()); + setAnchorTaskId(null); + }; const selectRange = (taskIds: number[]) => { setSelectedTaskIds(prev => { @@ -509,9 +526,11 @@ const TaskView = () => { onRemoveSearch={taskFilters.removeSearch} isAdmin={isAdmin(currentUser)} selectedTaskIds={selectedTaskIds} + anchorTaskId={anchorTaskId} onToggleSelected={toggleTask} onToggleSelectAll={selectAllVisible} onSelectRange={selectRange} + onSetAnchorTaskId={setAnchorTaskId} />