fix(ui): bulk-панель выделения заменяет тулбар на месте — список больше не сдвигается
- Home: панель действий рендерится вместо строки поиска (те же метрики: py-1, h-8 md:h-6) - Реестр: панель «N выбрано / Удалить» внутри тулбара TaskRegistry (swap содержимого строки)
This commit is contained in:
@@ -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 (
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Панель управления + активные фильтры (один ряд) */}
|
||||
{/* Панель управления + активные фильтры (один ряд); при выделении — bulk-панель на том же месте */}
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{isAdmin && selectedTaskIds && selectedTaskIds.size > 0 && onRequestBulkDelete ? (
|
||||
<>
|
||||
<span className="text-xs font-medium text-foreground">{selectedTaskIds.size} выбрано</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 md:h-6 text-[11px] gap-1 px-2 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={onRequestBulkDelete}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />Удалить
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
className="text-muted-foreground hover:text-foreground p-1 rounded"
|
||||
onClick={onClearSelection}
|
||||
title="Снять выделение"
|
||||
>
|
||||
<XIcon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="h-8 w-8 md:h-6 md:w-6 shrink-0 rounded-full" title="Настроить колонки" data-testid="button-column-settings">
|
||||
@@ -781,6 +806,8 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
|
||||
className="py-0"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Таблица задач */}
|
||||
|
||||
@@ -1269,13 +1269,13 @@ export default function HomePage() {
|
||||
|
||||
const tabContent = (
|
||||
<>
|
||||
{/* Bulk actions toolbar */}
|
||||
{selectedCount > 0 && (
|
||||
<div className="flex items-center gap-2 flex-wrap px-3 py-1.5 border-b border-border bg-muted/30 shrink-0">
|
||||
{/* Bulk actions toolbar swaps with the search row in place — no layout shift */}
|
||||
{selectedCount > 0 ? (
|
||||
<div className="flex items-center gap-2 flex-wrap px-3 py-1 border-b border-border bg-muted/30 shrink-0">
|
||||
<span className="text-xs font-medium text-foreground">{selectedCount} выбрано</span>
|
||||
{canBulkRead && (
|
||||
<Button
|
||||
variant="outline" size="sm" className="h-6 text-[11px] gap-1 px-2"
|
||||
variant="outline" size="sm" className="h-8 md:h-6 text-[11px] gap-1 px-2"
|
||||
onClick={() => bulkReadMutation.mutate(Array.from(selectedTaskIds))}
|
||||
disabled={bulkReadMutation.isPending}
|
||||
>
|
||||
@@ -1284,21 +1284,21 @@ export default function HomePage() {
|
||||
)}
|
||||
{canBulkSnooze && (
|
||||
<Button
|
||||
variant="outline" size="sm" className="h-6 text-[11px] gap-1 px-2"
|
||||
variant="outline" size="sm" className="h-8 md:h-6 text-[11px] gap-1 px-2"
|
||||
onClick={() => setSnoozeDialogOpen(true)}
|
||||
>
|
||||
<Clock className="w-3 h-3" />Отложить
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline" size="sm" className="h-6 text-[11px] gap-1 px-2"
|
||||
variant="outline" size="sm" className="h-8 md:h-6 text-[11px] gap-1 px-2"
|
||||
onClick={() => { setStatusDialogOpen(true); setSelectedTransitionId(null); }}
|
||||
>
|
||||
<ArrowRightLeft className="w-3 h-3" />Статус
|
||||
</Button>
|
||||
{canBulkHide && (
|
||||
<Button
|
||||
variant="outline" size="sm" className="h-6 text-[11px] gap-1 px-2"
|
||||
variant="outline" size="sm" className="h-8 md:h-6 text-[11px] gap-1 px-2"
|
||||
onClick={() => bulkHideMutation.mutate(Array.from(selectedTaskIds))}
|
||||
disabled={bulkHideMutation.isPending}
|
||||
>
|
||||
@@ -1307,7 +1307,7 @@ export default function HomePage() {
|
||||
)}
|
||||
{canBulkUnhide && (
|
||||
<Button
|
||||
variant="outline" size="sm" className="h-6 text-[11px] gap-1 px-2"
|
||||
variant="outline" size="sm" className="h-8 md:h-6 text-[11px] gap-1 px-2"
|
||||
onClick={() => bulkUnhideMutation.mutate(Array.from(selectedTaskIds))}
|
||||
disabled={bulkUnhideMutation.isPending}
|
||||
>
|
||||
@@ -1323,9 +1323,8 @@ export default function HomePage() {
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search + filter badges — one row, like TaskRegistry */}
|
||||
) : (
|
||||
/* Search + filter badges — one row, like TaskRegistry */
|
||||
<div className="flex items-center gap-2 flex-wrap px-3 py-1 border-b border-border shrink-0">
|
||||
{/* 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} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isOnline && tab === 'inbox' && !isUsingCache && !currentQuery.data && !currentQuery.isLoading && (
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-warning text-warning-foreground border-b border-warning-foreground/20 shrink-0">
|
||||
|
||||
@@ -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 = () => {
|
||||
</div>
|
||||
|
||||
<TabsContent value="registry" className="flex-1 mt-0 px-2 pt-1 pb-0 overflow-hidden flex flex-col">
|
||||
{isAdmin(currentUser) && selectedTaskIds.size > 0 && (
|
||||
<div className="flex items-center gap-2 flex-wrap px-2 py-1.5 border-b border-border bg-muted/30 shrink-0 mb-1">
|
||||
<span className="text-xs font-medium text-foreground">{selectedTaskIds.size} выбрано</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-[11px] gap-1 px-2 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />Удалить
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
className="text-muted-foreground hover:text-foreground p-1 rounded"
|
||||
onClick={clearSelection}
|
||||
title="Снять выделение"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-auto min-h-0">
|
||||
<TaskRegistry
|
||||
tasks={tasks}
|
||||
@@ -531,6 +510,8 @@ const TaskView = () => {
|
||||
onToggleSelectAll={selectAllVisible}
|
||||
onSelectRange={selectRange}
|
||||
onSetAnchorTaskId={setAnchorTaskId}
|
||||
onClearSelection={clearSelection}
|
||||
onRequestBulkDelete={() => setDeleteDialogOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user