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