import React, { useState } from 'react'; import { motion } from 'framer-motion'; import toast from 'react-hot-toast'; import type { EventRequestFormData } from './EventRequestForm'; import InvoiceBuilder from './InvoiceBuilder'; import type { InvoiceData, InvoiceItem } from './InvoiceBuilder'; import CustomAlert from '../universal/CustomAlert'; import { Icon } from '@iconify/react'; // Enhanced animation variants with faster transitions const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.035, when: "beforeChildren", duration: 0.3, ease: "easeOut" } } }; const itemVariants = { hidden: { opacity: 0, y: 10 }, visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 500, damping: 25, mass: 0.8, duration: 0.25 } } }; // Input field hover animation const inputHoverVariants = { hover: { scale: 1.01, boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", transition: { duration: 0.15 } } }; // Button animation const buttonVariants = { hover: { scale: 1.03, transition: { duration: 0.15, ease: "easeOut" } }, tap: { scale: 0.97, transition: { duration: 0.1 } } }; // Toggle animation const toggleVariants = { checked: { backgroundColor: "rgba(var(--p), 0.2)" }, unchecked: { backgroundColor: "rgba(0, 0, 0, 0.05)" }, hover: { scale: 1.01, transition: { duration: 0.15 } } }; interface ASFundingSectionProps { formData: EventRequestFormData; onDataChange: (data: Partial) => void; } const ASFundingSection: React.FC = ({ formData, onDataChange }) => { // Check initial budget status React.useEffect(() => { if (formData.invoiceData?.total) { checkBudgetLimit(formData.invoiceData.total); } }, [formData.expected_attendance]); const [invoiceFiles, setInvoiceFiles] = useState(formData.invoice_files || []); const [jsonInput, setJsonInput] = useState(''); const [jsonError, setJsonError] = useState(''); const [showJsonInput, setShowJsonInput] = useState(false); const [isDragging, setIsDragging] = useState(false); // Handle invoice file upload const handleInvoiceFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const newFiles = Array.from(e.target.files) as File[]; // Combine existing files with new files instead of replacing const combinedFiles = [...invoiceFiles, ...newFiles]; setInvoiceFiles(combinedFiles); onDataChange({ invoice_files: combinedFiles }); } }; // Handle removing individual files const handleRemoveFile = (indexToRemove: number) => { const updatedFiles = invoiceFiles.filter((_, index) => index !== indexToRemove); setInvoiceFiles(updatedFiles); onDataChange({ invoice_files: updatedFiles }); }; // Handle clearing all files const handleClearAllFiles = () => { setInvoiceFiles([]); onDataChange({ invoice_files: [] }); }; // Handle JSON input change const handleJsonInputChange = (e: React.ChangeEvent) => { setJsonInput(e.target.value); setJsonError(''); }; // Show JSON example const showJsonExample = () => { const example = { vendor: "Example Restaurant", items: [ { id: "item-1", description: "Burger", quantity: 2, unitPrice: 12.99, amount: 25.98 }, { id: "item-2", description: "Fries", quantity: 2, unitPrice: 4.99, amount: 9.98 } ], subtotal: 35.96, taxRate: 9.0, taxAmount: 3.24, tipPercentage: 13.9, tipAmount: 5.00, total: 44.20 }; setJsonInput(JSON.stringify(example, null, 2)); }; // Validate and apply JSON // Check budget limits and show warning if exceeded const checkBudgetLimit = (total: number) => { const maxBudget = Math.min(formData.expected_attendance * 10, 5000); if (total > maxBudget) { toast.error(`Total amount ($${total.toFixed(2)}) exceeds maximum funding of $${maxBudget.toFixed(2)} for ${formData.expected_attendance} attendees.`, { duration: 4000, position: 'top-center' }); return true; } return false; }; const validateAndApplyJson = () => { try { if (!jsonInput.trim()) { setJsonError('JSON input is empty'); return; } const data = JSON.parse(jsonInput); // Validate structure if (!data.vendor) { setJsonError('Vendor field is required'); return; } if (!Array.isArray(data.items) || data.items.length === 0) { setJsonError('Items array is required and must contain at least one item'); return; } // Validate items for (const item of data.items) { if (!item.description || typeof item.unitPrice !== 'number' || typeof item.quantity !== 'number') { setJsonError('Each item must have description, unitPrice, and quantity fields'); return; } } // Validate tax, tip, and total if (typeof data.taxAmount !== 'number') { setJsonError('Tax amount must be a number'); return; } if (typeof data.tipAmount !== 'number') { setJsonError('Tip amount must be a number'); return; } if (typeof data.total !== 'number') { setJsonError('Total is required and must be a number'); return; } // Create itemized invoice string for Pocketbase const itemizedInvoice = JSON.stringify({ vendor: data.vendor, items: data.items.map((item: InvoiceItem) => ({ item: item.description, quantity: item.quantity, unit_price: item.unitPrice, amount: item.amount })), subtotal: data.subtotal, tax: data.taxAmount, tip: data.tipAmount, total: data.total }, null, 2); // Check budget limits and show toast if needed checkBudgetLimit(data.total); // Apply the JSON data to the form onDataChange({ invoiceData: data, itemized_invoice: itemizedInvoice, as_funding_required: true }); toast.success('Invoice data applied successfully'); setShowJsonInput(false); } catch (error) { setJsonError('Invalid JSON format: ' + (error as Error).message); } }; // Handle drag events for file upload const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { const newFiles = Array.from(e.dataTransfer.files) as File[]; // Combine existing files with new files instead of replacing const combinedFiles = [...invoiceFiles, ...newFiles]; setInvoiceFiles(combinedFiles); onDataChange({ invoice_files: combinedFiles }); } }; // Handle invoice data change from the invoice builder const handleInvoiceDataChange = (data: InvoiceData) => { // Check budget limits and show toast if needed checkBudgetLimit(data.total); onDataChange({ invoiceData: data, itemized_invoice: JSON.stringify(data) }); }; return (

AS Funding Details

{/* Invoice Upload Section */}

Invoice Information

Upload your invoice files or create an itemized invoice below.

document.getElementById('invoice-files')?.click()} >
{invoiceFiles.length > 0 ? ( <>

{invoiceFiles.length} file(s) selected:

{invoiceFiles.map((file, index) => (
{file.name}
))}

Click or drag to add more files

) : ( <>

Drop your invoice files here or click to browse

Supports PDF, JPG, JPEG, PNG (multiple files allowed)

)}
{/* JSON/Builder Toggle */}

Invoice Details

{showJsonInput ? (
Show Example
{jsonError && (
{jsonError}
)}
Apply JSON
) : ( )}
); }; export default ASFundingSection;