Initial commit MKD fixes
This commit is contained in:
595
components/legal/CourtCases.tsx
Executable file
595
components/legal/CourtCases.tsx
Executable file
@@ -0,0 +1,595 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { LegalCourtCase } from '../../types';
|
||||
import { Gavel, ExternalLink, Calendar, CreditCard, Landmark, ChevronRight, Plus, X, Loader2, Search } from 'lucide-react';
|
||||
import { authFetch } from '../../services/apiClient';
|
||||
import { CaseDetailsModal } from './CaseDetailsModal';
|
||||
|
||||
/** Ссылка на Картотеку арбитражных дел (КАД) или сайт судов общей юрисдикции (СОЮ) */
|
||||
function getCourtCaseExternalUrl(type: string, caseNumber: string): { url: string; label: string } {
|
||||
const encoded = encodeURIComponent(caseNumber.trim());
|
||||
if (type === 'arbitration') {
|
||||
return { url: `https://kad.arbitr.ru/?q=${encoded}`, label: 'КАД' };
|
||||
}
|
||||
return { url: 'https://sudrf.ru/', label: 'СОЮ' };
|
||||
}
|
||||
|
||||
type SubTab = 'all' | 'debtors' | 'others';
|
||||
|
||||
export const CourtCases: React.FC = () => {
|
||||
const [cases, setCases] = useState<LegalCourtCase[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingCase, setEditingCase] = useState<LegalCourtCase | null>(null);
|
||||
const [selectedCase, setSelectedCase] = useState<LegalCourtCase | null>(null);
|
||||
const [showCaseDetailsModal, setShowCaseDetailsModal] = useState(false);
|
||||
const [subTab, setSubTab] = useState<SubTab>('all');
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [roleFilter, setRoleFilter] = useState<string>('');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const loadCases = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = new URLSearchParams();
|
||||
if (searchQuery.trim()) params.set('search', searchQuery.trim());
|
||||
const response = await authFetch(`/api/legal/court-cases${params.toString() ? '?' + params.toString() : ''}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setCases(Array.isArray(data) ? data : []);
|
||||
} else {
|
||||
setCases([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading court cases:', error);
|
||||
setCases([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery === '') {
|
||||
loadCases();
|
||||
return;
|
||||
}
|
||||
const t = setTimeout(() => loadCases(), 300);
|
||||
return () => clearTimeout(t);
|
||||
}, [searchQuery]);
|
||||
|
||||
const filteredCases = useMemo(() => {
|
||||
let list = cases;
|
||||
if (subTab === 'debtors') list = list.filter(c => c.type === 'debt_recovery');
|
||||
else if (subTab === 'others') list = list.filter(c => c.type === 'arbitration' || c.type === 'civil');
|
||||
if (statusFilter) list = list.filter(c => c.status === statusFilter);
|
||||
if (roleFilter) list = list.filter(c => c.role === roleFilter);
|
||||
return list;
|
||||
}, [cases, subTab, statusFilter, roleFilter]);
|
||||
|
||||
const countsByType = useMemo(() => ({
|
||||
arbitration: filteredCases.filter(c => c.type === 'arbitration').length,
|
||||
civil: filteredCases.filter(c => c.type === 'civil').length,
|
||||
debt_recovery: filteredCases.filter(c => c.type === 'debt_recovery').length
|
||||
}), [filteredCases]);
|
||||
|
||||
const upcomingHearings = useMemo(() => {
|
||||
const today = new Date();
|
||||
const from = new Date(today);
|
||||
const to = new Date(today);
|
||||
to.setDate(to.getDate() + 30);
|
||||
return filteredCases
|
||||
.filter(c => c.nextHearingDate && c.status !== 'closed')
|
||||
.map(c => ({ ...c, hearingDate: new Date(c.nextHearingDate!) }))
|
||||
.filter(c => c.hearingDate >= from && c.hearingDate <= to)
|
||||
.sort((a, b) => a.hearingDate.getTime() - b.hearingDate.getTime())
|
||||
.slice(0, 5);
|
||||
}, [filteredCases]);
|
||||
|
||||
const handleCreate = () => {
|
||||
setEditingCase(null);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleEdit = (courtCase: LegalCourtCase) => {
|
||||
setEditingCase(courtCase);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleOpenCard = (courtCase: LegalCourtCase) => {
|
||||
setSelectedCase(courtCase);
|
||||
setShowCaseDetailsModal(true);
|
||||
};
|
||||
|
||||
const handleCaseDetailsUpdate = (updatedCase: LegalCourtCase) => {
|
||||
setSelectedCase(updatedCase);
|
||||
setCases(prev => prev.map(c => c.id === updatedCase.id ? { ...c, ...updatedCase } : c));
|
||||
};
|
||||
|
||||
const handleSave = async (caseData: Partial<LegalCourtCase>) => {
|
||||
try {
|
||||
if (editingCase) {
|
||||
// Обновление
|
||||
const response = await authFetch(`/api/legal/court-cases/${editingCase.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(caseData)
|
||||
});
|
||||
if (response.ok) {
|
||||
await loadCases();
|
||||
setShowModal(false);
|
||||
setEditingCase(null);
|
||||
}
|
||||
} else {
|
||||
// Создание
|
||||
const response = await authFetch('/api/legal/court-cases', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(caseData)
|
||||
});
|
||||
if (response.ok) {
|
||||
await loadCases();
|
||||
setShowModal(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving court case:', error);
|
||||
alert('Ошибка при сохранении судебного дела');
|
||||
}
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const headers = ['Номер дела', 'Тип', 'Роль', 'Предмет', 'Должник', 'Адрес', 'Сумма', 'Статус', 'Дата заседания', 'Судья', 'Суд', 'Взыскано', 'У приставов'];
|
||||
const statusLabel = (s: string) => ({ pre_trial: 'Досудебное', litigation: 'В суде', decision_received: 'Решение получено', enforcement: 'ФССП', closed: 'Закрыто' })[s] || s;
|
||||
const typeLabel = (t: string) => ({ arbitration: 'Арбитраж', civil: 'СОЮ', debt_recovery: 'Взыскание долга' })[t] || t;
|
||||
const roleLabel = (r: string) => ({ plaintiff: 'Истец', defendant: 'Ответчик' })[r] || r;
|
||||
const escape = (v: string | number | undefined) => {
|
||||
const s = String(v ?? '');
|
||||
return s.includes(';') || s.includes('"') || s.includes('\n') ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
};
|
||||
const rows = filteredCases.map(c => [
|
||||
escape(c.caseNumber),
|
||||
escape(typeLabel(c.type)),
|
||||
escape(roleLabel(c.role)),
|
||||
escape(c.subject),
|
||||
escape(c.debtorName),
|
||||
escape(c.address),
|
||||
escape(c.amount),
|
||||
escape(statusLabel(c.status)),
|
||||
escape(c.nextHearingDate),
|
||||
escape(c.judge),
|
||||
escape(c.courtName ?? ''),
|
||||
escape(c.recoveredAmount ?? ''),
|
||||
escape(c.amountAtBailiffs ?? '')
|
||||
].join(';'));
|
||||
const csv = '\uFEFF' + [headers.join(';'), ...rows].join('\r\n');
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `court-cases-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 animate-fade-in">
|
||||
<div className="flex justify-between items-center px-1">
|
||||
<h3 className="font-black text-slate-500 text-[10px] uppercase tracking-[0.2em]">Судебная практика УК</h3>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
className="bg-primary-600 text-white px-4 py-2 rounded-xl shadow-lg shadow-primary-500/30 flex items-center gap-2 text-xs font-black uppercase tracking-wider active:scale-95 transition-all"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> Создать дело
|
||||
</button>
|
||||
<button onClick={handleExport} className="text-[10px] font-black text-primary-600 uppercase bg-primary-50 px-3 py-1 rounded-lg hover:bg-primary-100 transition-colors">Выгрузить отчет</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Подтабы */}
|
||||
<div className="flex p-1 bg-slate-200/50 rounded-2xl gap-1">
|
||||
{[
|
||||
{ id: 'all' as SubTab, label: 'Все' },
|
||||
{ id: 'debtors' as SubTab, label: 'По должникам' },
|
||||
{ id: 'others' as SubTab, label: 'Другие' }
|
||||
].map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setSubTab(tab.id)}
|
||||
className={`flex-1 py-2.5 px-4 rounded-xl text-[10px] font-black uppercase tracking-wider transition-all ${subTab === tab.id ? 'bg-white text-primary-600 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{subTab === 'debtors' && (
|
||||
<p className="text-[10px] text-slate-500 px-1">
|
||||
Подробный пайплайн и контроль приставов — во вкладке <strong>Взыскание</strong>.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Фильтры и поиск */}
|
||||
<div className="flex flex-wrap gap-3 items-center px-1">
|
||||
<div className="relative flex-1 min-w-[12rem]">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Поиск по номеру, предмету, должнику..."
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-9 pr-4 py-2.5 bg-white border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={e => setStatusFilter(e.target.value)}
|
||||
className="px-4 py-2.5 border border-slate-200 rounded-xl text-sm bg-white outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="">Все статусы</option>
|
||||
<option value="pre_trial">Досудебное</option>
|
||||
<option value="litigation">В суде</option>
|
||||
<option value="decision_received">Решение получено</option>
|
||||
<option value="enforcement">ФССП</option>
|
||||
<option value="closed">Закрыто</option>
|
||||
</select>
|
||||
<select
|
||||
value={roleFilter}
|
||||
onChange={e => setRoleFilter(e.target.value)}
|
||||
className="px-4 py-2.5 border border-slate-200 rounded-xl text-sm bg-white outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="">Все роли</option>
|
||||
<option value="plaintiff">Истец</option>
|
||||
<option value="defendant">Ответчик</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-slate-400" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Сводка: счётчики по типам и ближайшие заседания */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-white rounded-2xl border border-slate-200 p-4">
|
||||
<p className="text-[10px] font-black text-slate-400 uppercase tracking-wider mb-3">Дела по типам</p>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<span className="text-sm font-bold text-slate-700">Арбитраж: <strong className="text-slate-900">{countsByType.arbitration}</strong></span>
|
||||
<span className="text-sm font-bold text-slate-700">СОЮ: <strong className="text-slate-900">{countsByType.civil}</strong></span>
|
||||
<span className="text-sm font-bold text-slate-700">Взыскание долга: <strong className="text-slate-900">{countsByType.debt_recovery}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl border border-slate-200 p-4">
|
||||
<p className="text-[10px] font-black text-slate-400 uppercase tracking-wider mb-3">Ближайшие заседания (30 дней)</p>
|
||||
{upcomingHearings.length === 0 ? (
|
||||
<p className="text-sm text-slate-500">Нет назначенных заседаний</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{upcomingHearings.map(c => (
|
||||
<li
|
||||
key={c.id}
|
||||
onClick={() => handleOpenCard(c)}
|
||||
className="flex justify-between items-center text-sm cursor-pointer hover:bg-slate-50 rounded-lg px-2 py-1 -mx-2 -my-1"
|
||||
>
|
||||
<span className="font-bold text-slate-800 truncate flex-1">{c.caseNumber}</span>
|
||||
<span className="text-slate-500 shrink-0 ml-2">{c.nextHearingDate}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{filteredCases.map(c => (
|
||||
<div
|
||||
key={c.id}
|
||||
className="bg-white rounded-[2rem] border border-slate-200 shadow-sm overflow-hidden group hover:border-red-200 transition-all cursor-pointer"
|
||||
onClick={() => handleOpenCard(c)}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-3 rounded-2xl ${c.role === 'plaintiff' ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600'}`}>
|
||||
<Landmark className="w-6 h-6"/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-black text-slate-900">{c.caseNumber}</span>
|
||||
{(() => {
|
||||
const { url, label } = getCourtCaseExternalUrl(c.type, c.caseNumber);
|
||||
return (
|
||||
<a href={url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-700 transition-colors text-[10px] font-black uppercase" onClick={(e) => e.stopPropagation()} title={`Открыть в ${label}`}>
|
||||
{label} <ExternalLink className="w-3.5 h-3.5 inline"/>
|
||||
</a>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-wider mt-1">
|
||||
{c.type === 'arbitration' ? 'Арбитражный суд' : c.type === 'civil' ? 'Суд общей юрисдикции' : 'Взыскание долга'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-[10px] font-black px-2.5 py-1 rounded-full uppercase border ${c.role === 'plaintiff' ? 'bg-emerald-50 text-emerald-600 border-emerald-100' : 'bg-red-50 text-red-600 border-red-100'}`}>
|
||||
{c.role === 'plaintiff' ? 'Истец' : 'Ответчик'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-sm font-bold text-slate-700 leading-snug mb-6">{c.subject}</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-slate-50 p-4 rounded-2xl border border-slate-100">
|
||||
<p className="text-[9px] font-black text-slate-400 uppercase mb-1 flex items-center gap-1">
|
||||
<CreditCard className="w-3 h-3"/> Сумма иска
|
||||
</p>
|
||||
<p className="text-base font-black text-slate-800">{c.amount.toLocaleString()} ₽</p>
|
||||
</div>
|
||||
<div className={`p-4 rounded-2xl border ${c.nextHearingDate ? 'bg-red-50 border-red-100' : 'bg-slate-50 border-slate-100'}`}>
|
||||
<p className={`text-[9px] font-black uppercase mb-1 flex items-center gap-1 ${c.nextHearingDate ? 'text-red-400' : 'text-slate-400'}`}>
|
||||
<Calendar className="w-3 h-3"/> Заседание
|
||||
</p>
|
||||
<p className={`text-base font-black ${c.nextHearingDate ? 'text-red-600' : 'text-slate-400'}`}>
|
||||
{c.nextHearingDate || 'Не назначено'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 bg-slate-50 border-t border-slate-100 flex justify-between items-center group-hover:bg-slate-100 transition-colors">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] font-black text-slate-400 uppercase">Статус:</span>
|
||||
<span className="text-xs font-bold text-slate-700">
|
||||
{c.status === 'pre_trial' ? 'Досудебное' :
|
||||
c.status === 'litigation' ? 'Судебный процесс' :
|
||||
c.status === 'decision_received' ? 'Решение получено' :
|
||||
c.status === 'enforcement' ? 'ФССП' : 'Завершено'}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="text-[10px] font-black text-primary-600 uppercase flex items-center gap-1.5 hover:underline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEdit(c);
|
||||
}}
|
||||
title="Открыть форму редактирования"
|
||||
>
|
||||
Редактировать <ChevronRight className="w-4 h-4"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{filteredCases.length === 0 && (
|
||||
<div className="py-20 text-center text-slate-400">
|
||||
<Gavel className="w-12 h-12 mx-auto mb-3 opacity-20"/>
|
||||
<p className="font-bold uppercase tracking-widest text-xs mb-4">Судебные дела не найдены</p>
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
className="bg-primary-600 text-white px-6 py-2 rounded-xl text-xs font-black uppercase"
|
||||
>
|
||||
Создать первое дело
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Court Case Modal */}
|
||||
{showModal && (
|
||||
<CourtCaseModal
|
||||
key={editingCase?.id ?? 'new'}
|
||||
courtCase={editingCase}
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
setEditingCase(null);
|
||||
}}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCaseDetailsModal && selectedCase && (
|
||||
<CaseDetailsModal
|
||||
courtCase={selectedCase}
|
||||
onClose={() => {
|
||||
setShowCaseDetailsModal(false);
|
||||
setSelectedCase(null);
|
||||
}}
|
||||
onUpdate={handleCaseDetailsUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface CourtCaseModalProps {
|
||||
courtCase: LegalCourtCase | null;
|
||||
onClose: () => void;
|
||||
onSave: (data: Partial<LegalCourtCase>) => Promise<void>;
|
||||
}
|
||||
|
||||
const CourtCaseModal: React.FC<CourtCaseModalProps> = ({ courtCase, onClose, onSave }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
caseNumber: courtCase?.caseNumber || '',
|
||||
type: courtCase?.type || 'debt_recovery',
|
||||
role: courtCase?.role || 'plaintiff',
|
||||
subject: courtCase?.subject || '',
|
||||
debtorName: courtCase?.debtorName || '',
|
||||
address: courtCase?.address || '',
|
||||
amount: courtCase?.amount || 0,
|
||||
nextHearingDate: courtCase?.nextHearingDate || '',
|
||||
judge: courtCase?.judge || '',
|
||||
courtName: courtCase?.courtName ?? '',
|
||||
notes: courtCase?.notes ?? ''
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
try {
|
||||
await onSave(formData);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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-2xl 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">
|
||||
{courtCase ? 'Редактировать судебное дело' : 'Создать судебное дело'}
|
||||
</h3>
|
||||
<button onClick={onClose} className="p-2 text-slate-400 hover:text-slate-600">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Номер дела *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.caseNumber}
|
||||
onChange={(e) => setFormData({ ...formData, caseNumber: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="А40-12345/2024"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Тип суда *</label>
|
||||
<select
|
||||
value={formData.type}
|
||||
onChange={(e) => setFormData({ ...formData, type: e.target.value as any })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
required
|
||||
>
|
||||
<option value="arbitration">Арбитражный суд</option>
|
||||
<option value="civil">Суд общей юрисдикции</option>
|
||||
<option value="debt_recovery">Взыскание долга</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Роль *</label>
|
||||
<select
|
||||
value={formData.role}
|
||||
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
required
|
||||
>
|
||||
<option value="plaintiff">Истец</option>
|
||||
<option value="defendant">Ответчик</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Предмет дела *</label>
|
||||
<textarea
|
||||
value={formData.subject}
|
||||
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Должник</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.debtorName}
|
||||
onChange={(e) => setFormData({ ...formData, debtorName: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Адрес</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Сумма иска *</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.amount}
|
||||
onChange={(e) => setFormData({ ...formData, amount: parseFloat(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
min="0"
|
||||
step="0.01"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Дата заседания</label>
|
||||
<input
|
||||
type="date"
|
||||
value={formData.nextHearingDate}
|
||||
onChange={(e) => setFormData({ ...formData, nextHearingDate: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Судья</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.judge}
|
||||
onChange={(e) => setFormData({ ...formData, judge: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Название суда</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.courtName}
|
||||
onChange={(e) => setFormData({ ...formData, courtName: e.target.value })}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-black text-slate-700 uppercase mb-1">Примечания</label>
|
||||
<textarea
|
||||
value={formData.notes || ''}
|
||||
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex-1 px-4 py-2.5 bg-slate-100 text-slate-700 rounded-xl text-xs font-black uppercase hover:bg-slate-200 transition-colors"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="flex-1 px-4 py-2.5 bg-primary-600 text-white rounded-xl text-xs font-black uppercase hover:bg-primary-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Сохранение...' : 'Сохранить'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user