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:
dwindown
2025-09-28 00:09:06 +07:00
parent b2850ea145
commit 04db088ff9
29 changed files with 5471 additions and 482 deletions

View File

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