Vacancies sorting (#1143)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-03-15 16:01:49 +07:00 committed by GitHub
parent 728216c936
commit 36ddf8477a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 12 deletions

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Doc, DocumentQuery, Ref } from '@anticrm/core' import core, { Doc, DocumentQuery, Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation' import { createQuery } from '@anticrm/presentation'
import { Applicant, Vacancy } from '@anticrm/recruit' import { Applicant, Vacancy } from '@anticrm/recruit'
import { Button, getCurrentLocation, Icon, Label, navigate, Scroller, showPopup, IconAdd } from '@anticrm/ui' import { Button, getCurrentLocation, Icon, Label, navigate, Scroller, showPopup, IconAdd } from '@anticrm/ui'
@ -42,7 +42,7 @@
vacancies = res vacancies = res
}) })
function lowerIncludes (a?: string, b: string): boolean { function lowerIncludes (a: string | undefined, b: string): boolean {
return (a ?? '').toLowerCase().includes(b) return (a ?? '').toLowerCase().includes(b)
} }
@ -63,8 +63,8 @@
} }
$: resultQuery = vquery === '' ? {} : { $search: vquery } $: resultQuery = vquery === '' ? {} : { $search: vquery }
type ApplicationInfo = { count: number, modifiedOn: number }
let applications: Map<Ref<Vacancy>, number> | undefined let applications: Map<Ref<Vacancy>, ApplicationInfo> | undefined
const applicantQuery = createQuery() const applicantQuery = createQuery()
$: if (vacancies.length > 0) { $: if (vacancies.length > 0) {
@ -73,10 +73,13 @@
recruit.class.Applicant, recruit.class.Applicant,
{ ...(resultQuery as DocumentQuery<Applicant>), space: { $in: vacancies.map((it) => it._id) } }, { ...(resultQuery as DocumentQuery<Applicant>), space: { $in: vacancies.map((it) => it._id) } },
(res) => { (res) => {
const result = new Map<Ref<Vacancy>, number>() const result = new Map<Ref<Vacancy>, ApplicationInfo>()
for (const d of res) { 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 applications = result
@ -88,6 +91,8 @@
function showCreateDialog (ev: Event) { function showCreateDialog (ev: Event) {
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement) 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> </script>
<div class="ac-header full"> <div class="ac-header full">
@ -118,12 +123,21 @@
key: '', key: '',
presenter: recruit.component.VacancyCountPresenter, presenter: recruit.component.VacancyCountPresenter,
label: recruit.string.Applications, label: recruit.string.Applications,
props: { applications, resultQuery } props: { applications, resultQuery },
sortingKey: '@applications',
sortingFunction: applicationSorting
}, },
'company', 'company',
'location', 'location',
'description', 'description',
'modifiedOn' {
key: '',
presenter: recruit.component.VacancyModifiedPresenter,
label: core.string.Modified,
props: { applications },
sortingKey: 'modifiedOn',
sortingFunction: modifiedSorting
}
]} ]}
options={{}} options={{}}
query={{ query={{

View File

@ -21,14 +21,14 @@ import recruit from '../plugin'
import VacancyApplicationsPopup from './VacancyApplicationsPopup.svelte' import VacancyApplicationsPopup from './VacancyApplicationsPopup.svelte'
export let value: Vacancy 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> export let resultQuery: DocumentQuery<Doc>
</script> </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 }}> <Tooltip label={recruit.string.Applications} component={VacancyApplicationsPopup} props={{ value: value._id, resultQuery }}>
<div class="sm-tool-icon"> <div class="sm-tool-icon">
<span class="icon"><Icon icon={recruit.icon.Application} size={'small'} /></span>&nbsp;{(applications?.get(value._id) ?? 0)} <span class="icon"><Icon icon={recruit.icon.Application} size={'small'} /></span>&nbsp;{(applications?.get(value._id)?.count ?? 0)}
</div> </div>
</Tooltip> </Tooltip>
{/if} {/if}

View File

@ -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)}/>

View File

@ -47,6 +47,7 @@ import Vacancies from './components/Vacancies.svelte'
import VacancyItemPresenter from './components/VacancyItemPresenter.svelte' import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
import VacancyPresenter from './components/VacancyPresenter.svelte' import VacancyPresenter from './components/VacancyPresenter.svelte'
import VacancyCountPresenter from './components/VacancyCountPresenter.svelte' import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
import recruit from './plugin' import recruit from './plugin'
import PersonsPresenter from './components/review/PersonsPresenter.svelte' import PersonsPresenter from './components/review/PersonsPresenter.svelte'
@ -155,6 +156,7 @@ export default async (): Promise<Resources> => ({
Vacancies, Vacancies,
VacancyItemPresenter, VacancyItemPresenter,
VacancyCountPresenter, VacancyCountPresenter,
VacancyModifiedPresenter,
CreateReviewCategory, CreateReviewCategory,
EditReviewCategory, EditReviewCategory,

View File

@ -116,6 +116,7 @@ export default mergeIds(recruitId, recruit, {
VacancyItemPresenter: '' as AnyComponent, VacancyItemPresenter: '' as AnyComponent,
VacancyCountPresenter: '' as AnyComponent, VacancyCountPresenter: '' as AnyComponent,
OpinionsPresenter: '' as AnyComponent, OpinionsPresenter: '' as AnyComponent,
PersonsPresenter: '' as AnyComponent PersonsPresenter: '' as AnyComponent,
VacancyModifiedPresenter: '' as AnyComponent
} }
}) })

View File

@ -45,6 +45,8 @@
const q = createQuery() const q = createQuery()
$: sortingFunction = (config.find(it => (typeof it !== 'string') && it.sortingKey === sortKey) as BuildModelKey)?.sortingFunction
async function update ( async function update (
_class: Ref<Class<Doc>>, _class: Ref<Class<Doc>>,
query: DocumentQuery<Doc>, query: DocumentQuery<Doc>,
@ -58,6 +60,10 @@
query, query,
(result) => { (result) => {
objects = result objects = result
if (sortingFunction !== undefined) {
const sf = sortingFunction
objects.sort((a, b) => -1 * sortOrder * sf(a, b))
}
loading = false loading = false
}, },
{ sort: { [sortKey]: sortOrder }, ...options, limit: 200 } { sort: { [sortKey]: sortOrder }, ...options, limit: 200 }

View File

@ -124,6 +124,9 @@ export interface BuildModelKey {
label?: IntlString label?: IntlString
sortingKey?: string sortingKey?: string
// On client sorting function
sortingFunction?: (a: Doc, b: Doc) => number
} }
/** /**