import React, { useState, useRef } from 'react'; import { csvImportService, ParsedEtsyOrder, ParsedShippingRecord } from '../utils/csvImportService'; import { pdfParser, ParsedPackingSlip } from '../utils/pdfParser'; import { matchOrderItemsToProducts } from '../utils/productMatcher'; import { MissingProductsModal, MissingProduct } from '../components/MissingProductsModal'; import { DataManager } from '../utils/dataManager'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../store'; import { addOrder, updateOrder, setOrders } from '../store/slices/orderSlice'; import { Order } from '../store/slices/orderSlice'; import { Upload, FileText, Package, Truck, Trash2 } from 'lucide-react'; import toast from 'react-hot-toast'; import { dateTestResults } from '../utils/testDateParsing'; interface ImportResults { etsyOrders: ParsedEtsyOrder[]; shippingRecords: ParsedShippingRecord[]; matchedData: Array<{ order: ParsedEtsyOrder; shipping?: ParsedShippingRecord; confidence: number; }>; orderCosts: Array<{ orderNumber: string; date: string; productName?: string; grossRevenue: number; etsyFees: number; netRevenue: number; shippingCost: number; printingCost: number; totalCosts: number; grossProfit: number; grossMargin: number; netMargin: number; shippingConfidence: number; hasShippingData: boolean; hasPrintingData: boolean; }>; summary: ReturnType; } export default function DataImport() { // Test date parsing immediately console.log('=== DATE PARSING TEST RESULTS ==='); console.log('Date test results:', dateTestResults); const dispatch = useDispatch(); const orders = useSelector((state: RootState) => state.orders.orders); const products = useSelector((state: RootState) => state.products.products); // CSV Import State const [etsyFile, setEtsyFile] = useState(null); const [shippingFile, setShippingFile] = useState(null); const [isLoading, setIsLoading] = useState(false); const [results, setResults] = useState(null); // PDF Import State const [pdfFiles, setPdfFiles] = useState([]); const [isPdfProcessing, setIsPdfProcessing] = useState(false); const [pdfResults, setPdfResults] = useState([]); const [showMissingProductsModal, setShowMissingProductsModal] = useState(false); const [missingProducts, setMissingProducts] = useState([]); const [currentOrderForMissing, setCurrentOrderForMissing] = useState(''); // UI State const [activeTab, setActiveTab] = useState<'csv' | 'pdf'>('csv'); const [error, setError] = useState(''); const etsyFileRef = useRef(null); const shippingFileRef = useRef(null); const pdfFileRef = useRef(null); const handleFileChange = ( event: React.ChangeEvent, type: 'etsy' | 'shipping' | 'pdf' ) => { const files = event.target.files; if (!files) return; if (type === 'pdf') { const pdfFiles = Array.from(files).filter(file => file.type === 'application/pdf'); if (pdfFiles.length > 0) { setPdfFiles(pdfFiles); setError(''); } else { setError('Please select valid PDF files'); } } else { const file = files[0]; if (file && file.type === 'text/csv') { if (type === 'etsy') { setEtsyFile(file); } else { setShippingFile(file); } setError(''); } else { setError('Please select a valid CSV file'); } } }; const readFileAsText = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target?.result as string); reader.onerror = reject; reader.readAsText(file); }); }; const processCsvFiles = async () => { if (!etsyFile) { setError('Please select an Etsy statement CSV file'); return; } setIsLoading(true); setError(''); try { const etsyContent = await readFileAsText(etsyFile); const etsyOrders = csvImportService.parseEtsyStatement(etsyContent); let shippingRecords: ParsedShippingRecord[] = []; if (shippingFile) { const shippingContent = await readFileAsText(shippingFile); shippingRecords = csvImportService.parseAustraliaPostShipping(shippingContent); } const matchedData = csvImportService.matchOrdersWithShipping(etsyOrders, shippingRecords); const printingCosts = new Map(); orders.forEach(order => { if (order.items) { const totalPrinting = order.items.reduce((sum, item) => sum + (item.printingCost || 0), 0); if (totalPrinting > 0) { printingCosts.set(order.orderNumber, totalPrinting); } } }); const orderCosts = csvImportService.calculateOrderCosts(matchedData, printingCosts); const summary = csvImportService.generateSummary(orderCosts); setResults({ etsyOrders, shippingRecords, matchedData, orderCosts, summary }); // Automatically create orders from CSV data const csvOrders = etsyOrders.map(csvOrder => { // Check if order already exists const existingOrder = orders.find(order => order.orderNumber === csvOrder.orderNumber); if (existingOrder) { // Update existing order with CSV revenue data return { ...existingOrder, total: csvOrder.saleAmount, fees: { etsy: csvOrder.totalFees || 0, processing: 0, shipping: 0 } }; } else { // Create new order from CSV data return { _id: `csv-${csvOrder.orderNumber}`, orderNumber: csvOrder.orderNumber, total: csvOrder.saleAmount, status: 'delivered' as const, dateOrdered: csvOrder.date, customer: { name: 'Etsy Customer', email: '' }, items: [{ title: csvOrder.productName || 'Product from Etsy', quantity: 1, price: csvOrder.saleAmount, printingCost: 0 }], fees: { etsy: csvOrder.totalFees || 0, processing: 0, shipping: 0 } }; } }); // Update existing orders and add new ones const existingOrderNumbers = new Set(orders.map(o => o.orderNumber)); const ordersToUpdate = csvOrders.filter(o => existingOrderNumbers.has(o.orderNumber)); const ordersToAdd = csvOrders.filter(o => !existingOrderNumbers.has(o.orderNumber)); ordersToUpdate.forEach(order => dispatch(updateOrder(order))); ordersToAdd.forEach(order => dispatch(addOrder(order))); toast.success(`CSV imported! Created ${ordersToAdd.length} new orders and updated ${ordersToUpdate.length} existing orders.`); } catch (err) { console.error('Error processing CSV files:', err); setError('Error processing CSV files. Please check the file format.'); } finally { setIsLoading(false); } }; const processPdfFiles = async () => { if (pdfFiles.length === 0) { setError('Please select PDF packing slip files'); return; } setIsPdfProcessing(true); setError(''); try { const parsedSlips: ParsedPackingSlip[] = []; for (const file of pdfFiles) { console.log(`Processing PDF: ${file.name}`); const slip = await pdfParser.parsePackingSlip(file); if (slip) { parsedSlips.push(slip); } } setPdfResults(parsedSlips); for (const slip of parsedSlips) { await createOrUpdateOrderFromSlip(slip); } } catch (err) { console.error('Error processing PDF files:', err); setError('Error processing PDF files. Please check the file format.'); } finally { setIsPdfProcessing(false); } }; const createOrUpdateOrderFromSlip = async (slip: ParsedPackingSlip, customProducts?: any[]) => { console.log('Creating/updating order for slip:', slip); const existingOrder = orders.find(order => order.orderNumber === slip.orderNumber); // Check if we have CSV results for this order number to get revenue data let csvOrderData = null; if (results && results.etsyOrders) { csvOrderData = results.etsyOrders.find(csvOrder => csvOrder.orderNumber === slip.orderNumber); console.log('Found matching CSV data for order:', csvOrderData); } const productsToUse = customProducts || products; const { matches, missingProducts: missing } = matchOrderItemsToProducts(slip.items, productsToUse); if (missing.length > 0) { setMissingProducts(missing); setCurrentOrderForMissing(slip.orderNumber); setShowMissingProductsModal(true); return; } const orderItems = matches.map((match: any) => ({ title: match.orderItem.title, quantity: match.orderItem.quantity, price: match.orderItem.price || 0, printingCost: match.matchedProduct?.printingCost || 0 })); // Parse and format the order date let formattedOrderDate = new Date().toISOString(); if (slip.orderDate) { try { // Convert "21 Jul, 2025" format to ISO date const parsedDate = new Date(slip.orderDate); if (!isNaN(parsedDate.getTime())) { formattedOrderDate = parsedDate.toISOString(); } } catch (error) { console.warn('Failed to parse order date:', slip.orderDate, error); } } const orderData: Order = { _id: existingOrder?._id || Date.now().toString(), orderNumber: slip.orderNumber, total: csvOrderData?.saleAmount || existingOrder?.total || 0, // Use CSV revenue data if available status: existingOrder?.status || 'processing', dateOrdered: formattedOrderDate, customer: existingOrder?.customer || { name: slip.customerName, email: slip.customerEmail || '', }, items: orderItems, fees: csvOrderData ? { etsy: csvOrderData.totalFees || 0, processing: 0, shipping: 0 } : existingOrder?.fees }; if (existingOrder) { dispatch(updateOrder(orderData)); } else { dispatch(addOrder(orderData)); } }; const handleMissingProductsSubmit = (newProducts: any[]) => { // Products are already created by the MissingProductsModal // We need to use the updated product list for matching setShowMissingProductsModal(false); const slip = pdfResults.find(slip => slip.orderNumber === currentOrderForMissing); if (slip) { // Use the combined product list (existing + new) for matching const updatedProducts = [...products, ...newProducts]; createOrUpdateOrderFromSlip(slip, updatedProducts); } }; const handleClearTestData = () => { if (window.confirm('Clear all existing data for testing? This will download a backup first.')) { DataManager.clearWithBackup(); toast.success('Data cleared and backup downloaded!'); setTimeout(() => window.location.reload(), 1000); } }; const handleClearAllOrders = () => { const orderCount = orders.length; if (window.confirm(`This will delete all ${orderCount} existing orders. You'll need to re-upload your packing slips to get the correct dates. Are you sure?`)) { dispatch(setOrders([])); toast.success(`All ${orderCount} orders cleared! Now re-upload your packing slips with fixed date parsing.`); } }; const createOrdersFromCSV = () => { if (!results || !results.etsyOrders || results.etsyOrders.length === 0) { toast.error('No CSV data available. Please import Etsy CSV first.'); return; } const csvOrders = results.etsyOrders.map(csvOrder => { // Check if order already exists (from packing slip import) const existingOrder = orders.find(order => order.orderNumber === csvOrder.orderNumber); if (existingOrder) { // Update existing order with CSV revenue data return { ...existingOrder, total: csvOrder.saleAmount, fees: { etsy: csvOrder.totalFees || 0, processing: 0, shipping: 0 } }; } else { // Create new order from CSV data (no packing slip available) return { _id: `csv-${csvOrder.orderNumber}`, orderNumber: csvOrder.orderNumber, total: csvOrder.saleAmount, status: 'delivered' as const, dateOrdered: csvOrder.date, customer: { name: 'Etsy Customer', email: '' }, items: [{ title: csvOrder.productName || 'Product from Etsy', quantity: 1, price: csvOrder.saleAmount, printingCost: 0 }], fees: { etsy: csvOrder.totalFees || 0, processing: 0, shipping: 0 } }; } }); // Update existing orders and add new ones const existingOrderNumbers = new Set(orders.map(o => o.orderNumber)); const ordersToUpdate = csvOrders.filter(o => existingOrderNumbers.has(o.orderNumber)); const ordersToAdd = csvOrders.filter(o => !existingOrderNumbers.has(o.orderNumber)); ordersToUpdate.forEach(order => dispatch(updateOrder(order))); ordersToAdd.forEach(order => dispatch(addOrder(order))); toast.success(`Updated ${ordersToUpdate.length} existing orders and created ${ordersToAdd.length} new orders from CSV data.`); }; const debugDataState = () => { console.log('=== DEBUGGING DATA STATE ==='); console.log('Current orders in Redux:', orders.length); orders.forEach((order, i) => { console.log(`Order ${i + 1}:`, { orderNumber: order.orderNumber, total: order.total, items: order.items?.length || 0, fees: order.fees, customer: order.customer?.name }); }); console.log('CSV Results:', results ? { etsyOrders: results.etsyOrders?.length || 0, sampleOrder: results.etsyOrders?.[0] } : 'No CSV results'); console.log('Products:', products.length); toast.success(`Debug info logged to console. Orders: ${orders.length}, CSV: ${results?.etsyOrders?.length || 0}, Products: ${products.length}`); }; const testActualPDF = async () => { try { console.log('=== TESTING PDF PARSER ==='); // First, let's check what products we have in the database console.log('Current products in database:', products.map(p => ({ id: p._id, title: p.title }))); console.log('Testing actual packing slip PDF...'); const response = await fetch('/3748364725.pdf'); const arrayBuffer = await response.arrayBuffer(); const blob = new Blob([arrayBuffer], { type: 'application/pdf' }); const file = new File([blob], '3748364725.pdf', { type: 'application/pdf' }); console.log('File size:', file.size, 'bytes'); // Let's also extract the raw text to see what we're working with const { getDocument } = await import('pdfjs-dist'); const pdf = await getDocument(arrayBuffer).promise; let fullText = ''; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); const pageText = textContent.items.map((item: any) => item.str).join(' '); fullText += pageText + '\n'; console.log(`Page ${i} text:`, pageText); } console.log('\n=== FULL EXTRACTED TEXT ==='); console.log(fullText); // Now try our parser const result = await pdfParser.parsePackingSlip(file); console.log('\n=== PARSER RESULT ==='); console.log('Parse Result:', JSON.stringify(result, null, 2)); if (result.items && result.items.length > 0) { console.log('\n=== PARSED ITEMS ==='); console.log('Order Number:', result.orderNumber); result.items.forEach((item, index) => { console.log(`Item ${index + 1}: ${item.title} (Qty: ${item.quantity})`); }); } else { console.log('❌ No items found - we need to update the parser patterns based on the extracted text above'); } } catch (error) { console.error('Error testing PDF:', error); } }; return (

Data Import & Analysis

Import Etsy statements, Australia Post shipping data, and PDF packing slips for complete business analysis.

{/* Testing Helper */}

Fix dates | Clear all | Test parsing | Debug

{/* Date Fix Notice */} {orders.length > 0 && (

Date Fix Required for Existing Orders

You have {orders.length} existing orders that may have incorrect dates (showing today's date instead of actual order dates). To fix this: Click "Clear All Orders" above, then re-upload your packing slip PDFs. The updated date parsing will now extract the correct order dates.

)} {/* Import Options Tabs */}
{/* CSV Import Section */} {activeTab === 'csv' && (

Upload CSV Files for Financial Analysis

{/* Etsy CSV Upload */}
handleFileChange(e, 'etsy')} className="hidden" />
{etsyFile ? (

{etsyFile.name}

) : (

Click to upload Etsy statement

Contains sales, fees, and order data

)}
{/* Australia Post CSV Upload */}
handleFileChange(e, 'shipping')} className="hidden" />
{shippingFile ? (

{shippingFile.name}

) : (

Click to upload shipping data

Contains actual shipping costs and tracking

)}
)} {/* PDF Import Section */} {activeTab === 'pdf' && (

Upload PDF Packing Slips for Item Details

handleFileChange(e, 'pdf')} className="hidden" />
{pdfFiles.length > 0 ? (

{pdfFiles.length} PDF file(s) selected

{pdfFiles.map((file, index) => (

{file.name}

))}
) : (

Drag & drop PDF packing slips here

Or click to browse and select multiple files

Extracts item details, quantities, and customer info

)}
{/* PDF Results Display */} {pdfResults.length > 0 && (

PDF Processing Results

{pdfResults.map((slip, index) => (

Order #{slip.orderNumber}

Customer: {slip.customerName}

Items: {slip.items.length}

))}
)}
)} {error && (

{error}

)}
{/* Results Section - Show for CSV analysis only */} {results && activeTab === 'csv' && ( <> {/* Summary Cards */}

Total Orders

{results.summary.ordersProcessed}

Total Revenue

${results.summary.totalRevenue.toFixed(2)}

Total Profit

${results.summary.totalProfit.toFixed(2)}

Avg Profit Margin

{results.summary.averageGrossMargin.toFixed(1)}%

{/* Action Button to Create Orders */}

Re-sync Orders from CSV Data

Orders were automatically created during CSV import. Use this button to re-sync if you need to update the data.

{/* Cost Breakdown */}

Cost Breakdown

Etsy Fees

${results.summary.totalEtsyFees.toFixed(2)}

Shipping Costs

${results.summary.totalShippingCosts.toFixed(2)}

Printing Costs

${results.summary.totalPrintingCosts.toFixed(2)}

)} {/* Missing Products Modal */} {showMissingProductsModal && ( setShowMissingProductsModal(false)} onComplete={handleMissingProductsSubmit} /> )}
); }