feat: Enhanced release notes system, fixed invoice installments, and improved logo integration
- Updated release notes to use new JSON structure with individual commit timestamps - Removed hash display from release notes for cleaner UI - Fixed automatic recalculation of percentage-based installments in Invoice Editor and Preview - Integrated custom logo.svg in header and footer with cleaner styling - Moved all data files to /public/data/ for better organization - Cleaned up unused release data files and improved file structure
This commit is contained in:
@@ -20,6 +20,30 @@ const InvoicePreview = () => {
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [selectedTemplate] = useState('minimal');
|
||||
|
||||
// Calculate totals (same logic as InvoiceEditor)
|
||||
const calculateTotals = (items, discount = 0, fees = [], discounts = []) => {
|
||||
const subtotal = items.reduce((sum, item) => sum + (item.amount || 0), 0);
|
||||
|
||||
// Calculate total fees
|
||||
const totalFees = fees.reduce((sum, fee) => {
|
||||
const feeAmount = fee.type === 'percentage'
|
||||
? (subtotal * fee.value) / 100
|
||||
: fee.value;
|
||||
return sum + feeAmount;
|
||||
}, 0);
|
||||
|
||||
// Calculate total discounts
|
||||
const totalDiscounts = discounts.reduce((sum, discountItem) => {
|
||||
const discountAmount = discountItem.type === 'percentage'
|
||||
? (subtotal * discountItem.value) / 100
|
||||
: discountItem.value;
|
||||
return sum + discountAmount;
|
||||
}, 0);
|
||||
|
||||
const total = subtotal + totalFees - discount - totalDiscounts;
|
||||
return { subtotal, total };
|
||||
};
|
||||
|
||||
// Load invoice data from localStorage
|
||||
useEffect(() => {
|
||||
try {
|
||||
@@ -28,11 +52,41 @@ const InvoicePreview = () => {
|
||||
|
||||
if (savedInvoice) {
|
||||
const parsedInvoice = JSON.parse(savedInvoice);
|
||||
|
||||
// Recalculate totals and installments to ensure accuracy
|
||||
const { subtotal, total } = calculateTotals(
|
||||
parsedInvoice.items || [],
|
||||
parsedInvoice.discount || 0,
|
||||
parsedInvoice.fees || [],
|
||||
parsedInvoice.discounts || []
|
||||
);
|
||||
|
||||
// Update totals
|
||||
parsedInvoice.subtotal = subtotal;
|
||||
parsedInvoice.total = total;
|
||||
|
||||
// Recalculate installments if they exist
|
||||
if (parsedInvoice.paymentTerms?.installments?.length > 0) {
|
||||
const updatedInstallments = parsedInvoice.paymentTerms.installments.map(installment => {
|
||||
if (installment.type === 'percentage') {
|
||||
const baseAmount = parsedInvoice.paymentTerms?.type === 'downpayment'
|
||||
? total - (parsedInvoice.paymentTerms?.downPayment?.amount || 0)
|
||||
: total;
|
||||
const amount = (baseAmount * (installment.percentage || 0)) / 100;
|
||||
return { ...installment, amount };
|
||||
}
|
||||
return installment;
|
||||
});
|
||||
|
||||
parsedInvoice.paymentTerms = {
|
||||
...parsedInvoice.paymentTerms,
|
||||
installments: updatedInstallments
|
||||
};
|
||||
}
|
||||
|
||||
setInvoiceData(parsedInvoice);
|
||||
// Set page title with invoice number
|
||||
document.title = `Invoice Preview - ${parsedInvoice.invoiceNumber || 'Draft'} | DevTools`;
|
||||
} else {
|
||||
// No invoice data, redirect back to editor
|
||||
// No invoice data found, redirect to editor
|
||||
navigate('/invoice-editor');
|
||||
}
|
||||
|
||||
@@ -41,11 +95,10 @@ const InvoicePreview = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load invoice data:', error);
|
||||
navigate('/invoice-editor', { replace: true });
|
||||
navigate('/invoice-editor');
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
|
||||
// Format number with thousand separator
|
||||
const formatNumber = (num) => {
|
||||
if (!invoiceData?.settings?.thousandSeparator) return num.toString();
|
||||
|
||||
Reference in New Issue
Block a user