added erik format

This commit is contained in:
chark1es 2025-03-31 14:16:15 -07:00
parent ee67e0678e
commit c0200a72a1
2 changed files with 198 additions and 0 deletions

View file

@ -713,6 +713,28 @@ const ASFundingTab: React.FC<{ request: ExtendedEventRequest }> = ({ request })
</div>
</motion.div>
{/* Copyable Invoice Format */}
<motion.div
className="bg-base-100/10 p-5 rounded-lg border border-base-100/10"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="bg-info/20 p-3 rounded-full">
<Icon icon="mdi:content-copy" className="h-6 w-6 text-info" />
</div>
<div>
<h3 className="text-lg font-semibold">Copyable Format</h3>
<p className="text-sm text-gray-400">Copy formatted invoice data for easy sharing</p>
</div>
</div>
<div className="mt-4">
<CopyableInvoiceFormat invoiceData={invoiceData} />
</div>
</motion.div>
{/* File Preview Modal */}
<FilePreviewModal
isOpen={isPreviewModalOpen}
@ -726,6 +748,150 @@ const ASFundingTab: React.FC<{ request: ExtendedEventRequest }> = ({ request })
);
};
// Component for copyable invoice format
const CopyableInvoiceFormat: React.FC<{ invoiceData: any }> = ({ invoiceData }) => {
const [copied, setCopied] = useState(false);
const [formattedText, setFormattedText] = useState<string>('');
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 (
<div className="space-y-4">
<div className="bg-base-200/30 p-4 rounded-lg">
<div className="flex justify-between items-start mb-2">
<label className="text-sm font-medium text-gray-400">Formatted Invoice Data</label>
<button
onClick={copyToClipboard}
className="btn btn-sm btn-primary gap-2"
disabled={!formattedText || formattedText.includes('No') || formattedText.includes('Error')}
>
{copied ? (
<>
<Icon icon="mdi:check" className="h-4 w-4" />
Copied!
</>
) : (
<>
<Icon icon="mdi:content-copy" className="h-4 w-4" />
Copy
</>
)}
</button>
</div>
<div className="bg-base-300/50 p-3 rounded-lg mt-2 whitespace-pre-wrap break-words text-sm">
{formattedText}
</div>
<p className="text-xs text-gray-400 mt-2">
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}'}
</p>
</div>
</div>
);
};
// Separate component for invoice table
const InvoiceTable: React.FC<{ invoiceData: any, expectedAttendance?: number }> = ({ invoiceData, expectedAttendance }) => {
// If no invoice data is provided, show a message

View file

@ -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"),