Updated api endpoint structure, fixed issues, worked on admin page
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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 ¶ms.bounds {
|
filters.bounds = match ¶ms.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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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: [] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
148
ui/src/components/Admin/UpdateAirportModal.tsx
Normal file
148
ui/src/components/Admin/UpdateAirportModal.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user