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 = () => {
- +
)} );