From cfd3d21da862e70e8dfb4244d5ad00942d320ccd Mon Sep 17 00:00:00 2001 From: chark1es Date: Wed, 19 Feb 2025 02:08:42 -0800 Subject: [PATCH] add reimbursement forms --- .../dashboard/reimbursement/ReceiptForm.tsx | 352 +++++++++++ .../reimbursement/ReimbursementForm.tsx | 576 ++++++++---------- .../reimbursement/ReimbursementList.tsx | 250 ++++++-- src/config/reimbursement.yaml | 14 + 4 files changed, 804 insertions(+), 388 deletions(-) create mode 100644 src/components/dashboard/reimbursement/ReceiptForm.tsx create mode 100644 src/config/reimbursement.yaml diff --git a/src/components/dashboard/reimbursement/ReceiptForm.tsx b/src/components/dashboard/reimbursement/ReceiptForm.tsx new file mode 100644 index 0000000..81a3015 --- /dev/null +++ b/src/components/dashboard/reimbursement/ReceiptForm.tsx @@ -0,0 +1,352 @@ +import React, { useState } from 'react'; +import { Icon } from '@iconify/react'; +import FilePreview from '../universal/FilePreview'; + +interface ExpenseItem { + description: string; + amount: number; + category: string; +} + +interface ReceiptFormData { + field: File; + itemized_expenses: ExpenseItem[]; + tax: number; + date: string; + location_name: string; + location_address: string; + notes: string; +} + +interface ReceiptFormProps { + onSubmit: (data: ReceiptFormData) => void; + onCancel: () => void; +} + +const EXPENSE_CATEGORIES = [ + 'Travel', + 'Meals', + 'Supplies', + 'Equipment', + 'Software', + 'Event Expenses', + 'Other' +]; + +export default function ReceiptForm({ onSubmit, onCancel }: ReceiptFormProps) { + const [file, setFile] = useState(null); + const [previewUrl, setPreviewUrl] = useState(''); + const [itemizedExpenses, setItemizedExpenses] = useState([ + { description: '', amount: 0, category: '' } + ]); + const [tax, setTax] = useState(0); + const [date, setDate] = useState(new Date().toISOString().split('T')[0]); + const [locationName, setLocationName] = useState(''); + const [locationAddress, setLocationAddress] = useState(''); + const [notes, setNotes] = useState(''); + const [error, setError] = useState(''); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const selectedFile = e.target.files[0]; + + // Validate file type + if (!selectedFile.type.match('image/*') && selectedFile.type !== 'application/pdf') { + setError('Only images and PDF files are allowed'); + return; + } + + // Validate file size (5MB limit) + if (selectedFile.size > 5 * 1024 * 1024) { + setError('File size must be less than 5MB'); + return; + } + + setFile(selectedFile); + setPreviewUrl(URL.createObjectURL(selectedFile)); + setError(''); + } + }; + + const addExpenseItem = () => { + setItemizedExpenses([...itemizedExpenses, { description: '', amount: 0, category: '' }]); + }; + + const removeExpenseItem = (index: number) => { + if (itemizedExpenses.length === 1) return; + setItemizedExpenses(itemizedExpenses.filter((_, i) => i !== index)); + }; + + const handleExpenseItemChange = (index: number, field: keyof ExpenseItem, value: string | number) => { + const newItems = [...itemizedExpenses]; + newItems[index] = { + ...newItems[index], + [field]: value + }; + setItemizedExpenses(newItems); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!file) { + setError('Please upload a receipt'); + return; + } + + if (!locationName.trim()) { + setError('Location name is required'); + return; + } + + if (!locationAddress.trim()) { + setError('Location address is required'); + return; + } + + if (itemizedExpenses.some(item => !item.description || !item.category || item.amount <= 0)) { + setError('All expense items must be filled out completely'); + return; + } + + onSubmit({ + field: file, + itemized_expenses: itemizedExpenses, + tax, + date, + location_name: locationName, + location_address: locationAddress, + notes + }); + }; + + return ( +
+ {/* Left side - Form */} +
+
+ {error && ( +
+ + {error} +
+ )} + + {/* File Upload */} +
+ + +
+ + {/* Date */} +
+ + setDate(e.target.value)} + required + /> +
+ + {/* Location Name */} +
+ + setLocationName(e.target.value)} + required + /> +
+ + {/* Location Address */} +
+ + setLocationAddress(e.target.value)} + required + /> +
+ + {/* Notes */} +
+ +