- Add new database migrations for analysis data and job tracking - Implement enhanced document processing service with LLM integration - Add processing progress and queue status components - Create testing guides and utility scripts for CIM processing - Update frontend components for better user experience - Add environment configuration and backup files - Implement job queue service and upload progress tracking
370 lines
14 KiB
TypeScript
370 lines
14 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
ArrowLeft,
|
|
Download,
|
|
Share2,
|
|
FileText,
|
|
BarChart3,
|
|
Users,
|
|
TrendingUp,
|
|
DollarSign,
|
|
AlertTriangle,
|
|
CheckCircle,
|
|
Clock
|
|
} from 'lucide-react';
|
|
import { cn } from '../utils/cn';
|
|
import CIMReviewTemplate from './CIMReviewTemplate';
|
|
|
|
// Simple markdown to HTML converter
|
|
const markdownToHtml = (markdown: string): string => {
|
|
return markdown
|
|
.replace(/^### (.*$)/gim, '<h3 class="text-lg font-semibold text-gray-900 mt-4 mb-2">$1</h3>')
|
|
.replace(/^## (.*$)/gim, '<h2 class="text-xl font-bold text-gray-900 mt-6 mb-3">$1</h2>')
|
|
.replace(/^# (.*$)/gim, '<h1 class="text-2xl font-bold text-gray-900 mt-8 mb-4">$1</h1>')
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong class="font-semibold">$1</strong>')
|
|
.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>')
|
|
.replace(/`(.*?)`/g, '<code class="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono">$1</code>')
|
|
.replace(/\n\n/g, '</p><p class="mb-3">')
|
|
.replace(/^\n?/, '<p class="mb-3">')
|
|
.replace(/\n?$/, '</p>');
|
|
};
|
|
|
|
interface ExtractedData {
|
|
companyName?: string;
|
|
industry?: string;
|
|
revenue?: string;
|
|
ebitda?: string;
|
|
employees?: string;
|
|
founded?: string;
|
|
location?: string;
|
|
summary?: string;
|
|
keyMetrics?: Record<string, string>;
|
|
financials?: {
|
|
revenue: string[];
|
|
ebitda: string[];
|
|
margins: string[];
|
|
};
|
|
risks?: string[];
|
|
opportunities?: string[];
|
|
}
|
|
|
|
interface DocumentViewerProps {
|
|
documentId: string;
|
|
documentName: string;
|
|
extractedData?: ExtractedData;
|
|
cimReviewData?: any;
|
|
onBack?: () => void;
|
|
onDownload?: () => void;
|
|
onShare?: () => void;
|
|
}
|
|
|
|
const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
|
documentId,
|
|
documentName,
|
|
extractedData,
|
|
cimReviewData,
|
|
onBack,
|
|
onDownload,
|
|
onShare,
|
|
}) => {
|
|
const [activeTab, setActiveTab] = useState<'overview' | 'template' | 'raw'>('overview');
|
|
|
|
const tabs = [
|
|
{ id: 'overview', label: 'Overview', icon: FileText },
|
|
{ id: 'template', label: 'Review Template', icon: BarChart3 },
|
|
{ id: 'raw', label: 'Raw Data', icon: FileText },
|
|
];
|
|
|
|
const renderOverview = () => (
|
|
<div className="space-y-6">
|
|
{/* Document Header */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900">{documentName}</h2>
|
|
<p className="text-sm text-gray-600 mt-1">Document ID: {documentId}</p>
|
|
</div>
|
|
<div className="flex items-center space-x-3">
|
|
<button
|
|
onClick={onDownload}
|
|
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
>
|
|
<Download className="h-4 w-4 mr-2" />
|
|
Download
|
|
</button>
|
|
<button
|
|
onClick={onShare}
|
|
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
>
|
|
<Share2 className="h-4 w-4 mr-2" />
|
|
Share
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Key Metrics */}
|
|
{extractedData && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<DollarSign className="h-8 w-8 text-green-600" />
|
|
</div>
|
|
<div className="ml-4">
|
|
<p className="text-sm font-medium text-gray-500">Revenue</p>
|
|
<p className="text-2xl font-semibold text-gray-900">
|
|
{extractedData.revenue || 'N/A'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<TrendingUp className="h-8 w-8 text-blue-600" />
|
|
</div>
|
|
<div className="ml-4">
|
|
<p className="text-sm font-medium text-gray-500">EBITDA</p>
|
|
<p className="text-2xl font-semibold text-gray-900">
|
|
{extractedData.ebitda || 'N/A'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<Users className="h-8 w-8 text-purple-600" />
|
|
</div>
|
|
<div className="ml-4">
|
|
<p className="text-sm font-medium text-gray-500">Employees</p>
|
|
<p className="text-2xl font-semibold text-gray-900">
|
|
{extractedData.employees || 'N/A'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<Clock className="h-8 w-8 text-orange-600" />
|
|
</div>
|
|
<div className="ml-4">
|
|
<p className="text-sm font-medium text-gray-500">Founded</p>
|
|
<p className="text-2xl font-semibold text-gray-900">
|
|
{extractedData.founded || 'N/A'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Company Summary */}
|
|
{extractedData?.summary && (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Document Analysis</h3>
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<FileText className="h-5 w-5 text-blue-600" />
|
|
</div>
|
|
<div className="ml-3">
|
|
<h4 className="text-sm font-medium text-blue-900">Structured CIM Review Available</h4>
|
|
<p className="text-sm text-blue-700 mt-1">
|
|
This document has been analyzed and structured into a comprehensive CIM review template.
|
|
Switch to the "Template" tab to view the detailed analysis in a structured format.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Key Information Grid */}
|
|
{extractedData && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Opportunities */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center mb-4">
|
|
<CheckCircle className="h-5 w-5 text-green-600 mr-2" />
|
|
<h3 className="text-lg font-medium text-gray-900">Key Opportunities</h3>
|
|
</div>
|
|
{extractedData.opportunities && extractedData.opportunities.length > 0 ? (
|
|
<ul className="space-y-2">
|
|
{extractedData.opportunities.map((opportunity, index) => (
|
|
<li key={index} className="flex items-start">
|
|
<span className="text-green-500 mr-2">•</span>
|
|
<span className="text-gray-700">{opportunity}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p className="text-gray-500 italic">No opportunities identified</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Risks */}
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="flex items-center mb-4">
|
|
<AlertTriangle className="h-5 w-5 text-red-600 mr-2" />
|
|
<h3 className="text-lg font-medium text-gray-900">Key Risks</h3>
|
|
</div>
|
|
{extractedData.risks && extractedData.risks.length > 0 ? (
|
|
<ul className="space-y-2">
|
|
{extractedData.risks.map((risk, index) => (
|
|
<li key={index} className="flex items-start">
|
|
<span className="text-red-500 mr-2">•</span>
|
|
<span className="text-gray-700">{risk}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p className="text-gray-500 italic">No risks identified</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Financial Trends */}
|
|
{extractedData?.financials && (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">Financial Trends</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<h4 className="text-sm font-medium text-gray-500 mb-2">Revenue</h4>
|
|
<div className="space-y-1">
|
|
{extractedData.financials.revenue.map((value, index) => (
|
|
<div key={index} className="flex justify-between text-sm">
|
|
<span className="text-gray-600">FY{3-index}</span>
|
|
<span className="font-medium">{value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h4 className="text-sm font-medium text-gray-500 mb-2">EBITDA</h4>
|
|
<div className="space-y-1">
|
|
{extractedData.financials.ebitda.map((value, index) => (
|
|
<div key={index} className="flex justify-between text-sm">
|
|
<span className="text-gray-600">FY{3-index}</span>
|
|
<span className="font-medium">{value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h4 className="text-sm font-medium text-gray-500 mb-2">Margins</h4>
|
|
<div className="space-y-1">
|
|
{extractedData.financials.margins.map((value, index) => (
|
|
<div key={index} className="flex justify-between text-sm">
|
|
<span className="text-gray-600">FY{3-index}</span>
|
|
<span className="font-medium">{value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
const renderRawData = () => (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<div className="mb-6">
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Raw Extracted Data</h3>
|
|
<p className="text-sm text-gray-600">
|
|
This tab shows the raw JSON data extracted from the document during processing.
|
|
It includes all the structured information that was parsed from the CIM document,
|
|
including financial metrics, company details, and analysis results.
|
|
</p>
|
|
</div>
|
|
<pre className="bg-gray-50 rounded-lg p-4 overflow-x-auto text-sm">
|
|
<code>{JSON.stringify(extractedData, null, 2)}</code>
|
|
</pre>
|
|
</div>
|
|
);
|
|
|
|
const renderTemplateInfo = () => (
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
<h4 className="text-sm font-medium text-blue-900 mb-2">CIM Review Analysis</h4>
|
|
<p className="text-sm text-blue-700">
|
|
This tab displays the AI-generated analysis of your CIM document in a structured format.
|
|
The analysis has been organized into sections like Deal Overview, Financial Summary,
|
|
Management Team, and Investment Thesis. You can review, edit, and save this structured
|
|
analysis for your investment review process.
|
|
</p>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto">
|
|
{/* Header */}
|
|
<div className="bg-white shadow-sm border-b border-gray-200 px-4 py-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<button
|
|
onClick={onBack}
|
|
className="mr-4 p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
<ArrowLeft className="h-5 w-5" />
|
|
</button>
|
|
<div>
|
|
<h1 className="text-xl font-semibold text-gray-900">Document Viewer</h1>
|
|
<p className="text-sm text-gray-600">{documentName}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tab Navigation */}
|
|
<div className="bg-white border-b border-gray-200">
|
|
<div className="px-4 sm:px-6 lg:px-8">
|
|
<nav className="-mb-px flex space-x-8">
|
|
{tabs.map((tab) => {
|
|
const Icon = tab.icon;
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id as any)}
|
|
className={cn(
|
|
'flex items-center py-4 px-1 border-b-2 font-medium text-sm',
|
|
activeTab === tab.id
|
|
? 'border-blue-500 text-blue-600'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
|
)}
|
|
>
|
|
<Icon className="h-4 w-4 mr-2" />
|
|
{tab.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="px-4 py-6 sm:px-6 lg:px-8">
|
|
{activeTab === 'overview' && renderOverview()}
|
|
{activeTab === 'template' && (
|
|
<>
|
|
{renderTemplateInfo()}
|
|
<CIMReviewTemplate
|
|
initialData={cimReviewData}
|
|
cimReviewData={cimReviewData}
|
|
readOnly={false}
|
|
/>
|
|
</>
|
|
)}
|
|
{activeTab === 'raw' && renderRawData()}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DocumentViewer;
|