852 lines
48 KiB
TypeScript
Executable File
852 lines
48 KiB
TypeScript
Executable File
import React, { useState } from 'react';
|
||
// FIX: Added missing ChevronRight import
|
||
import { Search, ShieldCheck, AlertTriangle, Info, ShieldAlert, CheckCircle2, History, ChevronRight, Loader2, X, FileText, Phone, Mail } from 'lucide-react';
|
||
import { authFetch } from '../../services/apiClient';
|
||
|
||
interface LicenseItem {
|
||
number?: string;
|
||
series?: string;
|
||
validFrom?: number;
|
||
validTo?: number;
|
||
activities?: string[];
|
||
issueAuthority?: string;
|
||
}
|
||
|
||
interface CounterpartyCheckResult {
|
||
inn: string;
|
||
kpp?: string;
|
||
ogrn?: string;
|
||
name: string;
|
||
shortName?: string;
|
||
type: string;
|
||
status: string;
|
||
registrationDate?: string;
|
||
liquidationDate?: string;
|
||
address?: string;
|
||
okved?: string;
|
||
okveds?: any[];
|
||
management?: {
|
||
name: string;
|
||
post?: string;
|
||
};
|
||
finance?: {
|
||
taxSystem?: string;
|
||
income?: number;
|
||
revenue?: number;
|
||
expense?: number;
|
||
debt?: number;
|
||
penalty?: number;
|
||
year?: number;
|
||
};
|
||
authorities?: any;
|
||
phones?: any[];
|
||
emails?: any[];
|
||
employeeCount?: number;
|
||
capital?: { type?: string; value?: number };
|
||
smb?: { category?: string; issueDate?: number };
|
||
licenses?: LicenseItem[];
|
||
addressInvalidity?: any;
|
||
foundersInvalidity?: Array<{ name?: string; invalidity?: any }>;
|
||
managersInvalidity?: Array<{ name?: string; post?: string; invalidity?: any }>;
|
||
managementDisqualified?: boolean;
|
||
riskLevel: 'low' | 'medium' | 'high';
|
||
riskReasons: string[];
|
||
checkedDate: string;
|
||
rawData?: any;
|
||
}
|
||
|
||
export const ComplianceCheck: React.FC = () => {
|
||
const [inn, setInn] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [checkResult, setCheckResult] = useState<CounterpartyCheckResult | null>(null);
|
||
const [checkedCounterparties, setCheckedCounterparties] = useState<any[]>([]);
|
||
const [loadingHistory, setLoadingHistory] = useState(true);
|
||
const [selectedReport, setSelectedReport] = useState<any | null>(null);
|
||
const [showReportModal, setShowReportModal] = useState(false);
|
||
|
||
// Загружаем историю проверок при монтировании компонента
|
||
React.useEffect(() => {
|
||
loadHistory();
|
||
}, []);
|
||
|
||
const loadHistory = async () => {
|
||
try {
|
||
setLoadingHistory(true);
|
||
const response = await authFetch('/api/legal/counterparties');
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setCheckedCounterparties(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading history:', error);
|
||
} finally {
|
||
setLoadingHistory(false);
|
||
}
|
||
};
|
||
|
||
const handleCheck = async () => {
|
||
if (!inn.trim()) {
|
||
setError('Введите ИНН организации');
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
setError(null);
|
||
setCheckResult(null);
|
||
|
||
try {
|
||
const response = await authFetch('/api/legal/check-counterparty', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ inn: inn.trim() })
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || 'Ошибка при проверке контрагента');
|
||
}
|
||
|
||
const result = await response.json();
|
||
setCheckResult(result);
|
||
|
||
// Обновляем историю проверок
|
||
await loadHistory();
|
||
} catch (err) {
|
||
console.error('Error checking counterparty:', err);
|
||
setError(err instanceof Error ? err.message : 'Ошибка при проверке контрагента');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||
if (e.key === 'Enter') {
|
||
handleCheck();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 animate-fade-in">
|
||
{/* Search Tool */}
|
||
<div className="bg-slate-900 rounded-[2.5rem] p-8 text-white shadow-xl relative overflow-hidden">
|
||
<ShieldCheck className="absolute -bottom-4 -right-4 w-48 h-48 opacity-10 rotate-12" />
|
||
<div className="relative z-10 max-w-lg">
|
||
<h3 className="text-2xl font-black mb-2">Проверка контрагента</h3>
|
||
<p className="text-xs text-slate-400 font-medium mb-6">Автоматическая сверка по базам ФНС через DaData API.</p>
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
placeholder="Введите ИНН организации (10 или 12 цифр)..."
|
||
value={inn}
|
||
onChange={(e) => {
|
||
setInn(e.target.value.replace(/\D/g, ''));
|
||
setError(null);
|
||
}}
|
||
onKeyPress={handleKeyPress}
|
||
className="flex-1 px-5 py-3 bg-white/10 border border-white/20 rounded-2xl text-sm outline-none focus:ring-2 focus:ring-primary-500 transition-all placeholder:text-slate-500"
|
||
maxLength={12}
|
||
/>
|
||
<button
|
||
onClick={handleCheck}
|
||
disabled={loading || !inn.trim()}
|
||
className="bg-white text-slate-900 px-6 py-3 rounded-2xl font-black text-xs uppercase tracking-widest hover:bg-slate-100 transition-all active:scale-95 shadow-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||
>
|
||
{loading ? (
|
||
<>
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
Проверка...
|
||
</>
|
||
) : (
|
||
'Проверить'
|
||
)}
|
||
</button>
|
||
</div>
|
||
{error && (
|
||
<div className="mt-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl text-red-200 text-sm">
|
||
{error}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Check Result */}
|
||
{checkResult && (
|
||
<div className="bg-white rounded-[2rem] border border-slate-200 shadow-lg p-6">
|
||
<div className="flex justify-between items-start mb-4">
|
||
<div className="flex items-center gap-4">
|
||
<div className={`p-4 rounded-2xl ${
|
||
checkResult.riskLevel === 'high' ? 'bg-red-50 text-red-600' :
|
||
checkResult.riskLevel === 'medium' ? 'bg-amber-50 text-amber-600' :
|
||
'bg-emerald-50 text-emerald-600'
|
||
}`}>
|
||
{checkResult.riskLevel === 'high' ? <ShieldAlert className="w-7 h-7" /> :
|
||
checkResult.riskLevel === 'medium' ? <AlertTriangle className="w-7 h-7" /> :
|
||
<CheckCircle2 className="w-7 h-7" />}
|
||
</div>
|
||
<div>
|
||
<h4 className="font-black text-slate-800 text-lg leading-tight">{checkResult.name}</h4>
|
||
<p className="text-xs text-slate-500 mt-1">
|
||
ИНН: {checkResult.inn}
|
||
{checkResult.kpp && ` • КПП: ${checkResult.kpp}`}
|
||
{checkResult.ogrn && ` • ОГРН: ${checkResult.ogrn}`}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => setCheckResult(null)}
|
||
className="p-2 text-slate-400 hover:text-slate-600"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
{/* Risk Level */}
|
||
<div className={`p-4 rounded-xl border-2 ${
|
||
checkResult.riskLevel === 'high' ? 'bg-red-50 border-red-200' :
|
||
checkResult.riskLevel === 'medium' ? 'bg-amber-50 border-amber-200' :
|
||
'bg-emerald-50 border-emerald-200'
|
||
}`}>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-sm font-black text-slate-800">Уровень риска:</span>
|
||
<span className={`text-sm font-black uppercase ${
|
||
checkResult.riskLevel === 'high' ? 'text-red-600' :
|
||
checkResult.riskLevel === 'medium' ? 'text-amber-600' :
|
||
'text-emerald-600'
|
||
}`}>
|
||
{checkResult.riskLevel === 'high' ? 'Высокий риск' :
|
||
checkResult.riskLevel === 'medium' ? 'Средний риск' :
|
||
'Благонадежен'}
|
||
</span>
|
||
</div>
|
||
{checkResult.riskReasons.length > 0 && (
|
||
<ul className="text-xs text-slate-700 mt-2 space-y-1">
|
||
{checkResult.riskReasons.map((reason, idx) => (
|
||
<li key={idx} className="flex items-start gap-2">
|
||
<span className="text-red-500 mt-0.5">•</span>
|
||
<span>{reason}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
|
||
{/* Status */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Статус</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{checkResult.status === 'ACTIVE' ? 'Действующая' :
|
||
checkResult.status === 'LIQUIDATED' ? 'Ликвидирована' :
|
||
checkResult.status === 'LIQUIDATING' ? 'Ликвидируется' :
|
||
checkResult.status === 'BANKRUPT' ? 'Банкрот' :
|
||
checkResult.status === 'REORGANIZING' ? 'Реорганизуется' :
|
||
checkResult.status}
|
||
</p>
|
||
</div>
|
||
{checkResult.registrationDate && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Дата регистрации</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{new Date(checkResult.registrationDate).toLocaleDateString('ru-RU')}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Address */}
|
||
{checkResult.address && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Адрес</p>
|
||
<p className="text-sm text-slate-700">{checkResult.address}</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Finance */}
|
||
{checkResult.finance && (
|
||
<div className="bg-slate-50 rounded-xl p-4">
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-3">Финансовые показатели</p>
|
||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||
{checkResult.finance.revenue && (
|
||
<div>
|
||
<p className="text-slate-500">Выручка ({checkResult.finance.year})</p>
|
||
<p className="font-bold text-slate-800">
|
||
{checkResult.finance.revenue.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{checkResult.finance.income && (
|
||
<div>
|
||
<p className="text-slate-500">Доходы ({checkResult.finance.year})</p>
|
||
<p className="font-bold text-slate-800">
|
||
{checkResult.finance.income.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{checkResult.finance.debt && checkResult.finance.debt > 0 && (
|
||
<div>
|
||
<p className="text-red-600">Недоимки</p>
|
||
<p className="font-bold text-red-600">
|
||
{checkResult.finance.debt.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{checkResult.finance.penalty && checkResult.finance.penalty > 0 && (
|
||
<div>
|
||
<p className="text-red-600">Штрафы</p>
|
||
<p className="font-bold text-red-600">
|
||
{checkResult.finance.penalty.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Management */}
|
||
{checkResult.management && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Руководитель</p>
|
||
<p className="text-sm text-slate-700">
|
||
{checkResult.management.name}
|
||
{checkResult.management.post && ` • ${checkResult.management.post}`}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* OKVED */}
|
||
{checkResult.okved && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Основной ОКВЭД</p>
|
||
<p className="text-sm text-slate-700">
|
||
{checkResult.okved} {checkResult.okveds?.find(o => o.main)?.name || ''}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Capital */}
|
||
{checkResult.capital != null && checkResult.capital.value != null && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Уставный капитал</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{checkResult.capital.value.toLocaleString('ru-RU')} ₽
|
||
{checkResult.capital.type && <span className="text-slate-500 font-normal ml-1">({checkResult.capital.type})</span>}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* SMB */}
|
||
{checkResult.smb?.category && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Категория МСП</p>
|
||
<p className="text-sm text-slate-700">
|
||
{checkResult.smb.category === 'MICRO' ? 'Микропредприятие' :
|
||
checkResult.smb.category === 'SMALL' ? 'Малое' :
|
||
checkResult.smb.category === 'MEDIUM' ? 'Среднее' : checkResult.smb.category}
|
||
{checkResult.smb.issueDate && (
|
||
<span className="text-slate-500 text-xs ml-1">
|
||
(реестр с {new Date(checkResult.smb.issueDate).toLocaleDateString('ru-RU')})
|
||
</span>
|
||
)}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Licenses */}
|
||
{checkResult.licenses && checkResult.licenses.length > 0 && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Лицензии</p>
|
||
<ul className="text-sm text-slate-700 space-y-1">
|
||
{checkResult.licenses.map((lic, idx) => {
|
||
const validTo = lic.validTo ? new Date(lic.validTo).getTime() : null;
|
||
const expired = validTo != null && validTo < Date.now();
|
||
return (
|
||
<li key={idx} className={expired ? 'text-amber-600' : ''}>
|
||
{lic.number && <span className="font-medium">{lic.number}</span>}
|
||
{lic.validTo && (
|
||
<span className="ml-1">
|
||
{expired ? 'истекла' : 'до'} {new Date(lic.validTo).toLocaleDateString('ru-RU')}
|
||
</span>
|
||
)}
|
||
{lic.activities?.length ? ` — ${lic.activities.join(', ')}` : ''}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
{/* Management disqualified */}
|
||
{checkResult.managementDisqualified && (
|
||
<div className="p-3 bg-red-50 border border-red-200 rounded-xl">
|
||
<p className="text-xs font-black text-red-600 uppercase mb-1">Руководитель дисквалифицирован</p>
|
||
<p className="text-sm text-red-700">Директор не имеет права заключать сделки</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Invalidity details */}
|
||
{(checkResult.addressInvalidity || (checkResult.foundersInvalidity && checkResult.foundersInvalidity.length > 0) || (checkResult.managersInvalidity && checkResult.managersInvalidity.length > 0)) && (
|
||
<div className="p-3 bg-amber-50 border border-amber-200 rounded-xl">
|
||
<p className="text-xs font-black text-amber-700 uppercase mb-1">Недостоверные сведения</p>
|
||
<ul className="text-xs text-amber-800 space-y-0.5">
|
||
{checkResult.addressInvalidity && <li>• Адрес признан недостоверным (ФНС)</li>}
|
||
{checkResult.foundersInvalidity?.length ? <li>• Сведения об учредителях недостоверны</li> : null}
|
||
{checkResult.managersInvalidity?.length ? <li>• Сведения о руководителе недостоверны</li> : null}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Check History */}
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center px-1">
|
||
<h3 className="font-black text-slate-500 text-[10px] uppercase tracking-[0.2em]">История проверок</h3>
|
||
<button
|
||
onClick={loadHistory}
|
||
className="text-[10px] font-black text-primary-600 uppercase hover:underline"
|
||
>
|
||
Обновить
|
||
</button>
|
||
</div>
|
||
{loadingHistory ? (
|
||
<div className="flex items-center justify-center py-8">
|
||
<Loader2 className="w-5 h-5 animate-spin text-slate-400" />
|
||
</div>
|
||
) : checkedCounterparties.length === 0 ? (
|
||
<div className="bg-white p-8 rounded-xl border border-slate-200 text-center">
|
||
<p className="text-slate-400">История проверок пуста</p>
|
||
<p className="text-xs text-slate-500 mt-2">Проверьте контрагента, чтобы добавить запись в историю</p>
|
||
</div>
|
||
) : (
|
||
checkedCounterparties.map(cp => {
|
||
const isHighRisk = cp.riskLevel === 'high';
|
||
const isMediumRisk = cp.riskLevel === 'medium';
|
||
|
||
return (
|
||
<div key={cp.id} className="bg-white p-5 rounded-[2rem] border border-slate-200 shadow-sm flex flex-col md:flex-row justify-between gap-6 hover:shadow-md transition-all group">
|
||
<div className="flex items-center gap-4">
|
||
<div className={`p-4 rounded-2xl ${isHighRisk ? 'bg-red-50 text-red-600' : isMediumRisk ? 'bg-amber-50 text-amber-600' : 'bg-emerald-50 text-emerald-600'}`}>
|
||
{isHighRisk ? <ShieldAlert className="w-7 h-7" /> : isMediumRisk ? <AlertTriangle className="w-7 h-7" /> : <CheckCircle2 className="w-7 h-7" />}
|
||
</div>
|
||
<div>
|
||
<h4 className="font-black text-slate-800 text-base leading-tight">{cp.name}</h4>
|
||
<p className="text-[10px] text-slate-400 font-bold uppercase mt-1">
|
||
ИНН: {cp.inn} • Проверен: {cp.checkedDate ? new Date(cp.checkedDate).toLocaleDateString('ru-RU') : 'Не указано'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between md:justify-end gap-6 border-t md:border-t-0 border-slate-100 pt-4 md:pt-0">
|
||
<div className="text-right">
|
||
<p className={`text-[10px] font-black uppercase tracking-widest ${isHighRisk ? 'text-red-500' : isMediumRisk ? 'text-amber-500' : 'text-emerald-500'}`}>
|
||
{isHighRisk ? 'Высокий риск' : isMediumRisk ? 'Средний риск' : 'Благонадежен'}
|
||
</p>
|
||
<button
|
||
onClick={async () => {
|
||
try {
|
||
const response = await authFetch(`/api/legal/counterparties/${cp.id}`);
|
||
if (response.ok) {
|
||
const report = await response.json();
|
||
setSelectedReport(report);
|
||
setShowReportModal(true);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading report:', error);
|
||
}
|
||
}}
|
||
className="text-[10px] font-black text-primary-600 uppercase mt-1 hover:underline"
|
||
>
|
||
Подробный отчет
|
||
</button>
|
||
</div>
|
||
<div className="w-10 h-10 rounded-full bg-slate-50 flex items-center justify-center text-slate-300 group-hover:text-primary-500 transition-colors cursor-pointer"
|
||
onClick={async () => {
|
||
try {
|
||
const response = await authFetch(`/api/legal/counterparties/${cp.id}`);
|
||
if (response.ok) {
|
||
const report = await response.json();
|
||
setSelectedReport(report);
|
||
setShowReportModal(true);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading report:', error);
|
||
}
|
||
}}
|
||
>
|
||
<ChevronRight className="w-6 h-6"/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}))}
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3 p-4 bg-blue-50 rounded-2xl border border-blue-100">
|
||
<Info className="w-5 h-5 text-blue-500 flex-shrink-0" />
|
||
<p className="text-[11px] text-blue-700 leading-snug font-medium">
|
||
Система автоматически блокирует создание договоров с контрагентами, имеющими статус «Высокий риск» до ручного подтверждения Директором.
|
||
</p>
|
||
</div>
|
||
|
||
{/* Report Modal */}
|
||
{showReportModal && selectedReport && (
|
||
<ReportModal
|
||
report={selectedReport}
|
||
onClose={() => {
|
||
setShowReportModal(false);
|
||
setSelectedReport(null);
|
||
}}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Report Modal Component
|
||
interface ReportModalProps {
|
||
report: any;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const ReportModal: React.FC<ReportModalProps> = ({ report, onClose }) => {
|
||
return (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
||
<div className="bg-white rounded-2xl p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
||
<div className="flex justify-between items-center mb-6">
|
||
<h3 className="text-xl font-black text-slate-800">Детальный отчет о проверке</h3>
|
||
<button onClick={onClose} className="p-2 text-slate-400 hover:text-slate-600">
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
{/* Header Info */}
|
||
<div className="bg-slate-50 rounded-xl p-4">
|
||
<h4 className="text-lg font-black text-slate-800 mb-2">{report.name}</h4>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||
<div>
|
||
<p className="text-slate-500 text-xs">ИНН</p>
|
||
<p className="font-bold text-slate-800">{report.inn}</p>
|
||
</div>
|
||
{report.kpp && (
|
||
<div>
|
||
<p className="text-slate-500 text-xs">КПП</p>
|
||
<p className="font-bold text-slate-800">{report.kpp}</p>
|
||
</div>
|
||
)}
|
||
{report.ogrn && (
|
||
<div>
|
||
<p className="text-slate-500 text-xs">ОГРН</p>
|
||
<p className="font-bold text-slate-800">{report.ogrn}</p>
|
||
</div>
|
||
)}
|
||
<div>
|
||
<p className="text-slate-500 text-xs">Дата проверки</p>
|
||
<p className="font-bold text-slate-800">
|
||
{new Date(report.checkedDate).toLocaleString('ru-RU')}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Risk Level */}
|
||
<div className={`p-4 rounded-xl border-2 ${
|
||
report.riskLevel === 'high' ? 'bg-red-50 border-red-200' :
|
||
report.riskLevel === 'medium' ? 'bg-amber-50 border-amber-200' :
|
||
'bg-emerald-50 border-emerald-200'
|
||
}`}>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-sm font-black text-slate-800">Уровень риска:</span>
|
||
<span className={`text-sm font-black uppercase ${
|
||
report.riskLevel === 'high' ? 'text-red-600' :
|
||
report.riskLevel === 'medium' ? 'text-amber-600' :
|
||
'text-emerald-600'
|
||
}`}>
|
||
{report.riskLevel === 'high' ? 'Высокий риск' :
|
||
report.riskLevel === 'medium' ? 'Средний риск' :
|
||
'Благонадежен'}
|
||
</span>
|
||
</div>
|
||
{report.riskReasons && report.riskReasons.length > 0 && (
|
||
<ul className="text-xs text-slate-700 mt-2 space-y-1">
|
||
{report.riskReasons.map((reason: string, idx: number) => (
|
||
<li key={idx} className="flex items-start gap-2">
|
||
<span className="text-red-500 mt-0.5">•</span>
|
||
<span>{reason}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
|
||
{/* Status and Dates */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Статус</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{report.status === 'ACTIVE' ? 'Действующая' :
|
||
report.status === 'LIQUIDATED' ? 'Ликвидирована' :
|
||
report.status === 'LIQUIDATING' ? 'Ликвидируется' :
|
||
report.status === 'BANKRUPT' ? 'Банкрот' :
|
||
report.status === 'REORGANIZING' ? 'Реорганизуется' :
|
||
report.status}
|
||
</p>
|
||
</div>
|
||
{report.registrationDate && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Дата регистрации</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{new Date(report.registrationDate).toLocaleDateString('ru-RU')}
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.liquidationDate && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Дата ликвидации</p>
|
||
<p className="text-sm font-bold text-red-600">
|
||
{new Date(report.liquidationDate).toLocaleDateString('ru-RU')}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Address */}
|
||
{report.address && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Адрес</p>
|
||
<p className="text-sm text-slate-700">{report.address}</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Management */}
|
||
{report.managementName && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Руководитель</p>
|
||
<p className="text-sm text-slate-700">
|
||
{report.managementName}
|
||
{report.managementPost && ` • ${report.managementPost}`}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* OKVED */}
|
||
{report.okved && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Основной ОКВЭД</p>
|
||
<p className="text-sm text-slate-700">
|
||
{report.okved} {report.okveds?.find((o: any) => o.main)?.name || ''}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Finance */}
|
||
{report.finance && (
|
||
<div className="bg-slate-50 rounded-xl p-4">
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-3">Финансовые показатели</p>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 text-sm">
|
||
{report.finance.revenue !== undefined && report.finance.revenue !== null && (
|
||
<div>
|
||
<p className="text-slate-500">Выручка ({report.finance.year})</p>
|
||
<p className="font-bold text-slate-800">
|
||
{report.finance.revenue.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.finance.income !== undefined && report.finance.income !== null && (
|
||
<div>
|
||
<p className="text-slate-500">Доходы ({report.finance.year})</p>
|
||
<p className="font-bold text-slate-800">
|
||
{report.finance.income.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.finance.expense !== undefined && report.finance.expense !== null && (
|
||
<div>
|
||
<p className="text-slate-500">Расходы ({report.finance.year})</p>
|
||
<p className="font-bold text-slate-800">
|
||
{report.finance.expense.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.finance.debt !== undefined && report.finance.debt !== null && report.finance.debt > 0 && (
|
||
<div>
|
||
<p className="text-red-600">Недоимки</p>
|
||
<p className="font-bold text-red-600">
|
||
{report.finance.debt.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.finance.penalty !== undefined && report.finance.penalty !== null && report.finance.penalty > 0 && (
|
||
<div>
|
||
<p className="text-red-600">Штрафы</p>
|
||
<p className="font-bold text-red-600">
|
||
{report.finance.penalty.toLocaleString('ru-RU')} ₽
|
||
</p>
|
||
</div>
|
||
)}
|
||
{report.finance.taxSystem && (
|
||
<div>
|
||
<p className="text-slate-500">Система налогообложения</p>
|
||
<p className="font-bold text-slate-800">{report.finance.taxSystem}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Authorities */}
|
||
{report.authorities && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-2">Государственные органы</p>
|
||
<div className="space-y-2 text-sm">
|
||
{report.authorities.ftsRegistration && (
|
||
<div className="bg-slate-50 rounded-lg p-3">
|
||
<p className="font-bold text-slate-800">ИФНС регистрации</p>
|
||
<p className="text-slate-600">{report.authorities.ftsRegistration.name}</p>
|
||
{report.authorities.ftsRegistration.address && (
|
||
<p className="text-xs text-slate-500 mt-1">{report.authorities.ftsRegistration.address}</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
{report.authorities.ftsReport && (
|
||
<div className="bg-slate-50 rounded-lg p-3">
|
||
<p className="font-bold text-slate-800">ИФНС отчётности</p>
|
||
<p className="text-slate-600">{report.authorities.ftsReport.name}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Phones and Emails */}
|
||
{(report.phones?.length > 0 || report.emails?.length > 0) && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-2">Контакты</p>
|
||
<div className="space-y-2 text-sm">
|
||
{report.phones?.map((phone: any, idx: number) => (
|
||
<div key={idx} className="flex items-center gap-2">
|
||
<Phone className="w-4 h-4 text-slate-400" />
|
||
<span className="text-slate-700">{phone.value || phone}</span>
|
||
</div>
|
||
))}
|
||
{report.emails?.map((email: any, idx: number) => (
|
||
<div key={idx} className="flex items-center gap-2">
|
||
<Mail className="w-4 h-4 text-slate-400" />
|
||
<span className="text-slate-700">{email.value || email}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Employee Count */}
|
||
{report.employeeCount && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Среднесписочная численность</p>
|
||
<p className="text-sm font-bold text-slate-800">{report.employeeCount} человек</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Capital */}
|
||
{report.capital != null && report.capital.value != null && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Уставный капитал</p>
|
||
<p className="text-sm font-bold text-slate-800">
|
||
{report.capital.value.toLocaleString('ru-RU')} ₽
|
||
{report.capital.type && <span className="text-slate-500 font-normal ml-1">({report.capital.type})</span>}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* SMB */}
|
||
{report.smb?.category && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Категория МСП</p>
|
||
<p className="text-sm text-slate-700">
|
||
{report.smb.category === 'MICRO' ? 'Микропредприятие' :
|
||
report.smb.category === 'SMALL' ? 'Малое' :
|
||
report.smb.category === 'MEDIUM' ? 'Среднее' : report.smb.category}
|
||
{report.smb.issueDate && (
|
||
<span className="text-slate-500 text-xs ml-1">
|
||
(реестр с {new Date(report.smb.issueDate).toLocaleDateString('ru-RU')})
|
||
</span>
|
||
)}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Licenses */}
|
||
{report.licenses && report.licenses.length > 0 && (
|
||
<div className="bg-slate-50 rounded-xl p-4">
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-2">Лицензии</p>
|
||
<ul className="text-sm text-slate-700 space-y-2">
|
||
{report.licenses.map((lic: { number?: string; validTo?: number; validFrom?: number; activities?: string[] }, idx: number) => {
|
||
const validTo = lic.validTo ? new Date(lic.validTo).getTime() : null;
|
||
const expired = validTo != null && validTo < Date.now();
|
||
return (
|
||
<li key={idx} className={expired ? 'text-amber-600' : ''}>
|
||
{lic.number && <span className="font-bold">{lic.number}</span>}
|
||
{lic.validFrom && (
|
||
<span className="ml-1 text-slate-500">с {new Date(lic.validFrom).toLocaleDateString('ru-RU')}</span>
|
||
)}
|
||
{lic.validTo && (
|
||
<span className="ml-1">
|
||
{expired ? 'истекла' : 'до'} {new Date(lic.validTo).toLocaleDateString('ru-RU')}
|
||
</span>
|
||
)}
|
||
{lic.activities?.length ? (
|
||
<p className="text-xs text-slate-600 mt-0.5">{lic.activities.join(', ')}</p>
|
||
) : null}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
{/* Management disqualified */}
|
||
{report.managementDisqualified && (
|
||
<div className="p-4 bg-red-50 border-2 border-red-200 rounded-xl">
|
||
<p className="text-xs font-black text-red-600 uppercase mb-1">Руководитель дисквалифицирован</p>
|
||
<p className="text-sm text-red-700">Директор не имеет права заключать сделки</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Invalidity details */}
|
||
{(report.addressInvalidity || (report.foundersInvalidity && report.foundersInvalidity.length > 0) || (report.managersInvalidity && report.managersInvalidity.length > 0)) && (
|
||
<div className="p-4 bg-amber-50 border-2 border-amber-200 rounded-xl">
|
||
<p className="text-xs font-black text-amber-700 uppercase mb-2">Недостоверные сведения</p>
|
||
<ul className="text-sm text-amber-800 space-y-1">
|
||
{report.addressInvalidity && <li>• Адрес признан недостоверным (ФНС)</li>}
|
||
{report.foundersInvalidity?.length ? <li>• Сведения об учредителях недостоверны</li> : null}
|
||
{report.managersInvalidity?.length ? <li>• Сведения о руководителе недостоверны</li> : null}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
{/* Notes */}
|
||
{report.notes && (
|
||
<div>
|
||
<p className="text-xs font-black text-slate-500 uppercase mb-1">Примечания</p>
|
||
<p className="text-sm text-slate-700 bg-slate-50 p-3 rounded-lg">{report.notes}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-6 flex justify-end">
|
||
<button
|
||
onClick={onClose}
|
||
className="px-6 py-2.5 bg-primary-600 text-white rounded-xl text-xs font-black uppercase hover:bg-primary-700 transition-colors"
|
||
>
|
||
Закрыть
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}; |