gcx/frontend/admin-web/src/views/compliance/IpoReadinessPage.tsx

318 lines
19 KiB
TypeScript

import React from 'react';
import { t } from '@/i18n/locales';
/**
* D8.4 IPO准备度检查清单 - 独立页面
*
* 法律/财务/合规/治理/保险 五大类别
* Gantt时间线、依赖管理、阻塞项跟踪
*/
interface CheckItem {
id: string;
item: string;
category: string;
status: 'done' | 'progress' | 'blocked' | 'pending';
owner: string;
deadline: string;
dependency?: string;
note?: string;
}
const categories = [
{ key: 'legal', label: t('ipo_cat_legal'), icon: '§', color: 'var(--color-primary)' },
{ key: 'financial', label: t('ipo_cat_financial'), icon: '$', color: 'var(--color-success)' },
{ key: 'sox', label: t('ipo_cat_sox'), icon: '✓', color: 'var(--color-info)' },
{ key: 'governance', label: t('ipo_cat_governance'), icon: '◆', color: 'var(--color-warning)' },
{ key: 'insurance', label: t('ipo_cat_insurance'), icon: '☂', color: '#FF6B6B' },
];
const overallProgress = {
total: 28,
done: 16,
inProgress: 7,
blocked: 2,
pending: 3,
percent: 72,
};
const milestones: { name: string; date: string; status: 'done' | 'progress' | 'pending' }[] = [
{ name: 'S-1初稿提交', date: '2026-Q2', status: 'progress' },
{ name: 'SEC审核期', date: '2026-Q3', status: 'pending' },
{ name: '路演 (Roadshow)', date: '2026-Q3', status: 'pending' },
{ name: '定价 & 上市', date: '2026-Q4', status: 'pending' },
];
const checklistItems: CheckItem[] = [
// Legal
{ id: 'L1', item: 'FinCEN MSB牌照', category: 'legal', status: 'done', owner: 'Legal', deadline: '2026-01-15' },
{ id: 'L2', item: 'NY BitLicense申请', category: 'legal', status: 'progress', owner: 'Legal', deadline: '2026-06-30', note: '材料已提交,等待审核' },
{ id: 'L3', item: '各州MTL注册 (48州)', category: 'legal', status: 'progress', owner: 'Legal', deadline: '2026-05-31', note: '已完成38/48州' },
{ id: 'L4', item: 'SEC法律顾问意见书', category: 'legal', status: 'progress', owner: 'External Counsel', deadline: '2026-04-30' },
{ id: 'L5', item: '知识产权审计 (IP Audit)', category: 'legal', status: 'done', owner: 'Legal', deadline: '2026-02-01' },
{ id: 'L6', item: '商标注册 (USPTO)', category: 'legal', status: 'done', owner: 'Legal', deadline: '2026-01-20' },
// Financial
{ id: 'F1', item: '独立审计报告 (Deloitte)', category: 'financial', status: 'progress', owner: 'Finance', deadline: '2026-05-15', dependency: 'F2' },
{ id: 'F2', item: 'GAAP财务报表 (3年)', category: 'financial', status: 'done', owner: 'Finance', deadline: '2026-03-01' },
{ id: 'F3', item: '税务合规证明', category: 'financial', status: 'done', owner: 'Finance', deadline: '2026-02-28' },
{ id: 'F4', item: '收入确认政策 (ASC 606)', category: 'financial', status: 'done', owner: 'Finance', deadline: '2026-02-15' },
{ id: 'F5', item: '估值模型 & 定价区间', category: 'financial', status: 'pending', owner: 'Finance + IB', deadline: '2026-07-31' },
// SOX
{ id: 'S1', item: 'ICFR内部控制框架', category: 'sox', status: 'done', owner: 'Compliance', deadline: '2026-01-31' },
{ id: 'S2', item: 'IT通用控制 (ITGC)', category: 'sox', status: 'done', owner: 'Engineering', deadline: '2026-02-15' },
{ id: 'S3', item: '访问控制审计', category: 'sox', status: 'done', owner: 'Security', deadline: '2026-02-10' },
{ id: 'S4', item: '变更管理流程', category: 'sox', status: 'done', owner: 'Engineering', deadline: '2026-02-01' },
{ id: 'S5', item: 'SOX 404管理层评估', category: 'sox', status: 'progress', owner: 'Compliance', deadline: '2026-05-31', dependency: 'S1' },
// Governance
{ id: 'G1', item: '独立董事会组建 (3+)', category: 'governance', status: 'done', owner: 'Board', deadline: '2026-03-01', note: '4名独立董事已任命' },
{ id: 'G2', item: '审计委员会', category: 'governance', status: 'done', owner: 'Board', deadline: '2026-03-15' },
{ id: 'G3', item: '薪酬委员会', category: 'governance', status: 'done', owner: 'Board', deadline: '2026-03-15' },
{ id: 'G4', item: '公司章程 & 治理政策', category: 'governance', status: 'done', owner: 'Legal', deadline: '2026-02-28' },
{ id: 'G5', item: 'D&O保险', category: 'governance', status: 'blocked', owner: 'Legal', deadline: '2026-04-30', note: '等待承保方最终报价' },
{ id: 'G6', item: 'Insider Trading Policy', category: 'governance', status: 'done', owner: 'Legal', deadline: '2026-03-01' },
// Insurance
{ id: 'I1', item: '消费者保护基金 ≥$2M', category: 'insurance', status: 'done', owner: 'Finance', deadline: '2026-02-01' },
{ id: 'I2', item: 'AML/KYC体系', category: 'insurance', status: 'done', owner: 'Compliance', deadline: '2026-01-15' },
{ id: 'I3', item: '赔付机制运行报告', category: 'insurance', status: 'progress', owner: 'Operations', deadline: '2026-05-01' },
{ id: 'I4', item: 'Cyber保险', category: 'insurance', status: 'blocked', owner: 'Legal', deadline: '2026-04-15', note: '正在比价3家承保方' },
{ id: 'I5', item: '做市商协议签署', category: 'insurance', status: 'pending', owner: 'Finance + IB', deadline: '2026-07-15' },
{ id: 'I6', item: '承销商尽职调查', category: 'insurance', status: 'pending', owner: 'External', deadline: '2026-08-01' },
];
const statusConfig: Record<string, { label: string; bg: string; fg: string }> = {
done: { label: t('completed'), bg: 'var(--color-success-light)', fg: 'var(--color-success)' },
progress: { label: t('in_progress'), bg: 'var(--color-warning-light)', fg: 'var(--color-warning)' },
blocked: { label: t('blocked'), bg: 'var(--color-error-light)', fg: 'var(--color-error)' },
pending: { label: t('pending'), bg: 'var(--color-gray-100)', fg: 'var(--color-text-tertiary)' },
};
export const IpoReadinessPage: React.FC = () => {
return (
<div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 8 }}>{t('ipo_title')}</h1>
<p style={{ font: 'var(--text-body)', color: 'var(--color-text-secondary)', marginBottom: 24 }}>
{t('ipo_subtitle')}
</p>
{/* Summary Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16, marginBottom: 24 }}>
{[
{ label: t('ipo_total_items'), value: overallProgress.total, color: 'var(--color-text-primary)' },
{ label: t('completed'), value: overallProgress.done, color: 'var(--color-success)' },
{ label: t('in_progress'), value: overallProgress.inProgress, color: 'var(--color-warning)' },
{ label: t('blocked'), value: overallProgress.blocked, color: 'var(--color-error)' },
{ label: t('pending'), value: overallProgress.pending, color: 'var(--color-text-tertiary)' },
].map(s => (
<div key={s.label} style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, textAlign: 'center',
}}>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 8 }}>{s.label}</div>
<div style={{ font: 'var(--text-display)', color: s.color }}>{s.value}</div>
</div>
))}
</div>
{/* Overall Progress Bar */}
<div style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 24,
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ font: 'var(--text-label)', color: 'var(--color-text-primary)' }}>{t('ipo_overall_progress')}</span>
<span style={{ font: 'var(--text-h2)', color: 'var(--color-primary)' }}>{overallProgress.percent}%</span>
</div>
<div style={{ height: 12, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden', display: 'flex' }}>
<div style={{ width: `${(overallProgress.done / overallProgress.total * 100).toFixed(0)}%`, height: '100%', background: 'var(--color-success)' }} />
<div style={{ width: `${(overallProgress.inProgress / overallProgress.total * 100).toFixed(0)}%`, height: '100%', background: 'var(--color-warning)' }} />
<div style={{ width: `${(overallProgress.blocked / overallProgress.total * 100).toFixed(0)}%`, height: '100%', background: 'var(--color-error)' }} />
</div>
<div style={{ display: 'flex', gap: 16, marginTop: 8 }}>
{[
{ label: t('completed'), color: 'var(--color-success)' },
{ label: t('in_progress'), color: 'var(--color-warning)' },
{ label: t('blocked'), color: 'var(--color-error)' },
{ label: t('pending'), color: 'var(--color-gray-200)' },
].map(l => (
<div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} />
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{l.label}</span>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 24 }}>
{/* Left: Checklist by Category */}
<div>
{categories.map(cat => {
const items = checklistItems.filter(i => i.category === cat.key);
const catDone = items.filter(i => i.status === 'done').length;
return (
<div key={cat.key} style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16,
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 28, height: 28, borderRadius: 'var(--radius-sm)', display: 'flex',
alignItems: 'center', justifyContent: 'center', background: cat.color, color: 'white',
font: 'var(--text-label)', fontWeight: 700,
}}>
{cat.icon}
</div>
<span style={{ font: 'var(--text-h3)', color: 'var(--color-text-primary)' }}>{cat.label}</span>
</div>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-secondary)' }}>
{catDone}/{items.length} {t('ipo_unit_done')}
</span>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{[t('ipo_th_id'), t('ipo_th_item'), t('ipo_th_owner'), t('ipo_th_deadline'), t('ipo_th_status')].map(h => (
<th key={h} style={{
padding: '6px 0', textAlign: 'left', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)',
}}>{h}</th>
))}
</tr>
</thead>
<tbody>
{items.map(item => {
const st = statusConfig[item.status];
return (
<tr key={item.id} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '10px 0', fontFamily: 'var(--font-family-mono)', font: 'var(--text-body-sm)' }}>{item.id}</td>
<td style={{ padding: '10px 0' }}>
<div style={{ font: 'var(--text-body)' }}>{item.item}</div>
{item.note && <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginTop: 2 }}>{item.note}</div>}
{item.dependency && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-info)', marginTop: 2 }}>
{t('ipo_dependency')}: {item.dependency}
</div>
)}
</td>
<td style={{ padding: '10px 0', font: 'var(--text-body-sm)' }}>{item.owner}</td>
<td style={{ padding: '10px 0', font: 'var(--text-body-sm)', fontFamily: 'var(--font-family-mono)' }}>{item.deadline}</td>
<td style={{ padding: '10px 0' }}>
<span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)',
fontWeight: 600, background: st.bg, color: st.fg,
}}>{st.label}</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
})}
</div>
{/* Right: Timeline & Blockers */}
<div>
{/* IPO Timeline */}
<div style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16,
}}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_timeline')}</h2>
{milestones.map((m, i) => (
<div key={m.name} style={{ display: 'flex', gap: 12, marginBottom: i < milestones.length - 1 ? 0 : 0 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{
width: 12, height: 12, borderRadius: '50%',
background: m.status === 'progress' ? 'var(--color-warning)' : m.status === 'done' ? 'var(--color-success)' : 'var(--color-gray-200)',
border: m.status === 'progress' ? '2px solid var(--color-warning)' : 'none',
}} />
{i < milestones.length - 1 && (
<div style={{ width: 2, height: 40, background: 'var(--color-border-light)' }} />
)}
</div>
<div style={{ paddingBottom: 16 }}>
<div style={{ font: 'var(--text-label)', color: 'var(--color-text-primary)' }}>{m.name}</div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{m.date}</div>
</div>
</div>
))}
</div>
{/* Blockers */}
<div style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-error)', padding: 20, marginBottom: 16,
}}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-error)', marginBottom: 16 }}>{t('ipo_blockers')}</h2>
{checklistItems.filter(i => i.status === 'blocked').map(item => (
<div key={item.id} style={{
padding: 12, background: 'var(--color-error-light)', borderRadius: 'var(--radius-sm)', marginBottom: 8,
}}>
<div style={{ font: 'var(--text-label)', color: 'var(--color-error)' }}>{item.id}: {item.item}</div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 4 }}>
{t('ipo_owner')}: {item.owner} · {t('ipo_deadline')}: {item.deadline}
</div>
{item.note && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 2 }}>{item.note}</div>
)}
</div>
))}
</div>
{/* Category Progress */}
<div style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16,
}}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_category_progress')}</h2>
{categories.map(cat => {
const items = checklistItems.filter(i => i.category === cat.key);
const catDone = items.filter(i => i.status === 'done').length;
const pct = Math.round(catDone / items.length * 100);
return (
<div key={cat.key} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-primary)' }}>{cat.label}</span>
<span style={{ font: 'var(--text-label-sm)', color: cat.color }}>{pct}%</span>
</div>
<div style={{ height: 6, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}>
<div style={{ width: `${pct}%`, height: '100%', background: cat.color, borderRadius: 'var(--radius-full)' }} />
</div>
</div>
);
})}
</div>
{/* Key Contacts */}
<div style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20,
}}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_key_contacts')}</h2>
{[
{ role: '承销商 (Lead)', name: 'Goldman Sachs', status: '已签约' },
{ role: '审计师', name: 'Deloitte', status: '审计中' },
{ role: '法律顾问', name: 'Skadden, Arps', status: '已签约' },
{ role: 'SEC联络', name: 'SEC Division of Corp Finance', status: '对接中' },
].map(c => (
<div key={c.role} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div>
<div style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>{c.role}</div>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{c.name}</div>
</div>
<span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)',
background: 'var(--color-primary-surface)', color: 'var(--color-primary)', fontWeight: 600,
}}>{c.status}</span>
</div>
))}
</div>
</div>
</div>
</div>
);
};