const { React, ui } = ctx.libs; const { useState, useEffect, useMemo } = React; const { Card, CardContent, CardHeader, CardTitle, Badge, Progress, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Button, Input, Select, SelectTrigger, SelectContent, SelectItem, SelectValue } = ui; function Dashboard({ ctx }) { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [assigneeFilter, setAssigneeFilter] = useState('all'); useEffect(() => { async function loadData() { try { const usersRes = await ctx.request({ url: '/api/users' }); setUsers(usersRes.users || []); } catch (e) { console.error(e); } finally { setLoading(false); } } loadData(); }, []); function getUserName(uid) { if (!uid) return 'Не назначен'; const u = users.find(x => x.id === uid); return u ? (u.fullName || (u.firstName + ' ' + u.lastName).trim() || u.email) : String(uid); } const allTasks = ctx.forms[20] || []; const statusIds = [...new Set(allTasks.map(t => t.currentStatusId).filter(Boolean))]; const lastStatusId = statusIds.length > 0 ? Math.max(...statusIds) : 0; const filteredTasks = useMemo(() => { return allTasks.filter(t => { if (searchQuery && !t.title?.toLowerCase().includes(searchQuery.toLowerCase())) { return false; } if (statusFilter === 'closed' && !(t.isCompleted || t.currentStatusId === lastStatusId)) { return false; } if (statusFilter === 'active' && (t.isCompleted || t.currentStatusId === lastStatusId)) { return false; } if (assigneeFilter !== 'all') { if (assigneeFilter === 'unassigned' && t.assignedTo) return false; if (assigneeFilter !== 'unassigned' && String(t.assignedTo) !== assigneeFilter) return false; } return true; }); }, [allTasks, searchQuery, statusFilter, assigneeFilter, lastStatusId]); const total = allTasks.length; const closed = allTasks.filter(t => t.isCompleted || t.currentStatusId === lastStatusId).length; const inProgress = total - closed; const percent = total > 0 ? Math.round((closed / total) * 100) : 0; const today = new Date().toISOString().split('T')[0]; const overdue = allTasks.filter(t => { if (t.isCompleted || t.currentStatusId === lastStatusId) return false; return t.dueDate && String(t.dueDate).slice(0, 10) < today; }).length; function StatCard({ title, value, colorClass }) { return React.createElement(Card, { className: 'flex-1' }, React.createElement(CardContent, { className: 'p-4' }, React.createElement('div', { className: 'text-[10px] text-muted-foreground uppercase tracking-wide mb-1' }, title), React.createElement('div', { className: 'text-2xl font-bold ' + (colorClass || '') }, value) ) ); } function getStatusBadge(statusId, isCompleted) { if (isCompleted || statusId === lastStatusId) { return React.createElement(Badge, { variant: 'secondary', className: 'text-[10px]' }, 'Закрыт'); } return React.createElement(Badge, { className: 'text-[10px]' }, 'В работе'); } const uniqueAssignees = [...new Set(allTasks.map(t => t.assignedTo).filter(Boolean))]; return React.createElement('div', { className: 'space-y-4 p-4' }, React.createElement('h2', { className: 'text-base font-semibold' }, 'Сводка по договорам'), React.createElement('div', { className: 'flex gap-3' }, React.createElement(StatCard, { title: 'Всего', value: total }), React.createElement(StatCard, { title: 'В работе', value: inProgress, colorClass: 'text-yellow-600' }), React.createElement(StatCard, { title: 'Закрыто', value: closed, colorClass: 'text-green-600' }), React.createElement(StatCard, { title: 'Просрочено', value: overdue, colorClass: overdue > 0 ? 'text-red-600' : 'text-green-600' }) ), React.createElement(Card, null, React.createElement(CardHeader, { className: 'pb-2 pt-3 px-4' }, React.createElement(CardTitle, { className: 'text-sm' }, 'Выполнение') ), React.createElement(CardContent, { className: 'px-4 pb-3' }, React.createElement(Progress, { value: percent, className: 'h-2 mb-1' }), React.createElement('p', { className: 'text-xs text-muted-foreground' }, closed + ' из ' + total + ' завершены (' + percent + '%)' ) ) ), React.createElement(Card, null, React.createElement(CardHeader, { className: 'pb-2 pt-3 px-4' }, React.createElement(CardTitle, { className: 'text-sm' }, 'Договоры') ), React.createElement(CardContent, { className: 'px-4 pb-3' }, React.createElement('div', { className: 'flex gap-2 mb-3 flex-wrap' }, React.createElement(Input, { placeholder: 'Поиск...', value: searchQuery, onChange: e => setSearchQuery(e.target.value), className: 'h-7 text-xs w-40' }), React.createElement(Select, { value: statusFilter, onValueChange: v => setStatusFilter(v) }, React.createElement(SelectTrigger, { className: 'h-7 text-xs w-32' }, React.createElement(SelectValue, { placeholder: 'Статус' }) ), React.createElement(SelectContent, null, React.createElement(SelectItem, { value: 'all', className: 'text-xs' }, 'Все статусы'), React.createElement(SelectItem, { value: 'active', className: 'text-xs' }, 'В работе'), React.createElement(SelectItem, { value: 'closed', className: 'text-xs' }, 'Закрытые') ) ), React.createElement(Select, { value: assigneeFilter, onValueChange: v => setAssigneeFilter(v) }, React.createElement(SelectTrigger, { className: 'h-7 text-xs w-36' }, React.createElement(SelectValue, { placeholder: 'Ответственный' }) ), React.createElement(SelectContent, null, React.createElement(SelectItem, { value: 'all', className: 'text-xs' }, 'Все'), React.createElement(SelectItem, { value: 'unassigned', className: 'text-xs' }, 'Не назначен'), ...uniqueAssignees.map(uid => React.createElement(SelectItem, { key: uid, value: String(uid), className: 'text-xs' }, getUserName(uid) ) ) ) ), searchQuery || statusFilter !== 'all' || assigneeFilter !== 'all' ? React.createElement(Button, { variant: 'ghost', size: 'sm', className: 'h-7 px-2 text-xs', onClick: () => { setSearchQuery(''); setStatusFilter('all'); setAssigneeFilter('all'); } }, 'Сбросить') : null ), loading ? React.createElement('div', { className: 'py-4 text-center text-xs text-muted-foreground' }, 'Загрузка...') : React.createElement(Table, null, React.createElement(TableHeader, null, React.createElement(TableRow, null, React.createElement(TableHead, { className: 'w-16' }, '№'), React.createElement(TableHead, null, 'Название'), React.createElement(TableHead, { className: 'w-28' }, 'Статус'), React.createElement(TableHead, { className: 'w-40' }, 'Ответственный'), React.createElement(TableHead, { className: 'w-28 text-right' }, 'Срок') ) ), React.createElement(TableBody, null, filteredTasks.length === 0 ? React.createElement(TableRow, null, React.createElement(TableCell, { colSpan: 5, className: 'text-center text-xs text-muted-foreground py-4' }, 'Нет договоров') ) : filteredTasks.map(t => React.createElement(TableRow, { key: t.id }, React.createElement(TableCell, { className: 'text-xs font-mono text-muted-foreground' }, t.id), React.createElement(TableCell, { className: 'text-xs' }, React.createElement('a', { className: 'hover:underline cursor-pointer text-primary', onClick: () => ctx.navigate('/forms/20/tasks/' + t.id) }, t.title || '—') ), React.createElement(TableCell, null, getStatusBadge(t.currentStatusId, t.isCompleted)), React.createElement(TableCell, { className: 'text-xs' }, getUserName(t.assignedTo)), React.createElement(TableCell, { className: 'text-xs text-right ' + ( t.dueDate && String(t.dueDate).slice(0, 10) < today && !t.isCompleted ? 'text-red-600' : 'text-muted-foreground' ) }, t.dueDate ? String(t.dueDate).slice(0, 10) : '—') ) ) ) ) ) ) ); } return function Widget({ ctx }) { return React.createElement(Dashboard, { ctx }); };