mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-08 21:27:45 +03:00
Support for review participants (#1139)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
f7ea37e357
commit
728216c936
@ -56,6 +56,9 @@ export class TReview extends TTask implements Review {
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(Collection(contact.class.Employee), recruit.string.Participants)
|
||||
participants!: Ref<Employee>[]
|
||||
}
|
||||
|
||||
@Model(recruit.class.Opinion, core.class.AttachedDoc, 'recruit' as Domain)
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Doc, FindOptions } from '@anticrm/core'
|
||||
import { Builder } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core from '@anticrm/model-core'
|
||||
import task from '@anticrm/model-task'
|
||||
@ -94,21 +92,22 @@ function createStatusTableViewlet (builder: Builder): void {
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State,
|
||||
assignee: contact.class.Employee,
|
||||
doneState: task.class.DoneState
|
||||
doneState: task.class.DoneState,
|
||||
participants: contact.class.Employee
|
||||
}
|
||||
} as FindOptions<Doc>,
|
||||
config: [
|
||||
'',
|
||||
'$lookup.attachedTo',
|
||||
// '$lookup.assignee',
|
||||
{ key: '$lookup.participants', presenter: recruit.component.PersonsPresenter, label: recruit.string.Participants, sortingKey: '$lookup.participants' },
|
||||
// 'location',
|
||||
'company',
|
||||
'dueDate',
|
||||
{ key: '', presenter: recruit.component.OpinionsPresenter, label: recruit.string.Opinions, sortingKey: 'opinions' },
|
||||
'$lookup.state',
|
||||
'$lookup.doneState',
|
||||
{ presenter: attachment.component.AttachmentsPresenter, label: attachment.string.Files, sortingKey: 'attachments' },
|
||||
{ presenter: chunter.component.CommentsPresenter, label: chunter.string.Comments, sortingKey: 'comments' },
|
||||
// { presenter: attachment.component.AttachmentsPresenter, label: attachment.string.Files, sortingKey: 'attachments' },
|
||||
// { presenter: chunter.component.CommentsPresenter, label: chunter.string.Comments, sortingKey: 'comments' },
|
||||
'modifiedOn'
|
||||
]
|
||||
})
|
||||
@ -138,10 +137,12 @@ function createKanbanViewlet (builder: Builder): void {
|
||||
options: {
|
||||
lookup: {
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State
|
||||
state: task.class.State,
|
||||
assignee: contact.class.Employee,
|
||||
participants: contact.class.Employee
|
||||
}
|
||||
} as FindOptions<Doc>,
|
||||
config: ['$lookup.attachedTo', '$lookup.state']
|
||||
config: ['$lookup.attachedTo', '$lookup.state', '$lookup.participants', '$lookup.assignee']
|
||||
})
|
||||
}
|
||||
|
||||
@ -155,21 +156,22 @@ function createTableViewlet (builder: Builder): void {
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
state: task.class.State,
|
||||
assignee: contact.class.Employee,
|
||||
doneState: task.class.DoneState
|
||||
doneState: task.class.DoneState,
|
||||
participants: contact.class.Employee
|
||||
}
|
||||
} as FindOptions<Doc>,
|
||||
config: [
|
||||
'',
|
||||
'$lookup.attachedTo',
|
||||
// '$lookup.assignee',
|
||||
{ key: '$lookup.participants', presenter: recruit.component.PersonsPresenter, label: recruit.string.Participants, sortingKey: '$lookup.participants' },
|
||||
// 'location',
|
||||
'company',
|
||||
'dueDate',
|
||||
{ key: '', presenter: recruit.component.OpinionsPresenter, label: recruit.string.Opinions, sortingKey: 'opinions' },
|
||||
'$lookup.state',
|
||||
'$lookup.doneState',
|
||||
{ presenter: attachment.component.AttachmentsPresenter, label: attachment.string.Files, sortingKey: 'attachments' },
|
||||
{ presenter: chunter.component.CommentsPresenter, label: chunter.string.Comments, sortingKey: 'comments' },
|
||||
// { presenter: attachment.component.AttachmentsPresenter, label: attachment.string.Files, sortingKey: 'attachments' },
|
||||
// { presenter: chunter.component.CommentsPresenter, label: chunter.string.Comments, sortingKey: 'comments' },
|
||||
'modifiedOn'
|
||||
]
|
||||
})
|
||||
|
@ -41,6 +41,12 @@ const predicates: Record<string, PredicateFactory> = {
|
||||
}
|
||||
return (docs) => execPredicate(docs, propertyKey, (value) => o.includes(value))
|
||||
},
|
||||
$nin: (o, propertyKey) => {
|
||||
if (!Array.isArray(o)) {
|
||||
throw new Error('$nin predicate requires array')
|
||||
}
|
||||
return (docs) => execPredicate(docs, propertyKey, (value) => !o.includes(value))
|
||||
},
|
||||
|
||||
$like: (query: string, propertyKey: string): Predicate => {
|
||||
const searchString = query.split('%').map(it => escapeLikeForRegexp(it)).join('.*')
|
||||
|
@ -21,8 +21,9 @@ import type { Tx } from './tx'
|
||||
* @public
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type QuerySelector<T> = { // TODO: refactor this shit
|
||||
export type QuerySelector<T> = {
|
||||
$in?: T[]
|
||||
$nin?: T[]
|
||||
$like?: string
|
||||
$regex?: string
|
||||
$options?: string
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import type { AnyAttribute, Class, Doc, Ref } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import type { AnySvelteComponent } from '@anticrm/ui'
|
||||
import { CircleButton, Label } from '@anticrm/ui'
|
||||
@ -38,13 +38,18 @@
|
||||
$: attributeKey = typeof key === 'string' ? key : key.key
|
||||
$: typeClassId = attribute !== undefined ? getAttributePresenterClass(attribute) : undefined
|
||||
|
||||
let editor: Promise<AnySvelteComponent> | undefined
|
||||
let editor: Promise<void | AnySvelteComponent> | undefined
|
||||
|
||||
$: if (typeClassId !== undefined) {
|
||||
const typeClass = hierarchy.getClass(typeClassId)
|
||||
const editorMixin = hierarchy.as(typeClass, view.mixin.AttributeEditor)
|
||||
editor = getResource(editorMixin.editor)
|
||||
function update (attribute: AnyAttribute, typeClassId?: Ref<Class<Doc>>): void {
|
||||
if (typeClassId !== undefined) {
|
||||
const typeClass = hierarchy.getClass(typeClassId)
|
||||
const editorMixin = hierarchy.as(typeClass, view.mixin.AttributeEditor)
|
||||
editor = getResource(editorMixin.editor).catch((cause) => {
|
||||
console.error('failed to find editor for', _class, attribute, typeClassId)
|
||||
})
|
||||
}
|
||||
}
|
||||
$: update(attribute, typeClassId)
|
||||
|
||||
function onChange (value: any) {
|
||||
const doc = object as Doc
|
||||
|
107
packages/presentation/src/components/UserBoxList.svelte
Normal file
107
packages/presentation/src/components/UserBoxList.svelte
Normal file
@ -0,0 +1,107 @@
|
||||
<!--
|
||||
// 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 contact, { Person } from '@anticrm/contact'
|
||||
import type { Class, Doc, Ref } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { ActionIcon, CircleButton, IconAdd, IconClose, Label, ShowMore, showPopup } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { UserInfo } from '..'
|
||||
import { createQuery } from '../utils'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
|
||||
export let items: Ref<Person>[] = []
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let title: IntlString
|
||||
export let noItems: IntlString
|
||||
|
||||
let persons: Person[] = []
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query<Person>(_class, { _id: { $in: items } }, (result) => {
|
||||
persons = result
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function addRef (person: Person): Promise<void> {
|
||||
dispatch('open', person)
|
||||
}
|
||||
async function addPerson (evt: Event): Promise<void> {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
_class,
|
||||
title,
|
||||
allowDeselect: false,
|
||||
ignoreUsers: items
|
||||
},
|
||||
evt.target as HTMLElement,
|
||||
(result) => {
|
||||
// We have some value selected
|
||||
if (result !== undefined) {
|
||||
addRef(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function removePerson (person: Person): Promise<void> {
|
||||
dispatch('delete', person)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row">
|
||||
<ShowMore>
|
||||
<div class="persons-container">
|
||||
<div class="flex flex-reverse">
|
||||
<div class="ml-4">
|
||||
<CircleButton icon={IconAdd} size={'small'} selected on:click={addPerson} />
|
||||
</div>
|
||||
<div class="person-items">
|
||||
{#if items?.length === 0}
|
||||
<div class="flex flex-grow title-center">
|
||||
<Label label={noItems} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each persons as person}
|
||||
<div class="antiComponentBox flex-center">
|
||||
<UserInfo value={person} size={'medium'} />
|
||||
<div class="ml-1">
|
||||
<ActionIcon icon={IconClose} size={'small'} action={() => removePerson(person)} />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowMore>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.persons-container {
|
||||
padding: 0.5rem;
|
||||
color: var(--theme-caption-color);
|
||||
background: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.person-items {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
@ -33,12 +33,14 @@
|
||||
export let allowDeselect: boolean = false
|
||||
export let titleDeselect: IntlString | undefined = undefined
|
||||
|
||||
export let ignoreUsers: Ref<Person>[] = []
|
||||
|
||||
let search: string = ''
|
||||
let objects: Person[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const query = createQuery()
|
||||
$: query.query(_class, { name: { $like: '%' + search + '%' } }, result => { objects = result }, { limit: 200 })
|
||||
$: query.query<Person>(_class, { name: { $like: '%' + search + '%' }, _id: { $nin: ignoreUsers } }, result => { objects = result }, { limit: 200 })
|
||||
afterUpdate(() => { dispatch('update', Date.now()) })
|
||||
</script>
|
||||
|
||||
|
@ -23,6 +23,7 @@ export * from './types'
|
||||
|
||||
export { default as UserBox } from './components/UserBox.svelte'
|
||||
export { default as UserInfo } from './components/UserInfo.svelte'
|
||||
export { default as UserBoxList } from './components/UserBoxList.svelte'
|
||||
export { default as Avatar } from './components/Avatar.svelte'
|
||||
export { default as EditableAvatar } from './components/EditableAvatar.svelte'
|
||||
export { default as MessageViewer } from './components/MessageViewer.svelte'
|
||||
|
@ -617,6 +617,29 @@ export class LiveQuery extends TxProcessor implements Client {
|
||||
;(updatedDoc.$lookup as any)[key] = await this.client.findOne(lookup, { _id: ops[key] })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (key === '$push') {
|
||||
const pops = tx.operations[key] ?? {}
|
||||
for (const pkey of Object.keys(pops)) {
|
||||
if (q.options !== undefined) {
|
||||
const lookup = (q.options.lookup as any)?.[pkey]
|
||||
if (lookup !== undefined) {
|
||||
;(updatedDoc.$lookup as any)[pkey].push(await this.client.findOne(lookup, { _id: (pops as any)[pkey] as Ref<Doc> }))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (key === '$pull') {
|
||||
const pops = tx.operations[key] ?? {}
|
||||
for (const pkey of Object.keys(pops)) {
|
||||
if (q.options !== undefined) {
|
||||
const lookup = (q.options.lookup as any)?.[pkey]
|
||||
if (lookup !== undefined) {
|
||||
const pid = (pops as any)[pkey] as Ref<Doc>
|
||||
;(updatedDoc.$lookup as any)[pkey] = ((updatedDoc.$lookup as any)[pkey]).filter((it: Doc) => it._id !== pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -414,3 +414,13 @@
|
||||
|
||||
// Hide row menu in Tooltip
|
||||
.popup-tooltip .antiTable .antiTable-body__row:hover .antiTable-cells__firstCell .antiTable-cells__firstCell-menuRow { visibility: hidden; }
|
||||
|
||||
// Basic component view.
|
||||
.antiComponentBox {
|
||||
margin: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--theme-button-bg-focused);
|
||||
border: 1px solid var(--theme-button-border-enabled);
|
||||
border-radius: .75rem;
|
||||
box-shadow: 0px 3px 3px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
@ -81,7 +81,10 @@
|
||||
"NoReviewForCandidate": "No reviews",
|
||||
"CreateAnReview": "Create review",
|
||||
"CreateOpinion": "Create opinion",
|
||||
"OpinionValuePlaceholder": "10/10"
|
||||
"OpinionValuePlaceholder": "10/10",
|
||||
"Participants": "Participants",
|
||||
"NoParticipants": "No participants added",
|
||||
"PersonsLabel": "{name}"
|
||||
},
|
||||
"status": {
|
||||
"CandidateRequired": "Please select candidate",
|
||||
|
@ -82,7 +82,10 @@
|
||||
"NoReviewForCandidate": "Нет оценок",
|
||||
"CreateAnReview": "Добавить оценку",
|
||||
"CreateOpinion": "Добавить мнение",
|
||||
"OpinionValuePlaceholder": "10/10"
|
||||
"OpinionValuePlaceholder": "10/10",
|
||||
"Participants": "Участники",
|
||||
"NoParticipants": "Участники не добавлены",
|
||||
"PersonsLabel": "{name}"
|
||||
},
|
||||
"status": {
|
||||
"CandidateRequired": "Пожалуйста выберите кандидата",
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient, UserBoxList } from '@anticrm/presentation'
|
||||
import type { Candidate, Review, ReviewCategory } from '@anticrm/recruit'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { EditBox, Grid, Label } from '@anticrm/ui'
|
||||
@ -48,7 +48,9 @@
|
||||
const client = getClient()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['location', 'company', 'number', 'comments', 'startDate', 'description'] })
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['location', 'company', 'number', 'comments', 'startDate', 'description', 'verdict']
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -59,27 +61,44 @@
|
||||
<div class="card"><ReviewCategoryCard category={reviewCategory} /></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-1">
|
||||
<Grid column={1}>
|
||||
<div class="mt-6 mb-2">
|
||||
<Grid column={2}>
|
||||
<EditBox
|
||||
label={recruit.string.Company}
|
||||
bind:value={object.company}
|
||||
icon={contact.icon.Company}
|
||||
placeholder={recruit.string.Company}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={() => client.update(object, { company: object.company })}
|
||||
label={recruit.string.Company}
|
||||
bind:value={object.company}
|
||||
icon={contact.icon.Company}
|
||||
placeholder={recruit.string.Company}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={() => client.update(object, { company: object.company })}
|
||||
/>
|
||||
<EditBox
|
||||
label={recruit.string.Location}
|
||||
bind:value={object.location}
|
||||
icon={recruit.icon.Location}
|
||||
placeholder={recruit.string.Location}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={() => client.update(object, { location: object.location })}
|
||||
label={recruit.string.Location}
|
||||
bind:value={object.location}
|
||||
icon={recruit.icon.Location}
|
||||
placeholder={recruit.string.Location}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={() => client.update(object, { location: object.location })}
|
||||
/>
|
||||
</Grid>
|
||||
<div class="flex-row">
|
||||
<div class="mt-4 mb-2">
|
||||
<Label label={recruit.string.Participants} />
|
||||
</div>
|
||||
<UserBoxList
|
||||
_class={contact.class.Employee}
|
||||
items={object.participants}
|
||||
title={recruit.string.Participants}
|
||||
on:open={(evt) => {
|
||||
client.update(object, { $push: { participants: evt.detail._id } })
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
client.update(object, { $pull: { participants: evt.detail._id } })
|
||||
}}
|
||||
noItems={recruit.string.NoParticipants}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-1">
|
||||
|
@ -15,12 +15,13 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import { Employee, formatName, Person } from '@anticrm/contact'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import type { Review } from '@anticrm/recruit'
|
||||
import { ActionIcon, IconMoreH, showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import PersonsPresenter from './PersonsPresenter.svelte'
|
||||
import ReviewPresenter from './ReviewPresenter.svelte'
|
||||
|
||||
export let object: WithLookup<Review>
|
||||
@ -29,6 +30,14 @@
|
||||
function showCandidate () {
|
||||
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'full')
|
||||
}
|
||||
function getPersons (object: WithLookup<Review>): Person[] {
|
||||
const r = (object.$lookup?.participants as unknown as Employee[] ?? [])
|
||||
const assignee = object.$lookup?.assignee as Employee
|
||||
if (assignee != null && r.findIndex(it => it._id === assignee._id) === -1) {
|
||||
return [...r, assignee]
|
||||
}
|
||||
return r
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||
@ -56,7 +65,9 @@
|
||||
<div class="step-lr75"><CommentsPresenter value={object} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
<Avatar size={'x-small'} />
|
||||
{#if object.$lookup?.participants || object.$lookup?.assignee}
|
||||
<PersonsPresenter value={getPersons(object)}></PersonsPresenter>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { formatName, Person } from '@anticrm/contact'
|
||||
import { Hierarchy } from '@anticrm/core'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import recruit from '../../plugin'
|
||||
import { showPanel, Tooltip } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Person | Person[]
|
||||
export let inline: boolean = false
|
||||
|
||||
let persons: Person[] = []
|
||||
$: persons = Array.isArray(value) ? value : [value]
|
||||
|
||||
async function onClick (p: Person) {
|
||||
showPanel(view.component.EditDoc, p._id, Hierarchy.mixinOrClass(p), 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class='flex persons'>
|
||||
{#each persons as p}
|
||||
<Tooltip label={recruit.string.PersonsLabel} props={{ name: formatName(p.name) }}>
|
||||
<div class="flex-presenter" class:inline-presenter={inline} on:click={() => onClick(p)}>
|
||||
<div class="icon">
|
||||
<Avatar size={'x-small'} avatar={p.avatar} />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<style lang="scss">
|
||||
.persons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, min-content);
|
||||
.icon {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -48,6 +48,7 @@ import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||
import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
|
||||
import recruit from './plugin'
|
||||
import PersonsPresenter from './components/review/PersonsPresenter.svelte'
|
||||
|
||||
async function createApplication (object: Doc): Promise<void> {
|
||||
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
|
||||
@ -164,7 +165,8 @@ export default async (): Promise<Resources> => ({
|
||||
Reviews,
|
||||
Opinions,
|
||||
OpinionPresenter,
|
||||
OpinionsPresenter
|
||||
OpinionsPresenter,
|
||||
PersonsPresenter
|
||||
},
|
||||
completion: {
|
||||
ApplicationQuery: async (client: Client, query: string) => await queryApplication(client, query)
|
||||
|
@ -100,7 +100,10 @@ export default mergeIds(recruitId, recruit, {
|
||||
ReviewShortLabel: '' as IntlString,
|
||||
StartDate: '' as IntlString,
|
||||
DueDate: '' as IntlString,
|
||||
CandidateReviews: '' as IntlString
|
||||
CandidateReviews: '' as IntlString,
|
||||
Participants: '' as IntlString,
|
||||
NoParticipants: '' as IntlString,
|
||||
PersonsLabel: '' as IntlString
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
@ -112,6 +115,7 @@ export default mergeIds(recruitId, recruit, {
|
||||
component: {
|
||||
VacancyItemPresenter: '' as AnyComponent,
|
||||
VacancyCountPresenter: '' as AnyComponent,
|
||||
OpinionsPresenter: '' as AnyComponent
|
||||
OpinionsPresenter: '' as AnyComponent,
|
||||
PersonsPresenter: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Person } from '@anticrm/contact'
|
||||
import type { Employee, Person } from '@anticrm/contact'
|
||||
import type { AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
@ -83,6 +83,8 @@ export interface Review extends Task {
|
||||
dueDate: Timestamp | null
|
||||
|
||||
opinions?: number
|
||||
|
||||
participants?: Ref<Employee>[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,6 +339,12 @@ function getResultIds (ids: Set<Ref<Doc>>, _id: ObjQueryType<Ref<Doc>> | undefin
|
||||
result.push(id)
|
||||
}
|
||||
}
|
||||
} else if (_id.$nin !== undefined) {
|
||||
for (const id of Array.from(ids.values())) {
|
||||
if (!_id.$nin.includes(id)) {
|
||||
result.push(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = Array.from(ids)
|
||||
|
@ -159,7 +159,13 @@ abstract class MongoAdapterBase extends TxProcessor {
|
||||
const domain = this.hierarchy.getDomain(_class)
|
||||
if (domain !== DOMAIN_MODEL) {
|
||||
const arr = object[fullKey]
|
||||
targetObject.$lookup[key] = arr?.[0]
|
||||
if (arr !== undefined && Array.isArray(arr)) {
|
||||
if (arr.length === 1) {
|
||||
targetObject.$lookup[key] = arr[0]
|
||||
} else if (arr.length > 1) {
|
||||
targetObject.$lookup[key] = arr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
targetObject.$lookup[key] = this.modelDb.getObject(targetObject[key])
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user