mirror of
https://github.com/tiennm99/exchange-rate-export.git
synced 2026-10-11 12:18:57 +00:00
Merge branch 'refactor/source-cleanup-and-viewer-split'
History-preserving absorb: the branch's content lands in this merge commit and is restored to the previous main tree in the next commit. The branch ref is deleted after this merge; its history stays reachable through the second parent.
This commit is contained in:
commit
32123499cb
9 files changed
+784
-1040
No files matched your search
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Read",
|
||||
"Edit",
|
||||
"Bash(git status *)",
|
||||
"Bash(git diff *)",
|
||||
"Bash(git log *)",
|
||||
"Bash(git branch *)",
|
||||
"Bash(git checkout *)",
|
||||
"Bash(git add *)",
|
||||
"Bash(git commit *)",
|
||||
"Bash(git stash *)",
|
||||
"Bash(git merge *)",
|
||||
"Bash(git rebase *)",
|
||||
"Bash(git fetch *)",
|
||||
"Bash(git pull *)",
|
||||
"Bash(git show *)",
|
||||
"Bash(git blame *)",
|
||||
"Bash(ls *)",
|
||||
"Bash(cat *)",
|
||||
"Bash(find *)",
|
||||
"Bash(grep *)",
|
||||
"Bash(rg *)",
|
||||
"Bash(head *)",
|
||||
"Bash(tail *)",
|
||||
"Bash(wc *)",
|
||||
"Bash(sort *)",
|
||||
"Bash(uniq *)",
|
||||
"Bash(diff *)",
|
||||
"Bash(echo *)",
|
||||
"Bash(pwd)",
|
||||
"Bash(which *)",
|
||||
"Bash(mkdir *)",
|
||||
"Bash(cp *)",
|
||||
"Bash(mv *)",
|
||||
"Bash(touch *)",
|
||||
"Bash(cd *)",
|
||||
"Bash(npm run *)",
|
||||
"Bash(npm test *)",
|
||||
"Bash(npm install *)",
|
||||
"Bash(npx *)",
|
||||
"Bash(yarn *)",
|
||||
"Bash(pnpm *)",
|
||||
"Bash(gradle *)",
|
||||
"Bash(./gradlew *)",
|
||||
"Bash(go build *)",
|
||||
"Bash(go test *)",
|
||||
"Bash(go run *)",
|
||||
"Bash(go mod *)",
|
||||
"Bash(cargo *)",
|
||||
"Bash(make *)",
|
||||
"Bash(python *)",
|
||||
"Bash(pip *)",
|
||||
"Bash(node *)",
|
||||
"Bash(java *)",
|
||||
"Bash(javac *)",
|
||||
"Bash(sed *)",
|
||||
"Bash(awk *)",
|
||||
"Bash(jq *)",
|
||||
"Bash(tree *)",
|
||||
"Bash(date *)",
|
||||
"Bash(env *)",
|
||||
"Bash(printenv *)",
|
||||
"Bash(realpath *)",
|
||||
"Bash(dirname *)",
|
||||
"Bash(basename *)",
|
||||
"Bash(stat *)",
|
||||
"Bash(file *)",
|
||||
"Bash(xargs *)",
|
||||
"Bash(tee *)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(git push --force *)",
|
||||
"Bash(git push -f *)",
|
||||
"Bash(git push * --force *)",
|
||||
"Bash(git push * -f *)",
|
||||
"Bash(rm -rf /)",
|
||||
"Bash(rm -rf /*)",
|
||||
"Bash(rm -rf ~)",
|
||||
"Bash(rm -rf ~/*)",
|
||||
"Bash(curl * | bash *)",
|
||||
"Bash(curl * | sh *)",
|
||||
"Bash(wget * | bash *)",
|
||||
"Bash(wget * | sh *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
minor-and-patch:
|
||||
update-types: ["minor", "patch"]
|
||||
security:
|
||||
applies-to: security-updates
|
||||
patterns: ["*"]
|
||||
versioning-strategy: increase
|
||||
labels: ["dependencies", "security"]
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
@@ -1,3 +1,7 @@
|
||||
.claude/session-state
|
||||
|
||||
|
||||
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
|
||||
+11
-11
@@ -1,14 +1,14 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
{
|
||||
rules: {
|
||||
"react-hooks/set-state-in-effect": "off",
|
||||
},
|
||||
},
|
||||
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
|
||||
]);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||
|
||||
export default eslintConfig;
|
||||
+16
-12
@@ -10,24 +10,28 @@
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"@vercel/speed-insights": "^2.0.0",
|
||||
"axios": "^1.18.1",
|
||||
"date-fns": "^4.4.0",
|
||||
"next": "16.2.11",
|
||||
"react": "^19.2.7",
|
||||
"react-datepicker": "^9.1.0",
|
||||
"react-dom": "^19.2.7",
|
||||
"recharts": "^3.9.2",
|
||||
"write-excel-file": "^4.1.1"
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"axios": "^1.12.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"next": "16.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"recharts": "^3.8.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-config-next": "16.2.11",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"tailwindcss": "^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"pnpm": {
|
||||
"ignoredBuiltDependencies": ["sharp", "unrs-resolver"]
|
||||
}
|
||||
}
|
||||
Generated
+630
-979
File diff suppressed because it is too large.
Load diff
@@ -1,16 +1,3 @@
|
||||
overrides:
|
||||
'@babel/core': 7.29.6
|
||||
form-data: 4.0.6
|
||||
js-yaml: 4.3.0
|
||||
postcss: 8.5.18
|
||||
# next 16.2.11 declares sharp ^0.34.5, which excludes the patched 0.35.0, so
|
||||
# no upstream bump can reach the fix -- force it here.
|
||||
sharp: 0.35.0
|
||||
# Two independent major branches reach this package: minimatch@3 pulls 1.x,
|
||||
# minimatch@10 pulls 5.x. Each branch needs its own patched version.
|
||||
'brace-expansion@1': 1.1.16
|
||||
'brace-expansion@5': 5.0.7
|
||||
|
||||
allowBuilds:
|
||||
sharp: true
|
||||
unrs-resolver: true
|
||||
@@ -1,20 +0,0 @@
|
||||
export function rowsToCsv(rows) {
|
||||
return `${rows.map((row) => row.map(formatCsvCell).join(",")).join("\r\n")}\r\n`;
|
||||
}
|
||||
|
||||
export function downloadTextFile(text, filename, type) {
|
||||
const blob = new Blob([text], { type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function formatCsvCell(value) {
|
||||
const text = value == null ? "" : String(value);
|
||||
return /[",\r\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import { useState, useEffect, useCallback } from "react";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { format, subDays, differenceInCalendarDays } from "date-fns";
|
||||
import writeExcelFile from "write-excel-file/browser";
|
||||
import * as XLSX from "xlsx";
|
||||
import { LoadingSpinner, ErrorMessage, EmptyState } from "./exchange-rate-status";
|
||||
import { RateTable, ComparisonTable } from "./exchange-rate-table";
|
||||
import { RateTrendChart } from "./exchange-rate-chart";
|
||||
import { SunIcon, MoonIcon, SystemIcon, FetchIcon, ExcelIcon, CsvIcon } from "./exchange-rate-icons";
|
||||
import { loadFromUrl, loadSavedSettings, saveSettings, applyTheme } from "./exchange-rate-storage";
|
||||
import { CURRENCY_OPTIONS, DATE_PRESETS, getActivePreset, THEME_CYCLE, THEME_LABELS } from "./exchange-rate-constants";
|
||||
import { downloadTextFile, rowsToCsv } from "./exchange-rate-export-files";
|
||||
|
||||
export default function ExchangeRateViewer() {
|
||||
const [startDate, setStartDate] = useState(() => subDays(new Date(), 7));
|
||||
@@ -136,14 +135,27 @@ export default function ExchangeRateViewer() {
|
||||
|
||||
const baseFilename = `exchange_rates_${compareMode ? "compare" : selectedBank}_${selectedCurrency}_${format(new Date(), "yyyy-MM-dd")}`;
|
||||
|
||||
const handleExportExcel = async () => {
|
||||
const handleExportExcel = () => {
|
||||
if (results.length === 0) return;
|
||||
await writeExcelFile(getExportRows(), { sheet: "Exchange Rates" }).toFile(`${baseFilename}.xlsx`);
|
||||
const ws = XLSX.utils.aoa_to_sheet(getExportRows());
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, "Exchange Rates");
|
||||
XLSX.writeFile(wb, `${baseFilename}.xlsx`);
|
||||
};
|
||||
|
||||
const handleExportCsv = () => {
|
||||
if (results.length === 0) return;
|
||||
downloadTextFile(rowsToCsv(getExportRows()), `${baseFilename}.csv`, "text/csv;charset=utf-8;");
|
||||
const ws = XLSX.utils.aoa_to_sheet(getExportRows());
|
||||
const csv = XLSX.utils.sheet_to_csv(ws);
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${baseFilename}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const activePreset = getActivePreset(startDate, endDate);
|
||||
|
||||
Reference in new issue
Block a user