From 36ddf8477a3eac1360094adf5bf3debe5d14b187 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev <haiodo@users.noreply.github.com> Date: Tue, 15 Mar 2022 16:01:49 +0700 Subject: [PATCH] Vacancies sorting (#1143) Signed-off-by: Andrey Sobolev <haiodo@gmail.com> --- .../src/components/Vacancies.svelte | 30 ++++++++++++++----- .../components/VacancyCountPresenter.svelte | 6 ++-- .../VacancyModifiedPresenter.svelte | 25 ++++++++++++++++ plugins/recruit-resources/src/index.ts | 2 ++ plugins/recruit-resources/src/plugin.ts | 3 +- .../src/components/Table.svelte | 6 ++++ plugins/view/src/index.ts | 3 ++ 7 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 plugins/recruit-resources/src/components/VacancyModifiedPresenter.svelte diff --git a/plugins/recruit-resources/src/components/Vacancies.svelte b/plugins/recruit-resources/src/components/Vacancies.svelte index dba95f6964..65a322e79d 100644 --- a/plugins/recruit-resources/src/components/Vacancies.svelte +++ b/plugins/recruit-resources/src/components/Vacancies.svelte @@ -13,7 +13,7 @@ // limitations under the License. --> <script lang="ts"> - import { Doc, DocumentQuery, Ref } from '@anticrm/core' + import core, { Doc, DocumentQuery, Ref } from '@anticrm/core' import { createQuery } from '@anticrm/presentation' import { Applicant, Vacancy } from '@anticrm/recruit' import { Button, getCurrentLocation, Icon, Label, navigate, Scroller, showPopup, IconAdd } from '@anticrm/ui' @@ -42,7 +42,7 @@ vacancies = res }) - function lowerIncludes (a?: string, b: string): boolean { + function lowerIncludes (a: string | undefined, b: string): boolean { return (a ?? '').toLowerCase().includes(b) } @@ -63,8 +63,8 @@ } $: resultQuery = vquery === '' ? {} : { $search: vquery } - - let applications: Map<Ref<Vacancy>, number> | undefined + type ApplicationInfo = { count: number, modifiedOn: number } + let applications: Map<Ref<Vacancy>, ApplicationInfo> | undefined const applicantQuery = createQuery() $: if (vacancies.length > 0) { @@ -73,10 +73,13 @@ recruit.class.Applicant, { ...(resultQuery as DocumentQuery<Applicant>), space: { $in: vacancies.map((it) => it._id) } }, (res) => { - const result = new Map<Ref<Vacancy>, number>() + const result = new Map<Ref<Vacancy>, ApplicationInfo>() for (const d of res) { - result.set(d.space, (result.get(d.space) ?? 0) + 1) + const v = result.get(d.space) ?? { count: 0, modifiedOn: 0 } + v.count++ + v.modifiedOn = Math.max(v.modifiedOn, d.modifiedOn) + result.set(d.space, v) } applications = result @@ -88,6 +91,8 @@ function showCreateDialog (ev: Event) { showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement) } + const applicationSorting = (a:Doc, b:Doc) => ((applications?.get(b._id as Ref<Vacancy>)?.count ?? 0) - (applications?.get(a._id as Ref<Vacancy>)?.count ?? 0)) ?? 0 + const modifiedSorting = (a:Doc, b:Doc) => ((applications?.get(b._id as Ref<Vacancy>)?.modifiedOn ?? 0) - (applications?.get(a._id as Ref<Vacancy>)?.modifiedOn ?? 0)) ?? 0 </script> <div class="ac-header full"> @@ -118,12 +123,21 @@ key: '', presenter: recruit.component.VacancyCountPresenter, label: recruit.string.Applications, - props: { applications, resultQuery } + props: { applications, resultQuery }, + sortingKey: '@applications', + sortingFunction: applicationSorting }, 'company', 'location', 'description', - 'modifiedOn' + { + key: '', + presenter: recruit.component.VacancyModifiedPresenter, + label: core.string.Modified, + props: { applications }, + sortingKey: 'modifiedOn', + sortingFunction: modifiedSorting + } ]} options={{}} query={{ diff --git a/plugins/recruit-resources/src/components/VacancyCountPresenter.svelte b/plugins/recruit-resources/src/components/VacancyCountPresenter.svelte index 5bf977252f..c50d250586 100644 --- a/plugins/recruit-resources/src/components/VacancyCountPresenter.svelte +++ b/plugins/recruit-resources/src/components/VacancyCountPresenter.svelte @@ -21,14 +21,14 @@ import recruit from '../plugin' import VacancyApplicationsPopup from './VacancyApplicationsPopup.svelte' export let value: Vacancy -export let applications: Map<Ref<Vacancy>, number> | undefined +export let applications: Map<Ref<Vacancy>, {count: number, modifiedOn: number}> | undefined export let resultQuery: DocumentQuery<Doc> </script> -{#if (applications?.get(value._id) ?? 0) > 0} +{#if (applications?.get(value._id)?.count ?? 0) > 0} <Tooltip label={recruit.string.Applications} component={VacancyApplicationsPopup} props={{ value: value._id, resultQuery }}> <div class="sm-tool-icon"> - <span class="icon"><Icon icon={recruit.icon.Application} size={'small'} /></span> {(applications?.get(value._id) ?? 0)} + <span class="icon"><Icon icon={recruit.icon.Application} size={'small'} /></span> {(applications?.get(value._id)?.count ?? 0)} </div> </Tooltip> {/if} \ No newline at end of file diff --git a/plugins/recruit-resources/src/components/VacancyModifiedPresenter.svelte b/plugins/recruit-resources/src/components/VacancyModifiedPresenter.svelte new file mode 100644 index 0000000000..19d0adee34 --- /dev/null +++ b/plugins/recruit-resources/src/components/VacancyModifiedPresenter.svelte @@ -0,0 +1,25 @@ +<!-- +// Copyright © 2022 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +--> + +<script lang="ts"> +import { Ref } from '@anticrm/core' +import { Vacancy } from '@anticrm/recruit' +import { TimeSince } from '@anticrm/ui' + +export let value: Vacancy +export let applications: Map<Ref<Vacancy>, {count: number, modifiedOn: number}> | undefined +</script> + +<TimeSince value={Math.max(applications?.get(value._id)?.modifiedOn ?? 0, value.modifiedOn)}/> diff --git a/plugins/recruit-resources/src/index.ts b/plugins/recruit-resources/src/index.ts index b3563ee3f9..57b5828eee 100644 --- a/plugins/recruit-resources/src/index.ts +++ b/plugins/recruit-resources/src/index.ts @@ -47,6 +47,7 @@ import Vacancies from './components/Vacancies.svelte' import VacancyItemPresenter from './components/VacancyItemPresenter.svelte' import VacancyPresenter from './components/VacancyPresenter.svelte' import VacancyCountPresenter from './components/VacancyCountPresenter.svelte' +import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte' import recruit from './plugin' import PersonsPresenter from './components/review/PersonsPresenter.svelte' @@ -155,6 +156,7 @@ export default async (): Promise<Resources> => ({ Vacancies, VacancyItemPresenter, VacancyCountPresenter, + VacancyModifiedPresenter, CreateReviewCategory, EditReviewCategory, diff --git a/plugins/recruit-resources/src/plugin.ts b/plugins/recruit-resources/src/plugin.ts index db73aba0ab..d0765b3b04 100644 --- a/plugins/recruit-resources/src/plugin.ts +++ b/plugins/recruit-resources/src/plugin.ts @@ -116,6 +116,7 @@ export default mergeIds(recruitId, recruit, { VacancyItemPresenter: '' as AnyComponent, VacancyCountPresenter: '' as AnyComponent, OpinionsPresenter: '' as AnyComponent, - PersonsPresenter: '' as AnyComponent + PersonsPresenter: '' as AnyComponent, + VacancyModifiedPresenter: '' as AnyComponent } }) diff --git a/plugins/view-resources/src/components/Table.svelte b/plugins/view-resources/src/components/Table.svelte index 82d7ab7c36..a27ad69e76 100644 --- a/plugins/view-resources/src/components/Table.svelte +++ b/plugins/view-resources/src/components/Table.svelte @@ -45,6 +45,8 @@ const q = createQuery() + $: sortingFunction = (config.find(it => (typeof it !== 'string') && it.sortingKey === sortKey) as BuildModelKey)?.sortingFunction + async function update ( _class: Ref<Class<Doc>>, query: DocumentQuery<Doc>, @@ -58,6 +60,10 @@ query, (result) => { objects = result + if (sortingFunction !== undefined) { + const sf = sortingFunction + objects.sort((a, b) => -1 * sortOrder * sf(a, b)) + } loading = false }, { sort: { [sortKey]: sortOrder }, ...options, limit: 200 } diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 700b132a61..d48a059c1b 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -124,6 +124,9 @@ export interface BuildModelKey { label?: IntlString sortingKey?: string + + // On client sorting function + sortingFunction?: (a: Doc, b: Doc) => number } /**