fix(registry): Shift+клик выделяет диапазон от якоря, якорь хранится в TaskView

This commit is contained in:
2026-07-08 19:32:58 +03:00
parent 70dfe07fa6
commit be3ee4e625
2 changed files with 28 additions and 10 deletions

View File

@@ -114,9 +114,11 @@ interface TaskRegistryProps {
// Bulk selection
isAdmin?: boolean;
selectedTaskIds?: Set<number>;
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<number | null>(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;
}

View File

@@ -75,6 +75,7 @@ const TaskView = () => {
const [filteredTasksCount, setFilteredTasksCount] = useState(0);
const [createTaskDialogOpen, setCreateTaskDialogOpen] = useState(false);
const [selectedTaskIds, setSelectedTaskIds] = useState<Set<number>>(new Set());
const [anchorTaskId, setAnchorTaskId] = useState<number | null>(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}
/>
</div>
</TabsContent>