diff --git a/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx b/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx index b96855b..2f9d84b 100644 --- a/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx +++ b/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx @@ -713,6 +713,28 @@ const ASFundingTab: React.FC<{ request: ExtendedEventRequest }> = ({ request }) + {/* Copyable Invoice Format */} + +
+
+ +
+
+

Copyable Format

+

Copy formatted invoice data for easy sharing

+
+
+ +
+ +
+
+ {/* File Preview Modal */} = ({ request }) ); }; +// Component for copyable invoice format +const CopyableInvoiceFormat: React.FC<{ invoiceData: any }> = ({ invoiceData }) => { + const [copied, setCopied] = useState(false); + const [formattedText, setFormattedText] = useState(''); + + useEffect(() => { + if (!invoiceData) { + setFormattedText('No invoice data available'); + return; + } + + try { + // Parse invoice data if it's a string + let parsedInvoice = null; + + if (typeof invoiceData === 'string') { + try { + parsedInvoice = JSON.parse(invoiceData); + } catch (e) { + console.error('Failed to parse invoice data string:', e); + setFormattedText('Invalid invoice data format'); + return; + } + } else if (typeof invoiceData === 'object' && invoiceData !== null) { + parsedInvoice = invoiceData; + } else { + setFormattedText('No structured invoice data available'); + return; + } + + // Extract items array + let items = []; + if (parsedInvoice.items && Array.isArray(parsedInvoice.items)) { + items = parsedInvoice.items; + } else if (Array.isArray(parsedInvoice)) { + items = parsedInvoice; + } else if (parsedInvoice.items && typeof parsedInvoice.items === 'object') { + items = [parsedInvoice.items]; // Wrap single item in array + } else { + // Try to find any array in the object + for (const key in parsedInvoice) { + if (Array.isArray(parsedInvoice[key])) { + items = parsedInvoice[key]; + break; + } + } + } + + // If we still don't have items, check if the object itself looks like an item + if (items.length === 0 && (parsedInvoice.item || parsedInvoice.description || parsedInvoice.name)) { + items = [parsedInvoice]; + } + + // Format the items into the required string format + const formattedItems = items.map((item: any) => { + const quantity = parseFloat(item?.quantity || 1); + const itemName = typeof item?.item === 'object' + ? JSON.stringify(item.item) + : (item?.item || item?.description || item?.name || 'N/A'); + const unitPrice = parseFloat(item?.unit_price || item?.unitPrice || item?.price || 0); + + return `${quantity} ${itemName} x${unitPrice.toFixed(2)} each`; + }).join(' | '); + + // Get tax, tip and total + const tax = parseFloat(parsedInvoice.tax || parsedInvoice.taxAmount || 0); + const tip = parseFloat(parsedInvoice.tip || parsedInvoice.tipAmount || 0); + const total = parseFloat(parsedInvoice.total || 0) || + items.reduce((sum: number, item: any) => { + const quantity = parseFloat(item?.quantity || 1); + const price = parseFloat(item?.unit_price || item?.unitPrice || item?.price || 0); + return sum + (quantity * price); + }, 0) + tax + tip; + + // Get vendor/location + const location = parsedInvoice.vendor || parsedInvoice.location || 'Unknown Vendor'; + + // Build the final formatted string + let result = formattedItems; + + if (tax > 0) { + result += ` | Tax = ${tax.toFixed(2)}`; + } + + if (tip > 0) { + result += ` | Tip = ${tip.toFixed(2)}`; + } + + result += ` | Total = ${total.toFixed(2)} from ${location}`; + + setFormattedText(result); + } catch (error) { + console.error('Error formatting invoice data:', error); + setFormattedText('Error formatting invoice data'); + } + }, [invoiceData]); + + const copyToClipboard = () => { + navigator.clipboard.writeText(formattedText) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + toast.success('Copied to clipboard!'); + }) + .catch(err => { + console.error('Failed to copy text: ', err); + toast.error('Failed to copy text'); + }); + }; + + return ( +
+
+
+ + +
+
+ {formattedText} +
+

+ Format: N_1 {'{item_1}'} x{'{cost_1}'} each | N_2 {'{item_2}'} x{'{cost_2}'} each | Tax = {'{tax}'} | Tip = {'{tip}'} | Total = {'{total}'} from {'{location}'} +

+
+
+ ); +}; + // Separate component for invoice table const InvoiceTable: React.FC<{ invoiceData: any, expectedAttendance?: number }> = ({ invoiceData, expectedAttendance }) => { // If no invoice data is provided, show a message diff --git a/tailwind.config.mjs b/tailwind.config.mjs index 1c81a27..1695dc4 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -37,6 +37,38 @@ export default { "radial-gradient(circle at 0% 0%, var(--tw-gradient-stops))", }, }, + daisyui: { + themes: [ + { + light: { + primary: "#06659d", + secondary: "#4b92db", + accent: "#F3C135", + neutral: "#2a323c", + "base-100": "#ffffff", + "base-200": "#f8f9fa", + "base-300": "#e9ecef", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + dark: { + primary: "#88BFEC", + secondary: "#4b92db", + accent: "#F3C135", + neutral: "#191D24", + "base-100": "#0A0E1A", + "base-200": "#0d1324", + "base-300": "#1a2035", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + }, + ], + }, }, plugins: [ require("tailwindcss-motion"),