From 4a7ff31898d78dc69bb5608982a044ee347e7451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 20 May 2026 11:39:14 -0300 Subject: [PATCH] perf: move date filtering to backend to massively reduce payload size and implement smart polling for instant updates without lag --- backend/index.js | 38 +++++++++++++++++++++++++++++++++++++- src/components/Layout.tsx | 35 +++++++++++++++++++++++++---------- src/dataService.ts | 29 +++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/backend/index.js b/backend/index.js index 46df592..324ba0c 100644 --- a/backend/index.js +++ b/backend/index.js @@ -92,11 +92,37 @@ const formatRow = (row) => ({ ID_Pedido: row.pedido_id }); +const parseOrderDate = (dateStr) => { + if (!dateStr) return new Date(0); + if (dateStr.includes('T')) return new Date(dateStr); + const parts = dateStr.split(/[-/]/); + if (parts.length === 3) { + if (parts[0].length === 4) { + return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2])); + } else { + return new Date(Number(parts[2]), Number(parts[1]) - 1, Number(parts[0])); + } + } + const fallback = new Date(dateStr); + return isNaN(fallback.getTime()) ? new Date(0) : fallback; +}; + // GET data (for the frontend) app.get('/api/data', verifyToken, async (req, res) => { try { const result = await pool.query('SELECT * FROM orders ORDER BY id DESC'); - const formattedData = result.rows.map(formatRow); + let rows = result.rows; + + if (req.query.start && req.query.end) { + const startDate = new Date(req.query.start); + const endDate = new Date(req.query.end); + rows = rows.filter(row => { + const orderDate = parseOrderDate(row.data_pedido); + return orderDate >= startDate && orderDate <= endDate; + }); + } + + const formattedData = rows.map(formatRow); res.json(formattedData); } catch (error) { console.error("Error fetching data:", error); @@ -104,6 +130,16 @@ app.get('/api/data', verifyToken, async (req, res) => { } }); +// Smart Polling endpoint to check for updates instantly +app.get('/api/last-update', verifyToken, async (req, res) => { + try { + const result = await pool.query('SELECT MAX(id) as max_id FROM orders'); + res.json({ max_id: result.rows[0].max_id || 0 }); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + // POST data (for n8n) - Protected by API_KEY internally or via middleware if needed // Leaving it as it was, checking API_KEY manually? Wait, the previous version didn't actually use 'authenticate' middleware on the POST! // Let's add the authenticate middleware to the POST endpoint. diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 923ec27..d2e7b74 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,8 +1,8 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Outlet, Link, useLocation } from 'react-router-dom'; import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, Loader2, LogOut } from 'lucide-react'; import type { DateRange, OrderData } from '../types'; -import { fetchData, logout } from '../dataService'; +import { fetchData, fetchLastUpdate, logout } from '../dataService'; const Layout = () => { const location = useLocation(); @@ -18,9 +18,10 @@ const Layout = () => { return { start: new Date(parsed.start), end: new Date(parsed.end) }; } catch (e) { console.error(e); } } - const end = new Date(); const start = new Date(); - start.setMonth(start.getMonth() - 1); + start.setHours(0,0,0,0); + const end = new Date(); + end.setHours(23,59,59,999); return { start, end }; }); @@ -30,27 +31,41 @@ const Layout = () => { const saved = localStorage.getItem('nexstar_refresh_interval'); return saved ? Number(saved) : 0; }); + + const lastUpdateIdRef = useRef(0); const loadData = async (showLoading = false) => { if (showLoading) setIsLoading(true); - const data = await fetchData(); + const data = await fetchData(dateRange.start, dateRange.end); setOrdersData(data); + + // Also update the last known ID so we don't double-fetch immediately + const maxId = await fetchLastUpdate(); + lastUpdateIdRef.current = maxId; + if (showLoading) setIsLoading(false); }; + // Fetch new data immediately whenever the selected date range changes useEffect(() => { loadData(true); - }, []); + }, [dateRange]); + // Smart Polling Logic useEffect(() => { if (refreshInterval === 0) return; - const intervalId = setInterval(() => { - loadData(false); - }, refreshInterval); + const checkUpdate = async () => { + const latestId = await fetchLastUpdate(); + if (latestId > lastUpdateIdRef.current) { + lastUpdateIdRef.current = latestId; + loadData(false); // Background fetch of the new payload + } + }; + const intervalId = setInterval(checkUpdate, refreshInterval); return () => clearInterval(intervalId); - }, [refreshInterval]); + }, [refreshInterval, dateRange]); useEffect(() => { localStorage.setItem('nexstar_refresh_interval', refreshInterval.toString()); diff --git a/src/dataService.ts b/src/dataService.ts index 4d0aeee..2b36e8c 100644 --- a/src/dataService.ts +++ b/src/dataService.ts @@ -33,10 +33,35 @@ export const isAuthenticated = (): boolean => { return !!localStorage.getItem('auth_token'); }; -export const fetchData = async (): Promise => { +export const fetchLastUpdate = async (): Promise => { try { const token = localStorage.getItem('auth_token'); - const response = await fetch(`${API_URL}/data`, { + const response = await fetch(`${API_URL}/last-update`, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + if (response.status === 401 || response.status === 403) { + logout(); + return 0; + } + if (!response.ok) return 0; + const data = await response.json(); + return data.max_id || 0; + } catch (error) { + return 0; + } +}; + +export const fetchData = async (start?: Date, end?: Date): Promise => { + try { + const token = localStorage.getItem('auth_token'); + let url = `${API_URL}/data`; + if (start && end) { + url += `?start=${start.toISOString()}&end=${end.toISOString()}`; + } + + const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` }