Files
cim_summary/frontend/src/components/DocumentViewer.tsx
Jon c67dab22b4 Add comprehensive CIM processing features and UI improvements
- 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
2025-07-27 20:25:46 -04:00

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;