Skip to content

Commit edbe150

Browse files
committed
Add dashboard panel loading animation and live data refresh
1 parent 1845e46 commit edbe150

5 files changed

Lines changed: 96 additions & 10 deletions

File tree

apps/ingestion/src/dashboard.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ import axios from "axios";
55
const VATSIM_EVENT_URL = "https://my.vatsim.net/api/v2/events/latest";
66
const VATSIM_EVENT_INTERVAL = 60 * 60 * 1000;
77
const VATSIM_HISTORY_WINDOW = 24 * 60 * 60 * 1000;
8+
const VATSIM_HISTORY_INTERVAL = 5 * 60 * 1000;
9+
10+
let lastHistoryUpdateTimestamp: Date | null = null;
811

912
export async function updateDashboardData(vatsimData: VatsimData, controllers: ControllerLong[]): Promise<void> {
1013
updateVatsimEvents();
1114

1215
const stats = getDashboardStats(vatsimData, controllers);
1316
rdsSetSingle("dashboard:stats", stats);
1417

18+
if (lastHistoryUpdateTimestamp && Date.now() - lastHistoryUpdateTimestamp.getTime() < VATSIM_HISTORY_INTERVAL) {
19+
return;
20+
}
21+
lastHistoryUpdateTimestamp = new Date();
1522
const historyItem = { pilots: vatsimData.pilots.length, controllers: controllers.length };
1623
rdsSetRingStorage("dashboard:history", historyItem, VATSIM_HISTORY_WINDOW);
1724
}

apps/web/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ async function fetchDashboardData(): Promise<DashboardData | null> {
1414
export default async function Page() {
1515
const dashboardData = await fetchDashboardData();
1616
if (!dashboardData) return <div className="info-panel error">Failed to load dashboard data.</div>;
17-
return <DashboardPanel data={dashboardData} />;
17+
return <DashboardPanel initialData={dashboardData} />;
1818
}

apps/web/components/Panels/BasePanel.css

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
max-height: calc(100% - 6rem);
77
display: flex;
88
flex-flow: column;
9-
transition: ease left 0.2s;
9+
transition: ease left 0.3s;
1010
background: rgba(255, 255, 255, 0.5);
1111
backdrop-filter: blur(5px);
1212
border: 1px solid var(--color-border);
@@ -17,6 +17,60 @@
1717
left: -20rem;
1818
}
1919

20+
#panel-loader-wrapper {
21+
position: absolute;
22+
left: 0;
23+
right: 0;
24+
bottom: 0;
25+
top: 0;
26+
display: flex;
27+
justify-content: center;
28+
align-items: center;
29+
background-color: var(--color-blue);
30+
z-index: 2;
31+
}
32+
33+
#panel-loader {
34+
width: 48px;
35+
height: 48px;
36+
border-radius: 50%;
37+
position: relative;
38+
animation: rotate 1s linear infinite;
39+
}
40+
#panel-loader::before {
41+
content: "";
42+
box-sizing: border-box;
43+
position: absolute;
44+
inset: 0px;
45+
border-radius: 50%;
46+
border: 5px solid #fff;
47+
animation: prixClipFix 2s linear infinite;
48+
}
49+
50+
@keyframes rotate {
51+
100% {
52+
transform: rotate(360deg);
53+
}
54+
}
55+
56+
@keyframes prixClipFix {
57+
0% {
58+
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0);
59+
}
60+
25% {
61+
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0);
62+
}
63+
50% {
64+
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);
65+
}
66+
75% {
67+
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%);
68+
}
69+
100% {
70+
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0);
71+
}
72+
}
73+
2074
.panel-container {
2175
display: flex;
2276
background-color: white;

apps/web/components/Panels/BasePanel.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { useEffect, useRef, useState } from "react";
66

77
export default function BasePanel({ children }: { children: React.ReactNode }) {
88
const pathname = usePathname();
9-
const [rendered, setRendered] = useState<React.ReactNode>(children);
109
const [open, setOpen] = useState(true);
1110
const prevPath = useRef<string | null>(null);
1211

@@ -19,14 +18,24 @@ export default function BasePanel({ children }: { children: React.ReactNode }) {
1918
if (prevPath.current === type) return;
2019

2120
setOpen(false);
22-
const t = setTimeout(() => {
23-
setRendered(children);
21+
const openTimeout = setTimeout(() => {
2422
setOpen(true);
2523
prevPath.current = type;
26-
}, 200);
24+
}, 300);
2725

28-
return () => clearTimeout(t);
29-
}, [children, pathname]);
26+
return () => {
27+
clearTimeout(openTimeout);
28+
};
29+
}, [pathname]);
3030

31-
return <div className={`panel${open ? "" : " hide"}`}>{rendered}</div>;
31+
return (
32+
<div className={`panel${open ? "" : " hide"}`}>
33+
{!open && (
34+
<div id="panel-loader-wrapper">
35+
<div id="panel-loader"></div>
36+
</div>
37+
)}
38+
{children}
39+
</div>
40+
);
3241
}

apps/web/components/Panels/Dashboard/DashboardPanel.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ function getStoredOpenSections(): string[] {
2828
return [];
2929
}
3030

31-
export default function DashboardPanel({ data }: { data: DashboardData }) {
31+
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";
32+
33+
export default function DashboardPanel({ initialData }: { initialData: DashboardData }) {
34+
const [data, setData] = useState<DashboardData>(initialData);
3235
const historyRef = useRef<HTMLDivElement>(null);
3336
const statsRef = useRef<HTMLDivElement>(null);
3437
const eventsRef = useRef<HTMLDivElement>(null);
@@ -42,6 +45,19 @@ export default function DashboardPanel({ data }: { data: DashboardData }) {
4245

4346
useEffect(() => {
4447
setOpenSection(getStoredOpenSections());
48+
const fetchInterval = setInterval(async () => {
49+
const newData = fetch(`${BASE_URL}/data/dashboard`, { cache: "no-store" })
50+
.then((res) => {
51+
if (!res.ok) return null;
52+
return res.json();
53+
})
54+
.catch(() => null);
55+
if (newData) {
56+
setData(await newData);
57+
}
58+
}, 60000);
59+
60+
return () => clearInterval(fetchInterval);
4561
}, []);
4662

4763
useEffect(() => {

0 commit comments

Comments
 (0)