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:
dwindown
2025-09-28 23:30:44 +07:00
parent 78570f04f0
commit 68db19e076
13 changed files with 789 additions and 156 deletions

View File

@@ -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>