Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New BookingComponent #32

Merged
merged 15 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
966 changes: 227 additions & 739 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 2 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^4.1.0",
"@astrojs/tailwind": "^5.1.0",
"@maptiler/sdk": "^2.0.1",
"@nextui-org/react": "^2.2.10",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.1.0",
Expand All @@ -25,18 +24,14 @@
"astro-tooltips": "^0.6.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.0.8",
"lucide-react": "^0.468.0",
"moment": "^2.30.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-snowfall": "^2.2.0",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.3.3"
},
"devDependencies": {
"daisyui": "^4.10.2"
}
}
158 changes: 139 additions & 19 deletions src/components/BookingComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,177 @@
import React, { useEffect, useState } from "react";
import moment from "moment";
import { formatAsZulu, getPositionFromFrequency } from "../utils/BookingHelper";
import bookingType from "../components/BookingType";
import { ExternalLinkIcon } from './icons/ExternalLinkIcon';


const BookingComponent = () => {
const [ControlCenterBookings, setControlCenterBookings] = useState([]);
const [ControlCenterPositions, setControlCenterPositions] = useState([]);
const [VatsimNetworkSessions, setVatsimNetworkSessions] = useState([]);
const [dateArray, setDateArray] = useState([]);
const [loading, setLoading] = useState(true);

const acceptedFIRsRegex = /((?:BI|EF|EK|EN|ES)([A-Z][A-Z]_))\w+/i;
const mentorRegex = /((_X_)|(_X\d_)|(_M_))\w+/i;

/**
* Fetch data
*/
const fetchComponentData = async () => {
try {
let [ControlCenterBookingsData, ControlCenterPositionsData, VatsimNetworkSessionsData] = await Promise.all([
let [ControlCenterBookingsData, VatsimNetworkSessionsData] = await Promise.all([
fetch('https://cc.vatsim-scandinavia.org/api/bookings'),
fetch('https://cc.vatsim-scandinavia.org/api/positions'),
fetch('https://data.vatsim.net/v3/vatsim-data.json'),
]);

ControlCenterBookingsData = await ControlCenterBookingsData.json();
ControlCenterPositionsData = await ControlCenterPositionsData.json();
VatsimNetworkSessionsData = await VatsimNetworkSessionsData.json();

setControlCenterBookings(ControlCenterBookingsData);
setControlCenterPositions(ControlCenterPositionsData);
setVatsimNetworkSessions(VatsimNetworkSessionsData);
} catch (error) {
console.log('Error while fetching data:', error);
}
}

useEffect(() => {
const fetchData = async () => {
await Promise.all([
fetchComponentData(),
]);
setLoading(false);

/**
* Process data
*/
const processData = async () => {
const ControlCenterBookingsData = ControlCenterBookings.data;
const VatsimNetworkSessionsData = VatsimNetworkSessions.controllers;

const startDate = moment();
const endDate = moment().add(6, 'days');

// Create initial localDateArray using a Map for quick lookup
const dateMap = new Map();
for (let d = startDate; d <= endDate; d.add(1, 'days')) {
const dateString = d.format('YYYY-MM-DD');
dateMap.set(dateString, { date: dateString, data: [] });
}

// Insert CC bookings into localDateArray
ControlCenterBookingsData.forEach(booking => {
const bookingDate = moment(booking.time_start).format('YYYY-MM-DD');
const bookingData = dateMap.get(bookingDate);
if (bookingData) {
bookingData.data.push(booking);
}
});

// Match online sessions to bookings
const updatedSessions = [];
for (const session of VatsimNetworkSessionsData) {
if (acceptedFIRsRegex.test(session.callsign) && !mentorRegex.test(session.callsign)) {
const correctedCallsign = await getPositionFromFrequency(session.callsign, session.frequency);
const bookingData = dateMap.get(moment().format('YYYY-MM-DD'))?.data || [];

const existingBooking = bookingData.find(booking => booking.callsign === correctedCallsign);
if (existingBooking && moment.utc(existingBooking.time_start).isBefore(moment())) {
existingBooking.logon_time = session.logon_time;
} else {
updatedSessions.push({ ...session });
}
}
}

// Merge sessions into the correct date
updatedSessions.forEach(session => {
const dateKey = moment(session.logon_time).format('YYYY-MM-DD');
const dateData = dateMap.get(dateKey);
if (dateData) {
dateData.data.push(session);
}
});

fetchData();

const interval = setInterval(fetchData, 60000);
// Sorting
dateMap.forEach(dateData => {
dateData.data.sort((a, b) => {
const timeA = a.time_start || a.logon_time || Infinity;
const timeB = b.time_start || b.logon_time || Infinity;
return moment.utc(timeA).isBefore(moment.utc(timeB)) ? -1 : 1;
});
});

// Convert the dateMap back to an array for setting state
setDateArray(Array.from(dateMap.values()));
setLoading(false);
};


useEffect(() => {
// Fetches data and starts intercal of 1 min for data pulling.
const interval = setInterval(fetchComponentData, 60000);
fetchComponentData();
return () => clearInterval(interval);
}, []);

useEffect(() => {
// When data is fetched and states for CC Bookings and/or VATSIM updates.
if (!ControlCenterBookings.data || !VatsimNetworkSessions.controllers) { return } // Make sure there is data to be processed otherwise exit
processData();
}, [ControlCenterBookings, VatsimNetworkSessions])

return (
<table>
<tbody>
<tr></tr>
<table className="w-full h-full px-2">
<tbody className="h-full">
{loading ? (
<tr className="h-12">
<td colSpan={4}>
<iframe src="https://lottie.host/embed/e516525c-74c1-4db5-b2b2-bb135366e103/W8AodEgALN.json" className="w-full h-full" />
</td>
</tr>
) : (
<>
{dateArray.map((date, index) => (
<React.Fragment key={index}>
{date.data.length > 0 ? (
<>
<tr className="h-8 bg-snow dark:bg-secondary w-full font-bold text-black dark:text-white py-4 text-center">
<td colSpan={4}>{moment(date.date).format('dddd Do MMMM')}</td>
</tr>
{date.data.map((booking, index2) => (
<tr key={index2} className="h-6 even:bg-gray-50 odd:bg-white dark:even:bg-tertiary dark:odd:bg-black">
{booking.logon_time ? (
<td className="pl-[4px] text-[#447b68] font-bold">
<img className="circle" src="img/icons/circle-solid.svg" alt="" /> {booking.callsign}
</td>
) : (
<td className="pl-[4px]">
<img className="circle" src="img/icons/circle-regular.svg" alt="" /> {booking.callsign}
</td>
)}
<td className="px-[15px]">{bookingType(booking)}</td>
<td className="pl-[4px]">
{booking.time_start && !booking.logon_time
? formatAsZulu(booking.time_start)
: ""}
{booking.logon_time ? formatAsZulu(booking.logon_time) : ""}
</td>
<td className="pl-[4px]">{booking.time_end ? formatAsZulu(booking.time_end) : ""}</td>
</tr>
))}
</>
) : null}
</React.Fragment>
))}
{ControlCenterBookings.length < 1 ? null : (
<tr className="bg-snow dark:bg-secondary w-full font-bold text-black dark:text-white py-4 text-center h-12">
<td colSpan={4} className="underline hover:no-underline text-md">
<a href="https://cc.vatsim-scandinavia.org/booking" target="_blank" className="underline hover:no-underline">
See all bookings
</a>
<ExternalLinkIcon width="0.75rem" marginLeft="0.3rem" />
</td>
</tr>
)}
</>
)}
</tbody>
</table>
);
);

};

export default BookingComponent;
166 changes: 0 additions & 166 deletions src/components/BookingComponentv3.jsx

This file was deleted.

File renamed without changes.
Loading