From 89e6ca773b750930bccc88c2b505be74d211c12d Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:08:56 +0100 Subject: [PATCH 1/8] feat: application sorting --- api/src/routes/admin.rs | 5 ++-- core/src/database/query/application.rs | 32 +++++++++++++++++++++++- core/src/models/application.rs | 3 +++ core/src/services/application_service.rs | 3 ++- core/src/services/candidate_service.rs | 4 +-- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 8de69b6..c316fb4 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -120,12 +120,13 @@ pub async fn create_candidate( } #[allow(unused_variables)] -#[get("/candidates?&")] +#[get("/candidates?&&")] pub async fn list_candidates( conn: Connection<'_, Db>, session: AdminAuth, field: Option, page: Option, + sort: Option, ) -> Result>, Custom> { let db = conn.into_inner(); let private_key = session.get_private_key(); @@ -135,7 +136,7 @@ pub async fn list_candidates( } } - let candidates = ApplicationService::list_applications(&private_key, db, field, page) + let candidates = ApplicationService::list_applications(&private_key, db, field, page, sort) .await.map_err(to_custom_error)?; Ok( diff --git a/core/src/database/query/application.rs b/core/src/database/query/application.rs index 95705de..9b73569 100644 --- a/core/src/database/query/application.rs +++ b/core/src/database/query/application.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDateTime; use entity::{application, candidate}; use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait}; @@ -13,10 +14,32 @@ pub struct ApplicationCandidateJoin { pub email: Option, pub telephone: Option, pub field_of_study: Option, + pub created_at: NaiveDateTime, } use crate::{Query}; +fn get_ordering(sort: String) -> (application::Column, sea_orm::Order) +{ + let mut split = sort.split("_"); + let column = split.next(); + let order = split.next(); + + let column = match column { + Some("id") => application::Column::Id, + Some("createdAt") => application::Column::CreatedAt, + _ => application::Column::Id + }; + + let order = match order { + Some("asc") => sea_orm::Order::Asc, + Some("desc") => sea_orm::Order::Desc, + _ => sea_orm::Order::Asc, + }; + + (column, order) +} + impl Query { pub async fn find_application_by_id( db: &DbConn, @@ -41,14 +64,20 @@ impl Query { db: &DbConn, field_of_study: Option, page: Option, + sort: Option, ) -> Result, DbErr> { let select = application::Entity::find(); + let (column, order) = if let Some(sort) = sort { + get_ordering(sort) + } else { + (application::Column::Id, sea_orm::Order::Asc) + }; let query = if let Some(field) = field_of_study { select.filter(application::Column::FieldOfStudy.eq(field)) } else { select } - .order_by(application::Column::Id, sea_orm::Order::Asc) + .order_by(column, order) .join(JoinType::InnerJoin, application::Relation::Candidate.def()) .column_as(application::Column::Id, "application_id") .column_as(candidate::Column::Id, "candidate_id") @@ -56,6 +85,7 @@ impl Query { .column_as(candidate::Column::Surname, "surname") .column_as(candidate::Column::Email, "email") .column_as(candidate::Column::Telephone, "telephone") + .column_as(candidate::Column::CreatedAt, "created_at") .into_model::(); if let Some(page) = page { diff --git a/core/src/models/application.rs b/core/src/models/application.rs index a4b436b..271b008 100644 --- a/core/src/models/application.rs +++ b/core/src/models/application.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDateTime; use serde::{Serialize, Deserialize}; use crate::{database::query::application::ApplicationCandidateJoin, error::ServiceError}; @@ -16,6 +17,7 @@ pub struct ApplicationResponse { pub email: String, pub telephone: String, pub field_of_study: Option, + pub created_at: NaiveDateTime, } impl ApplicationResponse { @@ -40,6 +42,7 @@ impl ApplicationResponse { email: email.unwrap_or_default(), telephone: telephone.unwrap_or_default(), field_of_study: c.field_of_study, + created_at: c.created_at, } ) } diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 0176dae..6a7c970 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -267,8 +267,9 @@ impl ApplicationService { db: &DbConn, field_of_study: Option, page: Option, + sort: Option, ) -> Result, ServiceError> { - let applications = Query::list_applications(db, field_of_study, page).await?; + let applications = Query::list_applications(db, field_of_study, page, sort).await?; futures::future::try_join_all( applications diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 628b6be..a1970ef 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -79,12 +79,12 @@ pub mod tests { let db = get_memory_sqlite_connection().await; let admin = create_admin(&db).await; let private_key = crypto::decrypt_password(admin.private_key, "admin".to_string()).await.unwrap(); - let candidates = ApplicationService::list_applications(&private_key, &db, None, None).await.unwrap(); + let candidates = ApplicationService::list_applications(&private_key, &db, None, None, None).await.unwrap(); assert_eq!(candidates.len(), 0); put_user_data(&db).await; - let candidates = ApplicationService::list_applications(&private_key, &db, None, None).await.unwrap(); + let candidates = ApplicationService::list_applications(&private_key, &db, None, None, None).await.unwrap(); assert_eq!(candidates.len(), 1); } From fc7be0e6cc2d4781fd9e0aa470aa7101d46920bf Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:19:58 +0100 Subject: [PATCH 2/8] feat(admin dashboard): sort candidates by createdAt_desc by default --- .../routes/(admin)/admin/(authenticated)/dashboard/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte index ba4f391..c7ce898 100644 --- a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte +++ b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte @@ -20,7 +20,7 @@ const getCandidates = async () => { try { - candidates = await apiListCandidates(undefined, activeFilter.filter); + candidates = await apiListCandidates(undefined, {field: activeFilter.filter}); } catch { pushErrorText('Nepodařilo se načíst uchazeče'); } From 7cd988279d3df062f3faea4d6e71e85da9aa2883 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:22:08 +0100 Subject: [PATCH 3/8] feat(admin dashboard): change label --- .../(admin)/admin/(authenticated)/dashboard/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte index c7ce898..e001f35 100644 --- a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte +++ b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte @@ -26,7 +26,7 @@ } }; - type Class = 'Vše' | 'KBB' | 'IT' | 'GYM'; + type Class = 'Chronologicky' | 'KBB' | 'IT' | 'GYM'; type Filter = { class: Class; @@ -35,7 +35,7 @@ let filters: Array = [ { - class: 'Vše', + class: 'Chronologicky', filter: undefined }, { From aaeca06c33d343f5fe69dcc925426d256d20f31b Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:48:13 +0100 Subject: [PATCH 4/8] fix: application createdAt instead of candidate createdAt in application list response --- core/src/database/query/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/database/query/application.rs b/core/src/database/query/application.rs index 9b73569..b4b32bd 100644 --- a/core/src/database/query/application.rs +++ b/core/src/database/query/application.rs @@ -85,7 +85,7 @@ impl Query { .column_as(candidate::Column::Surname, "surname") .column_as(candidate::Column::Email, "email") .column_as(candidate::Column::Telephone, "telephone") - .column_as(candidate::Column::CreatedAt, "created_at") + .column_as(application::Column::CreatedAt, "created_at") .into_model::(); if let Some(page) = page { From d2cabea219c53481ae5844906bab4ea5b257e241 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:51:04 +0100 Subject: [PATCH 5/8] feat(admin dashboard): show createdAt --- frontend/src/lib/components/admin/table/Table.svelte | 12 ++++++++++++ frontend/src/lib/stores/candidate.ts | 1 + 2 files changed, 13 insertions(+) diff --git a/frontend/src/lib/components/admin/table/Table.svelte b/frontend/src/lib/components/admin/table/Table.svelte index a54199f..571a489 100644 --- a/frontend/src/lib/components/admin/table/Table.svelte +++ b/frontend/src/lib/components/admin/table/Table.svelte @@ -3,6 +3,14 @@ import type { CandidatePreview } from '$lib/stores/candidate'; export let candidates: Array = []; + + const formatRustChronoDateTime = (date?: string) => { + if (!date) return ''; + const [datePart, timePart] = date.split('T'); + const [_, month, day] = datePart.split('-'); + const [hour, minute, second] = timePart.split(':'); + return `${day}. ${month}. ${hour}:${minute}:${Number(second).toFixed(0).padStart(2, '0')}`; + };
@@ -16,6 +24,7 @@ Obor Rodné číslo Link + Vytvořeno @@ -38,6 +47,9 @@ {candidate.relatedApplications?.filter((a) => a !== candidate.applicationId)} + + {formatRustChronoDateTime(candidate.createdAt)} + diff --git a/frontend/src/lib/stores/candidate.ts b/frontend/src/lib/stores/candidate.ts index e90b133..fc5eb3e 100644 --- a/frontend/src/lib/stores/candidate.ts +++ b/frontend/src/lib/stores/candidate.ts @@ -47,6 +47,7 @@ export interface CandidatePreview { surname?: string; email?: string; fieldOfStudy?: string; + createdAt?: string; } export interface CandidateLogin { From 6d1b3a86c026ba57adb517a80f24cccc96dd24ac Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:51:26 +0100 Subject: [PATCH 6/8] feat(admin dashboard): show createdAt --- frontend/src/lib/@api/admin.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/@api/admin.ts b/frontend/src/lib/@api/admin.ts index 7972315..4345b60 100644 --- a/frontend/src/lib/@api/admin.ts +++ b/frontend/src/lib/@api/admin.ts @@ -101,15 +101,18 @@ export const apiLogout = async (fetchSsr?: Fetch) => { // List all candidates /admin/list/candidates export const apiListCandidates = async ( fetchSsr?: Fetch, - field?: string + params: { field?: string; column?: 'createdAt' | 'application', order?: 'asc' | 'desc' } = {column: 'createdAt', order: 'desc'} ): Promise> => { const apiFetch = fetchSsr || fetch; - const params = new URLSearchParams(); - if (field) { - params.append('field', field); + const searchParams = new URLSearchParams(); + if (params.field) { + searchParams.append('field', params.field); + } + if (params.column) { + searchParams.append('sort', `${params.column}_${params.order}`); } try { - const res = await apiFetch(API_URL + '/admin/list/candidates?' + params.toString(), { + const res = await apiFetch(API_URL + '/admin/list/candidates?' + searchParams.toString(), { method: 'GET', credentials: 'include' }); From c905effd928258ac275191d57059290b1005f7a1 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:53:57 +0100 Subject: [PATCH 7/8] feat: show createdAt only when chrono application table view --- .../src/lib/components/admin/table/Table.svelte | 13 +++++++++---- .../admin/(authenticated)/dashboard/+page.svelte | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/admin/table/Table.svelte b/frontend/src/lib/components/admin/table/Table.svelte index 571a489..e4803b5 100644 --- a/frontend/src/lib/components/admin/table/Table.svelte +++ b/frontend/src/lib/components/admin/table/Table.svelte @@ -3,6 +3,7 @@ import type { CandidatePreview } from '$lib/stores/candidate'; export let candidates: Array = []; + export let showCreatedAt: boolean; const formatRustChronoDateTime = (date?: string) => { if (!date) return ''; @@ -24,7 +25,9 @@ Obor Rodné číslo Link - Vytvořeno + {#if showCreatedAt} + Vytvořeno + {/if} @@ -47,9 +50,11 @@ {candidate.relatedApplications?.filter((a) => a !== candidate.applicationId)} - - {formatRustChronoDateTime(candidate.createdAt)} - + {#if showCreatedAt} + + {formatRustChronoDateTime(candidate.createdAt)} + + {/if} diff --git a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte index e001f35..f4eccb0 100644 --- a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte +++ b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte @@ -155,7 +155,7 @@
{/if} - deleteCandidate(event.detail.id)} /> +
deleteCandidate(event.detail.id)} /> From c122c65a610f79080afd3ef7730728f3d7cd10ae Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 15 Feb 2023 15:56:26 +0100 Subject: [PATCH 8/8] fix: ordering when refetching applications --- .../admin/(authenticated)/dashboard/+page.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte index f4eccb0..354cc62 100644 --- a/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte +++ b/frontend/src/routes/(admin)/admin/(authenticated)/dashboard/+page.svelte @@ -20,7 +20,11 @@ const getCandidates = async () => { try { - candidates = await apiListCandidates(undefined, {field: activeFilter.filter}); + // TODO: more generic implementation + candidates = await apiListCandidates( + undefined, + activeFilter.filter !== undefined ? { field: activeFilter.filter } : undefined + ); } catch { pushErrorText('Nepodařilo se načíst uchazeče'); } @@ -155,7 +159,11 @@ {/if} -
deleteCandidate(event.detail.id)} /> +
deleteCandidate(event.detail.id)} + />