/** * TasksPanel — extracted from AIChatPanel.renderTasksPanel() * Displays tasks from a configured table source with search and chat binding. */ import React from 'react'; import { X, Search, Loader2, ListTodo, MessageSquare, Plus, ChevronDown, } from 'lucide-react'; import { cn } from '@/shared/utils/cn'; type PanelTab = 'none' | 'contacts' | 'ai-agents' | 'tasks' | 'settings' | 'inbox'; interface TasksSourceConfig { tableId: number; tableName: string; tableIcon?: string; displayColumn?: string; descriptionColumn?: string; statusColumn?: string; priorityColumn?: string; statusDictTableId?: number; priorityDictTableId?: number; } interface TaskRow { id: number; data: Record; } interface Conversation { id: number; title?: string; updatedAt: string; metadata?: { boundRow?: { row_id: number; table_id: number } }; } interface BoundRow { table_id: number; row_id: number; table_name?: string; table_icon?: string; row_title?: string; } interface TasksSourceInlineSelectorProps { defaultSpaceId: number | null; onSelect: (config: TasksSourceConfig) => void; onCancel: () => void; showHeader?: boolean; } // Column name aliases for auto-detection const TITLE_ALIASES = ['title', 'what', 'name', 'subject', 'Название']; /** Get task row title using configured column or fallback aliases */ function getTaskRowTitle( row: { id: number; data: Record }, config?: TasksSourceConfig ): string { const d = row.data; if (config?.displayColumn && d[config.displayColumn]) { return String(d[config.displayColumn]); } for (const alias of TITLE_ALIASES) { if (d[alias]) return String(d[alias]); } return `Запись #${row.id}`; } /** Get task row field value */ function getTaskRowField( row: { id: number; data: Record }, column?: string ): unknown { if (!column) return undefined; return row.data[column]; } export interface TasksPanelProps { tasksSource: TasksSourceConfig | undefined; updateTasksSource: (config: TasksSourceConfig | undefined) => void; tasksSearch: string; setTasksSearch: (value: string) => void; isLoadingTasks: boolean; taskRows: TaskRow[]; filteredTaskRows: TaskRow[]; taskStatusDict: Array<{ id: number; name: string }>; conversations: Conversation[]; expandedTaskChats: number | null; setExpandedTaskChats: (id: number | null) => void; createNewConversation: () => void; selectConversation: (id: number) => void; setBoundRows: (rows: BoundRow[]) => void; setActivePanel: (panel: PanelTab) => void; effectiveSpaceId: number | null; TasksSourceInlineSelector: React.ComponentType; } export function TasksPanel({ tasksSource, updateTasksSource, tasksSearch, setTasksSearch, isLoadingTasks, taskRows, filteredTaskRows, taskStatusDict, conversations, expandedTaskChats, setExpandedTaskChats, createNewConversation, selectConversation, setBoundRows, setActivePanel, effectiveSpaceId, TasksSourceInlineSelector, }: TasksPanelProps) { return (
{tasksSource ? ( <>
{tasksSource.tableIcon || '📋'} {tasksSource.tableName}
{/* Search input for tasks */}
setTasksSearch(e.target.value)} placeholder="Поиск задач..." className="w-full pl-8 pr-3 py-1.5 text-xs rounded-lg bg-[var(--bg-tertiary)] border border-[var(--border-primary)] text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:ring-1 focus:ring-[var(--color-primary-500)]/30" /> {tasksSearch && ( )}
{isLoadingTasks ? (
) : taskRows.length === 0 ? (
Нет записей
) : filteredTaskRows.length === 0 && tasksSearch.trim() ? (
Ничего не найдено по запросу "{tasksSearch}"
) : ( filteredTaskRows.map(row => { // Task binding feature - conversations may have metadata.boundRow type ConversationWithMeta = typeof conversations[number] & { metadata?: { boundRow?: { row_id: number; table_id: number } } }; const rowChats = (conversations as ConversationWithMeta[]).filter(c => c.metadata?.boundRow && c.metadata.boundRow.row_id === row.id && c.metadata.boundRow.table_id === tasksSource?.tableId ); const chatCount = rowChats.length; const isExpanded = expandedTaskChats === row.id; const rowTitle = getTaskRowTitle(row, tasksSource); const rowDesc = getTaskRowField(row, tasksSource?.descriptionColumn) as string | undefined; const statusVal = getTaskRowField(row, tasksSource?.statusColumn); const statusName = statusVal && taskStatusDict.length > 0 ? taskStatusDict.find(s => s.id === Number(statusVal))?.name || '' : ''; return (
#{row.id}
{rowTitle} {rowDesc && (

{String(rowDesc)}

)} {statusName && ( {statusName} )}
{isExpanded && chatCount > 1 && (
{rowChats.map(chat => ( ))}
)}
); }) )}
) : (

Источник не настроен

Выберите таблицу для задач

{ updateTasksSource(config); }} onCancel={() => {}} showHeader={false} />
)}
); }