diff --git a/src/app/components/ExchangeRateViewer.js b/src/app/components/ExchangeRateViewer.js index b8706c5..ca5b102 100644 --- a/src/app/components/ExchangeRateViewer.js +++ b/src/app/components/ExchangeRateViewer.js @@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback } from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; -import { format, subDays, startOfMonth } from "date-fns"; +import { format, subDays, startOfMonth, isSameDay } from "date-fns"; import * as XLSX from "xlsx"; import { LoadingSpinner, ErrorMessage, EmptyState } from "./exchange-rate-status"; import { RateTable, ComparisonTable } from "./exchange-rate-table"; @@ -49,11 +49,46 @@ const DATE_PRESETS = [ { label: "This month", start: () => startOfMonth(new Date()), end: () => new Date() }, ]; +function getActivePreset(startDate, endDate) { + if (!startDate || !endDate) return null; + for (const preset of DATE_PRESETS) { + if (isSameDay(startDate, preset.start()) && isSameDay(endDate, preset.end())) { + return preset.label; + } + } + return null; +} + +// Inline SVG icons (16x16) to avoid adding an icon library dependency +function FetchIcon() { + return ( + + ); +} + +function ExcelIcon() { + return ( + + ); +} + +function CsvIcon() { + return ( + + ); +} + export default function ExchangeRateViewer() { - const [startDate, setStartDate] = useState(() => loadSavedSettings()?.startDate || subDays(new Date(), 7)); - const [endDate, setEndDate] = useState(() => loadSavedSettings()?.endDate || new Date()); - const [selectedBank, setSelectedBank] = useState(() => loadSavedSettings()?.bank || "bidv"); - const [selectedCurrency, setSelectedCurrency] = useState(() => loadSavedSettings()?.currency || "USD"); + const [startDate, setStartDate] = useState(() => subDays(new Date(), 7)); + const [endDate, setEndDate] = useState(() => new Date()); + const [selectedBank, setSelectedBank] = useState("bidv"); + const [selectedCurrency, setSelectedCurrency] = useState("USD"); const [compareMode, setCompareMode] = useState(false); const [showChart, setShowChart] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -62,9 +97,23 @@ export default function ExchangeRateViewer() { const [hasFetched, setHasFetched] = useState(false); const [progress, setProgress] = useState(null); + // Restore saved settings on client mount only (avoids SSR hydration mismatch) + const [hydrated, setHydrated] = useState(false); useEffect(() => { + const saved = loadSavedSettings(); + if (saved) { + if (saved.bank) setSelectedBank(saved.bank); + if (saved.currency) setSelectedCurrency(saved.currency); + if (saved.startDate) setStartDate(saved.startDate); + if (saved.endDate) setEndDate(saved.endDate); + } + setHydrated(true); + }, []); + + useEffect(() => { + if (!hydrated) return; saveSettings(selectedBank, selectedCurrency, startDate, endDate); - }, [selectedBank, selectedCurrency, startDate, endDate]); + }, [selectedBank, selectedCurrency, startDate, endDate, hydrated]); const fetchBank = async (bank) => { const response = await fetch("/api/exchange-rates", { @@ -129,8 +178,8 @@ export default function ExchangeRateViewer() { }, [startDate, endDate, selectedBank, selectedCurrency, compareMode]); useEffect(() => { - handleFetch(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + if (hydrated) handleFetch(); + }, [hydrated]); // eslint-disable-line react-hooks/exhaustive-deps const getExportRows = () => { if (compareMode) { @@ -170,27 +219,23 @@ export default function ExchangeRateViewer() { URL.revokeObjectURL(url); }; - const inputStyle = { - background: "var(--input-bg)", - color: "var(--foreground)", - borderColor: "var(--input-border)", - }; + const activePreset = getActivePreset(startDate, endDate); return (

Exchange Rate Export

-

+

Fetch exchange rates from Vietnamese banks and export to CSV.

{ if (e.key === "Enter" && !isLoading) handleFetch(); }} >
+ {/* Row 1: Selectors and date pickers */}
@@ -199,8 +244,7 @@ export default function ExchangeRateViewer() { value={selectedBank} onChange={(e) => setSelectedBank(e.target.value)} disabled={compareMode} - className="border rounded-lg px-3 py-2 text-sm w-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500/30 disabled:opacity-50" - style={inputStyle} + className="border rounded-lg px-3 py-2 text-sm w-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500/30 disabled:opacity-50 bg-[var(--input-bg)] text-[var(--foreground)] border-[var(--input-border)]" > @@ -212,8 +256,7 @@ export default function ExchangeRateViewer() { id="currency" value={selectedCurrency} onChange={(e) => setSelectedCurrency(e.target.value)} - className="border rounded-lg px-3 py-2 text-sm w-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500/30" - style={inputStyle} + className="border rounded-lg px-3 py-2 text-sm w-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500/30 bg-[var(--input-bg)] text-[var(--foreground)] border-[var(--input-border)]" > {CURRENCY_OPTIONS.map((c) => ( @@ -247,22 +290,26 @@ export default function ExchangeRateViewer() {
-
-