Updated queries/endpoints, made admin page
This commit is contained in:
117
ui/src/components/Admin/AirportTablePanel.tsx
Normal file
117
ui/src/components/Admin/AirportTablePanel.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { getAirports, importAirports, removeAirport } from "@/api/airport";
|
||||
import { Airport } from "@/api/airport.types";
|
||||
import { Text, Button, Card, Group, Pagination, ScrollArea, Table, TextInput, rem } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CiSearch } from "react-icons/ci";
|
||||
|
||||
|
||||
export default function AirportTablePanel({ setAirport }: { setAirport: (airport: Airport) => void }) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [airports, setAirports] = useState<Airport[]>([]);
|
||||
|
||||
async function getAirportData() {
|
||||
const response = await getAirports({
|
||||
page,
|
||||
limit: 100
|
||||
});
|
||||
setAirports(response.data);
|
||||
setTotalPages(response.meta.pages);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAirportData();
|
||||
}, [page, search]);
|
||||
|
||||
function handleSearchChange(event: any) {
|
||||
setSearch(event.currentTarget.value);
|
||||
}
|
||||
|
||||
const rows = airports.map((airport) => (
|
||||
<Table.Tr
|
||||
key={airport.icao}
|
||||
onClick={() => {
|
||||
console.log('here');
|
||||
setAirport(airport);
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Table.Td>{airport.icao}</Table.Td>
|
||||
<Table.Td>{airport.full_name}</Table.Td>
|
||||
<Table.Td>{airport.category}</Table.Td>
|
||||
<Table.Td>{airport.continent}</Table.Td>
|
||||
<Table.Td>{airport.iso_country}</Table.Td>
|
||||
<Table.Td>{airport.iso_region}</Table.Td>
|
||||
<Table.Td>{airport.municipality}</Table.Td>
|
||||
<Table.Td>{airport.gps_code}</Table.Td>
|
||||
<Table.Td>{airport.iata_code}</Table.Td>
|
||||
<Table.Td>{airport.local_code}</Table.Td>
|
||||
<Table.Td>{airport.point.x}</Table.Td>
|
||||
<Table.Td>{airport.point.y}</Table.Td>
|
||||
</Table.Tr>
|
||||
))
|
||||
|
||||
return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder>
|
||||
<TextInput
|
||||
placeholder="Search by ICAO"
|
||||
mb="md"
|
||||
leftSection={<CiSearch style={{ width: rem(16), height: rem(16) }} />}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<Table.ScrollContainer minWidth={500} h={500}>
|
||||
<Table highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>ICAO</Table.Th>
|
||||
<Table.Th>Full Name</Table.Th>
|
||||
<Table.Th>Category</Table.Th>
|
||||
<Table.Th>Continent</Table.Th>
|
||||
<Table.Th>ISO Country</Table.Th>
|
||||
<Table.Th>ISO Region</Table.Th>
|
||||
<Table.Th>Municipality</Table.Th>
|
||||
<Table.Th>GPS Code</Table.Th>
|
||||
<Table.Th>IATA Code</Table.Th>
|
||||
<Table.Th>Local Code</Table.Th>
|
||||
<Table.Th>Latitude</Table.Th>
|
||||
<Table.Th>Longitude</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>{rows}</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
<Group>
|
||||
<Pagination value={page} total={totalPages} onChange={setPage} />
|
||||
<PanelButton onClick={async () => {
|
||||
await importAirports();
|
||||
await getAirportData();
|
||||
}}>
|
||||
Import
|
||||
</PanelButton>
|
||||
<PanelButton color={'red'} onClick={async () => {
|
||||
await removeAirport({});
|
||||
await getAirportData();
|
||||
}}>
|
||||
Remove All
|
||||
</PanelButton>
|
||||
</Group>
|
||||
</Card>
|
||||
}
|
||||
|
||||
function PanelButton({ children, color = 'blue', onClick }: {children: any, color?: string, onClick: () => Promise<void> }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
return <Button
|
||||
loading={loading}
|
||||
variant='light'
|
||||
color={color}
|
||||
mt={'md'}
|
||||
radius={'md'}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
onClick().then(() => setLoading(false));
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
}
|
||||
159
ui/src/components/Admin/CreateAirportPanel.tsx
Normal file
159
ui/src/components/Admin/CreateAirportPanel.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { Airport, AirportCategory } from "@/api/airport.types";
|
||||
import { Card, TextInput, Select, Group, Flex, Space, Button } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function CreateAirportPanel({ airport, setAirport } : { airport?: Airport, setAirport: (airport: Airport | undefined) => void }) {
|
||||
const form = useForm<Airport>({
|
||||
initialValues: {
|
||||
icao: '',
|
||||
category: AirportCategory.SMALL,
|
||||
full_name: '',
|
||||
elevation_ft: 0,
|
||||
continent: '',
|
||||
iso_country: '',
|
||||
iso_region: '',
|
||||
municipality: '',
|
||||
gps_code: '',
|
||||
iata_code: '',
|
||||
local_code: '',
|
||||
point: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
srid: 4326
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(airport);
|
||||
if (airport) {
|
||||
form.setValues(airport);
|
||||
}
|
||||
}, [airport]);
|
||||
|
||||
return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder>
|
||||
Create Airport
|
||||
<form onSubmit={form.onSubmit((values) => {
|
||||
if (airport) {
|
||||
console.log('update');
|
||||
} else {
|
||||
console.log('create');
|
||||
}
|
||||
})}>
|
||||
<TextInput
|
||||
required
|
||||
label='ICAO'
|
||||
placeholder='KHEF'
|
||||
{...form.getInputProps('icao')}
|
||||
/>
|
||||
<Select
|
||||
required
|
||||
label='Category'
|
||||
placeholder='Select category'
|
||||
data={[
|
||||
{ value: AirportCategory.SMALL, label: 'Small' },
|
||||
{ value: AirportCategory.MEDIUM, label: 'Medium' },
|
||||
{ value: AirportCategory.LARGE, label: 'Large' },
|
||||
]}
|
||||
{...form.getInputProps('category')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='Full Name'
|
||||
placeholder='Manassas Regional Airport/Harry P. Davis Field'
|
||||
{...form.getInputProps('full_name')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='Elevation (ft)'
|
||||
placeholder='192'
|
||||
{...form.getInputProps('elevation_ft')}
|
||||
/>
|
||||
<Group>
|
||||
<TextInput
|
||||
required
|
||||
label='Continent'
|
||||
placeholder='NA'
|
||||
{...form.getInputProps('continent')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='ISO Country'
|
||||
placeholder='US'
|
||||
{...form.getInputProps('iso_country')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='ISO Region'
|
||||
placeholder='US-VA'
|
||||
{...form.getInputProps('iso_region')}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
required
|
||||
label='Municipality'
|
||||
placeholder='Manassas'
|
||||
{...form.getInputProps('municipality')}
|
||||
/>
|
||||
<Group>
|
||||
<TextInput
|
||||
required
|
||||
label='GPS Code'
|
||||
placeholder='KHEF'
|
||||
{...form.getInputProps('gps_code')}
|
||||
/>
|
||||
<TextInput
|
||||
label='IATA Code'
|
||||
placeholder='MNZ'
|
||||
{...form.getInputProps('iata_code')}
|
||||
/>
|
||||
<TextInput
|
||||
label='Local Code'
|
||||
placeholder='HEF'
|
||||
{...form.getInputProps('local_code')}
|
||||
/>
|
||||
</Group>
|
||||
<Group>
|
||||
<TextInput
|
||||
required
|
||||
label='Latitude'
|
||||
placeholder='38.72140121'
|
||||
{...form.getInputProps('point.x')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='Longitude'
|
||||
placeholder='-77.51540375'
|
||||
{...form.getInputProps('point.y')}
|
||||
/>
|
||||
</Group>
|
||||
<Flex justify={'end'} mt={'sm'}>
|
||||
<Space mr={'sm'}>
|
||||
<Button
|
||||
type='submit'
|
||||
variant='light'
|
||||
color='blue'
|
||||
radius={'md'}
|
||||
>
|
||||
{airport ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
type='button'
|
||||
variant='light'
|
||||
color='red'
|
||||
radius={'md'}
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
setAirport(undefined);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
</form>
|
||||
</Card>
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getAirports } from '@/api/airport';
|
||||
import { getAirport, getAirports } from '@/api/airport';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Autocomplete, Avatar, Button, Card, FileButton, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
||||
import './header.css';
|
||||
@@ -14,6 +14,7 @@ import { getFavorites, getPicture, setPicture } from '@/api/users';
|
||||
import { useToggle } from '@mantine/hooks';
|
||||
import { HeaderModal } from './HeaderModal';
|
||||
import { favoritesState } from '@/state/user';
|
||||
import { coordinatesState, zoomState } from '@/state/map';
|
||||
|
||||
export default function Header() {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
@@ -24,6 +25,8 @@ export default function Header() {
|
||||
const [refreshId, setRefreshId] = useState<NodeJS.Timeout | undefined>(undefined);
|
||||
const [profilePicture, setProfilePicture] = useState<File | null>(null);
|
||||
const router = useRouter();
|
||||
const [coordinates, setCoordinates] = useRecoilState(coordinatesState);
|
||||
const [zoom, setZoom] = useRecoilState(zoomState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !Cookies.get('logged_in')) {
|
||||
@@ -50,7 +53,7 @@ export default function Header() {
|
||||
|
||||
async function onChange(value: string) {
|
||||
setSearchValue(value);
|
||||
const airportData = await getAirports({ filter: value });
|
||||
const airportData = await getAirports({ name: value, icao: value });
|
||||
setAirports(
|
||||
airportData.data.map((airport) => ({
|
||||
key: airport.icao,
|
||||
@@ -60,9 +63,11 @@ export default function Header() {
|
||||
);
|
||||
}
|
||||
|
||||
function onClick(value: string) {
|
||||
router.push(`/airport/${value}`);
|
||||
setSearchValue('');
|
||||
async function onClick(value: string) {
|
||||
const airport = await getAirport({ icao: value });
|
||||
if (airport) {
|
||||
setCoordinates({ lat: airport.data.point.y, lon: airport.data.point.x });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { getAirports } from '@/api/airport';
|
||||
import { Airport } from '@/api/airport.types';
|
||||
import { Airport, AirportOrderField } from '@/api/airport.types';
|
||||
import { getMetars } from '@/api/metar';
|
||||
import { DivIcon, LatLngBounds } from 'leaflet';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -9,19 +9,22 @@ import ReactDOMServer from 'react-dom/server';
|
||||
import { Marker, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet';
|
||||
import MetarModal from './MetarModal';
|
||||
import { Avatar, MantineProvider } from '@mantine/core';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { coordinatesState, zoomState } from '@/state/map';
|
||||
|
||||
export default function MapTiles() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [airports, setAirports] = useState<Airport[]>([]);
|
||||
const [selectedAirport, setSelectedAirport] = useState<Airport | undefined>();
|
||||
const coordinates = useRecoilValue(coordinatesState);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [zoomLevel, setZoomLevel] = useState(8);
|
||||
const [zoom, setZoom] = useRecoilState(zoomState);
|
||||
// const [dragging, setDragging] = useState(false);
|
||||
const map = useMap();
|
||||
|
||||
const mapEvents = useMapEvents({
|
||||
zoomend: async () => {
|
||||
setZoomLevel(mapEvents.getZoom());
|
||||
setZoom(mapEvents.getZoom());
|
||||
await updateAirports(mapEvents.getBounds());
|
||||
},
|
||||
movestart: () => {
|
||||
@@ -33,6 +36,10 @@ export default function MapTiles() {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
map.setView([coordinates.lat, coordinates.lon]);
|
||||
}, [coordinates]);
|
||||
|
||||
function handleOpen(airport: Airport) {
|
||||
setSelectedAirport(airport);
|
||||
setIsOpen(true);
|
||||
@@ -46,6 +53,8 @@ export default function MapTiles() {
|
||||
northEast: { lat: ne.lat, lon: ne.lng },
|
||||
southWest: { lat: sw.lat, lon: sw.lng }
|
||||
},
|
||||
order_field: AirportOrderField.CATEGORY,
|
||||
order_by: 'asc',
|
||||
limit: 100,
|
||||
page: 1
|
||||
});
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { MapContainer } from 'react-leaflet';
|
||||
import { MapContainer, useMap } from 'react-leaflet';
|
||||
import MapTiles from './MapTiles';
|
||||
import './metars.css';
|
||||
import { coordinatesState, zoomState } from '@/state/map';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export default function Map() {
|
||||
const coordinates = useRecoilValue(coordinatesState);
|
||||
const zoom = useRecoilValue(zoomState);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MapContainer
|
||||
center={[38.7209, -77.5133]}
|
||||
zoom={8}
|
||||
center={[coordinates.lat, coordinates.lon]}
|
||||
zoom={zoom}
|
||||
maxZoom={14} // Zoomed in
|
||||
minZoom={3} // Zoomed out
|
||||
id='map-container'
|
||||
|
||||
Reference in New Issue
Block a user