From 8c788b66acd49776ca6692083d162fc9fb67438e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Fri, 26 Jun 2026 11:41:39 -0300 Subject: [PATCH] Add dashboard and RFV skeleton loaders --- src/index.css | 27 ++++++++++ src/pages/Dashboard.tsx | 78 ++++++++++++++++++++++++++--- src/pages/Rfm.tsx | 108 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 199 insertions(+), 14 deletions(-) diff --git a/src/index.css b/src/index.css index 3a06ca1..c0c4170 100644 --- a/src/index.css +++ b/src/index.css @@ -75,6 +75,33 @@ } @layer components { + .skeleton { + position: relative; + overflow: hidden; + border-radius: 0.75rem; + background: color-mix(in srgb, var(--color-dark-input) 82%, var(--color-dark-text) 18%); + } + + .skeleton::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient( + 90deg, + transparent, + color-mix(in srgb, var(--color-dark-text) 14%, transparent), + transparent + ); + animation: skeleton-shimmer 1.45s ease-in-out infinite; + } + + @keyframes skeleton-shimmer { + 100% { + transform: translateX(100%); + } + } + .app-shell { background: linear-gradient(180deg, rgba(255, 255, 255, 0.018), rgba(255, 255, 255, 0) 24rem), diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index bc8d0a0..f2c1319 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useOutletContext, useNavigate } from 'react-router-dom'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, LabelList } from 'recharts'; -import { DollarSign, Loader2, ShoppingCart, TrendingUp } from 'lucide-react'; +import { DollarSign, ShoppingCart, TrendingUp } from 'lucide-react'; import DateRangePicker from '../components/DateRangePicker'; import type { DashboardAnalytics, OrderData, DateRange } from '../types'; import { applyDashboardColors, buildDashboardMetrics } from '../analytics/dashboard'; @@ -52,6 +52,69 @@ const kpiIconStyle = (color: string) => ({ color }); +const DashboardSkeleton = () => ( +
+
+ {[0, 1, 2].map(item => ( +
+
+
+
+
+
+
+
+
+ ))} +
+ +
+ {[0, 1].map(item => ( +
+
+
+ {[0, 1, 2, 3, 4, 5].map(row => ( +
+
+
+
+ ))} +
+
+ ))} +
+ +
+
+
+ {[0, 1, 2, 3, 4].map(row => ( +
+
+
+
+
+
+ ))} +
+
+ +
+ {[0, 1].map(item => ( +
+
+
+
+ {[0, 1, 2, 3, 4, 5, 6, 7].map(bar => ( +
+ ))} +
+
+
+ ))} +
+
+); + type ChartTooltipPayload = { value: number; name?: string; @@ -194,6 +257,7 @@ const Dashboard = () => { const handleManualRefresh = () => { void loadDashboardMetrics(dateRange, { force: true }); }; + const shouldShowSkeleton = isMetricsLoading && !serverMetrics; return (
@@ -211,12 +275,10 @@ const Dashboard = () => { />
- {isMetricsLoading && ( -
- - Atualizando indicadores -
- )} + {shouldShowSkeleton ? ( + + ) : ( + <>
@@ -444,6 +506,8 @@ const Dashboard = () => {
+ + )}
); }; diff --git a/src/pages/Rfm.tsx b/src/pages/Rfm.tsx index 86793bb..8ff0f57 100644 --- a/src/pages/Rfm.tsx +++ b/src/pages/Rfm.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Link, useOutletContext } from 'react-router-dom'; -import { ChevronLeft, ChevronRight, Download, Filter, Loader2, Search, Users } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Download, Filter, Search, Users } from 'lucide-react'; import DateRangePicker from '../components/DateRangePicker'; import { exportToCSV, fetchRfmAnalytics, getCachedRfmAnalytics } from '../dataService'; import type { DateRange, RfmAnalytics, RfmClient, RfmSegment } from '../types'; @@ -157,6 +157,99 @@ const ScoreBadge = ({ value }: { value: number }) => ( ); +const RfmSkeleton = () => ( +
+
+ {[0, 1, 2].map(item => ( +
+
+
+
+
+ ))} +
+ +
+
+
+
+
+
+
+
+
+
+
+ {[0, 1, 2, 3].map(item => ( +
+
+
+
+ ))} + {[0, 1, 2].map(row => ( +
+ {[0, 1, 2, 3].map(column => ( +
+
+
+
+
+
+
+
+
+ ))} +
+ ))} +
+
+
+ +
+
+
+ {[0, 1, 2, 3, 4, 5, 6, 7, 8].map(item => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+
+
+ +
+
+
+
+
+
+
+
+ {[0, 1, 2, 3, 4, 5, 6].map(item => ( +
+ ))} +
+
+ {[0, 1, 2, 3, 4, 5].map(row => ( +
+ {[0, 1, 2, 3, 4, 5, 6].map(column => ( +
+ ))} +
+ ))} +
+
+
+
+); + const Rfm = () => { const { dateRange, setDateRange, refreshInterval, setRefreshInterval, themeMode } = useOutletContext<{ dateRange: DateRange; @@ -342,6 +435,7 @@ const Rfm = () => { exportToCSV(exportData, `rfv_${new Date().toISOString().split('T')[0]}.csv`); }; + const shouldShowSkeleton = isLoading && !analytics; return (
@@ -362,12 +456,10 @@ const Rfm = () => {
- {isLoading && ( -
- - Atualizando RFV -
- )} + {shouldShowSkeleton ? ( + + ) : ( + <>
@@ -697,6 +789,8 @@ const Rfm = () => {
+ + )}
); };