Updated api endpoint structure, fixed issues, worked on admin page

This commit is contained in:
2023-11-21 11:00:01 -05:00
parent cb9db1f3ba
commit 2e048fb1a0
12 changed files with 326 additions and 137 deletions

View File

@@ -27,8 +27,7 @@ pub struct InsertAirport {
#[derive(Debug)] #[derive(Debug)]
pub struct QueryFilters { pub struct QueryFilters {
pub name: Option<String>, pub search: Option<String>,
pub icao: Option<String>,
pub bounds: Option<Polygon<Point>>, pub bounds: Option<Polygon<Point>>,
pub category: Option<String>, pub category: Option<String>,
pub order_field: Option<QueryOrderField>, pub order_field: Option<QueryOrderField>,
@@ -38,8 +37,7 @@ pub struct QueryFilters {
impl Default for QueryFilters { impl Default for QueryFilters {
fn default() -> Self { fn default() -> Self {
QueryFilters { QueryFilters {
name: None, search: None,
icao: None,
bounds: None, bounds: None,
category: None, category: None,
order_field: None, order_field: None,
@@ -131,16 +129,17 @@ impl QueryAirport {
if let Some(category) = &filters.category { if let Some(category) = &filters.category {
query = query.filter(airports::category.eq(category)); query = query.filter(airports::category.eq(category));
} }
if let Some(icao) = &filters.icao { if let Some(search) = &filters.search {
if let Some(name) = &filters.name { query = query.filter(
query = query.filter( airports::icao.ilike(format!("%{}%", search))
airports::icao.ilike(format!("%{}%", icao)).or( .or(airports::full_name.ilike(format!("%{}%", search)))
airports::full_name.ilike(format!("%{}%", name)) .or(airports::iso_country.ilike(format!("%{}%", search)))
) .or(airports::iso_region.ilike(format!("%{}%", search)))
) .or(airports::municipality.ilike(format!("%{}%", search)))
} else { .or(airports::gps_code.ilike(format!("%{}%", search)))
query = query.filter(airports::icao.ilike(format!("%{}%", icao))) .or(airports::iata_code.ilike(format!("%{}%", search)))
} .or(airports::local_code.ilike(format!("%{}%", search)))
)
} }
if let Some(order_by) = &filters.order_by { if let Some(order_by) = &filters.order_by {
@@ -193,16 +192,17 @@ impl QueryAirport {
if let Some(category) = &filters.category { if let Some(category) = &filters.category {
query = query.filter(airports::category.eq(category)); query = query.filter(airports::category.eq(category));
} }
if let Some(icao) = &filters.icao { if let Some(search) = &filters.search {
if let Some(name) = &filters.name { query = query.filter(
query = query.filter( airports::icao.ilike(format!("%{}%", search))
airports::icao.ilike(format!("%{}%", icao)).or( .or(airports::full_name.ilike(format!("%{}%", search)))
airports::full_name.ilike(format!("%{}%", name)) .or(airports::iso_country.ilike(format!("%{}%", search)))
) .or(airports::iso_region.ilike(format!("%{}%", search)))
) .or(airports::municipality.ilike(format!("%{}%", search)))
} else { .or(airports::gps_code.ilike(format!("%{}%", search)))
query = query.filter(airports::icao.ilike(format!("%{}%", icao))) .or(airports::iata_code.ilike(format!("%{}%", search)))
} .or(airports::local_code.ilike(format!("%{}%", search)))
)
} }
let count: i64 = query.get_result(&mut conn)?; let count: i64 = query.get_result(&mut conn)?;
@@ -224,19 +224,19 @@ impl QueryAirport {
Ok(airport) Ok(airport)
} }
pub fn update(id: i32, airport: InsertAirport) -> Result<Self, ServiceError> { pub fn update(icao: String, airport: InsertAirport) -> Result<Self, ServiceError> {
let mut conn = db::connection()?; let mut conn = db::connection()?;
let airport = diesel::update(airports::table) let airport = diesel::update(airports::table)
.filter(airports::id.eq(id)) .filter(airports::icao.eq(icao))
.set(airport) .set(airport)
.get_result(&mut conn)?; .get_result(&mut conn)?;
Ok(airport) Ok(airport)
} }
pub fn delete(id: Option<i32>) -> Result<usize, ServiceError> { pub fn delete(icao: Option<String>) -> Result<usize, ServiceError> {
let mut conn = db::connection()?; let mut conn = db::connection()?;
let res = match id { let res = match icao {
Some(id) => diesel::delete(airports::table.filter(airports::id.eq(id))).execute(&mut conn)?, Some(icao) => diesel::delete(airports::table.filter(airports::icao.eq(icao))).execute(&mut conn)?,
None => diesel::delete(airports::table).execute(&mut conn)? None => diesel::delete(airports::table).execute(&mut conn)?
}; };
Ok(res) Ok(res)

View File

@@ -8,8 +8,7 @@ use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct GetAllParameters { struct GetAllParameters {
name: Option<String>, search: Option<String>,
icao: Option<String>,
bounds: Option<String>, bounds: Option<String>,
category: Option<String>, category: Option<String>,
order_field: Option<String>, order_field: Option<String>,
@@ -18,7 +17,7 @@ struct GetAllParameters {
page: Option<i32> page: Option<i32>
} }
#[get("/import")] #[post("/import")]
async fn import(auth: JwtAuth) -> HttpResponse { async fn import(auth: JwtAuth) -> HttpResponse {
let _ = match verify_role(&auth, "admin") { let _ = match verify_role(&auth, "admin") {
Ok(_) => {}, Ok(_) => {},
@@ -31,12 +30,11 @@ async fn import(auth: JwtAuth) -> HttpResponse {
}) })
} }
#[get("/search")] #[get("")]
async fn get_all(req: HttpRequest) -> HttpResponse { async fn get_all(req: HttpRequest) -> HttpResponse {
let params = web::Query::<GetAllParameters>::from_query(req.query_string()).unwrap(); let params = web::Query::<GetAllParameters>::from_query(req.query_string()).unwrap();
let mut filters = QueryFilters::default(); let mut filters = QueryFilters::default();
filters.name = params.name.clone(); filters.search = params.search.clone();
filters.icao = params.icao.clone();
filters.category = params.category.clone(); filters.category = params.category.clone();
filters.bounds = match &params.bounds { filters.bounds = match &params.bounds {
Some(b) => { Some(b) => {
@@ -119,7 +117,7 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
} }
} }
#[get("/search/{icao}")] #[get("/{icao}")]
async fn get(icao: web::Path<String>) -> HttpResponse { async fn get(icao: web::Path<String>) -> HttpResponse {
match QueryAirport::find(icao.into_inner()) { match QueryAirport::find(icao.into_inner()) {
Ok(a) => HttpResponse::Ok().json(Response { Ok(a) => HttpResponse::Ok().json(Response {
@@ -133,7 +131,7 @@ async fn get(icao: web::Path<String>) -> HttpResponse {
} }
} }
#[post("/create")] #[post("")]
async fn create(airport: web::Json<InsertAirport>, auth: JwtAuth) -> HttpResponse { async fn create(airport: web::Json<InsertAirport>, auth: JwtAuth) -> HttpResponse {
let _ = match verify_role(&auth, "admin") { let _ = match verify_role(&auth, "admin") {
Ok(_) => {}, Ok(_) => {},
@@ -148,8 +146,8 @@ async fn create(airport: web::Json<InsertAirport>, auth: JwtAuth) -> HttpRespons
} }
} }
#[put("/update/{icao}")] #[put("/{icao}")]
async fn update(icao: web::Path<i32>, airport: web::Json<InsertAirport>, auth: JwtAuth) -> HttpResponse { async fn update(icao: web::Path<String>, airport: web::Json<InsertAirport>, auth: JwtAuth) -> HttpResponse {
let _ = match verify_role(&auth, "admin") { let _ = match verify_role(&auth, "admin") {
Ok(_) => {}, Ok(_) => {},
Err(err) => return ResponseError::error_response(&err) Err(err) => return ResponseError::error_response(&err)
@@ -163,8 +161,8 @@ async fn update(icao: web::Path<i32>, airport: web::Json<InsertAirport>, auth: J
} }
} }
#[delete("/remove")] #[delete("")]
async fn remove_all(auth: JwtAuth) -> HttpResponse { async fn delete_all(auth: JwtAuth) -> HttpResponse {
let _ = match verify_role(&auth, "admin") { let _ = match verify_role(&auth, "admin") {
Ok(_) => {}, Ok(_) => {},
Err(err) => return ResponseError::error_response(&err) Err(err) => return ResponseError::error_response(&err)
@@ -178,8 +176,8 @@ async fn remove_all(auth: JwtAuth) -> HttpResponse {
} }
} }
#[delete("/remove/{icao}")] #[delete("/{icao}")]
async fn remove(icao: web::Path<i32>, auth: JwtAuth) -> HttpResponse { async fn delete(icao: web::Path<String>, auth: JwtAuth) -> HttpResponse {
let _ = match verify_role(&auth, "admin") { let _ = match verify_role(&auth, "admin") {
Ok(_) => {}, Ok(_) => {},
Err(err) => return ResponseError::error_response(&err) Err(err) => return ResponseError::error_response(&err)
@@ -199,8 +197,8 @@ pub fn init_routes(config: &mut web::ServiceConfig) {
.service(get) .service(get)
.service(create) .service(create)
.service(update) .service(update)
.service(remove) .service(delete)
.service(remove_all) .service(delete_all)
.service(import) .service(import)
); );
} }

View File

@@ -275,18 +275,19 @@ impl Metar {
return insert_metars; return insert_metars;
} }
pub async fn get_all(icaos: String) -> Result<Vec<Self>, ServiceError> { pub async fn get_all(icao_string: String) -> Result<Vec<Self>, ServiceError> {
if icaos.is_empty() { if icao_string.is_empty() {
return Ok(vec![]); return Ok(vec![]);
} }
let station_icaos: Vec<&str> = icaos.split(',').collect(); let icaos: Vec<&str> = icao_string.split(",").collect();
let mut db_metars = match QueryMetar::get_all(&station_icaos) {
let mut db_metars = match QueryMetar::get_all(&icaos) {
Ok(m) => Self::from_query(m), Ok(m) => Self::from_query(m),
Err(err) => return Err(err) Err(err) => return Err(err)
}; };
let missing_icaos = Self::get_missing_metar_icaos(&db_metars, &station_icaos); let missing_icaos = Self::get_missing_metar_icaos(&db_metars, &icaos);
if missing_icaos.is_empty() { if missing_icaos.is_empty() {
return Ok(db_metars); return Ok(db_metars);
} }

View File

@@ -1,6 +1,6 @@
use crate::{error_handler::ServiceError, db::Metadata}; use crate::{error_handler::ServiceError, db::Metadata};
use crate::metars::Metar; use crate::metars::Metar;
use actix_web::{get, web, HttpResponse}; use actix_web::{get, web, HttpResponse, HttpRequest};
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -10,23 +10,35 @@ pub struct MetarsResponse {
pub meta: Metadata pub meta: Metadata
} }
#[get("metars/{ids}")] #[derive(Debug, Serialize, Deserialize)]
async fn get_all(ids: web::Path<String>) -> HttpResponse { struct GetAllParameters {
let airports = match web::block(|| Ok::<_, ServiceError>(async {Metar::get_all(ids.into_inner()).await})) icaos: Option<String>
.await }
.unwrap()
.unwrap() #[get("metars")]
.await { async fn get_all(req: HttpRequest) -> HttpResponse {
Ok(a) => a, let params = web::Query::<GetAllParameters>::from_query(req.query_string()).unwrap();
Err(err) => { let icao_option = params.icaos.clone();
error!("{}", err); let icao_string = match icao_option {
return err.to_http_response(); Some(i) => i,
} None => return HttpResponse::UnprocessableEntity().body("Missing icaos parameter")
}; };
HttpResponse::Ok().json(MetarsResponse {
data: airports, let airports = match web::block(|| Ok::<_, ServiceError>(async {Metar::get_all(icao_string).await}))
meta: Metadata { page: 0, limit: 0, pages: 0, total: 0 } .await
}) .unwrap()
.unwrap()
.await {
Ok(a) => a,
Err(err) => {
error!("{}", err);
return err.to_http_response();
}
};
HttpResponse::Ok().json(MetarsResponse {
data: airports,
meta: Metadata { page: 0, limit: 0, pages: 0, total: 0 }
})
} }
pub fn init_routes(config: &mut web::ServiceConfig) { pub fn init_routes(config: &mut web::ServiceConfig) {

View File

@@ -1,22 +1,21 @@
import { AirportOrderField, Bounds, GetAirportResponse, GetAirportsResponse } from './airport.types'; import { Airport, AirportOrderField, Bounds, GetAirportResponse, GetAirportsResponse } from './airport.types';
import { getRequest, deleteRequest } from '.'; import { getRequest, deleteRequest, postRequest, putRequest } from '.';
interface GetAirportProps { interface GetAirportProps {
icao: string; icao: string;
} }
export async function getAirport({ icao }: GetAirportProps): Promise<GetAirportResponse> { export async function getAirport({ icao }: GetAirportProps): Promise<GetAirportResponse> {
const response = await getRequest(`airports/search/${icao}`); const response = await getRequest(`airports/${icao}`);
return response?.json() || { data: undefined }; return response?.json() || { data: undefined };
} }
interface GetAirportsProps { interface GetAirportsProps {
bounds?: Bounds; bounds?: Bounds;
category?: string; category?: string;
name?: string; search?: string;
order_field?: AirportOrderField; order_field?: AirportOrderField;
order_by?: 'asc' | 'desc'; order_by?: 'asc' | 'desc';
icao?: string;
page?: number; page?: number;
limit?: number; limit?: number;
} }
@@ -24,20 +23,18 @@ interface GetAirportsProps {
export async function getAirports({ export async function getAirports({
bounds, bounds,
category, category,
name, search,
icao,
order_field, order_field,
order_by, order_by,
limit = 10, limit = 10,
page = 1 page = 1
}: GetAirportsProps): Promise<GetAirportsResponse> { }: GetAirportsProps): Promise<GetAirportsResponse> {
const response = await getRequest('airports/search', { const response = await getRequest('airports', {
bounds: bounds bounds: bounds
? `${bounds?.northEast.lat},${bounds?.northEast.lon},${bounds?.southWest.lat},${bounds?.southWest.lon}` ? `${bounds?.northEast.lat},${bounds?.northEast.lon},${bounds?.southWest.lat},${bounds?.southWest.lon}`
: undefined, : undefined,
category: category ?? undefined, category: category ?? undefined,
name: name ?? undefined, search: search ?? undefined,
icao: icao ?? undefined,
order_field: order_field ?? undefined, order_field: order_field ?? undefined,
order_by: order_by ?? undefined, order_by: order_by ?? undefined,
limit, limit,
@@ -49,14 +46,24 @@ export async function getAirports({
export async function removeAirport({ icao }: { icao?: string }): Promise<any> { export async function removeAirport({ icao }: { icao?: string }): Promise<any> {
let response let response
if (icao) { if (icao) {
response = await deleteRequest(`airports/remove/${icao}`); response = await deleteRequest(`airports/${icao}`);
} else { } else {
response = await deleteRequest('airports/remove'); response = await deleteRequest('airports');
} }
return response.status == 204; return response.status == 204;
} }
export async function importAirports(): Promise<any> { export async function createAirport({ airport }: { airport: Airport }): Promise<any> {
const response = await getRequest('airports/import'); const response = await postRequest(`airports`, airport);
return response?.json() || { data: undefined };
}
export async function updateAirport({ airport }: { airport: Airport }): Promise<any> {
const response = await putRequest(`airports`, airport);
return response?.json() || { data: undefined };
}
export async function importAirports(): Promise<any> {
const response = await postRequest('airports/import');
return response?.json() || { data: undefined }; return response?.json() || { data: undefined };
} }

View File

@@ -41,6 +41,28 @@ export async function postRequest(endpoint: string, body?: any, options?: PostOp
return response; return response;
} }
export async function putRequest(endpoint: string, body?: any, options?: PostOptions): Promise<Response> {
const url = `${baseURL}/${endpoint}`;
let response;
if (body && (!options?.type || options.type === 'json')) {
response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(body)
});
} else {
response = await fetch(url, {
method: 'PUT',
credentials: 'include',
body
});
}
return response;
}
export async function deleteRequest(endpoint: string): Promise<Response> { export async function deleteRequest(endpoint: string): Promise<Response> {
const url = `${baseURL}/${endpoint}`; const url = `${baseURL}/${endpoint}`;
const response = await fetch(url, { const response = await fetch(url, {

View File

@@ -10,6 +10,6 @@ export async function getMetars(icaos: string[]): Promise<GetMetarsResponse> {
return { data: [] }; return { data: [] };
} }
const stationICAOs: string = icaos.map((icao) => icao).join(','); const stationICAOs: string = icaos.map((icao) => icao).join(',');
const response = await getRequest(`metars/${stationICAOs}`, {}); const response = await getRequest(`metars`, { icaos: stationICAOs });
return response?.json() || { data: [] }; return response?.json() || { data: [] };
} }

View File

@@ -3,26 +3,31 @@
import { Airport } from "@/api/airport.types"; import { Airport } from "@/api/airport.types";
import AirportTablePanel from "@/components/Admin/AirportTablePanel"; import AirportTablePanel from "@/components/Admin/AirportTablePanel";
import CreateAirportPanel from "@/components/Admin/CreateAirportPanel"; import CreateAirportPanel from "@/components/Admin/CreateAirportPanel";
import { Container, Grid } from "@mantine/core"; import UpdateAirportModal from "@/components/Admin/UpdateAirportModal";
import { Container, Grid, SimpleGrid } from "@mantine/core";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export default function Page() { export default function Page() {
const [airport, setAirport] = useState<Airport | undefined>(undefined); const [airport, setAirport] = useState<Airport | undefined>(undefined);
useEffect(() => { useEffect(() => {
console.log(airport);
}, [airport]); }, [airport]);
return <Container fluid> return (
<Grid p={'lg'}> <Container fluid>
<Grid.Col span={12}> <SimpleGrid cols={{ base: 1, xs: 1 }} spacing={'md'}>
<AirportTablePanel setAirport={setAirport} /> <Grid p={'lg'}>
</Grid.Col> <Grid.Col span={12}>
<Grid.Col span={12}> <AirportTablePanel setAirport={setAirport} />
<CreateAirportPanel airport={airport} setAirport={setAirport} /> </Grid.Col>
</Grid.Col> <Grid.Col span={12}>
</Grid> <CreateAirportPanel />
</Container>; </Grid.Col>
</Grid>
</SimpleGrid>
<UpdateAirportModal airport={airport} setAirport={setAirport} />
</Container>
);
} }

View File

@@ -1,6 +1,6 @@
import { getAirports, importAirports, removeAirport } from "@/api/airport"; import { getAirports, importAirports, removeAirport } from "@/api/airport";
import { Airport, AirportCategory, AirportOrderField, airportCategoryToText } from "@/api/airport.types"; import { Airport, airportCategoryToText } from "@/api/airport.types";
import { Text, Button, Card, Group, Pagination, ScrollArea, Table, TextInput, rem, UnstyledButton, Center } from "@mantine/core"; import { Text, Button, Card, Group, Pagination, Table, TextInput, rem, UnstyledButton, Center, Flex, Container, Grid, Space } from "@mantine/core";
import { HiChevronUp, HiChevronDown, HiSelector } from "react-icons/hi"; import { HiChevronUp, HiChevronDown, HiSelector } from "react-icons/hi";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { CiSearch } from "react-icons/ci"; import { CiSearch } from "react-icons/ci";
@@ -14,6 +14,7 @@ export default function AirportTablePanel({ setAirport }: { setAirport: (airport
async function getAirportData() { async function getAirportData() {
const response = await getAirports({ const response = await getAirports({
search,
page, page,
limit: 100 limit: 100
}); });
@@ -45,14 +46,12 @@ export default function AirportTablePanel({ setAirport }: { setAirport: (airport
<Table.Td>{airport.gps_code}</Table.Td> <Table.Td>{airport.gps_code}</Table.Td>
<Table.Td>{airport.iata_code}</Table.Td> <Table.Td>{airport.iata_code}</Table.Td>
<Table.Td>{airport.local_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> </Table.Tr>
)) ))
return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder> return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder>
<TextInput <TextInput
placeholder="Search by ICAO" placeholder="Search..."
mb="md" mb="md"
leftSection={<CiSearch style={{ width: rem(16), height: rem(16) }} />} leftSection={<CiSearch style={{ width: rem(16), height: rem(16) }} />}
value={search} value={search}
@@ -72,28 +71,36 @@ export default function AirportTablePanel({ setAirport }: { setAirport: (airport
<Table.Th>GPS Code</Table.Th> <Table.Th>GPS Code</Table.Th>
<Table.Th>IATA Code</Table.Th> <Table.Th>IATA Code</Table.Th>
<Table.Th>Local Code</Table.Th> <Table.Th>Local Code</Table.Th>
<Table.Th>Latitude</Table.Th>
<Table.Th>Longitude</Table.Th>
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody>{rows}</Table.Tbody> <Table.Tbody>{rows}</Table.Tbody>
</Table> </Table>
</Table.ScrollContainer> </Table.ScrollContainer>
<Group> <Grid mt={'md'} justify={'space-between'}>
<Pagination value={page} total={totalPages} onChange={setPage} /> <Grid.Col span={10}>
<PanelButton onClick={async () => { <Pagination value={page} total={totalPages} onChange={setPage} />
await importAirports(); </Grid.Col>
await getAirportData(); <Grid.Col span={2}>
}}> <Flex justify={'end'}>
Import <Space mr={'sm'}>
</PanelButton> <PanelButton onClick={async () => {
<PanelButton color={'red'} onClick={async () => { await importAirports();
await removeAirport({}); await getAirportData();
await getAirportData(); }}>
}}> Import
Remove All </PanelButton>
</PanelButton> </Space>
</Group> <Space>
<PanelButton color={'red'} onClick={async () => {
await removeAirport({});
await getAirportData();
}}>
Remove All
</PanelButton>
</Space>
</Flex>
</Grid.Col>
</Grid>
</Card> </Card>
} }
@@ -103,7 +110,6 @@ function PanelButton({ children, color = 'blue', onClick }: {children: any, colo
loading={loading} loading={loading}
variant='light' variant='light'
color={color} color={color}
mt={'md'}
radius={'md'} radius={'md'}
onClick={() => { onClick={() => {
setLoading(true); setLoading(true);

View File

@@ -1,9 +1,10 @@
import { createAirport } from "@/api/airport";
import { Airport, AirportCategory } from "@/api/airport.types"; import { Airport, AirportCategory } from "@/api/airport.types";
import { Card, TextInput, Select, Group, Flex, Space, Button } from "@mantine/core"; import { Card, TextInput, Select, Group, Flex, Space, Button } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { useEffect } from "react"; import { useEffect } from "react";
export default function CreateAirportPanel({ airport, setAirport } : { airport?: Airport, setAirport: (airport: Airport | undefined) => void }) { export default function CreateAirportPanel() {
const form = useForm<Airport>({ const form = useForm<Airport>({
initialValues: { initialValues: {
icao: '', icao: '',
@@ -25,20 +26,12 @@ export default function CreateAirportPanel({ airport, setAirport } : { airport?:
} }
}); });
useEffect(() => {
console.log(airport);
if (airport) {
form.setValues(airport);
}
}, [airport]);
return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder> return <Card shadow={'sm'} padding={'lg'} radius={'md'} withBorder>
Create Airport Create Airport
<form onSubmit={form.onSubmit((values) => { <form onSubmit={form.onSubmit(async (values) => {
if (airport) { const response = await createAirport({ airport: values });
console.log('update'); if (response.success) {
} else { form.reset();
console.log('create');
} }
})}> })}>
<TextInput <TextInput
@@ -136,7 +129,7 @@ export default function CreateAirportPanel({ airport, setAirport } : { airport?:
color='blue' color='blue'
radius={'md'} radius={'md'}
> >
{airport ? 'Update' : 'Create'} Create
</Button> </Button>
</Space> </Space>
<Space> <Space>
@@ -145,10 +138,7 @@ export default function CreateAirportPanel({ airport, setAirport } : { airport?:
variant='light' variant='light'
color='red' color='red'
radius={'md'} radius={'md'}
onClick={() => { onClick={() => form.reset()}
form.reset();
setAirport(undefined);
}}
> >
Reset Reset
</Button> </Button>

View File

@@ -0,0 +1,148 @@
import { removeAirport, updateAirport } from "@/api/airport";
import { Airport, AirportCategory } from "@/api/airport.types";
import { Button, Container, Flex, Group, Modal, Paper, Select, TextInput, Title } from "@mantine/core";
import { useForm } from "@mantine/form";
import { useEffect } from "react";
export default function UpdateAirportModal({ airport, setAirport }: { airport: Airport | undefined, setAirport: (airport: Airport | undefined) => void}) {
const form = useForm<Airport>({
initialValues: {
icao: airport?.icao || '',
category: airport?.category || AirportCategory.SMALL,
full_name: airport?.full_name || '',
elevation_ft: airport?.elevation_ft || 0,
continent: airport?.continent || '',
iso_country: airport?.iso_country || '',
iso_region: airport?.iso_region || '',
municipality: airport?.municipality || '',
gps_code: airport?.gps_code || '',
iata_code: airport?.iata_code || '',
local_code: airport?.local_code || '',
point: {
x: airport?.point.x || 0,
y: airport?.point.y || 0,
srid: airport?.point.srid || 4326
}
}
});
useEffect(() => {
if (airport) {
form.setValues(airport);
}
}, [airport]);
return (
<Modal opened={airport !== undefined} onClose={() => setAirport(undefined)} withCloseButton={false} size={'50%'}>
<Container>
<Title ta='center'>Update Airport</Title>
<Paper withBorder p={30} mt={30} radius={'md'} shadow={'sm'}>
<form onSubmit={form.onSubmit(async (values) => {
const response = await updateAirport({ airport: values });
if (response.success) {
setAirport(undefined);
}
})}>
<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')}
/>
<TextInput
required
label='Municipality'
placeholder='Manassas'
{...form.getInputProps('municipality')}
/>
</Group>
<Group>
<TextInput
required
label='GPS Code'
placeholder='KHEF'
{...form.getInputProps('gps_code')}
/>
<TextInput
required
label='IATA Code'
placeholder='HEF'
{...form.getInputProps('iata_code')}
/>
<TextInput
required
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'}>
<Button type='submit' variant='light'>Update Airport</Button>
{airport && <Button variant='light' color='red' ml={10} onClick={async () => {
if (await removeAirport({icao: airport.icao})) {
setAirport(undefined);
}
}}>Delete</Button>}
</Flex>
</form>
</Paper>
</Container>
</Modal>
);
}

View File

@@ -53,7 +53,7 @@ export default function Header() {
async function onChange(value: string) { async function onChange(value: string) {
setSearchValue(value); setSearchValue(value);
const airportData = await getAirports({ name: value, icao: value }); const airportData = await getAirports({ search: value });
setAirports( setAirports(
airportData.data.map((airport) => ({ airportData.data.map((airport) => ({
key: airport.icao, key: airport.icao,