Updated queries/endpoints, made admin page

This commit is contained in:
2023-11-20 16:48:20 -05:00
parent 319f64bc16
commit d45ed73eed
22 changed files with 735 additions and 127 deletions

View 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>
}

View 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>
}

View File

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

View File

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

View File

@@ -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'