diff --git a/src/components/RefreshStatus.tsx b/src/components/RefreshStatus.tsx
new file mode 100644
index 0000000..aba446a
--- /dev/null
+++ b/src/components/RefreshStatus.tsx
@@ -0,0 +1,18 @@
+type RefreshStatusProps = {
+ isRefreshing: boolean;
+ label?: string;
+ className?: string;
+};
+
+const RefreshStatus = ({ isRefreshing, label = 'Atualizando dados', className = '' }: RefreshStatusProps) => {
+ if (!isRefreshing) return null;
+
+ return (
+
+ {label}
+
+
+ );
+};
+
+export default RefreshStatus;
diff --git a/src/index.css b/src/index.css
index c0c4170..221ffaa 100644
--- a/src/index.css
+++ b/src/index.css
@@ -102,6 +102,26 @@
}
}
+ .refresh-progress {
+ animation: refresh-progress-slide 1.05s ease-in-out infinite;
+ box-shadow: 0 0 12px color-mix(in srgb, var(--color-brand-primary) 38%, transparent);
+ }
+
+ .refreshing-content {
+ opacity: 0.72;
+ transition: opacity 180ms ease;
+ }
+
+ @keyframes refresh-progress-slide {
+ 0% {
+ transform: translateX(-120%);
+ }
+
+ 100% {
+ transform: translateX(320%);
+ }
+ }
+
.app-shell {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.018), rgba(255, 255, 255, 0) 24rem),
diff --git a/src/pages/ClientDetails.tsx b/src/pages/ClientDetails.tsx
index 33bfddd..27c8c7d 100644
--- a/src/pages/ClientDetails.tsx
+++ b/src/pages/ClientDetails.tsx
@@ -3,6 +3,7 @@ import { useParams, Link, useOutletContext } from 'react-router-dom';
import { ArrowLeft, User, Tag, Package, DollarSign, Clock, Phone, ChevronDown, ChevronLeft, ChevronRight, ShoppingBag, ReceiptText } from 'lucide-react';
import { AreaChart, Area, BarChart, Bar, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import DateRangePicker from '../components/DateRangePicker';
+import RefreshStatus from '../components/RefreshStatus';
import type { ClientDetailsAnalytics, DateRange, OrderData } from '../types';
import { fetchClientDetailsAnalytics } from '../dataService';
import { formatDisplayName, removeTrailingSellerId } from '../displayFormatters';
@@ -204,7 +205,7 @@ const ClientDetails = () => {
});
};
- if (isLoading) {
+ if (isLoading && !details) {
return ;
}
@@ -237,6 +238,7 @@ const ClientDetails = () => {
const paginatedOrders = groupedOrders.slice(startIndex, startIndex + ordersPerPage);
const hasWeekdayPattern = purchaseWeekdays.some(day => day.value > 0);
const hasHourPattern = purchaseHours.some(hour => hour.value > 0);
+ const isRefreshing = isLoading && Boolean(details);
return (
@@ -278,6 +280,9 @@ const ClientDetails = () => {
+
+
+
@@ -582,6 +587,7 @@ const ClientDetails = () => {
+
);
};
diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx
index e9f2ce7..67119d7 100644
--- a/src/pages/Clients.tsx
+++ b/src/pages/Clients.tsx
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom';
import { Search, ChevronRight, Filter, ChevronLeft, X } from 'lucide-react';
+import RefreshStatus from '../components/RefreshStatus';
import type { ClientAnalyticsItem, ClientFilterOptions, ClientMetadataFilters, DateRange, RfmAnalytics, RfmClient } from '../types';
import { fetchClientAnalytics, fetchClientFilterOptions, fetchRfmAnalytics, getCachedClientAnalytics, getCachedClientFilterOptions, getCachedRfmAnalytics } from '../dataService';
import { endOfLocalDay, formatDateParam, parseLocalDateInput, rangeForDay, rangeForLastDays, rangeForPreviousDay, startOfLocalDay } from '../dateRanges';
@@ -439,6 +440,7 @@ const Clients = () => {
(clientTypeFilter === 'all' ? 0 : 1) +
(sortBy === 'recent' ? 0 : 1);
const hasActiveFilters = activeFilterCount > 0;
+ const isRefreshing = isLoading && clientAnalytics.length > 0;
return (
@@ -637,10 +639,12 @@ const Clients = () => {
+
+
{isLoading && clientAnalytics.length === 0 ? (
) : (
-
+
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index 7b4722f..7576d8a 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -3,6 +3,7 @@ import { useOutletContext, useNavigate } from 'react-router-dom';
import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
import { DollarSign, ShoppingCart, TrendingUp } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker';
+import RefreshStatus from '../components/RefreshStatus';
import type { DashboardAnalytics, OrderData, DateRange } from '../types';
import { applyDashboardColors, buildDashboardMetrics } from '../analytics/dashboard';
import { fetchDashboardAnalytics } from '../dataService';
@@ -459,6 +460,7 @@ const Dashboard = () => {
void loadDashboardMetrics(dateRange, { force: true });
};
const shouldShowSkeleton = isMetricsLoading && !serverMetrics;
+ const isRefreshing = isMetricsLoading && Boolean(serverMetrics);
return (
@@ -476,10 +478,12 @@ const Dashboard = () => {
/>
+
+
{shouldShowSkeleton ? (
) : (
- <>
+
@@ -738,7 +742,7 @@ const Dashboard = () => {
- >
+
)}
);
diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx
index 66f337d..8d8ede0 100644
--- a/src/pages/ProductDetails.tsx
+++ b/src/pages/ProductDetails.tsx
@@ -3,6 +3,7 @@ import { useParams, Link, useOutletContext } from 'react-router-dom';
import { ArrowLeft, Package, DollarSign } from 'lucide-react';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import DateRangePicker from '../components/DateRangePicker';
+import RefreshStatus from '../components/RefreshStatus';
import type { DateRange, ProductDetailsAnalytics } from '../types';
import { fetchProductDetailsAnalytics } from '../dataService';
@@ -110,7 +111,7 @@ const ProductDetails = () => {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
};
- if (isLoading) {
+ if (isLoading && !details) {
return ;
}
@@ -124,6 +125,7 @@ const ProductDetails = () => {
}
const { productInfo, chartData, totalSold, totalRevenue } = details;
+ const isRefreshing = isLoading && Boolean(details);
return (
@@ -150,6 +152,9 @@ const ProductDetails = () => {
onChange={setDateRange}
/>
+
+
+
@@ -205,6 +210,7 @@ const ProductDetails = () => {
+
);
};
diff --git a/src/pages/Products.tsx b/src/pages/Products.tsx
index 4d8c53a..4d62688 100644
--- a/src/pages/Products.tsx
+++ b/src/pages/Products.tsx
@@ -1,7 +1,8 @@
import { useEffect, useMemo, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom';
-import { Search, Package, TrendingUp, ChevronLeft, ChevronRight, Download, Loader2 } from 'lucide-react';
+import { Search, Package, TrendingUp, ChevronLeft, ChevronRight, Download } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker';
+import RefreshStatus from '../components/RefreshStatus';
import type { DateRange, ProductAnalyticsItem } from '../types';
import { exportToCSV, fetchProductAnalytics } from '../dataService';
import type { ProductSummary } from '../analytics/products';
@@ -94,6 +95,7 @@ const Products = () => {
const totalPages = Math.ceil(productsData.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedData = productsData.slice(startIndex, startIndex + itemsPerPage);
+ const isRefreshing = isLoading && productAnalytics.length > 0;
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
@@ -150,17 +152,12 @@ const Products = () => {
- {isLoading && productAnalytics.length > 0 && (
-
-
- Atualizando produtos
-
- )}
+
{isLoading && productAnalytics.length === 0 ? (
) : (
-
+
diff --git a/src/pages/Rfm.tsx b/src/pages/Rfm.tsx
index 34a1055..754b763 100644
--- a/src/pages/Rfm.tsx
+++ b/src/pages/Rfm.tsx
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom';
import { ChevronLeft, ChevronRight, Download, Filter, Search, Users } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker';
+import RefreshStatus from '../components/RefreshStatus';
import { exportToCSV, fetchRfmAnalytics, getCachedRfmAnalytics } from '../dataService';
import type { DateRange, RfmAnalytics, RfmClient, RfmSegment } from '../types';
@@ -451,6 +452,7 @@ const Rfm = () => {
exportToCSV(exportData, `rfv_${new Date().toISOString().split('T')[0]}.csv`);
};
const shouldShowSkeleton = isLoading && !analytics;
+ const isRefreshing = isLoading && Boolean(analytics);
return (
@@ -471,10 +473,12 @@ const Rfm = () => {
+
+
{shouldShowSkeleton ? (
) : (
- <>
+
@@ -804,7 +808,7 @@ const Rfm = () => {
- >
+
)}
);