UI consistency & code quality improvements
- Standardized InvoiceEditor CreateNew tab styling to match ObjectEditor design - Fixed CodeMirror focus issues during editing by removing problematic dependencies - Removed copy button from mindmap view for cleaner interface - Resolved ESLint warnings in PostmanTreeTable.js with useCallback optimization - Enhanced PDF generation with dynamic style swapping for better print output - Updated commits.json with latest changes
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
Globe, AlertTriangle, ChevronUp, ChevronDown
|
||||
} from 'lucide-react';
|
||||
import ToolLayout from '../components/ToolLayout';
|
||||
import CodeMirrorEditor from '../components/CodeMirrorEditor';
|
||||
|
||||
const InvoiceEditor = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -276,12 +277,13 @@ const InvoiceEditor = () => {
|
||||
// Utility functions for formatting
|
||||
const formatNumber = (num, useThousandSeparator = invoiceData.settings?.thousandSeparator) => {
|
||||
if (!useThousandSeparator) return num.toString();
|
||||
return new Intl.NumberFormat('en-US').format(num);
|
||||
return new Intl.NumberFormat('en-US', { minimumFractionDigits: invoiceData.settings?.decimalDigits ?? 2 }).format(num);
|
||||
};
|
||||
|
||||
const formatCurrency = (amount, showSymbol = true) => {
|
||||
const currency = invoiceData.settings?.currency || { code: 'USD', symbol: '$' };
|
||||
const formattedAmount = formatNumber(amount.toFixed(2));
|
||||
const decimalDigits = invoiceData.settings?.decimalDigits ?? 2;
|
||||
const formattedAmount = formatNumber(amount.toFixed(decimalDigits));
|
||||
|
||||
if (showSymbol && currency.symbol) {
|
||||
return `${currency.symbol} ${formattedAmount}`;
|
||||
@@ -777,39 +779,55 @@ const InvoiceEditor = () => {
|
||||
{(activeTab !== 'create' || !createNewCompleted) && (
|
||||
<div className="p-4">
|
||||
{activeTab === 'create' && (
|
||||
<div className="space-y-4">
|
||||
<div className="text-center py-8">
|
||||
<FileText className="mx-auto h-12 w-12 text-gray-400 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||
Start Building Your Invoice
|
||||
</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400 mb-6">
|
||||
Choose how you'd like to begin creating your professional invoice
|
||||
</p>
|
||||
<div className="text-center py-12">
|
||||
<FileText className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
Start Building Your Invoice
|
||||
</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400 mb-8 max-w-md mx-auto">
|
||||
Choose how you'd like to begin creating your professional invoice
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (hasModifiedData()) {
|
||||
setPendingTabChange('create_empty');
|
||||
setShowInputChangeModal(true);
|
||||
} else {
|
||||
handleStartEmpty();
|
||||
}
|
||||
}}
|
||||
className="flex flex-col items-center p-6 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-blue-500 dark:hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors group"
|
||||
>
|
||||
<Plus className="h-8 w-8 text-gray-400 group-hover:text-blue-500 dark:group-hover:text-blue-400 mb-2" />
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400">
|
||||
Start Empty
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 text-center mt-1">
|
||||
Create a blank invoice
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl mx-auto">
|
||||
<button
|
||||
onClick={handleStartEmpty}
|
||||
className="group relative overflow-hidden rounded-lg border-2 border-dashed border-blue-300 dark:border-blue-600 p-6 hover:border-blue-400 dark:hover:border-blue-500 transition-colors"
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<Plus className="h-8 w-8 text-blue-500 mb-3 group-hover:scale-110 transition-transform" />
|
||||
<span className="text-sm font-medium text-blue-600 dark:text-blue-400">Start Empty</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 mt-1">Create a blank invoice</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleLoadSample}
|
||||
className="group relative overflow-hidden rounded-lg border-2 border-dashed border-green-300 dark:border-green-600 p-6 hover:border-green-400 dark:hover:border-green-500 transition-colors"
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<FileText className="h-8 w-8 text-green-500 mb-3 group-hover:scale-110 transition-transform" />
|
||||
<span className="text-sm font-medium text-green-600 dark:text-green-400">Load Sample</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 mt-1">Start with example invoice</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (hasModifiedData()) {
|
||||
setPendingTabChange('create_sample');
|
||||
setShowInputChangeModal(true);
|
||||
} else {
|
||||
handleLoadSample();
|
||||
}
|
||||
}}
|
||||
className="flex flex-col items-center p-6 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-green-500 dark:hover:border-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 transition-colors group"
|
||||
>
|
||||
<FileText className="h-8 w-8 text-gray-400 group-hover:text-green-500 dark:group-hover:text-green-400 mb-2" />
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 group-hover:text-green-600 dark:group-hover:text-green-400">
|
||||
Load Sample
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 text-center mt-1">
|
||||
Start with example invoice
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -864,11 +882,14 @@ const InvoiceEditor = () => {
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Paste Invoice JSON Data
|
||||
</label>
|
||||
<textarea
|
||||
<CodeMirrorEditor
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onChange={setInputText}
|
||||
placeholder="Paste your invoice JSON data here..."
|
||||
className="w-full h-32 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
language="json"
|
||||
maxLines={12}
|
||||
showToggle={true}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -1194,7 +1215,7 @@ const InvoiceEditor = () => {
|
||||
/>
|
||||
</td>
|
||||
<td className="px-3 py-3 text-center" style={{ width: 'auto' }}>
|
||||
<div className="relative flex justify-center items-center">
|
||||
<div className="relative flex justify-end items-center">
|
||||
<span className="text-gray-500 dark:text-gray-400 px-2 py-1 text-xs rounded-1 bg-gray-100 dark:bg-gray-900/20">{invoiceData.settings?.currency?.symbol || '$'}</span>
|
||||
<input
|
||||
type="text"
|
||||
@@ -1203,7 +1224,7 @@ const InvoiceEditor = () => {
|
||||
const numValue = parseFloat(e.target.value.replace(/,/g, '')) || 0;
|
||||
updateLineItem(item.id, 'rate', numValue);
|
||||
}}
|
||||
className="pl-2 pr-2 py-1 text-center border-0 bg-transparent focus:outline-none focus:ring-0 focus:bg-blue-50 dark:focus:bg-blue-900/20 rounded text-gray-900 dark:text-white transition-colors"
|
||||
className="pl-2 py-1 text-center border-0 bg-transparent focus:outline-none focus:ring-0 focus:bg-blue-50 dark:focus:bg-blue-900/20 rounded text-gray-900 dark:text-white transition-colors"
|
||||
style={{ width: `${Math.max(formatNumber(item.rate).length * 8 + 20, 40)}px` }}
|
||||
onFocus={(e) => {
|
||||
// Show raw number on focus
|
||||
@@ -2156,7 +2177,7 @@ const InvoiceSettingsModal = ({ invoiceData, currencies, onUpdateSettings, onClo
|
||||
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700/50'
|
||||
}`}
|
||||
>
|
||||
Payment Methods
|
||||
Payment
|
||||
{activeTab === 'payment' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-500 dark:bg-blue-400"></div>
|
||||
)}
|
||||
@@ -2264,6 +2285,27 @@ const InvoiceSettingsModal = ({ invoiceData, currencies, onUpdateSettings, onClo
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Decimal Digits */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||
Decimal Digits
|
||||
</label>
|
||||
<select
|
||||
value={invoiceData.settings?.decimalDigits || 2}
|
||||
onChange={(e) => onUpdateSettings('decimalDigits', parseInt(e.target.value))}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value={0}>0 (1000)</option>
|
||||
<option value={1}>1 (1000.0)</option>
|
||||
<option value={2}>2 (1000.00)</option>
|
||||
<option value={3}>3 (1000.000)</option>
|
||||
</select>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
Number of decimal places to display
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2601,6 +2643,46 @@ const InvoiceSettingsModal = ({ invoiceData, currencies, onUpdateSettings, onClo
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Payment Status */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||
Payment Status Stamp
|
||||
</label>
|
||||
<select
|
||||
value={invoiceData.settings?.paymentStatus || ''}
|
||||
onChange={(e) => onUpdateSettings('paymentStatus', e.target.value || null)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="">No Status Stamp</option>
|
||||
<option value="PAID">PAID</option>
|
||||
<option value="PARTIALLY PAID">PARTIALLY PAID</option>
|
||||
<option value="UNPAID">UNPAID</option>
|
||||
<option value="OVERDUE">OVERDUE</option>
|
||||
<option value="PENDING">PENDING</option>
|
||||
</select>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
Add a status stamp to your invoice PDF
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Date - only show when PAID is selected */}
|
||||
{invoiceData.settings?.paymentStatus === 'PAID' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||
Payment Date
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={invoiceData.settings?.paymentDate || ''}
|
||||
onChange={(e) => onUpdateSettings('paymentDate', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
Date when payment was received
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user