From 5df69fe956652d52191b81cc460a959f3203d343 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 23 Apr 2023 21:21:11 -0400 Subject: [PATCH 1/2] enhanced geofence filtering --- .../pages/admin/geofence/GeofenceFilter.tsx | 73 +++++++++++++++++ .../src/pages/admin/geofence/GeofenceList.tsx | 5 +- server/api/src/lib.rs | 1 + server/api/src/private/admin.rs | 73 +++++------------ server/model/src/api/args.rs | 47 +++++++++++ server/model/src/db/geofence.rs | 81 +++++++++++++++---- server/model/src/db/project.rs | 29 ++++--- server/model/src/db/property.rs | 29 ++++--- server/model/src/db/route.rs | 22 +++-- server/model/src/db/tile_server.rs | 21 +++-- 10 files changed, 256 insertions(+), 125 deletions(-) create mode 100644 client/src/pages/admin/geofence/GeofenceFilter.tsx diff --git a/client/src/pages/admin/geofence/GeofenceFilter.tsx b/client/src/pages/admin/geofence/GeofenceFilter.tsx new file mode 100644 index 00000000..7ea1a7f2 --- /dev/null +++ b/client/src/pages/admin/geofence/GeofenceFilter.tsx @@ -0,0 +1,73 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react' +import { + // SavedQueriesList, + FilterLiveSearch, + FilterList, + FilterListItem, + useGetList, +} from 'react-admin' +import { useQuery } from 'react-query' +import { Card, CardContent } from '@mui/material' +import AutoModeIcon from '@mui/icons-material/AutoMode' +import AccountTree from '@mui/icons-material/AccountTree' +import MapIcon from '@mui/icons-material/Map' +import SupervisedUserCircleIcon from '@mui/icons-material/SupervisedUserCircle' + +import { useStatic } from '@hooks/useStatic' +import { RDM_FENCES, UNOWN_FENCES } from '@assets/constants' +import { KojiGeofence, KojiProject, KojiResponse } from '@assets/types' +import { fetchWrapper } from '@services/fetches' + +export function GeofenceFilter() { + const { scannerType } = useStatic.getState() + const projectData = useGetList('project') + const { data } = useQuery('parents', () => + fetchWrapper>( + '/internal/admin/geofence/parent', + ), + ) + return ( + + + {/* */} + + }> + {(projectData?.data || []).map((project) => ( + + ))} + + }> + {(data?.data || []).map((parent) => ( + + ))} + + }> + {['Polygon', 'MultiPolygon'].map((geo_type) => ( + + ))} + + }> + {[ + ...(scannerType === 'rdm' ? RDM_FENCES : UNOWN_FENCES), + 'unset', + ].map((mode) => ( + + ))} + + + + ) +} diff --git a/client/src/pages/admin/geofence/GeofenceList.tsx b/client/src/pages/admin/geofence/GeofenceList.tsx index dec81f5e..fa0b2364 100644 --- a/client/src/pages/admin/geofence/GeofenceList.tsx +++ b/client/src/pages/admin/geofence/GeofenceList.tsx @@ -9,12 +9,11 @@ import { TextField, TopToolbar, CreateButton, - SearchInput, ReferenceField, } from 'react-admin' import { ExportPolygon } from '@components/dialogs/Polygon' - +import { GeofenceFilter } from './GeofenceFilter' import { BulkAssignButton } from '../actions/AssignProjectFence' import { BulkExportButton, ExportButton } from '../actions/Export' import { BulkPushToProd, PushToProd } from '../actions/PushToApi' @@ -45,7 +44,7 @@ export default function GeofenceList() { return ( <> ]} + aside={} pagination={} title="Geofences" perPage={25} diff --git a/server/api/src/lib.rs b/server/api/src/lib.rs index ca089954..286424fd 100644 --- a/server/api/src/lib.rs +++ b/server/api/src/lib.rs @@ -100,6 +100,7 @@ pub async fn start() -> io::Result<()> { .service( web::scope("/admin") .service(private::admin::paginate) + .service(private::admin::parent_list) .service(private::admin::get_all) .service(private::admin::search) .service(private::admin::assign) diff --git a/server/api/src/private/admin.rs b/server/api/src/private/admin.rs index e97470a0..0cff9bf9 100644 --- a/server/api/src/private/admin.rs +++ b/server/api/src/private/admin.rs @@ -1,75 +1,31 @@ use super::*; -use model::error::ModelError; +use model::{api::args::AdminReq, error::ModelError}; use serde::Deserialize; use serde_json::json; use crate::model::{api::args::Response, db, KojiDb}; -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AdminReq { - pub page: Option, - pub per_page: Option, - pub sort_by: Option, - pub order: Option, - pub q: Option, -} - #[derive(Debug, Deserialize)] pub struct Search { pub query: String, } -pub struct AdminReqParsed { - pub page: u64, - pub per_page: u64, - pub sort_by: String, - pub order: String, - pub q: String, -} - -impl AdminReq { - fn parse(self) -> AdminReqParsed { - AdminReqParsed { - page: self.page.unwrap_or(0), - order: self.order.unwrap_or("ASC".to_string()), - per_page: self.per_page.unwrap_or(25), - sort_by: self.sort_by.unwrap_or("id".to_string()), - q: self.q.unwrap_or("".to_string()), - } - } -} - #[get("/{resource}/")] async fn paginate( db: web::Data, query: web::Query, path: actix_web::web::Path, ) -> Result { - let AdminReqParsed { - page, - per_page, - sort_by, - order, - q, - } = query.into_inner().parse(); + let parsed = query.into_inner().parse(); let resource = path.into_inner(); let paginated_results = match resource.to_lowercase().as_str() { - "geofence" => { - db::geofence::Query::paginate(&db.koji_db, page, per_page, order, sort_by, q).await - } - "project" => { - db::project::Query::paginate(&db.koji_db, page, per_page, order, sort_by, q).await - } - "property" => { - db::property::Query::paginate(&db.koji_db, page, per_page, order, sort_by, q).await - } - "route" => db::route::Query::paginate(&db.koji_db, page, per_page, order, sort_by, q).await, - "tileserver" => { - db::tile_server::Query::paginate(&db.koji_db, page, per_page, order, sort_by, q).await - } + "geofence" => db::geofence::Query::paginate(&db.koji_db, parsed).await, + "project" => db::project::Query::paginate(&db.koji_db, parsed).await, + "property" => db::property::Query::paginate(&db.koji_db, parsed).await, + "route" => db::route::Query::paginate(&db.koji_db, parsed).await, + "tileserver" => db::tile_server::Query::paginate(&db.koji_db, parsed).await, _ => Err(DbErr::Custom("Invalid Resource".to_string())), } .map_err(actix_web::error::ErrorInternalServerError)?; @@ -83,6 +39,21 @@ async fn paginate( })) } +#[get("/geofence/parent")] +async fn parent_list(db: web::Data) -> Result { + let results = db::geofence::Query::unique_parents(&db.koji_db) + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + + Ok(HttpResponse::Ok().json(Response { + data: Some(json!(results)), + message: "Success".to_string(), + status: "ok".to_string(), + stats: None, + status_code: 200, + })) +} + #[get("/{resource}/all/")] async fn get_all( db: web::Data, diff --git a/server/model/src/api/args.rs b/server/model/src/api/args.rs index 028de313..9940aeb6 100644 --- a/server/model/src/api/args.rs +++ b/server/model/src/api/args.rs @@ -589,3 +589,50 @@ impl Response { } } } + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdminReq { + pub page: Option, + pub per_page: Option, + pub sort_by: Option, + pub order: Option, + pub search: Option, + pub geo_type: Option, + pub project: Option, + pub mode: Option, + pub parent: Option, +} + +#[derive(Debug, Deserialize)] +pub struct Search { + pub query: String, +} + +impl AdminReq { + pub fn parse(self) -> AdminReqParsed { + AdminReqParsed { + page: self.page.unwrap_or(0), + order: self.order.unwrap_or("ASC".to_string()), + per_page: self.per_page.unwrap_or(25), + sort_by: self.sort_by.unwrap_or("id".to_string()), + q: self.search.unwrap_or("".to_string()), + geo_type: self.geo_type, + project: self.project, + mode: self.mode, + parent: self.parent, + } + } +} + +pub struct AdminReqParsed { + pub page: u64, + pub per_page: u64, + pub sort_by: String, + pub order: String, + pub q: String, + pub geo_type: Option, + pub project: Option, + pub mode: Option, + pub parent: Option, +} diff --git a/server/model/src/db/geofence.rs b/server/model/src/db/geofence.rs index 15378078..c3ffec11 100644 --- a/server/model/src/db/geofence.rs +++ b/server/model/src/db/geofence.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, str::FromStr}; use crate::{ api::{ - args::{ApiQueryArgs, UnknownId}, + args::{AdminReqParsed, ApiQueryArgs, UnknownId}, collection::Default, GeoFormats, ToCollection, }, @@ -89,6 +89,11 @@ pub struct GeofenceNoGeometry { // pub updated_at: DateTimeUtc, } +#[derive(Serialize, Deserialize, FromQueryResult)] +pub struct OnlyParent { + pub parent: Option, +} + impl GeofenceNoGeometry { fn to_json(self) -> Json { json!(self) @@ -380,21 +385,34 @@ impl Query { /// Returns paginated Geofence models pub async fn paginate( db: &DatabaseConnection, - page: u64, - posts_per_page: u64, - order: String, - sort_by: String, - q: String, + args: AdminReqParsed, ) -> Result>, DbErr> { - let column = Column::from_str(&sort_by).unwrap_or(Column::Name); + let column = Column::from_str(&args.sort_by).unwrap_or(Column::Name); + + let mut paginator = Entity::find() + .order_by(column, parse_order(&args.order)) + .filter(Column::Name.like(format!("%{}%", args.q).as_str())); + + if let Some(parent) = args.parent { + paginator = paginator.filter(Column::Parent.eq(parent)); + } + if let Some(geo_type) = args.geo_type { + paginator = paginator.filter(Column::GeoType.eq(geo_type)); + } + if let Some(mode) = args.mode { + paginator = paginator.filter(Column::Mode.eq(mode)); + } + if let Some(project_id) = args.project { + paginator = paginator + .inner_join(project::Entity) + .filter(project::Column::Id.eq(project_id)); + } + + let paginator = paginator.paginate(db, args.per_page); - let paginator = Entity::find() - .order_by(column, parse_order(&order)) - .filter(Column::Name.like(format!("%{}%", q).as_str())) - .paginate(db, posts_per_page); let total = paginator.num_items_and_pages().await?; - let results = paginator.fetch_page(page).await?; + let results = paginator.fetch_page(args.page).await?; let projects = future::try_join_all( results @@ -436,15 +454,19 @@ impl Query { }) .collect(); - if sort_by.contains("length") { - json_related_sort(&mut results, &sort_by.replace(".length", ""), order); + if args.sort_by.contains("length") { + json_related_sort( + &mut results, + &args.sort_by.replace(".length", ""), + args.order, + ); } Ok(PaginateResults { results, total: total.number_of_items, - has_prev: total.number_of_pages == page + 1, - has_next: page + 1 < total.number_of_pages, + has_prev: total.number_of_pages == args.page + 1, + has_next: args.page + 1 < total.number_of_pages, }) } @@ -833,4 +855,31 @@ impl Query { Ok(items.to_collection(None, None)) } + + pub async fn unique_parents(db: &DatabaseConnection) -> Result, ModelError> { + let items = Entity::find() + .filter(Column::Parent.is_not_null()) + .select_only() + .column(Column::Parent) + .distinct() + .into_model::() + .all(db) + .await?; + let items = Entity::find() + .filter(Column::Id.is_in(items.into_iter().filter_map(|item| { + if let Some(parent) = item.parent { + Some(parent) + } else { + None + } + }))) + .select_only() + .column(Column::Id) + .column(Column::Name) + .distinct() + .into_json() + .all(db) + .await?; + Ok(items) + } } diff --git a/server/model/src/db/project.rs b/server/model/src/db/project.rs index 27d1e253..8171b896 100644 --- a/server/model/src/db/project.rs +++ b/server/model/src/db/project.rs @@ -1,6 +1,9 @@ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.1 -use crate::utils::{json::JsonToModel, json_related_sort, parse_order}; +use crate::{ + api::args::AdminReqParsed, + utils::{json::JsonToModel, json_related_sort, parse_order}, +}; use super::*; use sea_orm::entity::prelude::*; @@ -95,22 +98,18 @@ impl Query { pub async fn paginate( db: &DatabaseConnection, - page: u64, - posts_per_page: u64, - order: String, - sort_by: String, - q: String, + args: AdminReqParsed, ) -> Result>, DbErr> { let paginator = project::Entity::find() .order_by( - Column::from_str(&sort_by).unwrap_or(Column::Name), - parse_order(&order), + Column::from_str(&args.sort_by).unwrap_or(Column::Name), + parse_order(&args.order), ) - .filter(Column::Name.like(format!("%{}%", q).as_str())) - .paginate(db, posts_per_page); + .filter(Column::Name.like(format!("%{}%", args.q).as_str())) + .paginate(db, args.per_page); let total = paginator.num_items_and_pages().await?; - let results: Vec = match paginator.fetch_page(page).await { + let results: Vec = match paginator.fetch_page(args.page).await { Ok(results) => results, Err(err) => { println!("[project] Error paginating, {:?}", err); @@ -142,15 +141,15 @@ impl Query { }) .collect(); - if sort_by == "geofences" { - json_related_sort(&mut results, &sort_by, order); + if args.sort_by == "geofences" { + json_related_sort(&mut results, &args.sort_by, args.order); } Ok(PaginateResults { results, total: total.number_of_items, - has_prev: total.number_of_pages == page + 1, - has_next: page + 1 < total.number_of_pages, + has_prev: total.number_of_pages == args.page + 1, + has_next: args.page + 1 < total.number_of_pages, }) } diff --git a/server/model/src/db/property.rs b/server/model/src/db/property.rs index d7e2d685..f9f73411 100644 --- a/server/model/src/db/property.rs +++ b/server/model/src/db/property.rs @@ -1,8 +1,11 @@ //! SeaORM Entity. Generated by sea-orm-codegen 0.10.1 -use crate::utils::{ - json::{parse_property_value, JsonToModel}, - parse_order, +use crate::{ + api::args::AdminReqParsed, + utils::{ + json::{parse_property_value, JsonToModel}, + parse_order, + }, }; use super::{sea_orm_active_enums::Category, *}; @@ -42,21 +45,17 @@ pub struct Query; impl Query { pub async fn paginate( db: &DatabaseConnection, - page: u64, - posts_per_page: u64, - order: String, - sort_by: String, - q: String, + args: AdminReqParsed, ) -> Result>, DbErr> { - let column = Column::from_str(&sort_by).unwrap_or(Column::Name); + let column = Column::from_str(&args.sort_by).unwrap_or(Column::Name); let paginator = property::Entity::find() - .order_by(column, parse_order(&order)) - .filter(Column::Name.like(format!("%{}%", q).as_str())) - .paginate(db, posts_per_page); + .order_by(column, parse_order(&args.order)) + .filter(Column::Name.like(format!("%{}%", args.q).as_str())) + .paginate(db, args.per_page); let total = paginator.num_items_and_pages().await?; - let results: Vec = paginator.fetch_page(page).await?; + let results: Vec = paginator.fetch_page(args.page).await?; let geofences = future::try_join_all( results @@ -88,8 +87,8 @@ impl Query { Ok(PaginateResults { results, total: total.number_of_items, - has_prev: total.number_of_pages == page + 1, - has_next: page + 1 < total.number_of_pages, + has_prev: total.number_of_pages == args.page + 1, + has_next: args.page + 1 < total.number_of_pages, }) } diff --git a/server/model/src/db/route.rs b/server/model/src/db/route.rs index ba6e3dee..3ea03ab0 100644 --- a/server/model/src/db/route.rs +++ b/server/model/src/db/route.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use std::str::FromStr; use crate::{ - api::{GeoFormats, ToCollection, ToFeature}, + api::{args::AdminReqParsed, GeoFormats, ToCollection, ToFeature}, db::sea_orm_active_enums::Type, error::ModelError, utils::{get_enum, json::JsonToModel, parse_order}, @@ -101,22 +101,18 @@ pub struct Query; impl Query { pub async fn paginate( db: &DatabaseConnection, - page: u64, - posts_per_page: u64, - order: String, - sort_by: String, - q: String, + args: AdminReqParsed, ) -> Result>, DbErr> { - let column = Column::from_str(&sort_by).unwrap_or(Column::Name); + let column = Column::from_str(&args.sort_by).unwrap_or(Column::Name); let paginator = Entity::find() - .order_by(column, parse_order(&order)) - .filter(Column::Name.like(format!("%{}%", q).as_str())) - .paginate(db, posts_per_page); + .order_by(column, parse_order(&args.order)) + .filter(Column::Name.like(format!("%{}%", args.q).as_str())) + .paginate(db, args.per_page); let total = paginator.num_items_and_pages().await?; let results: Vec = paginator - .fetch_page(page) + .fetch_page(args.page) .await? .into_iter() .map(|model| { @@ -143,8 +139,8 @@ impl Query { Ok(PaginateResults { results, total: total.number_of_items, - has_prev: total.number_of_pages == page + 1, - has_next: page + 1 < total.number_of_pages, + has_prev: total.number_of_pages == args.page + 1, + has_next: args.page + 1 < total.number_of_pages, }) } diff --git a/server/model/src/db/tile_server.rs b/server/model/src/db/tile_server.rs index 4404dac6..2cf3319f 100644 --- a/server/model/src/db/tile_server.rs +++ b/server/model/src/db/tile_server.rs @@ -5,6 +5,7 @@ use serde_json::json; use std::str::FromStr; use crate::{ + api::args::AdminReqParsed, error::ModelError, utils::{json::JsonToModel, parse_order}, }; @@ -49,22 +50,18 @@ impl Query { pub async fn paginate( db: &DatabaseConnection, - page: u64, - posts_per_page: u64, - order: String, - sort_by: String, - q: String, + args: AdminReqParsed, ) -> Result>, DbErr> { let paginator = Entity::find() .order_by( - Column::from_str(&sort_by).unwrap_or(Column::Name), - parse_order(&order), + Column::from_str(&args.sort_by).unwrap_or(Column::Name), + parse_order(&args.order), ) - .filter(Column::Name.like(format!("%{}%", q).as_str())) - .paginate(db, posts_per_page); + .filter(Column::Name.like(format!("%{}%", args.q).as_str())) + .paginate(db, args.per_page); let total = paginator.num_items_and_pages().await?; - let results: Vec = match paginator.fetch_page(page).await { + let results: Vec = match paginator.fetch_page(args.page).await { Ok(results) => results, Err(err) => { log::error!("[project] Error paginating, {:?}", err); @@ -77,8 +74,8 @@ impl Query { Ok(PaginateResults { results, total: total.number_of_items, - has_prev: total.number_of_pages == page + 1, - has_next: page + 1 < total.number_of_pages, + has_prev: total.number_of_pages == args.page + 1, + has_next: args.page + 1 < total.number_of_pages, }) } From 3a956832b490dabe8151ab59634c7832667dcd5c Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 23 Apr 2023 22:12:00 -0400 Subject: [PATCH 2/2] route filtering - add the same kind of arguments to the route resource - move some buttons to a drop down menu as the list was getting crowded - --- client/src/pages/admin/actions/Extras.tsx | 43 +++++++++++++++ client/src/pages/admin/actions/PushToApi.tsx | 12 ++++- .../pages/admin/geofence/GeofenceFilter.tsx | 10 ++-- .../src/pages/admin/geofence/GeofenceList.tsx | 7 ++- client/src/pages/admin/route/RouteFilter.tsx | 53 +++++++++++++++++++ client/src/pages/admin/route/RouteList.tsx | 18 ++++--- server/api/src/private/admin.rs | 18 +++++-- server/model/src/api/args.rs | 9 ++-- server/model/src/db/geofence.rs | 3 +- server/model/src/db/route.rs | 39 ++++++++++++-- 10 files changed, 179 insertions(+), 33 deletions(-) create mode 100644 client/src/pages/admin/actions/Extras.tsx create mode 100644 client/src/pages/admin/route/RouteFilter.tsx diff --git a/client/src/pages/admin/actions/Extras.tsx b/client/src/pages/admin/actions/Extras.tsx new file mode 100644 index 00000000..71931051 --- /dev/null +++ b/client/src/pages/admin/actions/Extras.tsx @@ -0,0 +1,43 @@ +import * as React from 'react' +import { DeleteWithUndoButton } from 'react-admin' +import { IconButton, Menu, MenuItem } from '@mui/material' +import MoreVertIcon from '@mui/icons-material/MoreVert' + +import { ExportButton } from './Export' +import { PushToProd } from './PushToApi' + +export function ExtraMenuActions({ resource }: { resource: string }) { + const [anchorEl, setAnchorEl] = React.useState(null) + + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation() + setAnchorEl(null) + } + + return ( + <> + { + e.stopPropagation() + setAnchorEl(e.currentTarget) + }} + > + + + + + + + + + + + + + + + ) +} diff --git a/client/src/pages/admin/actions/PushToApi.tsx b/client/src/pages/admin/actions/PushToApi.tsx index ef04034c..536d1b58 100644 --- a/client/src/pages/admin/actions/PushToApi.tsx +++ b/client/src/pages/admin/actions/PushToApi.tsx @@ -11,16 +11,18 @@ import { import { useMutation } from 'react-query' import type { BasicKojiEntry } from '@assets/types' -import { capitalize } from '@mui/material' +import { SxProps, capitalize } from '@mui/material' import { fetchWrapper } from '@services/fetches' export function BaseButton({ onClick, + sx, }: { onClick: React.MouseEventHandler | undefined + sx?: SxProps }) { return ( - ) @@ -28,8 +30,10 @@ export function BaseButton({ export function PushToProd({ resource, + sx, }: { resource: string + sx?: SxProps }) { const record = useRecordContext() const notify = useNotify() @@ -52,6 +56,7 @@ export function PushToProd({ return ( { event.stopPropagation() sync.mutate() @@ -62,8 +67,10 @@ export function PushToProd({ export function BulkPushToProd({ resource, + sx, }: { resource: string + sx?: SxProps }) { const { selectedIds } = useListContext() const unselectAll = useUnselectAll(resource) @@ -95,6 +102,7 @@ export function BulkPushToProd({ return ( { event.stopPropagation() unselectAll() diff --git a/client/src/pages/admin/geofence/GeofenceFilter.tsx b/client/src/pages/admin/geofence/GeofenceFilter.tsx index 7ea1a7f2..052f0392 100644 --- a/client/src/pages/admin/geofence/GeofenceFilter.tsx +++ b/client/src/pages/admin/geofence/GeofenceFilter.tsx @@ -28,7 +28,7 @@ export function GeofenceFilter() { ), ) return ( - + {/* */} @@ -51,12 +51,8 @@ export function GeofenceFilter() { ))} }> - {['Polygon', 'MultiPolygon'].map((geo_type) => ( - + {['Polygon', 'MultiPolygon'].map((geotype) => ( + ))} }> diff --git a/client/src/pages/admin/geofence/GeofenceList.tsx b/client/src/pages/admin/geofence/GeofenceList.tsx index fa0b2364..94ae9e70 100644 --- a/client/src/pages/admin/geofence/GeofenceList.tsx +++ b/client/src/pages/admin/geofence/GeofenceList.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import { BulkDeleteWithUndoButton, Datagrid, - DeleteWithUndoButton, EditButton, List, Pagination, @@ -15,10 +14,11 @@ import { import { ExportPolygon } from '@components/dialogs/Polygon' import { GeofenceFilter } from './GeofenceFilter' import { BulkAssignButton } from '../actions/AssignProjectFence' -import { BulkExportButton, ExportButton } from '../actions/Export' +import { BulkExportButton } from '../actions/Export' import { BulkPushToProd, PushToProd } from '../actions/PushToApi' import { GeofenceExpand } from './GeofenceExpand' import { BulkAssignFenceButton } from '../actions/AssignParentFence' +import { ExtraMenuActions } from '../actions/Extras' function ListActions() { return ( @@ -61,9 +61,8 @@ export default function GeofenceList() { - - + diff --git a/client/src/pages/admin/route/RouteFilter.tsx b/client/src/pages/admin/route/RouteFilter.tsx new file mode 100644 index 00000000..91014d97 --- /dev/null +++ b/client/src/pages/admin/route/RouteFilter.tsx @@ -0,0 +1,53 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import * as React from 'react' +import { + // SavedQueriesList, + FilterLiveSearch, + FilterList, + FilterListItem, +} from 'react-admin' +import { useQuery } from 'react-query' +import { Card, CardContent } from '@mui/material' +import AutoModeIcon from '@mui/icons-material/AutoMode' +import SupervisedUserCircleIcon from '@mui/icons-material/SupervisedUserCircle' + +import { useStatic } from '@hooks/useStatic' +import { RDM_ROUTES, UNOWN_ROUTES } from '@assets/constants' +import { BasicKojiEntry, KojiResponse } from '@assets/types' +import { fetchWrapper } from '@services/fetches' + +export function RouteFilter() { + const { scannerType } = useStatic.getState() + const { data } = useQuery('unique_geofences', () => + fetchWrapper>( + '/internal/admin/route/parent', + ), + ) + return ( + + + {/* */} + + }> + {[ + ...(scannerType === 'rdm' ? RDM_ROUTES : UNOWN_ROUTES), + 'unset', + ].map((mode) => ( + + ))} + + }> +
+ {(data?.data || []).map((fence) => ( + + ))} +
+
+
+
+ ) +} diff --git a/client/src/pages/admin/route/RouteList.tsx b/client/src/pages/admin/route/RouteList.tsx index 55bc98ae..9a335c53 100644 --- a/client/src/pages/admin/route/RouteList.tsx +++ b/client/src/pages/admin/route/RouteList.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import { BulkDeleteWithUndoButton, Datagrid, - DeleteWithUndoButton, EditButton, List, NumberField, @@ -11,12 +10,13 @@ import { TopToolbar, CreateButton, ReferenceField, - SearchInput, } from 'react-admin' import { ExportPolygon } from '@components/dialogs/Polygon' -import { BulkExportButton, ExportButton } from '../actions/Export' +import { BulkExportButton } from '../actions/Export' import { BulkPushToProd, PushToProd } from '../actions/PushToApi' +import { RouteFilter } from './RouteFilter' +import { ExtraMenuActions } from '../actions/Extras' function ListActions() { return ( @@ -29,8 +29,8 @@ function ListActions() { function BulkActions() { return ( <> - + ) @@ -40,7 +40,7 @@ export default function RouteList() { return ( <> ]} + aside={} pagination={} title="Routes" perPage={25} @@ -54,9 +54,11 @@ export default function RouteList() { - - - + + diff --git a/server/api/src/private/admin.rs b/server/api/src/private/admin.rs index 0cff9bf9..6dddaf3c 100644 --- a/server/api/src/private/admin.rs +++ b/server/api/src/private/admin.rs @@ -39,11 +39,19 @@ async fn paginate( })) } -#[get("/geofence/parent")] -async fn parent_list(db: web::Data) -> Result { - let results = db::geofence::Query::unique_parents(&db.koji_db) - .await - .map_err(actix_web::error::ErrorInternalServerError)?; +#[get("/{resource}/parent")] +async fn parent_list( + db: web::Data, + path: actix_web::web::Path, +) -> Result { + let resource = path.into_inner(); + + let results = match resource.to_lowercase().as_str() { + "geofence" => db::geofence::Query::unique_parents(&db.koji_db).await, + "route" => db::route::Query::unique_geofence(&db.koji_db).await, + _ => Err(ModelError::Custom("Invalid Resource".to_string())), + } + .map_err(actix_web::error::ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(Response { data: Some(json!(results)), diff --git a/server/model/src/api/args.rs b/server/model/src/api/args.rs index 9940aeb6..afbbfbcd 100644 --- a/server/model/src/api/args.rs +++ b/server/model/src/api/args.rs @@ -598,10 +598,11 @@ pub struct AdminReq { pub sort_by: Option, pub order: Option, pub search: Option, - pub geo_type: Option, + pub geotype: Option, pub project: Option, pub mode: Option, pub parent: Option, + pub geofenceid: Option, } #[derive(Debug, Deserialize)] @@ -617,10 +618,11 @@ impl AdminReq { per_page: self.per_page.unwrap_or(25), sort_by: self.sort_by.unwrap_or("id".to_string()), q: self.search.unwrap_or("".to_string()), - geo_type: self.geo_type, + geotype: self.geotype, project: self.project, mode: self.mode, parent: self.parent, + geofenceid: self.geofenceid, } } } @@ -631,8 +633,9 @@ pub struct AdminReqParsed { pub sort_by: String, pub order: String, pub q: String, - pub geo_type: Option, + pub geotype: Option, pub project: Option, pub mode: Option, pub parent: Option, + pub geofenceid: Option, } diff --git a/server/model/src/db/geofence.rs b/server/model/src/db/geofence.rs index c3ffec11..977504ca 100644 --- a/server/model/src/db/geofence.rs +++ b/server/model/src/db/geofence.rs @@ -396,7 +396,8 @@ impl Query { if let Some(parent) = args.parent { paginator = paginator.filter(Column::Parent.eq(parent)); } - if let Some(geo_type) = args.geo_type { + + if let Some(geo_type) = args.geotype { paginator = paginator.filter(Column::GeoType.eq(geo_type)); } if let Some(mode) = args.mode { diff --git a/server/model/src/db/route.rs b/server/model/src/db/route.rs index 3ea03ab0..6920863a 100644 --- a/server/model/src/db/route.rs +++ b/server/model/src/db/route.rs @@ -61,6 +61,11 @@ pub struct RouteNoGeometry { pub updated_at: DateTimeUtc, } +#[derive(Serialize, Deserialize, FromQueryResult)] +pub struct OnlyGeofenceId { + pub geofence_id: u32, +} + impl ToFeatureFromModel for Model { fn to_feature(self, internal: bool) -> Result { let Self { @@ -105,10 +110,18 @@ impl Query { ) -> Result>, DbErr> { let column = Column::from_str(&args.sort_by).unwrap_or(Column::Name); - let paginator = Entity::find() + let mut paginator = Entity::find() .order_by(column, parse_order(&args.order)) - .filter(Column::Name.like(format!("%{}%", args.q).as_str())) - .paginate(db, args.per_page); + .filter(Column::Name.like(format!("%{}%", args.q).as_str())); + + if let Some(geofence_id) = args.geofenceid { + paginator = paginator.filter(Column::GeofenceId.eq(geofence_id)); + } + if let Some(mode) = args.mode { + paginator = paginator.filter(Column::Mode.eq(mode)); + } + + let paginator = paginator.paginate(db, args.per_page); let total = paginator.num_items_and_pages().await?; let results: Vec = paginator @@ -540,4 +553,24 @@ impl Query { .all(db) .await?) } + + pub async fn unique_geofence(db: &DatabaseConnection) -> Result, ModelError> { + let items = Entity::find() + .select_only() + .column(Column::GeofenceId) + .distinct() + .into_model::() + .all(db) + .await?; + let items = geofence::Entity::find() + .filter(geofence::Column::Id.is_in(items.into_iter().map(|item| item.geofence_id))) + .select_only() + .column(geofence::Column::Id) + .column(geofence::Column::Name) + .distinct() + .into_json() + .all(db) + .await?; + Ok(items) + } }