fix(registry): Shift+клик выделяет диапазон от якоря, якорь хранится в TaskView
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user