perf: avoid blocking page render on data refresh
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
import { Outlet, Link, useLocation } from 'react-router-dom';
|
import { Outlet, Link, useLocation } from 'react-router-dom';
|
||||||
import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut, Megaphone } from 'lucide-react';
|
import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut, Megaphone } from 'lucide-react';
|
||||||
import type { DateRange, OrderData, StockData } from '../types';
|
import type { DateRange, OrderData, StockData } from '../types';
|
||||||
@@ -32,19 +32,22 @@ const Layout = () => {
|
|||||||
return saved ? Number(saved) : 0;
|
return saved ? Number(saved) : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadData = async (showLoading = false) => {
|
const loadData = useCallback(async (showLoading = false) => {
|
||||||
if (showLoading) setIsLoading(true);
|
if (showLoading) setIsLoading(true);
|
||||||
|
try {
|
||||||
const [data, stock] = await Promise.all([fetchData(), fetchStock()]);
|
const [data, stock] = await Promise.all([fetchData(), fetchStock()]);
|
||||||
setOrdersData(data);
|
setOrdersData(data);
|
||||||
setStockData(stock);
|
setStockData(stock);
|
||||||
|
} finally {
|
||||||
if (showLoading) setIsLoading(false);
|
if (showLoading) setIsLoading(false);
|
||||||
};
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// The dashboard has to fetch its initial server state after mount.
|
// The dashboard fetches its initial server state after mount without blocking route render.
|
||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
void loadData(true);
|
void loadData(false).finally(() => setIsLoading(false));
|
||||||
}, []);
|
}, [loadData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refreshInterval === 0) return;
|
if (refreshInterval === 0) return;
|
||||||
@@ -54,7 +57,7 @@ const Layout = () => {
|
|||||||
}, refreshInterval);
|
}, refreshInterval);
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [refreshInterval]);
|
}, [loadData, refreshInterval]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString());
|
localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString());
|
||||||
@@ -146,13 +149,13 @@ const Layout = () => {
|
|||||||
|
|
||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
<div className="flex-1 overflow-y-auto p-8 relative">
|
<div className="flex-1 overflow-y-auto p-8 relative">
|
||||||
{isLoading ? (
|
{isLoading && (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="absolute right-8 top-8 z-10 flex items-center gap-2 rounded-xl border border-dark-border bg-dark-card px-3 py-2 text-sm font-semibold text-dark-muted shadow-sm">
|
||||||
<Loader2 className="w-8 h-8 text-brand-primary animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin text-brand-primary" />
|
||||||
|
Atualizando dados
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<Outlet context={{ dateRange, setDateRange, ordersData, stockData, refreshInterval, setRefreshInterval, loadData }} />
|
|
||||||
)}
|
)}
|
||||||
|
<Outlet context={{ dateRange, setDateRange, ordersData, stockData, isDataLoading: isLoading, refreshInterval, setRefreshInterval, loadData }} />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ const ClientDetails = () => {
|
|||||||
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
|
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!groupedOrders.length && ordersData.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-zinc-500 dark:text-dark-muted font-medium">Carregando cliente...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!groupedOrders.length) {
|
if (!groupedOrders.length) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ const ProductDetails = () => {
|
|||||||
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
|
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!productInfo && ordersData.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-zinc-500 dark:text-dark-muted font-medium">Carregando produto...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!productInfo) {
|
if (!productInfo) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
|
|||||||
Reference in New Issue
Block a user