From ff55d0618e9375242d32adf687014222e682ec36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 29 Jun 2026 09:55:19 -0300 Subject: [PATCH] Add skeleton loaders to client and product pages --- src/pages/ClientDetails.tsx | 82 +++++++++++++++++++++++++++++++++--- src/pages/Clients.tsx | 42 ++++++++++++++++++ src/pages/ProductDetails.tsx | 43 ++++++++++++++++--- src/pages/Products.tsx | 37 +++++++++++++++- src/pages/Rfm.tsx | 31 ++++++++++---- 5 files changed, 216 insertions(+), 19 deletions(-) diff --git a/src/pages/ClientDetails.tsx b/src/pages/ClientDetails.tsx index e9092d8..33bfddd 100644 --- a/src/pages/ClientDetails.tsx +++ b/src/pages/ClientDetails.tsx @@ -61,6 +61,82 @@ const getOrderMetadata = (order: OrderData) => { ].filter(Boolean); }; +const ClientDetailsSkeleton = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ {[0, 1, 2, 3].map(item => ( +
+
+
+
+
+
+
+
+
+ ))} +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ {[0, 1].map(item => ( +
+
+
+ {[0, 1, 2, 3, 4, 5, 6].map(bar => ( +
+ ))} +
+
+ ))} +
+
+ +
+ {[0, 1, 2, 3].map(item => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+); + const ClientDetails = () => { const { clientToken } = useParams<{ clientToken: string }>(); const decodedClientToken = clientToken ? decodeURIComponent(clientToken) : ''; @@ -129,11 +205,7 @@ const ClientDetails = () => { }; if (isLoading) { - return ( -
-

Carregando cliente...

-
- ); + return ; } if (!details?.hasClient) { diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx index a7a24e5..e9f2ce7 100644 --- a/src/pages/Clients.tsx +++ b/src/pages/Clients.tsx @@ -133,6 +133,40 @@ const dateFilterPresets = [ const filterSelectClassName = "w-full h-9 bg-dark-input border border-dark-border text-dark-text text-sm rounded-lg px-3 focus:outline-none focus:border-brand-primary transition-colors cursor-pointer"; +const ClientsTableSkeleton = () => ( +
+
+
+ {[0, 1, 2, 3, 4, 5, 6, 7].map(item => ( +
+ ))} +
+
+
+ {[0, 1, 2, 3, 4, 5, 6, 7].map(row => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+
+
+
+
+); + const Clients = () => { const { dateRange, setDateRange, themeMode } = useOutletContext<{ dateRange: DateRange, @@ -152,6 +186,7 @@ const Clients = () => { const filterMenuRef = useRef(null); const [rfmAnalytics, setRfmAnalytics] = useState(initialRfmAnalytics || null); const [clientAnalytics, setClientAnalytics] = useState(initialClientAnalytics || []); + const [isLoading, setIsLoading] = useState(!initialClientAnalytics); // Pagination state const [currentPage, setCurrentPage] = useState(1); @@ -164,11 +199,13 @@ const Clients = () => { const cachedOptions = getCachedClientFilterOptions(dateRange); const cachedClients = getCachedClientAnalytics(dateRange, metadataFilters); const cachedRfm = getCachedRfmAnalytics(dateRange, metadataFilters); + const hasCachedClients = Boolean(cachedClients); if (isMounted) { if (cachedOptions) setFilterOptions(cachedOptions); if (cachedClients) setClientAnalytics(cachedClients); setRfmAnalytics(cachedRfm || null); + setIsLoading(!hasCachedClients); } const filterOptionsPromise = fetchClientFilterOptions(dateRange, { force: true }); @@ -179,6 +216,7 @@ const Clients = () => { if (isMounted) { setFilterOptions(options); setClientAnalytics(clientsData); + setIsLoading(false); } const rfmData = await rfmPromise; @@ -599,6 +637,9 @@ const Clients = () => {
+ {isLoading && clientAnalytics.length === 0 ? ( + + ) : (
@@ -710,6 +751,7 @@ const Clients = () => { + )} ); }; diff --git a/src/pages/ProductDetails.tsx b/src/pages/ProductDetails.tsx index 63aeb00..66f337d 100644 --- a/src/pages/ProductDetails.tsx +++ b/src/pages/ProductDetails.tsx @@ -32,6 +32,43 @@ const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { return null; }; +const ProductDetailsSkeleton = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ {[0, 1].map(item => ( +
+
+
+
+
+
+
+
+
+ ))} +
+ +
+
+
+
+
+); + const ProductDetails = () => { const { id } = useParams<{ id: string }>(); const { dateRange, setDateRange } = useOutletContext<{ @@ -74,11 +111,7 @@ const ProductDetails = () => { }; if (isLoading) { - return ( -
-

Carregando produto...

-
- ); + return ; } if (!details?.productInfo) { diff --git a/src/pages/Products.tsx b/src/pages/Products.tsx index fae105d..4d8c53a 100644 --- a/src/pages/Products.tsx +++ b/src/pages/Products.tsx @@ -6,6 +6,37 @@ import type { DateRange, ProductAnalyticsItem } from '../types'; import { exportToCSV, fetchProductAnalytics } from '../dataService'; import type { ProductSummary } from '../analytics/products'; +const ProductTableSkeleton = () => ( +
+
+
+ {[0, 1, 2, 3, 4, 5].map(item => ( +
+ ))} +
+
+
+ {[0, 1, 2, 3, 4, 5, 6, 7].map(row => ( +
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+
+
+
+
+); + const Products = () => { const { dateRange, setDateRange } = useOutletContext<{ dateRange: DateRange, @@ -119,13 +150,16 @@ const Products = () => {
- {isLoading && ( + {isLoading && productAnalytics.length > 0 && (
Atualizando produtos
)} + {isLoading && productAnalytics.length === 0 ? ( + + ) : (
@@ -217,6 +251,7 @@ const Products = () => { + )} ); }; diff --git a/src/pages/Rfm.tsx b/src/pages/Rfm.tsx index 8ff0f57..34a1055 100644 --- a/src/pages/Rfm.tsx +++ b/src/pages/Rfm.tsx @@ -189,14 +189,29 @@ const RfmSkeleton = () => ( {[0, 1, 2].map(row => (
{[0, 1, 2, 3].map(column => ( -
-
-
-
-
-
-
-
+
+ {column === 0 ? ( + <> +
+
+
+ + ) : ( + <> +
+
+
+
+
+
+
+ + )}
))}