feat: add loading progress indicator with day count and progress bar

Show total days being fetched and an animated progress bar during
data loading to give users better feedback on long date ranges.
This commit is contained in:
tiennm99 committed 2026-04-04 13:25:14 +07:00
1 parent 5d600e0ab9
commit c1e37861d1
3 files changed
+12 -6

No files matched your search

+1 -1
View File
@@ -180,7 +180,7 @@ export async function POST(request) {
results.push(...chunkResults);
}
return Response.json({ data: results, total: dates.length });
return Response.json({ data: results, total: dates.length, chunks: Math.ceil(dates.length / PARALLEL_CHUNK_SIZE) });
} catch (error) {
console.error("Server error:", error);
return Response.json(
+3 -2
View File
@@ -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, isSameDay } from "date-fns";
import { format, subDays, startOfMonth, isSameDay, differenceInCalendarDays } from "date-fns";
import * as XLSX from "xlsx";
import { LoadingSpinner, ErrorMessage, EmptyState } from "./exchange-rate-status";
import { RateTable, ComparisonTable } from "./exchange-rate-table";
@@ -146,7 +146,8 @@ export default function ExchangeRateViewer() {
setIsLoading(true);
setResults([]);
setHasFetched(false);
setProgress(null);
const totalDays = differenceInCalendarDays(endDate, startDate) + 1;
setProgress({ current: 0, total: totalDays });
try {
if (compareMode) {
const [bidvData, tcbData] = await Promise.all([fetchBank("bidv"), fetchBank("tcb")]);
+8 -3
View File
@@ -5,11 +5,16 @@ import React from "react";
export function LoadingSpinner({ progress }) {
return (
<div className="flex items-center justify-center py-12">
<div className="flex flex-col items-center gap-3">
<div className="flex flex-col items-center gap-3 w-64">
<div className="w-8 h-8 border-3 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
<p className="text-[var(--muted)]">
{progress ? `Fetching ${progress.current}/${progress.total} days...` : "Fetching exchange rates..."}
<p className="text-[var(--muted)] text-sm">
{progress?.total ? `Fetching rates for ${progress.total} day${progress.total > 1 ? "s" : ""}...` : "Fetching exchange rates..."}
</p>
{progress?.total > 1 && (
<div className="w-full h-1.5 rounded-full bg-[var(--card-border)] overflow-hidden">
<div className="h-full bg-blue-500 rounded-full animate-pulse" style={{ width: "100%" }} />
</div>
)}
</div>
</div>
);