diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index 988bd24bb4..40ed7e623f 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -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' diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts index 8f122fdadd..3fa93afc99 100644 --- a/models/recruit/src/plugin.ts +++ b/models/recruit/src/plugin.ts @@ -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, diff --git a/plugins/recruit-assets/lang/en.json b/plugins/recruit-assets/lang/en.json index 91ba06dd58..9b9094c813 100644 --- a/plugins/recruit-assets/lang/en.json +++ b/plugins/recruit-assets/lang/en.json @@ -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", diff --git a/plugins/recruit-assets/lang/ru.json b/plugins/recruit-assets/lang/ru.json index 3cffb01324..c60f20271a 100644 --- a/plugins/recruit-assets/lang/ru.json +++ b/plugins/recruit-assets/lang/ru.json @@ -91,7 +91,9 @@ "GotoRecruitApplication": "Перейти к Приложению Рекрутинг", "AddDropHere": "Добавить или перетянуть резюме", "CopyId": "Копировать ID", - "CopyLink": "Копировать ссылку" + "CopyLink": "Копировать ссылку", + "HasActiveApplicant":"Только активные", + "HasNoActiveApplicant": "Не активные" }, "status": { "TalentRequired": "Пожалуйста выберите таланта", diff --git a/plugins/recruit-resources/src/components/ApplicantFilter.svelte b/plugins/recruit-resources/src/components/ApplicantFilter.svelte new file mode 100644 index 0000000000..4432c6da2f --- /dev/null +++ b/plugins/recruit-resources/src/components/ApplicantFilter.svelte @@ -0,0 +1,52 @@ + + + +
+
diff --git a/plugins/recruit-resources/src/index.ts b/plugins/recruit-resources/src/index.ts index b4bc10f300..82f34018fd 100644 --- a/plugins/recruit-resources/src/index.ts +++ b/plugins/recruit-resources/src/index.ts @@ -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 { 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>> { + const promise = new Promise>>((resolve, reject) => { + let refresh: boolean = false + + const lq = FilterQuery.getLiveQuery(filter.index) + refresh = lq.query( + recruit.class.Applicant, + { + doneState: undefined + }, + (refs: FindResult) => { + 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> { + const result = await getActiveTalants(filter, onUpdate) + return { $in: result } +} +async function hasNoActiveApplicant (filter: Filter, onUpdate: () => void): Promise> { + const result = await getActiveTalants(filter, onUpdate) + return { $nin: result } +} + export default async (): Promise => ({ actionImpl: { CreateOpinion: createOpinion, @@ -145,12 +182,16 @@ export default async (): Promise => ({ 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 } }) diff --git a/plugins/recruit-resources/src/plugin.ts b/plugins/recruit-resources/src/plugin.ts index 903a087f79..8339404634 100644 --- a/plugins/recruit-resources/src/plugin.ts +++ b/plugins/recruit-resources/src/plugin.ts @@ -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 @@ -119,6 +122,12 @@ export default mergeIds(recruitId, recruit, { CreateCandidate: '' as AnyComponent }, function: { - ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref) => Promise> + ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref) => Promise>, + HasActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise>>, + HasNoActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise>> + }, + filter: { + HasActive: '' as Ref, + NoActive: '' as Ref } }) diff --git a/plugins/view-resources/src/components/filter/FilterSection.svelte b/plugins/view-resources/src/components/filter/FilterSection.svelte index 0b182bad6e..6a8bd854a0 100644 --- a/plugins/view-resources/src/components/filter/FilterSection.svelte +++ b/plugins/view-resources/src/components/filter/FilterSection.svelte @@ -13,16 +13,15 @@ // limitations under the License. -->
@@ -104,9 +105,9 @@ toggle() }} > - {#await getModeLabel(filter.mode) then label} - {#if label} - + {#await modeValuePromise then mode} + {#if mode?.label} + {/if} {/await} @@ -125,29 +126,33 @@ toggle(true) }} > - {#await getModeLabel(filter.nested.mode) then label} - {#if label} - + {#await modeValuePromise then mode} + {#if mode?.label} + {/if} {/await} {/if} - + {#await modeValuePromise then mode} + {#if !(mode?.disableValueSelector ?? false)} + + {/if} + {/await} {/if} diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 3e1b0d2363..ce8a5584ef 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -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>> }