From 243bc1dede58349349d1efdefe9fc38b6c5e95d9 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 7 Feb 2023 18:15:59 +0700 Subject: [PATCH] TSK-608: Move Vacancy support. (#2597) Signed-off-by: Andrey Sobolev --- models/recruit/src/index.ts | 18 ++ models/recruit/src/plugin.ts | 6 +- packages/query/src/index.ts | 41 ++- .../ui/src/components/PopupInstance.svelte | 63 +++-- packages/ui/src/popups.ts | 8 +- plugins/recruit-assets/lang/en.json | 3 +- plugins/recruit-assets/lang/ru.json | 3 +- plugins/recruit-resources/src/actionImpl.ts | 7 + .../src/components/EditVacancy.svelte | 4 +- .../src/components/MoveApplication.svelte | 265 ++++++++++++++++++ plugins/recruit-resources/src/index.ts | 5 +- plugins/recruit-resources/src/plugin.ts | 3 +- .../components/issues/IssuePresenter.svelte | 2 +- .../components/issues/TitlePresenter.svelte | 2 +- .../view-resources/src/components/Move.svelte | 32 +-- plugins/view-resources/src/utils.ts | 35 ++- 16 files changed, 422 insertions(+), 75 deletions(-) create mode 100644 plugins/recruit-resources/src/actionImpl.ts create mode 100644 plugins/recruit-resources/src/components/MoveApplication.svelte diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index ab815991c1..544f9c044b 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -1014,6 +1014,24 @@ export function createModel (builder: Builder): void { label: recruit.string.RelatedIssues } }) + + createAction( + builder, + { + label: view.string.Move, + action: recruit.actionImpl.MoveApplicant, + icon: view.icon.Move, + input: 'any', + category: view.category.General, + target: recruit.class.Applicant, + context: { + mode: ['context', 'browser'], + group: 'tools' + }, + override: [task.action.Move] + }, + recruit.action.MoveApplicant + ) } export { recruitOperation } from './migration' diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts index 4f5633bd45..f7c3f8d08c 100644 --- a/models/recruit/src/plugin.ts +++ b/models/recruit/src/plugin.ts @@ -29,10 +29,12 @@ export default mergeIds(recruitId, recruit, { CreateGlobalApplication: '' as Ref, CopyApplicationId: '' as Ref, CopyApplicationLink: '' as Ref, - CopyCandidateLink: '' as Ref + CopyCandidateLink: '' as Ref, + MoveApplicant: '' as Ref }, actionImpl: { - CreateOpinion: '' as ViewAction + CreateOpinion: '' as ViewAction, + MoveApplicant: '' as ViewAction }, category: { Recruit: '' as Ref diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts index 4f50fb6d47..98a91a3278 100644 --- a/packages/query/src/index.ts +++ b/packages/query/src/index.ts @@ -230,30 +230,34 @@ export class LiveQuery extends TxProcessor implements Client { } } - private async checkSearch (q: Query, pos: number, _id: Ref): Promise { + private async checkSearch (q: Query, _id: Ref): Promise { + const match = await this.findOne(q._class, { $search: q.query.$search, _id }, q.options) if (q.result instanceof Promise) { q.result = await q.result } - const match = await this.findOne(q._class, { $search: q.query.$search, _id }, q.options) if (match === undefined) { if (q.options?.limit === q.result.length) { await this.refresh(q) return true } else { + const pos = q.result.findIndex((p) => p._id === _id) q.result.splice(pos, 1) q.total-- } } else { + const pos = q.result.findIndex((p) => p._id === _id) q.result[pos] = match } return false } - private async getCurrentDoc (q: Query, pos: number, _id: Ref): Promise { + private async getCurrentDoc (q: Query, _id: Ref): Promise { + const current = await this.findOne(q._class, { _id }, q.options) if (q.result instanceof Promise) { q.result = await q.result } - const current = await this.findOne(q._class, { _id }, q.options) + + const pos = q.result.findIndex((p) => p._id === _id) if (current !== undefined && this.match(q, current)) { q.result[pos] = current } else { @@ -279,10 +283,11 @@ export class LiveQuery extends TxProcessor implements Client { await this.__updateLookup(q, updatedDoc, ops) } - private async checkUpdatedDocMatch (q: Query, pos: number, updatedDoc: WithLookup): Promise { + private async checkUpdatedDocMatch (q: Query, updatedDoc: WithLookup): Promise { if (q.result instanceof Promise) { q.result = await q.result } + const pos = q.result.findIndex((p) => p._id === updatedDoc._id) if (!this.match(q, updatedDoc)) { if (q.options?.limit === q.result.length) { await this.refresh(q) @@ -315,21 +320,22 @@ export class LiveQuery extends TxProcessor implements Client { if (pos !== -1) { // If query contains search we must check use fulltext if (q.query.$search != null && q.query.$search.length > 0) { - const searchRefresh = await this.checkSearch(q, pos, tx.objectId) + const searchRefresh = await this.checkSearch(q, tx.objectId) if (searchRefresh) return {} } else { const updatedDoc = q.result[pos] if (updatedDoc.modifiedOn < tx.modifiedOn) { await this.__updateMixinDoc(q, updatedDoc, tx) - const updateRefresh = await this.checkUpdatedDocMatch(q, pos, updatedDoc) + const updateRefresh = await this.checkUpdatedDocMatch(q, updatedDoc) if (updateRefresh) return {} } else { - const currentRefresh = await this.getCurrentDoc(q, pos, updatedDoc._id) + const currentRefresh = await this.getCurrentDoc(q, updatedDoc._id) if (currentRefresh) return {} } } this.sort(q, tx) - await this.updatedDocCallback(q.result[pos], q) + const udoc = q.result.find((p) => p._id === tx.objectId) + await this.updatedDocCallback(udoc, q) } else if (isMixin) { // Mixin potentially added to object we doesn't have in out results const doc = await this.findOne(q._class, { _id: tx.objectId }, q.options) @@ -398,24 +404,26 @@ export class LiveQuery extends TxProcessor implements Client { if (pos !== -1) { // If query contains search we must check use fulltext if (q.query.$search != null && q.query.$search.length > 0) { - const searchRefresh = await this.checkSearch(q, pos, tx.objectId) + const searchRefresh = await this.checkSearch(q, tx.objectId) if (searchRefresh) return } else { const updatedDoc = q.result[pos] if (updatedDoc.modifiedOn < tx.modifiedOn) { await this.__updateDoc(q, updatedDoc, tx) - const updateRefresh = await this.checkUpdatedDocMatch(q, pos, updatedDoc) + const updateRefresh = await this.checkUpdatedDocMatch(q, updatedDoc) if (updateRefresh) return } else { - const currentRefresh = await this.getCurrentDoc(q, pos, updatedDoc._id) + const currentRefresh = await this.getCurrentDoc(q, updatedDoc._id) if (currentRefresh) return } } this.sort(q, tx) - await this.updatedDocCallback(q.result[pos], q) + const udoc = q.result.find((p) => p._id === tx.objectId) + await this.updatedDocCallback(udoc, q) } else if (await this.matchQuery(q, tx)) { this.sort(q, tx) - await this.updatedDocCallback(q.result[pos], q) + const udoc = q.result.find((p) => p._id === tx.objectId) + await this.updatedDocCallback(udoc, q) } await this.handleDocUpdateLookup(q, tx) } @@ -953,10 +961,13 @@ export class LiveQuery extends TxProcessor implements Client { return false } - private async updatedDocCallback (updatedDoc: Doc, q: Query): Promise { + private async updatedDocCallback (updatedDoc: Doc | undefined, q: Query): Promise { q.result = q.result as Doc[] if (q.options?.limit !== undefined && q.result.length > q.options.limit) { + if (updatedDoc === undefined) { + return await this.refresh(q) + } if (q.result[q.options?.limit]._id === updatedDoc._id) { return await this.refresh(q) } diff --git a/packages/ui/src/components/PopupInstance.svelte b/packages/ui/src/components/PopupInstance.svelte index 7a517789e9..df5ab13f3c 100644 --- a/packages/ui/src/components/PopupInstance.svelte +++ b/packages/ui/src/components/PopupInstance.svelte @@ -14,12 +14,11 @@ // limitations under the License. --> - + { + if (modalHTML) { + fitPopup(modalHTML, element) + } + }} + on:keydown={handleKeydown} +/> +{JSON.stringify(options)} diff --git a/packages/ui/src/popups.ts b/packages/ui/src/popups.ts index b0a61f6dad..e46de86666 100644 --- a/packages/ui/src/popups.ts +++ b/packages/ui/src/popups.ts @@ -55,14 +55,15 @@ export function showPopup ( return popups }) } + const _element = element instanceof HTMLElement ? getPopupPositionElement(element) : element if (typeof component === 'string') { getResource(component) .then((resolved) => - addPopup({ id, is: resolved, props, element, onClose, onUpdate, close: closePopupOp, options }) + addPopup({ id, is: resolved, props, element: _element, onClose, onUpdate, close: closePopupOp, options }) ) .catch((err) => console.log(err)) } else { - addPopup({ id, is: component, props, element, onClose, onUpdate, close: closePopupOp, options }) + addPopup({ id, is: component, props, element: _element, onClose, onUpdate, close: closePopupOp, options }) } return closePopupOp } @@ -352,7 +353,8 @@ export function getPopupPositionElement ( return undefined } export function getEventPositionElement (evt: MouseEvent): PopupAlignment | undefined { + const rect = DOMRect.fromRect({ width: 1, height: 1, x: evt.clientX, y: evt.clientY }) return { - getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: evt.clientX, y: evt.clientY }) + getBoundingClientRect: () => rect } } diff --git a/plugins/recruit-assets/lang/en.json b/plugins/recruit-assets/lang/en.json index 29c5ad8edf..a10951bb44 100644 --- a/plugins/recruit-assets/lang/en.json +++ b/plugins/recruit-assets/lang/en.json @@ -104,7 +104,8 @@ "VacancyMatching": "Match Talents to vacancy", "Score": "Score", "Match": "Match", - "PerformMatch": "Match" + "PerformMatch": "Match", + "MoveApplication": "Move to another vacancy" }, "status": { "TalentRequired": "Please select talent", diff --git a/plugins/recruit-assets/lang/ru.json b/plugins/recruit-assets/lang/ru.json index e15a6f3a28..453107acaf 100644 --- a/plugins/recruit-assets/lang/ru.json +++ b/plugins/recruit-assets/lang/ru.json @@ -106,7 +106,8 @@ "VacancyMatching": "Подбор кандидатов на вакансию", "Score": "Оценка", "Match": "Совпадение", - "PerformMatch": "Сопоставить" + "PerformMatch": "Сопоставить", + "MoveApplication": "Поменять Вакансию" }, "status": { "TalentRequired": "Пожалуйста выберите таланта", diff --git a/plugins/recruit-resources/src/actionImpl.ts b/plugins/recruit-resources/src/actionImpl.ts new file mode 100644 index 0000000000..a8d85a3c5b --- /dev/null +++ b/plugins/recruit-resources/src/actionImpl.ts @@ -0,0 +1,7 @@ +import { Doc } from '@hcengineering/core' +import { showPopup } from '@hcengineering/ui' +import MoveApplication from './components/MoveApplication.svelte' + +export async function MoveApplicant (docs: Doc | Doc[]): Promise { + showPopup(MoveApplication, { selected: Array.isArray(docs) ? docs : [docs] }) +} diff --git a/plugins/recruit-resources/src/components/EditVacancy.svelte b/plugins/recruit-resources/src/components/EditVacancy.svelte index cbff54b127..c7a0d6bdb4 100644 --- a/plugins/recruit-resources/src/components/EditVacancy.svelte +++ b/plugins/recruit-resources/src/components/EditVacancy.svelte @@ -21,10 +21,11 @@ import { createQuery, getClient } from '@hcengineering/presentation' import { Vacancy } from '@hcengineering/recruit' import { FullDescriptionBox } from '@hcengineering/text-editor' - import { Button, EditBox, Grid, IconMoreH, showPopup } from '@hcengineering/ui' + import { Button, Component, EditBox, Grid, IconMoreH, showPopup } from '@hcengineering/ui' import { ClassAttributeBar, ContextMenu } from '@hcengineering/view-resources' import { createEventDispatcher } from 'svelte' import recruit from '../plugin' + import tracker from '@hcengineering/tracker' export let _id: Ref @@ -132,6 +133,7 @@ space={object.space} attachments={object.attachments ?? 0} /> + {/if} diff --git a/plugins/recruit-resources/src/components/MoveApplication.svelte b/plugins/recruit-resources/src/components/MoveApplication.svelte new file mode 100644 index 0000000000..4729037c6b --- /dev/null +++ b/plugins/recruit-resources/src/components/MoveApplication.svelte @@ -0,0 +1,265 @@ + + + + + + { + dispatch('close') + }} +> + +
+
+
+ +
+
+ + + + + +
+ +
+ +
+
+ { + _space = evt.detail + }} + component={VacancyOrgPresenter} + componentProps={{ inline: true }} + > + + + + +
+
+ + + {#if states.length > 0} + + {/if} + +
+ + diff --git a/plugins/recruit-resources/src/index.ts b/plugins/recruit-resources/src/index.ts index 96842c034e..e7c022c0d3 100644 --- a/plugins/recruit-resources/src/index.ts +++ b/plugins/recruit-resources/src/index.ts @@ -64,6 +64,8 @@ import VacancyList from './components/VacancyList.svelte' import VacancyTemplateEditor from './components/VacancyTemplateEditor.svelte' import MatchVacancy from './components/MatchVacancy.svelte' +import { MoveApplicant } from './actionImpl' + async function createOpinion (object: Doc): Promise { showPopup(CreateOpinion, { space: object.space, review: object._id }) } @@ -265,7 +267,8 @@ async function noneApplicant (filter: Filter, onUpdate: () => void): Promise => ({ actionImpl: { - CreateOpinion: createOpinion + CreateOpinion: createOpinion, + MoveApplicant }, validator: { ApplicantValidator: applicantValidator diff --git a/plugins/recruit-resources/src/plugin.ts b/plugins/recruit-resources/src/plugin.ts index 04874a59b5..722eef0062 100644 --- a/plugins/recruit-resources/src/plugin.ts +++ b/plugins/recruit-resources/src/plugin.ts @@ -117,7 +117,8 @@ export default mergeIds(recruitId, recruit, { VacancyMatching: '' as IntlString, Score: '' as IntlString, Match: '' as IntlString, - PerformMatch: '' as IntlString + PerformMatch: '' as IntlString, + MoveApplication: '' as IntlString }, space: { CandidatesPublic: '' as Ref diff --git a/plugins/tracker-resources/src/components/issues/IssuePresenter.svelte b/plugins/tracker-resources/src/components/issues/IssuePresenter.svelte index 8980722669..acbae3e0f6 100644 --- a/plugins/tracker-resources/src/components/issues/IssuePresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/IssuePresenter.svelte @@ -64,7 +64,7 @@ {/if} - + {title} diff --git a/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte b/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte index 3cbb2e50d5..e515eaf91b 100644 --- a/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/TitlePresenter.svelte @@ -36,7 +36,7 @@ {value.title} diff --git a/plugins/view-resources/src/components/Move.svelte b/plugins/view-resources/src/components/Move.svelte index dcdd1d57ce..5ff5498c7a 100644 --- a/plugins/view-resources/src/components/Move.svelte +++ b/plugins/view-resources/src/components/Move.svelte @@ -14,15 +14,16 @@ // limitations under the License. -->