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:
dwindown
2025-09-28 17:14:54 +07:00
parent 9993614073
commit 78570f04f0
20 changed files with 712 additions and 395 deletions

View File

@@ -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();