Шаг 2.2 (часть 2) плана production-готовности: - 14 файлов в components/task-detail/ (типы, вкладки, диалоги, шапка, таб-бар, мобильный accordion, хук условий полей) - посимвольная сверка: все несовпадения объяснены (export-префиксы/импорты/пропсы) - check/lint/build чисто, vitest 104/104
170 lines
7.1 KiB
TypeScript
170 lines
7.1 KiB
TypeScript
import { ChevronLeft, ChevronRight, Maximize2, Minimize2, FileText, MessageSquare } from 'lucide-react';
|
||
import { SYSTEM_TAB_ICONS, SYSTEM_TAB_LABELS } from './systemTabs';
|
||
import type { FormTab } from './types';
|
||
|
||
interface TaskTabsBarProps {
|
||
isMobile: boolean;
|
||
chatEnabled: boolean;
|
||
chatLayout: 'left' | 'tab';
|
||
expandedPane: 'none' | 'chat' | 'task';
|
||
onToggleExpand: (pane: 'chat' | 'task') => void;
|
||
formTabs: FormTab[];
|
||
orderedSystemTabs: string[];
|
||
subtasksCount: number;
|
||
selectedTabId: string;
|
||
onSelectTab: (value: string) => void;
|
||
isTaskFinal: boolean;
|
||
tabsScrollRef: (el: HTMLDivElement | null) => void;
|
||
tabsScrollEl: HTMLDivElement | null;
|
||
showLeftArrow: boolean;
|
||
showRightArrow: boolean;
|
||
}
|
||
|
||
export function TaskTabsBar({
|
||
isMobile,
|
||
chatEnabled,
|
||
chatLayout,
|
||
expandedPane,
|
||
onToggleExpand: toggleExpand,
|
||
formTabs,
|
||
orderedSystemTabs,
|
||
subtasksCount,
|
||
selectedTabId,
|
||
onSelectTab: setSelectedTabId,
|
||
isTaskFinal,
|
||
tabsScrollRef,
|
||
tabsScrollEl,
|
||
showLeftArrow,
|
||
showRightArrow,
|
||
}: TaskTabsBarProps) {
|
||
// Кнопка вкладки
|
||
const TabBtn = ({ value, icon: Icon, children, count }: {
|
||
value: string;
|
||
icon?: React.ElementType;
|
||
children: React.ReactNode;
|
||
count?: number;
|
||
}) => (
|
||
<button
|
||
onClick={() => setSelectedTabId(value)}
|
||
aria-current={selectedTabId === value ? 'page' : undefined}
|
||
data-testid={`tab-btn-${value}`}
|
||
className={`whitespace-nowrap text-xs px-3 py-2 border-b-2 flex items-center gap-1.5 transition-colors shrink-0 ${
|
||
selectedTabId === value
|
||
? 'border-primary text-foreground font-medium'
|
||
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground/30'
|
||
}`}
|
||
>
|
||
{Icon && <Icon className="w-3 h-3" />}
|
||
<span className={isTaskFinal ? 'line-through' : ''}>{children}</span>
|
||
{count !== undefined && (
|
||
<span className="text-[11px] bg-muted rounded-full px-1 leading-4">{count}</span>
|
||
)}
|
||
</button>
|
||
);
|
||
return (
|
||
<>
|
||
{/* Горизонтальная полоса вкладок — десктоп */}
|
||
{!isMobile && (
|
||
<div className="border-b bg-background flex items-stretch overflow-hidden shrink-0">
|
||
{/* Чат слева — метка в левой секции таббара (десктоп) */}
|
||
{chatLayout === 'left' && chatEnabled && expandedPane !== 'task' && (
|
||
<div className="w-1/3 border-r px-3 flex items-center justify-between shrink-0">
|
||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Чат</span>
|
||
<button
|
||
onClick={() => toggleExpand('chat')}
|
||
className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
||
title={expandedPane === 'chat' ? 'Свернуть чат' : 'Развернуть чат'}
|
||
>
|
||
{expandedPane === 'chat' ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* Кнопка разворота задачи — перед вкладками (только на десктопе с чатом) */}
|
||
{chatLayout === 'left' && chatEnabled && expandedPane !== 'chat' && (
|
||
<button
|
||
onClick={() => toggleExpand('task')}
|
||
className="px-2 flex items-center text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0"
|
||
title={expandedPane === 'task' ? 'Свернуть задачу' : 'Развернуть задачу'}
|
||
>
|
||
{expandedPane === 'task' ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
|
||
</button>
|
||
)}
|
||
|
||
{/* Кнопки вкладок */}
|
||
{expandedPane !== 'chat' && (
|
||
<div className="relative flex-1 overflow-hidden">
|
||
{showLeftArrow && (
|
||
<button
|
||
className="absolute left-0 top-0 z-10 w-6 h-full bg-background flex items-center justify-center border-r shadow-sm text-muted-foreground hover:text-foreground transition-colors"
|
||
onClick={() => tabsScrollEl?.scrollBy({ left: -160, behavior: 'smooth' })}
|
||
>
|
||
<ChevronLeft className="w-3 h-3" />
|
||
</button>
|
||
)}
|
||
<div
|
||
ref={tabsScrollRef}
|
||
className="overflow-x-auto [&::-webkit-scrollbar]:hidden h-full"
|
||
style={{ scrollbarWidth: 'none' }}
|
||
>
|
||
<div className="flex items-stretch h-full">
|
||
|
||
{formTabs.map(tab => (
|
||
<TabBtn key={tab.id} value={tab.id.toString()}>{tab.name}</TabBtn>
|
||
))}
|
||
{orderedSystemTabs.map(st => (
|
||
<TabBtn
|
||
key={st}
|
||
value={st}
|
||
icon={SYSTEM_TAB_ICONS[st]}
|
||
count={st === 'subtasks' ? subtasksCount : undefined}
|
||
>
|
||
{SYSTEM_TAB_LABELS[st]}
|
||
</TabBtn>
|
||
))}
|
||
</div>
|
||
</div>
|
||
{showRightArrow && (
|
||
<button
|
||
className="absolute right-0 top-0 z-10 w-6 h-full bg-background flex items-center justify-center border-l shadow-sm text-muted-foreground hover:text-foreground transition-colors"
|
||
onClick={() => tabsScrollEl?.scrollBy({ left: 160, behavior: 'smooth' })}
|
||
>
|
||
<ChevronRight className="w-3 h-3" />
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Мобильный таб-бар — только Задача / Чат */}
|
||
{isMobile && chatEnabled && (
|
||
<div className="border-b bg-background flex items-stretch overflow-hidden shrink-0">
|
||
<button
|
||
onClick={() => setSelectedTabId('task')}
|
||
className={`flex-1 text-xs px-3 py-2 border-b-2 flex items-center justify-center gap-1.5 transition-colors ${
|
||
selectedTabId !== 'chat'
|
||
? 'border-primary text-foreground font-medium'
|
||
: 'border-transparent text-muted-foreground'
|
||
}`}
|
||
>
|
||
<FileText className="w-3.5 h-3.5" />
|
||
Задача
|
||
</button>
|
||
<button
|
||
onClick={() => setSelectedTabId('chat')}
|
||
className={`flex-1 text-xs px-3 py-2 border-b-2 flex items-center justify-center gap-1.5 transition-colors ${
|
||
selectedTabId === 'chat'
|
||
? 'border-primary text-foreground font-medium'
|
||
: 'border-transparent text-muted-foreground'
|
||
}`}
|
||
>
|
||
<MessageSquare className="w-3.5 h-3.5" />
|
||
Чат
|
||
</button>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|