mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
Active/Inactive Talants filter (#2237)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
175e6f9585
commit
4a9740e64d
@ -546,7 +546,7 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['_class', 'title', 'source', 'city', 'skills', 'modifiedOn', 'onsite', 'remote']
|
||||
filters: ['_class', 'title', 'source', 'city', 'skills', 'modifiedOn', 'onsite', 'remote', 'applications']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ClassFilters, {
|
||||
@ -724,6 +724,32 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
recruit.action.CopyCandidateLink
|
||||
)
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: recruit.component.ApplicantFilter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.FilterMode,
|
||||
core.space.Model,
|
||||
{
|
||||
label: recruit.string.HasActiveApplicant,
|
||||
result: recruit.function.HasActiveApplicant,
|
||||
disableValueSelector: true
|
||||
},
|
||||
recruit.filter.HasActive
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.FilterMode,
|
||||
core.space.Model,
|
||||
{
|
||||
label: recruit.string.HasNoActiveApplicant,
|
||||
result: recruit.function.HasNoActiveApplicant,
|
||||
disableValueSelector: true
|
||||
},
|
||||
recruit.filter.NoActive
|
||||
)
|
||||
}
|
||||
|
||||
export { recruitOperation } from './migration'
|
||||
|
@ -79,7 +79,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
ReviewPresenter: '' as AnyComponent,
|
||||
Opinions: '' as AnyComponent,
|
||||
OpinionPresenter: '' as AnyComponent,
|
||||
NewCandidateHeader: '' as AnyComponent
|
||||
NewCandidateHeader: '' as AnyComponent,
|
||||
ApplicantFilter: '' as AnyComponent
|
||||
},
|
||||
template: {
|
||||
DefaultVacancy: '' as Ref<KanbanTemplate>,
|
||||
|
@ -89,7 +89,9 @@
|
||||
"GotoRecruitApplication": "Switch to Recruit Application",
|
||||
"AddDropHere": "Add or drop resume",
|
||||
"CopyId": "Copy ID",
|
||||
"CopyLink": "Copy link"
|
||||
"CopyLink": "Copy link",
|
||||
"HasActiveApplicant":"Active Only",
|
||||
"HasNoActiveApplicant": "No Active"
|
||||
},
|
||||
"status": {
|
||||
"TalentRequired": "Please select talent",
|
||||
|
@ -91,7 +91,9 @@
|
||||
"GotoRecruitApplication": "Перейти к Приложению Рекрутинг",
|
||||
"AddDropHere": "Добавить или перетянуть резюме",
|
||||
"CopyId": "Копировать ID",
|
||||
"CopyLink": "Копировать ссылку"
|
||||
"CopyLink": "Копировать ссылку",
|
||||
"HasActiveApplicant":"Только активные",
|
||||
"HasNoActiveApplicant": "Не активные"
|
||||
},
|
||||
"status": {
|
||||
"TalentRequired": "Пожалуйста выберите таланта",
|
||||
|
@ -0,0 +1,52 @@
|
||||
<!--
|
||||
// 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 { Class, Doc, Ref } from '@anticrm/core'
|
||||
import { Button } from '@anticrm/ui'
|
||||
import { Filter } from '@anticrm/view'
|
||||
import { FilterQuery } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let filter: Filter
|
||||
export let onChange: (e: Filter) => void
|
||||
filter.onRemove = () => {
|
||||
FilterQuery.remove(filter.index)
|
||||
}
|
||||
|
||||
filter.modes = [recruit.filter.HasActive, recruit.filter.NoActive]
|
||||
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="selectPopup">
|
||||
<Button
|
||||
label={recruit.string.HasActiveApplicant}
|
||||
on:click={async () => {
|
||||
filter.mode = recruit.filter.HasActive
|
||||
onChange(filter)
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={recruit.string.HasNoActiveApplicant}
|
||||
on:click={async () => {
|
||||
filter.mode = recruit.filter.NoActive
|
||||
onChange(filter)
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
@ -13,12 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Client, Doc } from '@anticrm/core'
|
||||
import type { Client, Doc, FindResult, ObjQueryType, Ref } from '@anticrm/core'
|
||||
import { IntlString, OK, Resources, Severity, Status, translate } from '@anticrm/platform'
|
||||
import { ObjectSearchResult } from '@anticrm/presentation'
|
||||
import { Applicant } from '@anticrm/recruit'
|
||||
import task from '@anticrm/task'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { Filter } from '@anticrm/view'
|
||||
import { FilterQuery } from '@anticrm/view-resources'
|
||||
import ApplicantFilter from './components/ApplicantFilter.svelte'
|
||||
import ApplicationItem from './components/ApplicationItem.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import Applications from './components/Applications.svelte'
|
||||
@ -45,8 +48,8 @@ import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
|
||||
import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
||||
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
|
||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||
import { getApplicationTitle, copyToClipboard } from './utils'
|
||||
import recruit from './plugin'
|
||||
import { copyToClipboard, getApplicationTitle } from './utils'
|
||||
|
||||
async function createOpinion (object: Doc): Promise<void> {
|
||||
showPopup(CreateOpinion, { space: object.space, review: object._id })
|
||||
@ -111,6 +114,40 @@ export async function queryApplication (client: Client, search: string): Promise
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getActiveTalants (filter: Filter, onUpdate: () => void): Promise<Array<Ref<Doc>>> {
|
||||
const promise = new Promise<Array<Ref<Doc>>>((resolve, reject) => {
|
||||
let refresh: boolean = false
|
||||
|
||||
const lq = FilterQuery.getLiveQuery(filter.index)
|
||||
refresh = lq.query(
|
||||
recruit.class.Applicant,
|
||||
{
|
||||
doneState: undefined
|
||||
},
|
||||
(refs: FindResult<Applicant>) => {
|
||||
const result = Array.from(new Set(refs.map((p) => p.attachedTo)))
|
||||
FilterQuery.results.set(filter.index, result)
|
||||
resolve(result)
|
||||
onUpdate()
|
||||
}
|
||||
)
|
||||
|
||||
if (!refresh) {
|
||||
resolve(FilterQuery.results.get(filter.index) ?? [])
|
||||
}
|
||||
})
|
||||
return await promise
|
||||
}
|
||||
|
||||
async function hasActiveApplicant (filter: Filter, onUpdate: () => void): Promise<ObjQueryType<any>> {
|
||||
const result = await getActiveTalants(filter, onUpdate)
|
||||
return { $in: result }
|
||||
}
|
||||
async function hasNoActiveApplicant (filter: Filter, onUpdate: () => void): Promise<ObjQueryType<any>> {
|
||||
const result = await getActiveTalants(filter, onUpdate)
|
||||
return { $nin: result }
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: {
|
||||
CreateOpinion: createOpinion,
|
||||
@ -145,12 +182,16 @@ export default async (): Promise<Resources> => ({
|
||||
OpinionPresenter,
|
||||
OpinionsPresenter,
|
||||
|
||||
NewCandidateHeader
|
||||
NewCandidateHeader,
|
||||
|
||||
ApplicantFilter
|
||||
},
|
||||
completion: {
|
||||
ApplicationQuery: async (client: Client, query: string) => await queryApplication(client, query)
|
||||
},
|
||||
function: {
|
||||
ApplicationTitleProvider: getApplicationTitle
|
||||
ApplicationTitleProvider: getApplicationTitle,
|
||||
HasActiveApplicant: hasActiveApplicant,
|
||||
HasNoActiveApplicant: hasNoActiveApplicant
|
||||
}
|
||||
})
|
||||
|
@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Client, Doc, Ref, Space } from '@anticrm/core'
|
||||
import { Client, Doc, ObjQueryType, Ref, Space } from '@anticrm/core'
|
||||
import type { IntlString, Resource, StatusCode } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import recruit, { recruitId } from '@anticrm/recruit'
|
||||
import { TagCategory } from '@anticrm/tags'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { Filter, FilterMode } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
status: {
|
||||
@ -101,7 +102,9 @@ export default mergeIds(recruitId, recruit, {
|
||||
NumberSkills: '' as IntlString,
|
||||
AddDropHere: '' as IntlString,
|
||||
TalentSelect: '' as IntlString,
|
||||
FullDescription: '' as IntlString
|
||||
FullDescription: '' as IntlString,
|
||||
HasActiveApplicant: '' as IntlString,
|
||||
HasNoActiveApplicant: '' as IntlString
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
@ -119,6 +122,12 @@ export default mergeIds(recruitId, recruit, {
|
||||
CreateCandidate: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>
|
||||
ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>,
|
||||
HasActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
||||
HasNoActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
||||
},
|
||||
filter: {
|
||||
HasActive: '' as Ref<FilterMode>,
|
||||
NoActive: '' as Ref<FilterMode>
|
||||
}
|
||||
})
|
||||
|
@ -13,16 +13,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IntlString, translate } from '@anticrm/platform'
|
||||
import { Class, Doc, Ref, RefTo } from '@anticrm/core'
|
||||
import { eventToHTMLElement, IconClose, showPopup, Icon, Label } from '@anticrm/ui'
|
||||
import { Filter, FilterMode } from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../../plugin'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import task from '@anticrm/task'
|
||||
import type { State } from '@anticrm/task'
|
||||
import { onDestroy } from 'svelte'
|
||||
import task from '@anticrm/task'
|
||||
import { eventToHTMLElement, Icon, IconClose, Label, showPopup } from '@anticrm/ui'
|
||||
import { Filter, FilterMode } from '@anticrm/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import view from '../../plugin'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let filter: Filter
|
||||
@ -70,8 +69,8 @@
|
||||
dispatch('change')
|
||||
}
|
||||
|
||||
async function getModeLabel (mode: Ref<FilterMode>): Promise<IntlString | undefined> {
|
||||
return (await client.findOne(view.class.FilterMode, { _id: mode }))?.label
|
||||
async function getMode (mode: Ref<FilterMode>): Promise<FilterMode | undefined> {
|
||||
return await client.findOne(view.class.FilterMode, { _id: mode })
|
||||
}
|
||||
|
||||
function onChange (e: Filter | undefined) {
|
||||
@ -87,6 +86,8 @@
|
||||
filter.nested?.onRemove?.()
|
||||
filter.onRemove?.()
|
||||
})
|
||||
|
||||
$: modeValuePromise = getMode(filter.mode)
|
||||
</script>
|
||||
|
||||
<div class="filter-section">
|
||||
@ -104,9 +105,9 @@
|
||||
toggle()
|
||||
}}
|
||||
>
|
||||
{#await getModeLabel(filter.mode) then label}
|
||||
{#if label}
|
||||
<span><Label {label} params={{ value: filter.value.length }} /></span>
|
||||
{#await modeValuePromise then mode}
|
||||
{#if mode?.label}
|
||||
<span><Label label={mode.label} params={{ value: filter.value.length }} /></span>
|
||||
{/if}
|
||||
{/await}
|
||||
</button>
|
||||
@ -125,29 +126,33 @@
|
||||
toggle(true)
|
||||
}}
|
||||
>
|
||||
{#await getModeLabel(filter.nested.mode) then label}
|
||||
{#if label}
|
||||
<span><Label {label} params={{ value: filter.value.length }} /></span>
|
||||
{#await modeValuePromise then mode}
|
||||
{#if mode?.label}
|
||||
<span><Label label={mode.label} params={{ value: filter.value.length }} /></span>
|
||||
{/if}
|
||||
{/await}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="filter-button"
|
||||
on:click={(e) => {
|
||||
showPopup(
|
||||
currentFilter.key.component,
|
||||
{
|
||||
_class: currentFilter.key._class,
|
||||
filter: currentFilter,
|
||||
onChange
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}}
|
||||
>
|
||||
<span>{countLabel}</span>
|
||||
</button>
|
||||
{#await modeValuePromise then mode}
|
||||
{#if !(mode?.disableValueSelector ?? false)}
|
||||
<button
|
||||
class="filter-button"
|
||||
on:click={(e) => {
|
||||
showPopup(
|
||||
currentFilter.key.component,
|
||||
{
|
||||
_class: currentFilter.key._class,
|
||||
filter: currentFilter,
|
||||
onChange
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}}
|
||||
>
|
||||
<span>{countLabel}</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/await}
|
||||
<button
|
||||
class="filter-button right-round"
|
||||
on:click={() => {
|
||||
|
@ -54,12 +54,14 @@
|
||||
const targetClass = isCollection ? (attribute.type as Collection<AttachedDoc>).of : attribute.type._class
|
||||
const clazz = hierarchy.getClass(targetClass)
|
||||
const filter = hierarchy.as(clazz, view.mixin.AttributeFilter)
|
||||
|
||||
const attrOf = hierarchy.getClass(attribute.attributeOf)
|
||||
if (filter.component === undefined) return undefined
|
||||
return {
|
||||
_class,
|
||||
key: isCollection ? '_id' : key,
|
||||
label: attribute.label,
|
||||
icon: attribute.icon,
|
||||
icon: attribute.icon ?? clazz.icon ?? attrOf.icon ?? view.icon.Setting,
|
||||
component: filter.component
|
||||
}
|
||||
}
|
||||
@ -190,9 +192,11 @@
|
||||
click(type)
|
||||
}}
|
||||
>
|
||||
{#if type.icon}
|
||||
<div class="icon mr-3"><Icon icon={type.icon} size={'small'} /></div>
|
||||
{/if}
|
||||
<div class="icon mr-3">
|
||||
{#if type.icon}
|
||||
<Icon icon={type.icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="pr-1"><Label label={type.label} /></div>
|
||||
</button>
|
||||
{/if}
|
||||
|
@ -42,7 +42,7 @@ export interface KeyFilter {
|
||||
key: string
|
||||
component: AnyComponent
|
||||
label: IntlString
|
||||
icon: Asset | undefined
|
||||
icon: Asset | AnySvelteComponent | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +50,7 @@ export interface KeyFilter {
|
||||
*/
|
||||
export interface FilterMode extends Doc {
|
||||
label: IntlString
|
||||
disableValueSelector?: boolean
|
||||
result: Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user