From 013e97c8d3dc29c814cfc2a777252f3d9f4c6e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=D1=81=20=D0=A1=D1=83=D0=BB=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Fri, 4 Sep 2026 14:42:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(ui):=20bulk-=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8F=D0=B5=D1=82=20=D1=82?= =?UTF-8?q?=D1=83=D0=BB=D0=B1=D0=B0=D1=80=20=D0=BD=D0=B0=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B5=20=E2=80=94=20=D1=81=D0=BF=D0=B8=D1=81=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D1=81=D0=B4=D0=B2=D0=B8=D0=B3=D0=B0=D0=B5=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Home: панель действий рендерится вместо строки поиска (те же метрики: py-1, h-8 md:h-6) - Реестр: панель «N выбрано / Удалить» внутри тулбара TaskRegistry (swap содержимого строки) --- client/src/components/TaskRegistry.tsx | 33 +++++++++++++++++++++++--- client/src/pages/Home.tsx | 22 ++++++++--------- client/src/pages/TaskView.tsx | 25 +++---------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/client/src/components/TaskRegistry.tsx b/client/src/components/TaskRegistry.tsx index 056ef69..009a54e 100644 --- a/client/src/components/TaskRegistry.tsx +++ b/client/src/components/TaskRegistry.tsx @@ -7,7 +7,7 @@ import { Badge } from '@/components/ui/badge'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; -import { Settings2, Calendar, User, Search, X as XIcon, Paperclip, AlertCircle, CheckCircle2, GripVertical } from 'lucide-react'; +import { Settings2, Calendar, User, Search, X as XIcon, Paperclip, AlertCircle, CheckCircle2, GripVertical, Trash2 } from 'lucide-react'; import { type ColumnSort } from '@/hooks/useColumnFilters'; import { resolveFieldDisplayValue, type FieldDisplayContext } from '@/lib/fieldDisplay'; import { statusBadgeStyle } from '@/lib/statusColor'; @@ -121,6 +121,9 @@ interface TaskRegistryProps { onToggleSelectAll?: () => void; onSelectRange?: (taskIds: number[]) => void; onSetAnchorTaskId?: (taskId: number | null) => void; + // Bulk actions (тулбар заменяется панелью на месте — без сдвига layout) + onClearSelection?: () => void; + onRequestBulkDelete?: () => void; } // Отображает значение поля с учётом типа @@ -258,7 +261,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, isAdmin, selectedTaskIds, anchorTaskId, onToggleSelected, onToggleSelectAll, onSelectRange, onSetAnchorTaskId }: 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, onClearSelection, onRequestBulkDelete }: TaskRegistryProps) => { const { toast } = useToast(); // Текущая редактируемая ячейка: { taskId, columnKey } @@ -683,8 +686,30 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users = return (
- {/* Панель управления + активные фильтры (один ряд) */} + {/* Панель управления + активные фильтры (один ряд); при выделении — bulk-панель на том же месте */}
+ {isAdmin && selectedTaskIds && selectedTaskIds.size > 0 && onRequestBulkDelete ? ( + <> + {selectedTaskIds.size} выбрано + +
+ + + ) : ( + <>
{/* Таблица задач */} diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index b26772e..916631e 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1269,13 +1269,13 @@ export default function HomePage() { const tabContent = ( <> - {/* Bulk actions toolbar */} - {selectedCount > 0 && ( -
+ {/* Bulk actions toolbar swaps with the search row in place — no layout shift */} + {selectedCount > 0 ? ( +
{selectedCount} выбрано {canBulkRead && ( )} {canBulkHide && (
- )} - - {/* Search + filter badges — one row, like TaskRegistry */} + ) : ( + /* Search + filter badges — one row, like TaskRegistry */
{/* Grouping settings button — compact rounded-full, like TaskRegistry column settings */} {(tab === 'inbox' || tab === 'snoozed' || tab === 'hidden') && ( @@ -1375,6 +1374,7 @@ export default function HomePage() { onRemoveFilter={clearFilter} onRemoveSort={clearSort} onClearAll={clearAll} /> )}
+ )} {!isOnline && tab === 'inbox' && !isUsingCache && !currentQuery.data && !currentQuery.isLoading && (
diff --git a/client/src/pages/TaskView.tsx b/client/src/pages/TaskView.tsx index b85159b..07cb788 100644 --- a/client/src/pages/TaskView.tsx +++ b/client/src/pages/TaskView.tsx @@ -9,7 +9,7 @@ import { Sidebar } from '@/components/Sidebar'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { ArrowLeft, Grid, Table, Plus, Link2, AlertCircle, Trash2, X } from 'lucide-react'; +import { ArrowLeft, Grid, Table, Plus, Link2, AlertCircle } from 'lucide-react'; import { Link } from 'wouter'; import TaskRegistry from '../components/TaskRegistry'; import { useColumnFilters } from '@/hooks/useColumnFilters'; @@ -479,27 +479,6 @@ const TaskView = () => {
- {isAdmin(currentUser) && selectedTaskIds.size > 0 && ( -
- {selectedTaskIds.size} выбрано - -
- -
- )}
{ onToggleSelectAll={selectAllVisible} onSelectRange={selectRange} onSetAnchorTaskId={setAnchorTaskId} + onClearSelection={clearSelection} + onRequestBulkDelete={() => setDeleteDialogOpen(true)} />