mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-02 21:33:07 +00:00
parent
29371e128e
commit
e4783e09f9
@ -62,8 +62,8 @@ export class TAttribute extends TDoc implements AnyAttribute {
|
||||
label!: IntlString
|
||||
}
|
||||
|
||||
@Model(core.class.Type, core.class.Doc)
|
||||
export class TType extends TDoc implements Type<any> {
|
||||
@Model(core.class.Type, core.class.Obj)
|
||||
export class TType extends TObj implements Type<any> {
|
||||
label!: IntlString
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Implements, Model, Prop, TypeBoolean, TypeState, TypeString } from '@anticrm/model'
|
||||
import { Implements, Model, Prop, TypeBoolean, TypeRef, TypeString } from '@anticrm/model'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import core from './component'
|
||||
import { TDoc } from './core'
|
||||
@ -53,7 +53,7 @@ export class TState extends TDoc implements State {
|
||||
|
||||
@Implements(core.interface.DocWithState)
|
||||
export class TDocWithState extends TDoc {
|
||||
@Prop(TypeState(), 'State' as IntlString)
|
||||
@Prop(TypeRef(core.class.State), 'State' as IntlString)
|
||||
state!: Ref<State>
|
||||
|
||||
@Prop(TypeString(), 'No.' as IntlString)
|
||||
|
@ -38,7 +38,6 @@
|
||||
"@anticrm/recruit-resources": "~0.6.0",
|
||||
"@anticrm/chunter": "~0.6.0",
|
||||
"@anticrm/model-chunter": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/activity": "~0.6.0"
|
||||
"@anticrm/view": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import type { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core'
|
||||
@ -80,7 +79,7 @@ export class TCandidate extends TPerson implements Candidate {
|
||||
@UX('Application' as IntlString, recruit.icon.RecruitApplication, 'APP' as IntlString)
|
||||
export class TApplicant extends TAttachedDoc implements Applicant {
|
||||
// We need to declare, to provide property with label
|
||||
@Prop(TypeString(), 'Candidate' as IntlString)
|
||||
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
|
||||
declare attachedTo: Ref<Candidate>
|
||||
|
||||
@Prop(TypeString(), 'Attachments' as IntlString)
|
||||
@ -89,8 +88,8 @@ export class TApplicant extends TAttachedDoc implements Applicant {
|
||||
@Prop(TypeString(), 'Comments' as IntlString)
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeString(), 'Assigned recruiter' as IntlString)
|
||||
employee!: Ref<Employee>
|
||||
@Prop(TypeRef(contact.class.Employee), 'Assigned recruiter' as IntlString)
|
||||
employee!: Ref<Employee> | null
|
||||
|
||||
// We need this two to make typescript happy.
|
||||
declare state: TDocWithState['state']
|
||||
@ -228,14 +227,6 @@ export function createModel (builder: Builder): void {
|
||||
attachedTo: recruit.class.Applicant,
|
||||
sequence: 0
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
objectClass: recruit.class.Applicant,
|
||||
icon: recruit.icon.RecruitApplication,
|
||||
txClass: core.class.TxUpdateDoc,
|
||||
component: recruit.activity.TxApplicantUpdate,
|
||||
display: 'inline'
|
||||
}, recruit.ids.TxApplicantUpdate)
|
||||
}
|
||||
|
||||
export { default } from './plugin'
|
||||
|
@ -13,14 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import type { Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { IntlString, Resource } from '@anticrm/platform'
|
||||
import type { Ref, Space, Doc } from '@anticrm/core'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/view'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { recruitId } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit-resources/src/plugin'
|
||||
import { TxViewlet } from '@anticrm/activity'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
action: {
|
||||
@ -48,11 +47,5 @@ export default mergeIds(recruitId, recruit, {
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
},
|
||||
ids: {
|
||||
TxApplicantUpdate: '' as Ref<TxViewlet>
|
||||
},
|
||||
activity: {
|
||||
TxApplicantUpdate: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
@ -16,7 +16,7 @@
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import type { Doc, DocWithState, Domain, FindOptions, Ref } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TDoc, TSpaceWithStates } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
@ -42,8 +42,8 @@ export class TTask extends TDoc implements Task {
|
||||
@Prop(TypeString(), 'Description' as IntlString)
|
||||
description!: string
|
||||
|
||||
@Prop(TypeString(), 'Assignee' as IntlString)
|
||||
assignee!: Ref<Employee>
|
||||
@Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
|
||||
assignee!: Ref<Employee> | null
|
||||
|
||||
@Prop(TypeString(), 'Comments' as IntlString)
|
||||
comments!: number
|
||||
|
@ -84,7 +84,7 @@ export abstract class MemDb extends TxProcessor {
|
||||
const result: LookupData<T> = {}
|
||||
for (const key in lookup) {
|
||||
const id = (doc as any)[key] as Ref<Doc>
|
||||
if (id !== undefined) {
|
||||
if (id != null) {
|
||||
(result as any)[key] = this.getObject(id)
|
||||
}
|
||||
}
|
||||
|
@ -308,8 +308,8 @@ export function TypeDate (): Type<string> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeState (): Type<string> {
|
||||
return { _class: core.class.State, label: 'TypeState' as IntlString }
|
||||
export function TypeRef (_class: Ref<Class<Doc>>): Type<string> {
|
||||
return { _class: _class, label: 'TypeRef' as IntlString }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,12 +15,13 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref, Class, Doc } from '@anticrm/core'
|
||||
import type { AttachedDoc, AttachedDoc, Doc } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import type { AnySvelteComponent } from '@anticrm/ui'
|
||||
import { CircleButton, Label } from '@anticrm/ui'
|
||||
import { getClient } from '../utils'
|
||||
import view from '@anticrm/view'
|
||||
import { getClient } from '../utils'
|
||||
|
||||
// export let _class: Ref<Class<Doc>>
|
||||
export let key: string
|
||||
@ -45,8 +46,13 @@
|
||||
editor = getResource(editorMixin.editor)
|
||||
}
|
||||
|
||||
function onChange(value: any) {
|
||||
client.updateDoc(_class, object.space, object._id, { [key]: value }, true).then(result => console.log('UPDATE RESULT', result))
|
||||
function onChange (value: any) {
|
||||
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
const adoc = object as AttachedDoc
|
||||
client.updateCollection(_class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection, { [key]: value })
|
||||
} else {
|
||||
client.updateDoc(_class, object.space, object._id, { [key]: value }, true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -29,8 +29,11 @@
|
||||
export let _class: Ref<Class<Person>>
|
||||
export let title: IntlString
|
||||
export let caption: IntlString
|
||||
export let value: Ref<Person>
|
||||
export let value: Ref<Person> | null | undefined
|
||||
export let show: boolean = false
|
||||
export let allowDeselect = false
|
||||
export let titleDeselect: IntlString | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let selected: Person | undefined
|
||||
@ -40,11 +43,13 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function updateSelected(value: Ref<Person>) {
|
||||
async function updateSelected (value: Ref<Person>) {
|
||||
selected = await client.findOne(_class, { _id: value })
|
||||
}
|
||||
|
||||
$: updateSelected(value)
|
||||
$: if (value != null) {
|
||||
updateSelected(value)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (btn && show) {
|
||||
@ -59,10 +64,19 @@
|
||||
btn.focus()
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(UsersPopup, { _class, title, caption }, container, (result) => {
|
||||
if (result) {
|
||||
showPopup(UsersPopup, { _class, title, caption, allowDeselect, selected: value, titleDeselect }, container, (result) => {
|
||||
if (result === undefined) {
|
||||
// Value is not changed.
|
||||
opened = false
|
||||
return
|
||||
}
|
||||
if (result != null) {
|
||||
value = result._id
|
||||
dispatch('change', value)
|
||||
} else {
|
||||
value = null
|
||||
selected = undefined
|
||||
dispatch('change', null)
|
||||
}
|
||||
opened = false
|
||||
})
|
||||
|
@ -22,17 +22,23 @@
|
||||
import type { Ref, Class } from '@anticrm/core'
|
||||
import type { Person } from '@anticrm/contact'
|
||||
import { createQuery } from '../utils'
|
||||
import { ActionIcon } from '@anticrm/ui'
|
||||
import BlueCheck from './icons/BlueCheck.svelte'
|
||||
|
||||
export let _class: Ref<Class<Person>>
|
||||
export let title: IntlString
|
||||
export let caption: IntlString
|
||||
export let selected: Ref<Person> | undefined
|
||||
|
||||
export let allowDeselect: boolean = false
|
||||
export let titleDeselect: IntlString | undefined = undefined
|
||||
|
||||
let search: string = ''
|
||||
let objects: Person[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const query = createQuery()
|
||||
$: query.query(_class, { name: { $like: '%'+search+'%' } }, result => { objects = result })
|
||||
$: query.query(_class, { name: { $like: '%' + search + '%' } }, result => { objects = result })
|
||||
afterUpdate(() => { dispatch('update', Date.now()) })
|
||||
</script>
|
||||
|
||||
@ -46,7 +52,12 @@
|
||||
<div class="flex-col box">
|
||||
{#each objects as person}
|
||||
<button class="menu-item" on:click={() => { dispatch('close', person) }}>
|
||||
<UserInfo size={'medium'} value={person} />
|
||||
<div class='flex-grow'>
|
||||
<UserInfo size={'medium'} value={person} />
|
||||
</div>
|
||||
{#if allowDeselect && person._id === selected}
|
||||
<ActionIcon direction={'top'} label={titleDeselect ?? 'Deselect'} icon={BlueCheck} action={() => { dispatch('close', null) }} size={'small'}/>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
22
packages/presentation/src/components/icons/BlueCheck.svelte
Normal file
22
packages/presentation/src/components/icons/BlueCheck.svelte
Normal file
@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<svg viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_b_991_16450)">
|
||||
<rect width="24" height="24" rx="12" fill="#4474F6"/>
|
||||
</g>
|
||||
<path d="M8.25 12L10.9989 15L15.75 9" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<filter id="filter0_b_991_16450" x="-100" y="-100" width="224" height="224" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImage" stdDeviation="50"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_991_16450"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_991_16450" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
</svg>
|
@ -129,8 +129,15 @@
|
||||
actions = result
|
||||
})
|
||||
|
||||
function getValue (utx: TxUpdateDoc<Doc>, key: string): any {
|
||||
return (utx.operations as any)[key]
|
||||
async function getValue (m: AttributeModel, utx: TxUpdateDoc<Doc>): Promise<any> {
|
||||
const val = (utx.operations as any)[m.key]
|
||||
console.log(m._class, m.key, val, typeof val)
|
||||
|
||||
if (client.getHierarchy().isDerived(m._class, core.class.Doc) && typeof val === 'string') {
|
||||
// We have an reference, we need to find a real object to pass for presenter
|
||||
return await client.findOne(m._class, { _id: val as Ref<Doc> })
|
||||
}
|
||||
return val
|
||||
}
|
||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||
showPopup(
|
||||
@ -207,8 +214,14 @@
|
||||
{/if}
|
||||
{#if viewlet === undefined && model.length > 0 && tx.updateTx}
|
||||
{#each model as m}
|
||||
<span>changed {m.label} to</span>
|
||||
<div class="strong"><svelte:component this={m.presenter} value={getValue(tx.updateTx, m.key)} /></div>
|
||||
{#await getValue(m, tx.updateTx) then value}
|
||||
{#if value === null}
|
||||
<span>unset {m.label}</span>
|
||||
{:else}
|
||||
<span>changed {m.label} to</span>
|
||||
<div class="strong"><svelte:component this={m.presenter} {value} /></div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
|
||||
<div>
|
||||
|
@ -13,28 +13,43 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
|
||||
import { Applicant } from '@anticrm/recruit'
|
||||
import contact from '@anticrm/contact'
|
||||
|
||||
export let object: Applicant
|
||||
const client = getClient()
|
||||
|
||||
function change () {
|
||||
client.updateDoc(object._class, object.space, object._id, { employee: object.employee })
|
||||
function change() {
|
||||
client.updateCollection(
|
||||
object._class,
|
||||
object.space,
|
||||
object._id,
|
||||
object.attachedTo,
|
||||
object.attachedToClass,
|
||||
object.collection,
|
||||
{ employee: object.employee }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-between header">
|
||||
<UserBox _class={contact.class.Employee} title='Assigned recruiter' caption='Recruiters' bind:value={object.employee} on:change={change} />
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
title="Assigned recruiter"
|
||||
caption="Recruiters"
|
||||
bind:value={object.employee}
|
||||
on:change={change}
|
||||
allowDeselect
|
||||
titleDeselect={'Unassign recruiter'}
|
||||
/>
|
||||
<AttributeBarEditor key={'state'} {object} showHeader={false} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
width: 100%;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Ref, Space, SpaceWithStates } from '@anticrm/core'
|
||||
@ -33,7 +32,7 @@
|
||||
|
||||
export let space: Ref<SpaceWithStates>
|
||||
export let candidate: Ref<Candidate> // | null = null
|
||||
export let employee: Ref<Employee> // | null = null
|
||||
export let employee: Ref<Employee> | null = null
|
||||
|
||||
export let preserveCandidate = false
|
||||
|
||||
@ -56,18 +55,31 @@
|
||||
if (sequence === undefined) {
|
||||
throw new Error('sequence object not found')
|
||||
}
|
||||
const incResult = await client.updateDoc(view.class.Sequence, view.space.Sequence, sequence._id, {
|
||||
$inc: { sequence: 1 }
|
||||
}, true)
|
||||
const id = await client.addCollection(recruit.class.Applicant, _space, candidate, recruit.class.Candidate, 'applications', {
|
||||
state: state._id,
|
||||
number: incResult.object.sequence,
|
||||
employee: employee
|
||||
})
|
||||
const incResult = await client.updateDoc(
|
||||
view.class.Sequence,
|
||||
view.space.Sequence,
|
||||
sequence._id,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
const id = await client.addCollection(
|
||||
recruit.class.Applicant,
|
||||
_space,
|
||||
candidate,
|
||||
recruit.class.Candidate,
|
||||
'applications',
|
||||
{
|
||||
state: state._id,
|
||||
number: incResult.object.sequence,
|
||||
employee: employee
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function validate (candidate: Ref<Candidate>, space: Ref<Space>) {
|
||||
if (candidate === undefined) {
|
||||
if (candidate == null) {
|
||||
status = new Status(Severity.INFO, recruit.status.CandidateRequired, {})
|
||||
} else {
|
||||
if (space === undefined) {
|
||||
@ -84,22 +96,32 @@
|
||||
}
|
||||
|
||||
$: validate(candidate, _space)
|
||||
|
||||
</script>
|
||||
|
||||
<Card label={'Create Application'}
|
||||
okAction={createApplication}
|
||||
canSave={status.severity === Severity.OK}
|
||||
spaceClass={recruit.class.Vacancy}
|
||||
spaceLabel={'Vacancy'}
|
||||
spacePlaceholder={'Select vacancy'}
|
||||
bind:space={_space}
|
||||
on:close={() => { dispatch('close') }}>
|
||||
<Card
|
||||
label={'Create Application'}
|
||||
okAction={createApplication}
|
||||
canSave={status.severity === Severity.OK}
|
||||
spaceClass={recruit.class.Vacancy}
|
||||
spaceLabel={'Vacancy'}
|
||||
spacePlaceholder={'Select vacancy'}
|
||||
bind:space={_space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
{#if !preserveCandidate}
|
||||
<UserBox _class={recruit.class.Candidate} title='Candidate' caption='Candidates' bind:value={candidate} />
|
||||
<UserBox _class={recruit.class.Candidate} title="Candidate" caption="Candidates" bind:value={candidate} />
|
||||
{/if}
|
||||
<UserBox _class={contact.class.Employee} title='Assigned recruiter' caption='Recruiters' bind:value={employee} />
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
title="Assigned recruiter"
|
||||
caption="Recruiters"
|
||||
bind:value={employee}
|
||||
allowDeselect
|
||||
titleDeselect={'Unassign recruiter'}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
@ -1,48 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 type { Class, State, TxUpdateDoc } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
import { Component } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let tx: TxUpdateDoc<Applicant>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const stateClass = client.getModel().getObject(core.class.State) as Class<State>
|
||||
const statePresenter = client.getHierarchy().as(stateClass, view.mixin.AttributePresenter)
|
||||
|
||||
</script>
|
||||
|
||||
{#if tx.operations.state}
|
||||
<div class="flex-row-center update-container">
|
||||
<span>updated State to</span>
|
||||
{#if statePresenter?.presenter}
|
||||
{#await client.findOne(core.class.State, { _id: tx.operations.state }) then st}
|
||||
{#if st}
|
||||
<Component is={statePresenter.presenter} props={{ value: st }}/>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.update-container span { margin-right: .5rem; }
|
||||
</style>
|
@ -25,7 +25,6 @@ import KanbanCard from './components/KanbanCard.svelte'
|
||||
import EditVacancy from './components/EditVacancy.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte'
|
||||
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
@ -49,8 +48,5 @@ export default async (): Promise<Resources> => ({
|
||||
ApplicationPresenter,
|
||||
ApplicationsPresenter,
|
||||
EditVacancy
|
||||
},
|
||||
activity: {
|
||||
TxApplicantUpdate
|
||||
}
|
||||
})
|
||||
|
@ -53,7 +53,7 @@ export interface Candidate extends Person {
|
||||
export interface Applicant extends DocWithState, AttachedDoc {
|
||||
attachments?: number
|
||||
comments?: number
|
||||
employee: Ref<Employee>
|
||||
employee: Ref<Employee> | null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@
|
||||
"UploadDropFilesHere": "Upload or drop files here",
|
||||
"NoAttachmentsForTask": "There are no attachments for this task.",
|
||||
"AssigneeRequired": "Assignee is required",
|
||||
"More": "Options"
|
||||
"More": "Options",
|
||||
"TaskUnAssign": "Unassign"
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import type { Data, Ref, Space } from '@anticrm/core'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import { OK, Status } from '@anticrm/platform'
|
||||
@ -29,12 +29,12 @@
|
||||
let _space = space
|
||||
const status: Status = OK
|
||||
|
||||
let assignee: Ref<EmployeeAccount> // | null = null
|
||||
let assignee: Ref<Employee> | null = null
|
||||
|
||||
const object: Data<Task> = {
|
||||
name: '',
|
||||
description: '',
|
||||
assignee: undefined as unknown as Ref<Employee>,
|
||||
assignee: null,
|
||||
number: 0
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
<Card
|
||||
label={task.string.CreateTask}
|
||||
okAction={createTask}
|
||||
canSave={object.name.length > 0 && assignee !== undefined}
|
||||
canSave={object.name.length > 0}
|
||||
spaceClass={task.class.Project}
|
||||
spaceLabel={task.string.ProjectName}
|
||||
spacePlaceholder={task.string.SelectProject}
|
||||
@ -99,10 +99,12 @@
|
||||
focus
|
||||
/>
|
||||
<UserBox
|
||||
_class={contact.class.EmployeeAccount}
|
||||
_class={contact.class.Employee}
|
||||
title="Assignee *"
|
||||
caption="Assign this task"
|
||||
bind:value={assignee}
|
||||
allowDeselect
|
||||
titleDeselect={task.string.TaskUnAssign}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
@ -34,6 +34,8 @@
|
||||
caption="Assignee"
|
||||
bind:value={object.assignee}
|
||||
on:change={change}
|
||||
allowDeselect
|
||||
titleDeselect={task.string.TaskUnAssign}
|
||||
/>
|
||||
<AttributeBarEditor key={'state'} {object} showHeader={false} />
|
||||
</div>
|
||||
|
@ -30,6 +30,7 @@ export default mergeIds(taskId, task, {
|
||||
SelectProject: '' as IntlString,
|
||||
TaskName: '' as IntlString,
|
||||
TaskAssignee: '' as IntlString,
|
||||
TaskUnAssign: '' as IntlString,
|
||||
TaskDescription: '' as IntlString,
|
||||
NoAttachmentsForTask: '' as IntlString,
|
||||
UploadDropFilesHere: '' as IntlString,
|
||||
|
@ -33,7 +33,7 @@ export interface Task extends Doc {
|
||||
|
||||
name: string
|
||||
description: string
|
||||
assignee: Ref<Employee>
|
||||
assignee: Ref<Employee> | null
|
||||
|
||||
comments?: number
|
||||
attachments?: number
|
||||
|
@ -1,36 +1,35 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
import type { Class, Client, Doc, FindOptions, FindResult, Obj, Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import type { Ref, Class, Obj, FindOptions, Doc, Client, FindResult } from '@anticrm/core'
|
||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action, ActionTarget, BuildModelOptions } from '@anticrm/view'
|
||||
|
||||
import view, { AttributeModel } from '@anticrm/view'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getObjectPresenter(client: Client, _class: Ref<Class<Obj>>, preserveKey: string): Promise<AttributeModel> {
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>>, preserveKey: string): Promise<AttributeModel> {
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
|
||||
if (presenterMixin.presenter === undefined) {
|
||||
if (clazz.extends !== undefined) {
|
||||
return getObjectPresenter(client, clazz.extends, preserveKey)
|
||||
return await getObjectPresenter(client, clazz.extends, preserveKey)
|
||||
} else {
|
||||
throw new Error('object presenter not found for ' + preserveKey)
|
||||
}
|
||||
@ -38,37 +37,48 @@ export async function getObjectPresenter(client: Client, _class: Ref<Class<Obj>>
|
||||
const presenter = await getResource(presenterMixin.presenter)
|
||||
return {
|
||||
key: preserveKey,
|
||||
_class,
|
||||
label: clazz.label,
|
||||
presenter
|
||||
} as AttributeModel
|
||||
}
|
||||
}
|
||||
|
||||
async function getAttributePresenter(client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string) {
|
||||
async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string): Promise<AttributeModel> {
|
||||
const attribute = client.getHierarchy().getAttribute(_class, key)
|
||||
const clazz = client.getHierarchy().getClass(attribute.type._class)
|
||||
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
|
||||
let attrClass = attribute.type._class
|
||||
const clazz = client.getHierarchy().getClass(attrClass)
|
||||
let presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
|
||||
let parent = clazz.extends
|
||||
while (presenterMixin.presenter === undefined && parent !== undefined) {
|
||||
const pclazz = client.getHierarchy().getClass(parent)
|
||||
attrClass = parent
|
||||
presenterMixin = client.getHierarchy().as(pclazz, view.mixin.AttributePresenter)
|
||||
parent = pclazz.extends
|
||||
}
|
||||
if (presenterMixin.presenter === undefined) {
|
||||
throw new Error('attribute presenter not found for ' + preserveKey)
|
||||
}
|
||||
const presenter = await getResource(presenterMixin.presenter)
|
||||
return {
|
||||
key: preserveKey,
|
||||
_class: attrClass,
|
||||
label: attribute.label,
|
||||
presenter
|
||||
} as AttributeModel
|
||||
}
|
||||
}
|
||||
|
||||
async function getPresenter(client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string, options?: FindOptions<Doc>): Promise<AttributeModel> {
|
||||
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string, options?: FindOptions<Doc>): Promise<AttributeModel> {
|
||||
if (typeof key === 'object') {
|
||||
const {presenter, label} = key
|
||||
const { presenter, label } = key
|
||||
return {
|
||||
key: '',
|
||||
_class,
|
||||
label: label as IntlString,
|
||||
presenter: await getResource(presenter as AnyComponent)
|
||||
}
|
||||
}
|
||||
if (key.length === 0) {
|
||||
return getObjectPresenter(client, _class, preserveKey)
|
||||
return await getObjectPresenter(client, _class, preserveKey)
|
||||
} else {
|
||||
const split = key.split('.')
|
||||
if (split[0] === '$lookup') {
|
||||
@ -80,36 +90,37 @@ async function getPresenter(client: Client, _class: Ref<Class<Obj>>, key: string
|
||||
const model = await getPresenter(client, lookupClass, lookupKey, preserveKey)
|
||||
if (lookupKey === '') {
|
||||
const attribute = client.getHierarchy().getAttribute(_class, split[1])
|
||||
model.label = attribute.label as IntlString
|
||||
model.label = attribute.label
|
||||
} else {
|
||||
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey)
|
||||
model.label = attribute.label as IntlString
|
||||
model.label = attribute.label
|
||||
}
|
||||
return model
|
||||
}
|
||||
return getAttributePresenter(client, _class, key, preserveKey)
|
||||
return await getAttributePresenter(client, _class, key, preserveKey)
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildModel(options: BuildModelOptions): Promise<AttributeModel[]> {
|
||||
export async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
|
||||
console.log('building table model for', options._class)
|
||||
// eslint-disable-next-line array-callback-return
|
||||
const model = options.keys.map(key => {
|
||||
try {
|
||||
const result = getPresenter(options.client, options._class, key, key, options.options)
|
||||
return result
|
||||
} catch(err: any) {
|
||||
} catch (err: any) {
|
||||
if (!(options.ignoreMissing ?? false)) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(model)
|
||||
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
|
||||
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
|
||||
}
|
||||
|
||||
function filterActions(client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Ref<Action>[] {
|
||||
const result: Ref<Action>[] = []
|
||||
for (const target of targets) {
|
||||
function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Array<Ref<Action>> {
|
||||
const result: Array<Ref<Action>> = []
|
||||
for (const target of targets) {
|
||||
if (client.getHierarchy().isDerived(_class, target.target)) {
|
||||
result.push(target.action)
|
||||
}
|
||||
@ -117,7 +128,7 @@ function filterActions(client: Client, _class: Ref<Class<Obj>>, targets: ActionT
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getActions(client: Client, _class: Ref<Class<Obj>>): Promise<FindResult<Action>> {
|
||||
export async function getActions (client: Client, _class: Ref<Class<Obj>>): Promise<FindResult<Action>> {
|
||||
const targets = await client.findAll(view.class.ActionTarget, {})
|
||||
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets) }})
|
||||
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets) } })
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ export const viewId = 'view' as Plugin
|
||||
export interface AttributeModel {
|
||||
key: string
|
||||
label: IntlString
|
||||
_class: Ref<Class<Doc>>
|
||||
presenter: AnySvelteComponent
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user