import React, { useState, useEffect, useRef } from 'react'; import { Icon } from '@iconify/react'; import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { DataSyncService } from '../../../scripts/database/DataSyncService'; import { Collections } from '../../../schemas/pocketbase/schema'; import ReceiptForm from './ReceiptForm'; import { toast } from 'react-hot-toast'; import { motion, AnimatePresence } from 'framer-motion'; import FilePreview from '../universal/FilePreview'; import type { ItemizedExpense, Reimbursement, Receipt } from '../../../schemas/pocketbase'; interface ReceiptFormData { file: File; itemized_expenses: ItemizedExpense[]; tax: number; date: string; location_name: string; location_address: string; notes: string; } // Extended Reimbursement interface with form-specific fields interface ReimbursementRequest extends Partial> { title: string; total_amount: number; date_of_purchase: string; payment_method: string; status: 'submitted' | 'under_review' | 'approved' | 'rejected' | 'paid' | 'in_progress'; additional_info: string; receipts: string[]; department: 'internal' | 'external' | 'projects' | 'events' | 'other'; } const PAYMENT_METHODS = [ 'Personal Credit Card', 'Personal Debit Card', 'Cash', 'Personal Check', 'Other' ]; const DEPARTMENTS = [ 'internal', 'external', 'projects', 'events', 'other' ] as const; const DEPARTMENT_LABELS = { internal: 'Internal', external: 'External', projects: 'Projects', events: 'Events', other: 'Other' }; // Add these animation variants const containerVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 30, staggerChildren: 0.1 } } }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } } }; export default function ReimbursementForm() { const [request, setRequest] = useState({ title: '', total_amount: 0, date_of_purchase: new Date().toISOString().split('T')[0], payment_method: '', status: 'submitted', additional_info: '', receipts: [], department: 'internal' }); const [receipts, setReceipts] = useState<(ReceiptFormData & { id: string })[]>([]); const [showReceiptForm, setShowReceiptForm] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); const [showReceiptDetails, setShowReceiptDetails] = useState(false); const [selectedReceiptDetails, setSelectedReceiptDetails] = useState(null); const [hasZelleInfo, setHasZelleInfo] = useState(null); const [isLoading, setIsLoading] = useState(true); const auth = Authentication.getInstance(); useEffect(() => { checkZelleInformation(); }, []); const checkZelleInformation = async () => { try { setIsLoading(true); const pb = auth.getPocketBase(); const userId = pb.authStore.model?.id; if (!userId) { // Silently return without error when on dashboard page if (window.location.pathname.includes('/dashboard')) { setIsLoading(false); return; } throw new Error('User not authenticated'); } const user = await pb.collection('users').getOne(userId); setHasZelleInfo(!!user.zelle_information); } catch (error) { // Only log error if not on dashboard page or if it's not an authentication error if (!window.location.pathname.includes('/dashboard') || !(error instanceof Error && error.message === 'User not authenticated')) { console.error('Error checking Zelle information:', error); } } finally { setIsLoading(false); } }; if (isLoading) { return (

Loading...

); } if (hasZelleInfo === false) { return (

Zelle Information Required

Before submitting a reimbursement request, you need to provide your Zelle information. This is required for processing your reimbursement payments.

); } const handleAddReceipt = async (receiptData: ReceiptFormData) => { try { const pb = auth.getPocketBase(); const userId = pb.authStore.model?.id; if (!userId) { // Silently return without error when on dashboard page if (window.location.pathname.includes('/dashboard')) { return; } toast.error('User not authenticated'); throw new Error('User not authenticated'); } // Create receipt record const formData = new FormData(); formData.append('file', receiptData.file); formData.append('created_by', userId); formData.append('itemized_expenses', JSON.stringify(receiptData.itemized_expenses)); formData.append('tax', receiptData.tax.toString()); formData.append('date', new Date(receiptData.date).toISOString()); formData.append('location_name', receiptData.location_name); formData.append('location_address', receiptData.location_address); formData.append('notes', receiptData.notes); const response = await pb.collection('receipts').create(formData); // Sync the receipts collection to update IndexedDB const dataSync = DataSyncService.getInstance(); await dataSync.syncCollection(Collections.RECEIPTS); // Add receipt to state setReceipts(prev => [...prev, { ...receiptData, id: response.id }]); // Update total amount const totalAmount = receiptData.itemized_expenses.reduce((sum, item) => sum + item.amount, 0) + receiptData.tax; setRequest(prev => ({ ...prev, total_amount: prev.total_amount + totalAmount, receipts: [...prev.receipts, response.id] })); setShowReceiptForm(false); toast.success('Receipt added successfully'); } catch (error) { console.error('Error creating receipt:', error); toast.error('Failed to add receipt'); setError('Failed to add receipt. Please try again.'); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (isSubmitting) return; if (!request.title.trim()) { toast.error('Title is required'); setError('Title is required'); return; } if (!request.payment_method) { toast.error('Payment method is required'); setError('Payment method is required'); return; } if (receipts.length === 0) { toast.error('At least one receipt is required'); setError('At least one receipt is required'); return; } setIsSubmitting(true); setError(''); try { const pb = auth.getPocketBase(); const userId = pb.authStore.model?.id; if (!userId) { // Silently return without error when on dashboard page if (window.location.pathname.includes('/dashboard')) { setIsSubmitting(false); return; } throw new Error('User not authenticated'); } // Create reimbursement record const formData = new FormData(); formData.append('title', request.title); formData.append('total_amount', request.total_amount.toString()); formData.append('date_of_purchase', new Date(request.date_of_purchase).toISOString()); formData.append('payment_method', request.payment_method); formData.append('status', 'submitted'); formData.append('submitted_by', userId); formData.append('additional_info', request.additional_info); formData.append('receipts', JSON.stringify(request.receipts)); formData.append('department', request.department); // Create the reimbursement record const newReimbursement = await pb.collection('reimbursement').create(formData); // Sync the reimbursements collection to update IndexedDB const dataSync = DataSyncService.getInstance(); // Force sync with specific filter to ensure the new record is fetched await dataSync.syncCollection( Collections.REIMBURSEMENTS, `submitted_by="${userId}"`, '-created', 'audit_notes' ); // Verify the new record is in IndexedDB const syncedData = await dataSync.getData( Collections.REIMBURSEMENTS, true, // Force sync again to be sure `id="${newReimbursement.id}"` ); if (syncedData.length === 0) { console.warn('New reimbursement not found in IndexedDB after sync, forcing another sync'); // Try one more time with a slight delay setTimeout(async () => { await dataSync.syncCollection(Collections.REIMBURSEMENTS); }, 500); } // Reset form setRequest({ title: '', total_amount: 0, date_of_purchase: new Date().toISOString().split('T')[0], payment_method: '', status: 'submitted', additional_info: '', receipts: [], department: 'internal' }); setReceipts([]); setError(''); toast.success('🎉 Reimbursement request submitted successfully! Check "My Requests" to view it.', { duration: 5000, position: 'top-center', style: { background: '#10B981', color: '#FFFFFF', padding: '16px', borderRadius: '8px', } }); } catch (error) { console.error('Error submitting reimbursement request:', error); toast.error('Failed to submit reimbursement request. Please try again.'); } finally { setIsSubmitting(false); } }; return ( <>
{error && ( {error} )} {/* Title */}
setRequest(prev => ({ ...prev, title: e.target.value }))} required />
{/* Date of Purchase */}
setRequest(prev => ({ ...prev, date_of_purchase: e.target.value }))} required />
{/* Payment Method */}
{/* Department */}
{/* Additional Info */}