feat(documents): папки шаблонов, маппинги и поле document_template; fix(batch): атомарное создание задач, idempotency и массовое удаление в реестре

This commit is contained in:
2026-07-08 13:33:14 +03:00
parent fc8e85832c
commit 6826d82850
34 changed files with 3770 additions and 467 deletions

View File

@@ -111,6 +111,11 @@ interface TaskRegistryProps {
searches: string[];
onAddSearch: (value: string) => void;
onRemoveSearch: (index: number) => void;
// Bulk selection
isAdmin?: boolean;
selectedTaskIds?: Set<number>;
onToggleSelected?: (taskId: number) => void;
onToggleSelectAll?: () => void;
}
// Отображает значение поля с учётом типа
@@ -248,7 +253,7 @@ 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 }: 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 }: TaskRegistryProps) => {
const { toast } = useToast();
// Текущая редактируемая ячейка: { taskId, columnKey }
@@ -754,6 +759,22 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
<Table>
<TableHeader>
<TableRow>
{isAdmin && (
<TableHead className="w-8 px-2">
<Checkbox
checked={filteredTasks.length > 0 && filteredTasks.every(t => selectedTaskIds?.has(t.id))}
data-state={
filteredTasks.length > 0 &&
filteredTasks.some(t => selectedTaskIds?.has(t.id)) &&
!filteredTasks.every(t => selectedTaskIds?.has(t.id))
? 'indeterminate'
: (filteredTasks.length > 0 && filteredTasks.every(t => selectedTaskIds?.has(t.id)) ? 'checked' : 'unchecked')
}
onCheckedChange={() => onToggleSelectAll?.()}
aria-label="Выделить все видимые задачи"
/>
</TableHead>
)}
{columnOrder.filter(key => visibleColumns[key]).map(key => {
if (key === 'title') {
return (
@@ -835,6 +856,16 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
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' : ''}`}>
{isAdmin && (
<TableCell className="py-1 px-2 w-8" onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedTaskIds?.has(task.id) ?? false}
onCheckedChange={() => onToggleSelected?.(task.id)}
onClick={(e) => e.stopPropagation()}
aria-label="Выделить задачу"
/>
</TableCell>
)}
{columnOrder.filter(key => visibleColumns[key]).map(key => {
if (key === 'title') {
return (