windsurf/converter/app/page.tsx

217 lines
6.9 KiB
TypeScript
Raw Permalink Normal View History

2024-11-20 23:27:41 +00:00
'use client';
import { useState } from 'react';
import { FileUpload } from '@/components/file-upload';
import { SchemaDefinition, type SchemaField } from '@/components/schema-definition';
import { Button } from '@/components/ui/button';
import { useToast } from '@/hooks/use-toast';
interface ExtractedData {
company: string;
address: string;
total_sum: number;
items: {
item: string;
unit_price: number;
quantity: number;
sum: number;
}[];
}
interface FileWithText {
file: File;
text?: string;
}
export default function Home() {
const [files, setFiles] = useState<FileWithText[]>([]);
const [schema, setSchema] = useState<SchemaField[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
const [processedData, setProcessedData] = useState<ExtractedData[]>([]);
const { toast } = useToast();
const handleFilesUploaded = (newFiles: File[]) => {
const filesWithText = newFiles.map(file => ({ file }));
setFiles(prev => [...prev, ...filesWithText]);
};
const handleTextExtracted = (fileName: string, text: string) => {
setFiles(prev =>
prev.map(f =>
f.file.name === fileName ? { ...f, text } : f
)
);
};
const handleSchemaChange = (newSchema: SchemaField[]) => {
setSchema(newSchema);
};
const handleStartExtraction = async () => {
setIsProcessing(true);
const results: ExtractedData[] = [];
try {
for (const { file, text } of files) {
if (!text) {
toast({
title: 'Error',
description: `Text not yet extracted for ${file.name}`,
variant: 'destructive',
});
continue;
}
const response = await fetch('/api/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text,
schema,
}),
});
if (!response.ok) {
throw new Error(`Failed to process ${file.name}`);
}
const data = await response.json();
results.push(data);
toast({
title: 'Success',
description: `Processed ${file.name}`,
});
}
setProcessedData(results);
} catch (error) {
console.error('Error during extraction:', error);
toast({
title: 'Error',
description: 'Failed to process files',
variant: 'destructive',
});
} finally {
setIsProcessing(false);
}
};
return (
<div className="min-h-screen p-8">
<div className="max-w-4xl mx-auto space-y-8">
<h1 className="text-3xl font-bold">PDF Data Extractor</h1>
<div className="space-y-8">
<section>
<h2 className="text-xl font-semibold mb-4">Upload PDF Files</h2>
<FileUpload
onFilesUploaded={handleFilesUploaded}
onTextExtracted={handleTextExtracted}
/>
</section>
<section>
<SchemaDefinition onSchemaChange={handleSchemaChange} />
</section>
<section className="flex justify-center space-x-4">
<Button
size="lg"
onClick={handleStartExtraction}
disabled={files.length === 0 || isProcessing}
>
{isProcessing ? 'Processing...' : 'Start Extraction'}
</Button>
{processedData.length > 0 && (
<Button
size="lg"
variant="outline"
onClick={async () => {
try {
const response = await fetch('/api/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: processedData }),
});
if (!response.ok) {
throw new Error('Failed to generate Excel file');
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'extracted_data.xlsx';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
toast({
title: 'Success',
description: 'Excel file downloaded successfully',
});
} catch (error) {
console.error('Error downloading file:', error);
toast({
title: 'Error',
description: 'Failed to download Excel file',
variant: 'destructive',
});
}
}}
>
Download Excel
</Button>
)}
</section>
{processedData.length > 0 && (
<section>
<h2 className="text-xl font-semibold mb-4">Extracted Data</h2>
<div className="space-y-4">
{processedData.map((data, index) => (
<div key={index} className="border rounded-lg p-4 space-y-2">
<h3 className="font-semibold">{data.company}</h3>
<p className="text-sm text-gray-600">{data.address}</p>
<p className="font-medium">Total: ${data.total_sum.toFixed(2)}</p>
<div className="mt-4">
<h4 className="font-medium mb-2">Items:</h4>
<table className="w-full text-sm">
<thead>
<tr className="border-b">
<th className="text-left py-2">Item</th>
<th className="text-right">Unit Price</th>
<th className="text-right">Quantity</th>
<th className="text-right">Sum</th>
</tr>
</thead>
<tbody>
{data.items.map((item, itemIndex) => (
<tr key={itemIndex} className="border-b">
<td className="py-2">{item.item}</td>
<td className="text-right">${item.unit_price.toFixed(2)}</td>
<td className="text-right">{item.quantity}</td>
<td className="text-right">${item.sum.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
))}
</div>
</section>
)}
</div>
</div>
</div>
);
}