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 ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ <> +

+ {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 ( +
+
+
+

{metar}

+ +
+
+
+ ); +}; + +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