318 lines
19 KiB
TypeScript
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>
|
|
);
|
|
};
|