feat: Invoice Editor improvements and code cleanup
Major Invoice Editor updates: - ✅ Fixed tripled scrollbar issue by removing unnecessary overflow classes - ✅ Implemented dynamic currency system with JSON data loading - ✅ Fixed F4 PDF generation error with proper paper size handling - ✅ Added proper padding to Total section matching table headers - ✅ Removed print functionality (users can print from PDF download) - ✅ Streamlined preview toolbar: Back, Size selector, Download PDF - ✅ Fixed all ESLint warnings and errors - ✅ Removed console.log statements across codebase for cleaner production - ✅ Added border-top to Total section for better visual consistency - ✅ Improved print CSS and removed JSX warnings Additional improvements: - Added currencies.json to public folder for proper HTTP access - Enhanced MinimalTemplate with better spacing and layout - Clean build with no warnings or errors - Updated release notes with new features
This commit is contained in:
@@ -1,32 +1,41 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Table,
|
||||
Database,
|
||||
Download,
|
||||
Upload,
|
||||
FileText,
|
||||
Search,
|
||||
Plus,
|
||||
X,
|
||||
Braces,
|
||||
Code,
|
||||
Eye,
|
||||
Trash2,
|
||||
ArrowUpDown,
|
||||
Edit3,
|
||||
Globe,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
BrushCleaning,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import ToolLayout from "../components/ToolLayout";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Plus, Upload, FileText, Globe, Download, X, Table, Trash2, Database, Braces, Code, Eye, Minimize2, Maximize2, Search, ArrowUpDown, AlertTriangle, Edit3 } from 'lucide-react';
|
||||
import ToolLayout from '../components/ToolLayout';
|
||||
import CodeEditor from '../components/CodeEditor';
|
||||
import StructuredEditor from "../components/StructuredEditor";
|
||||
import Papa from "papaparse";
|
||||
|
||||
// Hook to detect dark mode
|
||||
const useDarkMode = () => {
|
||||
const [isDark, setIsDark] = useState(() => {
|
||||
return document.documentElement.classList.contains('dark');
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver(() => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return isDark;
|
||||
};
|
||||
|
||||
const TableEditor = () => {
|
||||
const isDark = useDarkMode();
|
||||
const [data, setData] = useState([]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
|
||||
// Sync table data to localStorage for navigation guard
|
||||
useEffect(() => {
|
||||
localStorage.setItem('tableEditorData', JSON.stringify(data));
|
||||
}, [data]);
|
||||
const [inputText, setInputText] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -498,7 +507,7 @@ const TableEditor = () => {
|
||||
|
||||
|
||||
const processedValues = values.map((val) => {
|
||||
val = val.trim();
|
||||
val = String(val).trim();
|
||||
// Remove quotes and handle NULL
|
||||
if (val === "NULL") return "";
|
||||
if (val.startsWith("'") && val.endsWith("'")) {
|
||||
@@ -1608,7 +1617,7 @@ const TableEditor = () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (hasNumber && values.every((val) => !isNaN(val) && val.trim() !== "")) {
|
||||
if (hasNumber && values.every((val) => !isNaN(val) && String(val).trim() !== "")) {
|
||||
if (allIntegers) {
|
||||
const maxVal = Math.max(...values.map((v) => Math.abs(Number(v))));
|
||||
if (maxVal < 128)
|
||||
@@ -1783,13 +1792,13 @@ const TableEditor = () => {
|
||||
icon={Table}
|
||||
>
|
||||
{/* Input Section with Tabs */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden mb-6">
|
||||
<div className="w-full bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden mb-4 sm:mb-6">
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700 overflow-x-auto scrollbar-hide">
|
||||
<div className="flex min-w-max">
|
||||
<button
|
||||
onClick={() => handleTabChange("create")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
activeTab === "create"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -1800,7 +1809,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange("url")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
activeTab === "url"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -1811,7 +1820,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange("paste")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
activeTab === "paste"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -1822,7 +1831,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleTabChange("upload")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
activeTab === "upload"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -1997,11 +2006,13 @@ const TableEditor = () => {
|
||||
|
||||
{activeTab === "paste" && (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
<CodeEditor
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onChange={(value) => setInputText(value)}
|
||||
language="javascript"
|
||||
placeholder="Paste CSV, TSV, JSON, or SQL INSERT statements here..."
|
||||
className="tool-input h-32 resize-none"
|
||||
height="128px"
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center text-sm text-gray-600 dark:text-gray-400">
|
||||
@@ -2055,10 +2066,10 @@ const TableEditor = () => {
|
||||
|
||||
{data.length > 0 && (
|
||||
<div
|
||||
className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden ${
|
||||
className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 min-w-0 ${
|
||||
isTableFullscreen
|
||||
? "fixed inset-0 z-50 rounded-none border-0 shadow-none"
|
||||
: ""
|
||||
? "fixed inset-0 z-50 rounded-none border-0 shadow-none overflow-hidden"
|
||||
: "overflow-x-auto"
|
||||
}`}
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -2103,7 +2114,7 @@ const TableEditor = () => {
|
||||
onClick={clearData}
|
||||
className="flex items-center gap-2 px-3 py-2 text-sm font-medium transition-colors border-l border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
<BrushCleaning className="h-4 w-4" />
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Clear All</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2141,8 +2152,8 @@ const TableEditor = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table Body - Edge to Edge */}
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Table Body - edge to edge */}
|
||||
<div className="flex flex-col h-full min-w-0">
|
||||
{/* Controls */}
|
||||
<div className="px-4 py-3 flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 border-b border-gray-200 dark:border-gray-700">
|
||||
{/* Search Bar */}
|
||||
@@ -2215,7 +2226,8 @@ const TableEditor = () => {
|
||||
|
||||
{/* Table */}
|
||||
<div
|
||||
className={`overflow-auto ${isTableFullscreen ? "max-h-[calc(100vh-200px)]" : "max-h-[500px]"}`}
|
||||
className={`overflow-auto w-full ${isTableFullscreen ? "max-h-[calc(100vh-200px)]" : "max-h-[500px]"}`}
|
||||
style={{ maxWidth: '100%' }}
|
||||
>
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
|
||||
@@ -2616,7 +2628,7 @@ const TableEditor = () => {
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setExportTab("json")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
exportTab === "json"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -2627,7 +2639,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setExportTab("csv")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
exportTab === "csv"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -2638,7 +2650,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setExportTab("tsv")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
exportTab === "tsv"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -2649,7 +2661,7 @@ const TableEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setExportTab("sql")}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
||||
className={`flex items-center gap-1 sm:gap-2 px-3 sm:px-4 py-3 text-sm font-medium transition-colors whitespace-nowrap ${
|
||||
exportTab === "sql"
|
||||
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500"
|
||||
: "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
@@ -2664,10 +2676,12 @@ const TableEditor = () => {
|
||||
<div className="p-4">
|
||||
{exportTab === "json" && (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
<CodeEditor
|
||||
value={getExportData("json")}
|
||||
readOnly
|
||||
className="w-full h-64 font-mono text-sm border border-gray-300 dark:border-gray-600 rounded-md p-3 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||
language="json"
|
||||
readOnly={true}
|
||||
height="256px"
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -2718,10 +2732,12 @@ const TableEditor = () => {
|
||||
|
||||
{exportTab === "csv" && (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
<CodeEditor
|
||||
value={getExportData("csv")}
|
||||
readOnly
|
||||
className="w-full h-64 font-mono text-sm border border-gray-300 dark:border-gray-600 rounded-md p-3 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||
language="javascript"
|
||||
readOnly={true}
|
||||
height="256px"
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
@@ -2748,10 +2764,12 @@ const TableEditor = () => {
|
||||
|
||||
{exportTab === "tsv" && (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
<CodeEditor
|
||||
value={getExportData("tsv")}
|
||||
readOnly
|
||||
className="w-full h-64 font-mono text-sm border border-gray-300 dark:border-gray-600 rounded-md p-3 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||
language="javascript"
|
||||
readOnly={true}
|
||||
height="256px"
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
@@ -2821,10 +2839,12 @@ const TableEditor = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
<CodeEditor
|
||||
value={getExportData("sql")}
|
||||
readOnly
|
||||
className="w-full h-64 font-mono text-sm border border-gray-300 dark:border-gray-600 rounded-md p-3 bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||
language="javascript"
|
||||
readOnly={true}
|
||||
height="256px"
|
||||
theme={isDark ? 'dark' : 'light'}
|
||||
/>
|
||||
|
||||
{/* Intelligent Schema Analysis */}
|
||||
@@ -3162,7 +3182,7 @@ const ClearConfirmationModal = ({
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors flex items-center gap-2"
|
||||
>
|
||||
<BrushCleaning className="h-4 w-4" />
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Clear All Data
|
||||
</button>
|
||||
</div>
|
||||
@@ -3559,7 +3579,7 @@ const InputChangeConfirmationModal = ({
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-amber-600 hover:bg-amber-700 rounded-md transition-colors flex items-center gap-2"
|
||||
>
|
||||
<BrushCleaning className="h-4 w-4" />
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Switch & Clear Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user