diff --git a/src/App.css b/src/App.css
index 74b5e05..fe1bfcf 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,38 +1,7 @@
-.App {
- text-align: center;
+html {
+ background-color: #313340;
}
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+* {
+ font-family: 'Roboto Mono';
}
diff --git a/src/App.js b/src/App.js
index 3784575..3dbdb77 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,24 +1,118 @@
-import logo from './logo.svg';
+import { useState, useEffect, useMemo } from 'react';
import './App.css';
+import '@fontsource/roboto-mono';
+import Schedule from './components/schedule/Schedule';
+import Radar from './components/Radar';
+import { format, formatDistanceToNow } from 'date-fns';
+import Metar from './components/metar/Metar';
+
+const statuses = {
+ OPEN: 'ON TIME',
+};
+const days = [
+ 'Sunday',
+ 'Monday',
+ 'Tuesday',
+ 'Wednesday',
+ 'Thursday',
+ 'Friday',
+ 'Saturday',
+];
+const pad = n => String(n).padStart(2, '0');
+
+function formatUtcDate(date) {
+ return (
+ `${days[date.getUTCDay()]} ${pad(date.getUTCDate())}/${pad(date.getUTCMonth() + 1)}/${date.getUTCFullYear()} ` +
+ `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}Z`
+ );
+}
+
+const normaliseData = data => {
+ return data.map(d => {
+ const date = format(d.start, 'dd/MM');
+ const start = format(d.start, 'HH:mm');
+ const end = format(d.end, 'HH:mm');
+ const status = statuses[d.status] ?? d.status;
+
+ return {
+ ...d,
+ date,
+ start,
+ end,
+ status,
+ };
+ });
+};
function App() {
+ const [time, setTime] = useState(new Date());
+ const [data, setData] = useState({});
+ const [dataUpdated, setDataUpdated] = useState(new Date());
+
+ useEffect(() => {
+ const timeInterval = setInterval(() => {
+ setTime(new Date());
+ }, 1000);
+
+ return () => clearInterval(timeInterval);
+ }, []);
+
+ const fetchData = async () => {
+ try {
+ const res = await fetch('https://mattfidd.rocks/veroDashboard.json');
+ const json = await res.json();
+ setData(normaliseData(json.slice(1)));
+ setDataUpdated(json[0].updatedAt);
+ } catch (err) {
+ console.error('Error fetching data:', err);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+
+ const interval = setInterval(() => {
+ fetchData();
+ }, 5 * 1000); // every 5 seconds
+
+ return () => clearInterval(interval);
+ }, []);
+
+ const formattedTime = useMemo(() => {
+ return formatUtcDate(time);
+ }, [time]);
+
return (
-
+ <>
+
+ {formattedTime}
+
+
+
+
+
+
+
+
+
+ Last updated {formatDistanceToNow(dataUpdated, { addSuffix: true })}
+
+ >
);
}
diff --git a/src/components/Radar.jsx b/src/components/Radar.jsx
new file mode 100644
index 0000000..5365d06
--- /dev/null
+++ b/src/components/Radar.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+
+const Radar = () => {
+ return (
+
+
+
+ );
+};
+
+export default Radar;
diff --git a/src/components/metar/Metar.jsx b/src/components/metar/Metar.jsx
new file mode 100644
index 0000000..7ad9a07
--- /dev/null
+++ b/src/components/metar/Metar.jsx
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react';
+
+const TafDisplay = ({ taf }) => {
+ const lines = taf
+ .split(/\s+(?=FM\d{6})/) // split on whitespace before "FM" lines
+ .map((line, index) => {line}
);
+
+ return {lines}
;
+};
+
+const Metar = ({ data }) => {
+ const [metar, setMetar] = useState('');
+ const [taf, setTaf] = useState('');
+
+ const fetchData = async () => {
+ try {
+ const [metarData, tafData] = await Promise.all([
+ fetch('https://mattfidd.rocks/proxy/metar').then(r => r.text()),
+ fetch('https://mattfidd.rocks/proxy/taf').then(r => r.text()),
+ ]);
+
+ setMetar(metarData);
+ setTaf(tafData);
+ } catch (err) {
+ console.error('Error fetching data:', err);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+
+ const interval = setInterval(() => {
+ fetchData();
+ }, 60 * 1000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+ );
+};
+
+export default Metar;
diff --git a/src/components/schedule/Schedule.jsx b/src/components/schedule/Schedule.jsx
new file mode 100644
index 0000000..0885ca4
--- /dev/null
+++ b/src/components/schedule/Schedule.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+
+import ScheduleRow from './ScheduleRow';
+import ScheduleCell from './ScheduleCell';
+
+const widths = [85, 85, 85, 85, 400, 150];
+
+const Schedule = ({ data }) => {
+ return (
+
+
+
+
+ Pilot
+
+
+ Date
+
+
+ Depart
+
+
+ Arrive
+
+
+ Destination
+
+
+
+ Gate
+
+
+ Status
+
+
+
+ {data?.length > 0 &&
+ data.map((row, i) => {
+ return (
+
+ {row.name}
+ {row.date}
+ {row.start}
+ {row.end}
+
+ {row.description?.length
+ ? row.description
+ : row.backseat
+ ? 'backseat'
+ : undefined}
+
+
+ {row.location}
+
+ {row.status}
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default Schedule;
diff --git a/src/components/schedule/ScheduleCell.jsx b/src/components/schedule/ScheduleCell.jsx
new file mode 100644
index 0000000..a25702c
--- /dev/null
+++ b/src/components/schedule/ScheduleCell.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+const ScheduleCell = ({
+ children,
+ width = '100',
+ color = '#E0A951',
+ end = false,
+} = {}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default ScheduleCell;
diff --git a/src/components/schedule/ScheduleRow.jsx b/src/components/schedule/ScheduleRow.jsx
new file mode 100644
index 0000000..f455f7f
--- /dev/null
+++ b/src/components/schedule/ScheduleRow.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+const bgs = {
+ true: 'none',
+ false: '#2F3136',
+};
+
+const ScheduleRow = ({ children, bg, border }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default ScheduleRow;
diff --git a/src/index.css b/src/index.css
index ec2585e..8464098 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,13 +1,13 @@
body {
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
+ font-family:
+ source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
diff --git a/src/index.js b/src/index.js
index d563c0f..9168dc4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,7 +8,7 @@ const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
+ ,
);
// If you want to start measuring performance in your app, pass a function