feat(registry): массовое выделение задач через Ctrl/Cmd и Shift + клик
This commit is contained in:
@@ -116,6 +116,7 @@ interface TaskRegistryProps {
|
|||||||
selectedTaskIds?: Set<number>;
|
selectedTaskIds?: Set<number>;
|
||||||
onToggleSelected?: (taskId: number) => void;
|
onToggleSelected?: (taskId: number) => void;
|
||||||
onToggleSelectAll?: () => void;
|
onToggleSelectAll?: () => void;
|
||||||
|
onSelectRange?: (taskIds: number[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отображает значение поля с учётом типа
|
// Отображает значение поля с учётом типа
|
||||||
@@ -253,11 +254,13 @@ const REGISTRY_INLINE_TYPES = new Set([
|
|||||||
// Типы полей, которые переключаются по одиночному клику
|
// Типы полей, которые переключаются по одиночному клику
|
||||||
const REGISTRY_QUICK_TOGGLE_TYPES = new Set(['checkbox', 'toggle']);
|
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 }: 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, onToggleSelected, onToggleSelectAll, onSelectRange }: TaskRegistryProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Текущая редактируемая ячейка: { taskId, columnKey }
|
// Текущая редактируемая ячейка: { taskId, columnKey }
|
||||||
const [editingCell, setEditingCell] = useState<{ taskId: number; columnKey: string } | null>(null);
|
const [editingCell, setEditingCell] = useState<{ taskId: number; columnKey: string } | null>(null);
|
||||||
|
// Последняя выделенная задача — якорь для Shift+клика
|
||||||
|
const [lastSelectedTaskId, setLastSelectedTaskId] = useState<number | null>(null);
|
||||||
|
|
||||||
// Функция для получения имени пользователя по ID
|
// Функция для получения имени пользователя по ID
|
||||||
const getUserName = (userId: number): string => {
|
const getUserName = (userId: number): string => {
|
||||||
@@ -615,6 +618,39 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (task: TaskData, e: React.MouseEvent) => {
|
||||||
|
if (!isAdmin || !onToggleSelected) {
|
||||||
|
onTaskClick?.(task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCtrl = e.ctrlKey || e.metaKey;
|
||||||
|
const isShift = e.shiftKey;
|
||||||
|
|
||||||
|
if (isShift && lastSelectedTaskId != null) {
|
||||||
|
e.preventDefault();
|
||||||
|
const ids = filteredTasks.map(t => t.id);
|
||||||
|
const start = ids.indexOf(lastSelectedTaskId);
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обычный клик по строке открывает задачу
|
||||||
|
onTaskClick?.(task);
|
||||||
|
};
|
||||||
|
|
||||||
const isEditing = (taskId: number, columnKey: string) =>
|
const isEditing = (taskId: number, columnKey: string) =>
|
||||||
editingCell?.taskId === taskId && editingCell?.columnKey === columnKey;
|
editingCell?.taskId === taskId && editingCell?.columnKey === columnKey;
|
||||||
|
|
||||||
@@ -855,7 +891,12 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
|
|||||||
const isOverdue = !isCompleted && !!task.dueDate && new Date(task.dueDate) < new Date();
|
const isOverdue = !isCompleted && !!task.dueDate && new Date(task.dueDate) < new Date();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={task.id} data-testid={`row-task-${task.id}`} className={`${isCompleted ? 'bg-gray-100 dark:bg-gray-900 opacity-70' : ''} ${isOverdue ? 'border-l-2 border-l-red-500 bg-red-50 dark:bg-red-950/20' : ''}`}>
|
<TableRow
|
||||||
|
key={task.id}
|
||||||
|
data-testid={`row-task-${task.id}`}
|
||||||
|
className={`${isCompleted ? 'bg-gray-100 dark:bg-gray-900 opacity-70' : ''} ${isOverdue ? 'border-l-2 border-l-red-500 bg-red-50 dark:bg-red-950/20' : ''} ${isAdmin ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={(e) => handleRowClick(task, e)}
|
||||||
|
>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<TableCell className="py-1 px-2 w-8" onClick={(e) => e.stopPropagation()}>
|
<TableCell className="py-1 px-2 w-8" onClick={(e) => e.stopPropagation()}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -289,6 +289,14 @@ const TaskView = () => {
|
|||||||
|
|
||||||
const clearSelection = () => setSelectedTaskIds(new Set());
|
const clearSelection = () => setSelectedTaskIds(new Set());
|
||||||
|
|
||||||
|
const selectRange = (taskIds: number[]) => {
|
||||||
|
setSelectedTaskIds(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
taskIds.forEach(id => next.add(id));
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Автооткрытие диалога создания задачи при предзаполнении из URL (QR-коды).
|
// Автооткрытие диалога создания задачи при предзаполнении из URL (QR-коды).
|
||||||
// Сначала запускаем автоматизации task.before_create через check-create endpoint.
|
// Сначала запускаем автоматизации task.before_create через check-create endpoint.
|
||||||
// Диалог открывается только если автоматизации не блокируют создание.
|
// Диалог открывается только если автоматизации не блокируют создание.
|
||||||
@@ -478,6 +486,7 @@ const TaskView = () => {
|
|||||||
selectedTaskIds={selectedTaskIds}
|
selectedTaskIds={selectedTaskIds}
|
||||||
onToggleSelected={toggleTask}
|
onToggleSelected={toggleTask}
|
||||||
onToggleSelectAll={selectAllVisible}
|
onToggleSelectAll={selectAllVisible}
|
||||||
|
onSelectRange={selectRange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user