Inactive employee (#2157)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-29 11:51:29 +06:00 committed by GitHub
parent 61216392e9
commit 168ae1cd54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 595 additions and 269 deletions

View File

@ -2,6 +2,11 @@
## 0.6.30 (upcoming) ## 0.6.30 (upcoming)
Core:
- Allow to leave workspace
- Allow to kick employee
HR: HR:
- Allow to change assignee in Kanban - Allow to change assignee in Kanban

View File

@ -37,6 +37,7 @@
"@anticrm/ui": "~0.6.0", "@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.6", "@anticrm/platform": "~0.6.6",
"@anticrm/contact": "~0.6.5", "@anticrm/contact": "~0.6.5",
"@anticrm/contact-resources": "~0.6.0" "@anticrm/contact-resources": "~0.6.0",
"@anticrm/view": "~0.6.0"
} }
} }

View File

@ -121,6 +121,8 @@ export class TStatus extends TAttachedDoc implements Status {
@Model(contact.class.Employee, contact.class.Person) @Model(contact.class.Employee, contact.class.Person)
@UX(contact.string.Employee, contact.icon.Person, undefined, 'name') @UX(contact.string.Employee, contact.icon.Person, undefined, 'name')
export class TEmployee extends TPerson implements Employee { export class TEmployee extends TPerson implements Employee {
active!: boolean
@Prop(Collection(contact.class.Status), contact.string.Status) @Prop(Collection(contact.class.Status), contact.string.Status)
statuses?: number statuses?: number
} }
@ -243,7 +245,7 @@ export function createModel (builder: Builder): void {
}) })
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributeEditor, { builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: contact.component.PersonEditor inlineEditor: contact.component.EmployeeEditor
}) })
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
@ -393,6 +395,22 @@ export function createModel (builder: Builder): void {
group: 'create' group: 'create'
} }
}) })
createAction(
builder,
{
action: contact.actionImpl.KickEmployee,
label: contact.string.KickEmployee,
category: contact.category.Contact,
target: contact.class.Employee,
input: 'focus',
context: {
mode: ['context'],
group: 'other'
}
},
contact.action.KickEmployee
)
} }
export { contactOperation } from './migration' export { contactOperation } from './migration'

View File

@ -17,7 +17,7 @@
import { TxOperations } from '@anticrm/core' import { TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import core from '@anticrm/model-core' import core from '@anticrm/model-core'
import contact from './index' import contact, { DOMAIN_CONTACT } from './index'
async function createSpace (tx: TxOperations): Promise<void> { async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, { const current = await tx.findOne(core.class.Space, {
@ -56,8 +56,22 @@ async function createSpace (tx: TxOperations): Promise<void> {
} }
} }
async function setActiveEmployee (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_CONTACT,
{
_class: contact.class.Employee
},
{
active: true
}
)
}
export const contactOperation: MigrateOperation = { export const contactOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {}, async migrate (client: MigrationClient): Promise<void> {
await setActiveEmployee(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> { async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System) const tx = new TxOperations(client, core.account.System)
await createSpace(tx) await createSpace(tx)

View File

@ -20,6 +20,7 @@ import {} from '@anticrm/core'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation' import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
import { IntlString, mergeIds, Resource } from '@anticrm/platform' import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui' import type { AnyComponent } from '@anticrm/ui'
import { Action, ActionCategory, ViewAction } from '@anticrm/view'
export default mergeIds(contactId, contact, { export default mergeIds(contactId, contact, {
component: { component: {
@ -38,7 +39,8 @@ export default mergeIds(contactId, contact, {
Members: '' as AnyComponent, Members: '' as AnyComponent,
MemberPresenter: '' as AnyComponent, MemberPresenter: '' as AnyComponent,
EditMember: '' as AnyComponent, EditMember: '' as AnyComponent,
EmployeeArrayEditor: '' as AnyComponent EmployeeArrayEditor: '' as AnyComponent,
EmployeeEditor: '' as AnyComponent
}, },
string: { string: {
Persons: '' as IntlString, Persons: '' as IntlString,
@ -71,5 +73,14 @@ export default mergeIds(contactId, contact, {
EmployeeCategory: '' as Ref<ObjectSearchCategory>, EmployeeCategory: '' as Ref<ObjectSearchCategory>,
PersonCategory: '' as Ref<ObjectSearchCategory>, PersonCategory: '' as Ref<ObjectSearchCategory>,
OrganizationCategory: '' as Ref<ObjectSearchCategory> OrganizationCategory: '' as Ref<ObjectSearchCategory>
},
category: {
Contact: '' as Ref<ActionCategory>
},
action: {
KickEmployee: '' as Ref<Action>
},
actionImpl: {
KickEmployee: '' as ViewAction
} }
}) })

View File

@ -54,7 +54,8 @@ export const demoOperation: MigrateOperation = {
if (current === undefined) { if (current === undefined) {
const employee = await tx.createDoc(contact.class.Employee, contact.space.Employee, { const employee = await tx.createDoc(contact.class.Employee, contact.space.Employee, {
name: 'Chen,Rosamund', name: 'Chen,Rosamund',
city: 'Mountain View' city: 'Mountain View',
active: true
}) })
await tx.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, { await tx.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {

View File

@ -0,0 +1,60 @@
<!--
// 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 contact, { Employee } from '@anticrm/contact'
import type { Class, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { ButtonKind, ButtonSize, LabelAndProps } from '@anticrm/ui'
import presentation from '..'
import IconPerson from './icons/Person.svelte'
import UserBox from './UserBox.svelte'
export let _class: Ref<Class<Employee>> = contact.class.Employee
export let options: FindOptions<Employee> | undefined = undefined
export let docQuery: DocumentQuery<Employee> | undefined = {
active: true
}
export let label: IntlString
export let placeholder: IntlString = presentation.string.Search
export let value: Ref<Employee> | null | undefined
export let allowDeselect = false
export let titleDeselect: IntlString | undefined = undefined
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let focusIndex = -1
export let showTooltip: LabelAndProps | undefined = undefined
</script>
<UserBox
{_class}
{options}
{docQuery}
{label}
icon={IconPerson}
{placeholder}
bind:value
{allowDeselect}
{titleDeselect}
{kind}
{size}
{justify}
{width}
{focusIndex}
{showTooltip}
on:change
/>

View File

@ -16,7 +16,6 @@
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core' import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { import {
AnyComponent,
Button, Button,
CheckBox, CheckBox,
createFocusManager, createFocusManager,
@ -32,6 +31,7 @@
import { createEventDispatcher, afterUpdate } from 'svelte' import { createEventDispatcher, afterUpdate } from 'svelte'
import presentation from '..' import presentation from '..'
import { createQuery, getClient } from '../utils' import { createQuery, getClient } from '../utils'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let options: FindOptions<Doc> | undefined = undefined export let options: FindOptions<Doc> | undefined = undefined
@ -53,13 +53,7 @@
export let groupBy = '_class' export let groupBy = '_class'
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
update: (doc: Doc) => string
}
| undefined = undefined
let search: string = '' let search: string = ''
let objects: Doc[] = [] let objects: Doc[] = []
@ -151,7 +145,7 @@
// We expect reference to new object. // We expect reference to new object.
const newPerson = await client.findOne(_class, { _id: res }) const newPerson = await client.findOne(_class, { _id: res })
if (newPerson !== undefined) { if (newPerson !== undefined) {
search = c.update(newPerson) search = c.update?.(newPerson) ?? ''
} }
} }
}) })

View File

@ -23,7 +23,6 @@
Button, Button,
eventToHTMLElement, eventToHTMLElement,
getFocusManager, getFocusManager,
AnyComponent,
Tooltip, Tooltip,
TooltipAlignment, TooltipAlignment,
ButtonKind, ButtonKind,
@ -33,6 +32,7 @@
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core' import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Space>> export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false } export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
@ -40,17 +40,13 @@
export let value: Ref<Space> | undefined export let value: Ref<Space> | undefined
export let focusIndex = -1 export let focusIndex = -1
export let focus = false export let focus = false
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined export let labelDirection: TooltipAlignment | undefined = undefined
export let kind: ButtonKind = 'no-border' export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center' export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined export let width: string | undefined = undefined
export let allowDeselect = false
let selected: Space | undefined let selected: Space | undefined
@ -70,6 +66,7 @@
{ {
_class, _class,
label, label,
allowDeselect,
options: { sort: { modifiedOn: -1 } }, options: { sort: { modifiedOn: -1 } },
selected, selected,
spaceQuery, spaceQuery,

View File

@ -15,7 +15,8 @@
<script lang="ts"> <script lang="ts">
import type { Class, DocumentQuery, Ref, Space } from '@anticrm/core' import type { Class, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { AnyComponent, ButtonKind, ButtonSize } from '@anticrm/ui' import { ButtonKind, ButtonSize } from '@anticrm/ui'
import { ObjectCreate } from '../types'
import SpaceSelect from './SpaceSelect.svelte' import SpaceSelect from './SpaceSelect.svelte'
export let space: Ref<Space> | undefined = undefined export let space: Ref<Space> | undefined = undefined
@ -26,13 +27,9 @@
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center' export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined export let width: string | undefined = undefined
export let allowDeselect = false
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
</script> </script>
<SpaceSelect <SpaceSelect
@ -41,6 +38,7 @@
focusIndex={-10} focusIndex={-10}
{_class} {_class}
spaceQuery={query} spaceQuery={query}
{allowDeselect}
{label} {label}
{size} {size}
{kind} {kind}

View File

@ -14,20 +14,15 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Class, Doc, DocumentQuery, Ref, Space } from '@anticrm/core' import type { Class, Doc, DocumentQuery, Ref, Space } from '@anticrm/core'
import { IntlString } from '@anticrm/platform' import { ObjectCreate } from '../types'
import { AnyComponent } from '@anticrm/ui'
import ObjectPopup from './ObjectPopup.svelte' import ObjectPopup from './ObjectPopup.svelte'
import SpaceInfo from './SpaceInfo.svelte' import SpaceInfo from './SpaceInfo.svelte'
export let _class: Ref<Class<Space>> export let _class: Ref<Class<Space>>
export let selected: Ref<Space> | undefined export let selected: Ref<Space> | undefined
export let spaceQuery: DocumentQuery<Space> | undefined export let spaceQuery: DocumentQuery<Space> | undefined
export let create: export let create: ObjectCreate | undefined = undefined
| { export let allowDeselect = false
component: AnyComponent
label: IntlString
}
| undefined = undefined
$: _create = $: _create =
create !== undefined create !== undefined
@ -43,7 +38,7 @@
{selected} {selected}
bind:docQuery={spaceQuery} bind:docQuery={spaceQuery}
multiSelect={false} multiSelect={false}
allowDeselect={false} {allowDeselect}
shadows={true} shadows={true}
create={_create} create={_create}
on:update on:update

View File

@ -15,10 +15,9 @@
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Contact, formatName } from '@anticrm/contact' import contact, { Contact, formatName } from '@anticrm/contact'
import type { Class, FindOptions, Ref } from '@anticrm/core' import type { Class, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform' import type { Asset, IntlString } from '@anticrm/platform'
import { import {
AnyComponent,
AnySvelteComponent, AnySvelteComponent,
Button, Button,
ButtonKind, ButtonKind,
@ -30,6 +29,7 @@
} from '@anticrm/ui' } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import presentation from '..' import presentation from '..'
import { ObjectCreate } from '../types'
import { getClient } from '../utils' import { getClient } from '../utils'
import IconPerson from './icons/Person.svelte' import IconPerson from './icons/Person.svelte'
import UserInfo from './UserInfo.svelte' import UserInfo from './UserInfo.svelte'
@ -38,6 +38,7 @@
export let _class: Ref<Class<Contact>> export let _class: Ref<Class<Contact>>
export let excluded: Ref<Contact>[] | undefined = undefined export let excluded: Ref<Contact>[] | undefined = undefined
export let options: FindOptions<Contact> | undefined = undefined export let options: FindOptions<Contact> | undefined = undefined
export let docQuery: DocumentQuery<Contact> | undefined = undefined
export let label: IntlString export let label: IntlString
export let icon: Asset | AnySvelteComponent | undefined = IconPerson export let icon: Asset | AnySvelteComponent | undefined = IconPerson
export let placeholder: IntlString = presentation.string.Search export let placeholder: IntlString = presentation.string.Search
@ -52,12 +53,7 @@
export let focusIndex = -1 export let focusIndex = -1
export let showTooltip: LabelAndProps | undefined = undefined export let showTooltip: LabelAndProps | undefined = undefined
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -85,6 +81,7 @@
{ {
_class, _class,
options, options,
docQuery,
ignoreUsers: excluded ?? [], ignoreUsers: excluded ?? [],
icon, icon,
allowDeselect, allowDeselect,
@ -108,54 +105,31 @@
) )
} }
} }
$: hideIcon = size === 'x-large' || (size === 'large' && kind !== 'link')
</script> </script>
<div bind:this={container} class="min-w-0" class:w-full={width === '100%'}> <div bind:this={container} class="min-w-0" class:w-full={width === '100%'}>
{#if kind !== 'link'} <Button
<Button {focusIndex}
{focusIndex} icon={hideIcon && selected ? undefined : icon}
icon={size === 'x-large' && selected ? undefined : icon} width={width ?? 'min-content'}
width={width ?? 'min-content'} {size}
{size} {kind}
{kind} {justify}
{justify} {showTooltip}
{showTooltip} on:click={_click}
on:click={_click} >
> <span slot="content" class="overflow-label disabled">
<span slot="content" class="overflow-label disabled"> {#if selected}
{#if selected} {#if hideIcon}
{#if size === 'x-large'} <UserInfo value={selected} size={kind === 'link' ? 'x-small' : 'medium'} {icon} />
<UserInfo value={selected} size={'medium'} {icon} />
{:else}
{getName(selected)}
{/if}
{:else} {:else}
<Label {label} /> {getName(selected)}
{/if} {/if}
</span> {:else}
</Button> <Label {label} />
{:else} {/if}
<Button </span>
{focusIndex} </Button>
icon={(size === 'x-large' || size === 'large') && selected ? undefined : icon}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<span slot="content" class="overflow-label disabled">
{#if selected}
{#if size === 'x-large' || size === 'large'}
<UserInfo value={selected} size={'x-small'} {icon} />
{:else}
{getName(selected)}
{/if}
{:else}
<Label {label} />
{/if}
</span>
</Button>
{/if}
</div> </div>

View File

@ -13,20 +13,22 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Person } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import type { Class, Doc, Ref } from '@anticrm/core' import type { Class, DocumentQuery, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform' import { ButtonKind, ButtonSize, Label, TooltipAlignment } from '@anticrm/ui'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import { Tooltip, showPopup, Button } from '@anticrm/ui' import { Tooltip, showPopup, Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import presentation, { CombineAvatars, UsersPopup } from '..' import presentation, { CombineAvatars, UsersPopup } from '..'
import { createQuery } from '../utils' import { createQuery } from '../utils'
import Members from './icons/Members.svelte' import Members from './icons/Members.svelte'
export let items: Ref<Person>[] = [] export let items: Ref<Employee>[] = []
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Employee>> = contact.class.Employee
export let label: IntlString export let label: IntlString
export let docQuery: DocumentQuery<Employee> | undefined = {
active: true
}
export let kind: ButtonKind = 'no-border' export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
@ -34,11 +36,11 @@
export let width: string | undefined = undefined export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined export let labelDirection: TooltipAlignment | undefined = undefined
let persons: Person[] = [] let persons: Employee[] = []
const query = createQuery() const query = createQuery()
$: query.query<Person>(_class, { _id: { $in: items } }, (result) => { $: query.query<Employee>(_class, { _id: { $in: items } }, (result) => {
persons = result persons = result
}) })
@ -50,6 +52,7 @@
{ {
_class, _class,
label, label,
docQuery,
multiSelect: true, multiSelect: true,
allowDeselect: false, allowDeselect: false,
selectedUsers: items selectedUsers: items
@ -80,9 +83,9 @@
{#if persons.length > 0} {#if persons.length > 0}
<div class="flex-row-center flex-nowrap pointer-events-none"> <div class="flex-row-center flex-nowrap pointer-events-none">
<CombineAvatars {_class} bind:items size={'inline'} /> <CombineAvatars {_class} bind:items size={'inline'} />
{#await translate(presentation.string.NumberMembers, { count: persons.length }) then text} <span class="overflow-label ml-1-5">
<span class="overflow-label ml-1-5">{text}</span> <Label label={presentation.string.NumberMembers} params={{ count: persons.length }} />
{/await} </span>
</div> </div>
{/if} {/if}
</svelte:fragment> </svelte:fragment>

View File

@ -15,17 +15,19 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, afterUpdate } from 'svelte' import { createEventDispatcher, afterUpdate } from 'svelte'
import { Contact, getFirstName, Person } from '@anticrm/contact' import { Contact, getFirstName, Person } from '@anticrm/contact'
import type { Class, Doc, FindOptions, Ref } from '@anticrm/core' import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform' import type { Asset, IntlString } from '@anticrm/platform'
import { AnyComponent, AnySvelteComponent, Icon, Label } from '@anticrm/ui' import { AnySvelteComponent, Icon, Label } from '@anticrm/ui'
import presentation from '..' import presentation from '..'
import { getClient } from '../utils' import { getClient } from '../utils'
import ObjectPopup from './ObjectPopup.svelte' import ObjectPopup from './ObjectPopup.svelte'
import UserInfo from './UserInfo.svelte' import UserInfo from './UserInfo.svelte'
import { ObjectCreate } from '../types'
export let _class: Ref<Class<Contact>> export let _class: Ref<Class<Contact>>
export let options: FindOptions<Contact> | undefined = undefined export let options: FindOptions<Contact> | undefined = undefined
export let selected: Ref<Person> | undefined export let selected: Ref<Person> | undefined
export let docQuery: DocumentQuery<Contact> | undefined = undefined
export let multiSelect: boolean = false export let multiSelect: boolean = false
export let allowDeselect: boolean = false export let allowDeselect: boolean = false
@ -35,14 +37,9 @@
export let ignoreUsers: Ref<Person>[] = [] export let ignoreUsers: Ref<Person>[] = []
export let shadows: boolean = true export let shadows: boolean = true
export let icon: Asset | AnySvelteComponent | undefined = undefined export let icon: Asset | AnySvelteComponent | undefined = undefined
export let create: ObjectCreate | undefined = undefined
const hierarchy = getClient().getHierarchy() const hierarchy = getClient().getHierarchy()
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
$: _create = $: _create =
create !== undefined create !== undefined
@ -68,6 +65,7 @@
{allowDeselect} {allowDeselect}
{titleDeselect} {titleDeselect}
{placeholder} {placeholder}
{docQuery}
groupBy={'_class'} groupBy={'_class'}
bind:selectedObjects={selectedUsers} bind:selectedObjects={selectedUsers}
bind:ignoreObjects={ignoreUsers} bind:ignoreObjects={ignoreUsers}

View File

@ -37,6 +37,7 @@ export { default as SpacesMultiPopup } from './components/SpacesMultiPopup.svelt
export { default as UserBox } from './components/UserBox.svelte' export { default as UserBox } from './components/UserBox.svelte'
export { default as UserBoxList } from './components/UserBoxList.svelte' export { default as UserBoxList } from './components/UserBoxList.svelte'
export { default as UserInfo } from './components/UserInfo.svelte' export { default as UserInfo } from './components/UserInfo.svelte'
export { default as EmployeeBox } from './components/EmployeeBox.svelte'
export { default as UsersPopup } from './components/UsersPopup.svelte' export { default as UsersPopup } from './components/UsersPopup.svelte'
export { default as MembersBox } from './components/MembersBox.svelte' export { default as MembersBox } from './components/MembersBox.svelte'
export { default as IconMembers } from './components/icons/Members.svelte' export { default as IconMembers } from './components/icons/Members.svelte'

View File

@ -1,6 +1,6 @@
import { Client, Doc } from '@anticrm/core' import { Client, Doc } from '@anticrm/core'
import { Asset, IntlString, Resource } from '@anticrm/platform' import { Asset, IntlString, Resource } from '@anticrm/platform'
import { AnySvelteComponent } from '@anticrm/ui' import { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
/** /**
* @public * @public
@ -14,6 +14,15 @@ export interface ObjectSearchResult {
iconProps?: any iconProps?: any
} }
/**
* @public
*/
export interface ObjectCreate {
component: AnyComponent
label: IntlString
update?: (doc: Doc) => string
}
/** /**
* @public * @public
*/ */

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { Class, Ref, Space } from '@anticrm/core' import { Class, Ref, Space } from '@anticrm/core'
import { SpaceMultiBoxList, UserBoxList } from '@anticrm/presentation' import { SpaceMultiBoxList, UserBoxList } from '@anticrm/presentation'
import { DropdownLabelsIntl } from '@anticrm/ui' import { DropdownLabelsIntl } from '@anticrm/ui'
@ -31,7 +31,6 @@
<div class="filterBlockContainer"> <div class="filterBlockContainer">
<div class="simpleFilterButton"> <div class="simpleFilterButton">
<UserBoxList <UserBoxList
_class={contact.class.Employee}
items={selectedParticipants} items={selectedParticipants}
label={attachment.string.FileBrowserFilterFrom} label={attachment.string.FileBrowserFilterFrom}
on:update={(evt) => { on:update={(evt) => {

View File

@ -17,7 +17,7 @@
import { AttachmentDroppable, AttachmentsPresenter } from '@anticrm/attachment-resources' import { AttachmentDroppable, AttachmentsPresenter } from '@anticrm/attachment-resources'
import type { Card } from '@anticrm/board' import type { Card } from '@anticrm/board'
import { CommentsPresenter } from '@anticrm/chunter-resources' import { CommentsPresenter } from '@anticrm/chunter-resources'
import contact, { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import type { Ref, WithLookup } from '@anticrm/core' import type { Ref, WithLookup } from '@anticrm/core'
import notification from '@anticrm/notification' import notification from '@anticrm/notification'
import view from '@anticrm/view' import view from '@anticrm/view'
@ -217,12 +217,7 @@
</div> </div>
{#if (object.members?.length ?? 0) > 0} {#if (object.members?.length ?? 0) > 0}
<div class="flex justify-end mt-1 mb-2" style:pointer-events={dragoverAttachment ? 'none' : 'all'}> <div class="flex justify-end mt-1 mb-2" style:pointer-events={dragoverAttachment ? 'none' : 'all'}>
<UserBoxList <UserBoxList items={object.members} label={board.string.Members} on:update={updateMembers} />
_class={contact.class.Employee}
items={object.members}
label={board.string.Members}
on:update={updateMembers}
/>
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import contact, { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core' import { Ref } from '@anticrm/core'
import { UserBoxList } from '@anticrm/presentation' import { UserBoxList } from '@anticrm/presentation'
@ -8,4 +8,4 @@
export let value: Ref<Employee>[] export let value: Ref<Employee>[]
</script> </script>
<UserBoxList items={value} _class={contact.class.Employee} label={board.string.Members} on:update /> <UserBoxList items={value} label={board.string.Members} on:update />

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Ref } from '@anticrm/core' import { Ref } from '@anticrm/core'
import { createQuery, getClient, UserBox, MessageBox } from '@anticrm/presentation' import { createQuery, getClient, EmployeeBox, MessageBox } from '@anticrm/presentation'
import type { TodoItem } from '@anticrm/task' import type { TodoItem } from '@anticrm/task'
import task, { calcRank } from '@anticrm/task' import task, { calcRank } from '@anticrm/task'
import { import {
@ -30,7 +30,7 @@
DateRangePresenter DateRangePresenter
} from '@anticrm/ui' } from '@anticrm/ui'
import { ContextMenu, HTMLPresenter } from '@anticrm/view-resources' import { ContextMenu, HTMLPresenter } from '@anticrm/view-resources'
import contact, { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import board from '../../plugin' import board from '../../plugin'
import { getDateIcon } from '../../utils/BoardUtils' import { getDateIcon } from '../../utils/BoardUtils'
@ -273,8 +273,7 @@
on:change={(e) => updateDueDate(item, e.detail)} on:change={(e) => updateDueDate(item, e.detail)}
noShift noShift
/> />
<UserBox <EmployeeBox
_class={contact.class.Employee}
label={board.string.Assignee} label={board.string.Assignee}
bind:value={item.assignee} bind:value={item.assignee}
allowDeselect={true} allowDeselect={true}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact' import { Employee, EmployeeAccount } from '@anticrm/contact'
import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core' import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core'
import { Card, getClient, UserBoxList } from '@anticrm/presentation' import { Card, getClient, UserBoxList } from '@anticrm/presentation'
import ui, { EditBox, DateRangePresenter } from '@anticrm/ui' import ui, { EditBox, DateRangePresenter } from '@anticrm/ui'
@ -71,6 +71,6 @@
<svelte:fragment slot="pool"> <svelte:fragment slot="pool">
<DateRangePresenter bind:value withTime editable labelNull={ui.string.SelectDate} /> <DateRangePresenter bind:value withTime editable labelNull={ui.string.SelectDate} />
<DateRangePresenter bind:value={dueDate} labelNull={calendar.string.DueTo} withTime editable /> <DateRangePresenter bind:value={dueDate} labelNull={calendar.string.DueTo} withTime editable />
<UserBoxList _class={contact.class.Employee} bind:items={participants} label={calendar.string.Participants} /> <UserBoxList bind:items={participants} label={calendar.string.Participants} />
</svelte:fragment> </svelte:fragment>
</Card> </Card>

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact' import { Employee, EmployeeAccount } from '@anticrm/contact'
import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core' import { Class, Doc, getCurrentAccount, Ref } from '@anticrm/core'
import { Card, getClient, UserBoxList } from '@anticrm/presentation' import { Card, getClient, UserBoxList } from '@anticrm/presentation'
import ui, { EditBox, DateRangePresenter } from '@anticrm/ui' import ui, { EditBox, DateRangePresenter } from '@anticrm/ui'
@ -74,6 +74,6 @@
<svelte:fragment slot="pool"> <svelte:fragment slot="pool">
<!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> --> <!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> -->
<DateRangePresenter bind:value withTime={true} editable={true} labelNull={ui.string.SelectDate} /> <DateRangePresenter bind:value withTime={true} editable={true} labelNull={ui.string.SelectDate} />
<UserBoxList _class={contact.class.Employee} bind:items={participants} label={calendar.string.Participants} /> <UserBoxList bind:items={participants} label={calendar.string.Participants} />
</svelte:fragment> </svelte:fragment>
</Card> </Card>

View File

@ -73,6 +73,9 @@
<UsersPopup <UsersPopup
selected={undefined} selected={undefined}
_class={contact.class.Employee} _class={contact.class.Employee}
docQuery={{
active: true
}}
multiSelect={true} multiSelect={true}
allowDeselect={true} allowDeselect={true}
selectedUsers={selectedEmployees} selectedUsers={selectedEmployees}

View File

@ -70,9 +70,5 @@
dispatch('close') dispatch('close')
}} }}
> >
<UserBoxList <UserBoxList label={chunter.string.Members} on:update={(evt) => (employeeIds = evt.detail)} />
_class={contact.class.Employee}
label={chunter.string.Members}
on:update={(evt) => (employeeIds = evt.detail)}
/>
</SpaceCreateCard> </SpaceCreateCard>

View File

@ -63,6 +63,8 @@
"Member": "Member", "Member": "Member",
"Members": "Members", "Members": "Members",
"NoMembers": "No members added", "NoMembers": "No members added",
"AddMember": "Add member" "AddMember": "Add member",
"KickEmployee": "Kick an employee",
"KickEmployeeDescr": "Are you sure you want to kick the employee out of the workspace? This action cannot be undone"
} }
} }

View File

@ -63,6 +63,8 @@
"Member": "Сотрудник", "Member": "Сотрудник",
"Members": "Сотрудники", "Members": "Сотрудники",
"NoMembers": "Нет добавленных сотрудников", "NoMembers": "Нет добавленных сотрудников",
"AddMember": "Добавить сотрудника" "AddMember": "Добавить сотрудника",
"KickEmployee": "Исключить сотрудника",
"KickEmployeeDescr": "Вы действительно хотите выгнать сотрудника из рабочего пространства? Это действие нельзя отменить"
} }
} }

View File

@ -43,6 +43,7 @@
"@anticrm/notification-resources": "~0.6.0", "@anticrm/notification-resources": "~0.6.0",
"@anticrm/panel": "~0.6.0", "@anticrm/panel": "~0.6.0",
"@anticrm/view-resources": "~0.6.0", "@anticrm/view-resources": "~0.6.0",
"@anticrm/attachment": "~0.6.1" "@anticrm/attachment": "~0.6.1",
"@anticrm/login-resources": "~0.6.2"
} }
} }

View File

@ -1,17 +1,16 @@
<script lang="ts"> <script lang="ts">
import { Person } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core' import { Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform' import { IntlString } from '@anticrm/platform'
import { UserBoxList } from '@anticrm/presentation' import { UserBoxList } from '@anticrm/presentation'
import contact from '../plugin'
export let label: IntlString export let label: IntlString
export let value: Ref<Person>[] export let value: Ref<Employee>[]
export let onChange: (refs: Ref<Person>[]) => void export let onChange: (refs: Ref<Employee>[]) => void
let timer: any let timer: any
function onUpdate (evt: CustomEvent<Ref<Person>[]>): void { function onUpdate (evt: CustomEvent<Ref<Employee>[]>): void {
clearTimeout(timer) clearTimeout(timer)
timer = setTimeout(() => { timer = setTimeout(() => {
onChange(evt.detail) onChange(evt.detail)
@ -19,13 +18,4 @@
} }
</script> </script>
<UserBoxList <UserBoxList items={value} {label} on:update={onUpdate} kind={'link'} size={'medium'} justify={'left'} width={'100%'} />
_class={contact.class.Employee}
items={value}
{label}
on:update={onUpdate}
kind={'link'}
size={'medium'}
justify={'left'}
width={'100%'}
/>

View File

@ -0,0 +1,49 @@
<!--
// 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 { Employee } from '@anticrm/contact'
import { DocumentQuery, Ref, RefTo } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { EmployeeBox } from '@anticrm/presentation'
import contact from '../plugin'
import { ButtonKind, ButtonSize } from '@anticrm/ui'
export let value: Ref<Employee> | undefined
export let label: IntlString = contact.string.Employee
export let onChange: (value: any) => void
export let type: RefTo<Employee> | undefined
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
$: _class = type?.to ?? contact.class.Employee
const query: DocumentQuery<Employee> = {
active: true
}
</script>
<EmployeeBox
{_class}
docQuery={query}
{label}
{kind}
{size}
{justify}
{width}
bind:value
on:change={(e) => onChange(e.detail)}
/>

View File

@ -14,10 +14,11 @@
// limitations under the License. // limitations under the License.
// //
import { Contact, formatName } from '@anticrm/contact' import { Contact, Employee, formatName } from '@anticrm/contact'
import { Class, Client, Ref } from '@anticrm/core' import { Class, Client, Ref } from '@anticrm/core'
import { Resources } from '@anticrm/platform' import { Resources } from '@anticrm/platform'
import { Avatar, ObjectSearchResult, UserInfo } from '@anticrm/presentation' import { Avatar, getClient, MessageBox, ObjectSearchResult, UserInfo } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import Channels from './components/Channels.svelte' import Channels from './components/Channels.svelte'
import ChannelsEditor from './components/ChannelsEditor.svelte' import ChannelsEditor from './components/ChannelsEditor.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte' import ChannelsPresenter from './components/ChannelsPresenter.svelte'
@ -45,6 +46,8 @@ import Members from './components/Members.svelte'
import MemberPresenter from './components/MemberPresenter.svelte' import MemberPresenter from './components/MemberPresenter.svelte'
import EditMember from './components/EditMember.svelte' import EditMember from './components/EditMember.svelte'
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte' import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
import EmployeeEditor from './components/EmployeeEditor.svelte'
import { leaveWorkspace } from '@anticrm/login-resources'
export { export {
Channels, Channels,
@ -55,7 +58,8 @@ export {
ChannelsDropdown, ChannelsDropdown,
EmployeePresenter, EmployeePresenter,
EmployeeBrowser, EmployeeBrowser,
MemberPresenter MemberPresenter,
EmployeeEditor
} }
async function queryContact ( async function queryContact (
@ -73,7 +77,30 @@ async function queryContact (
})) }))
} }
async function kickEmployee (doc: Employee): Promise<void> {
const client = getClient()
const email = await client.findOne(contact.class.EmployeeAccount, { employee: doc._id })
if (email === undefined) return
showPopup(
MessageBox,
{
label: contact.string.KickEmployee,
message: contact.string.KickEmployeeDescr
},
undefined,
(res?: boolean) => {
if (res === true) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
leaveWorkspace(email.email)
}
}
)
}
export default async (): Promise<Resources> => ({ export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee
},
component: { component: {
PersonEditor, PersonEditor,
OrganizationEditor, OrganizationEditor,
@ -94,7 +121,8 @@ export default async (): Promise<Resources> => ({
Members, Members,
MemberPresenter, MemberPresenter,
EditMember, EditMember,
EmployeeArrayEditor EmployeeArrayEditor,
EmployeeEditor
}, },
completion: { completion: {
EmployeeQuery: async (client: Client, query: string) => await queryContact(contact.class.Employee, client, query), EmployeeQuery: async (client: Client, query: string) => await queryContact(contact.class.Employee, client, query),

View File

@ -56,6 +56,8 @@ export default mergeIds(contactId, contact, {
Member: '' as IntlString, Member: '' as IntlString,
Members: '' as IntlString, Members: '' as IntlString,
NoMembers: '' as IntlString, NoMembers: '' as IntlString,
AddMember: '' as IntlString AddMember: '' as IntlString,
KickEmployee: '' as IntlString,
KickEmployeeDescr: '' as IntlString
} }
}) })

View File

@ -105,6 +105,7 @@ export interface Status extends AttachedDoc {
* @public * @public
*/ */
export interface Employee extends Person { export interface Employee extends Person {
active: boolean
statuses?: number statuses?: number
} }

View File

@ -13,9 +13,9 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact, { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core' import { Ref } from '@anticrm/core'
import { Card, getClient, SpaceSelector, UserBox } from '@anticrm/presentation' import { Card, getClient, SpaceSelector, EmployeeBox } from '@anticrm/presentation'
import { Button, createFocusManager, EditBox, FocusHandler } from '@anticrm/ui' import { Button, createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import hr from '../plugin' import hr from '../plugin'
@ -74,16 +74,13 @@
<SpaceSelector _class={hr.class.Department} label={hr.string.ParentDepartmentLabel} bind:space /> <SpaceSelector _class={hr.class.Department} label={hr.string.ParentDepartmentLabel} bind:space />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="pool"> <svelte:fragment slot="pool">
<UserBox <EmployeeBox
focusIndex={3} focusIndex={3}
_class={contact.class.Employee}
label={hr.string.TeamLead} label={hr.string.TeamLead}
placeholder={hr.string.TeamLead} placeholder={hr.string.TeamLead}
bind:value={lead} bind:value={lead}
allowDeselect allowDeselect
titleDeselect={hr.string.UnAssignLead} titleDeselect={hr.string.UnAssignLead}
kind={'no-border'}
size={'small'}
/> />
</svelte:fragment> </svelte:fragment>
</Card> </Card>

View File

@ -52,7 +52,10 @@
_class: contact.class.Employee, _class: contact.class.Employee,
selected: value.$lookup?.teamLead, selected: value.$lookup?.teamLead,
allowDeselect: true, allowDeselect: true,
placeholder: hr.string.TeamLead placeholder: hr.string.TeamLead,
docQuery: {
active: true
}
}, },
eventToHTMLElement(event), eventToHTMLElement(event),
changeLead changeLead

View File

@ -35,6 +35,7 @@
{size} {size}
{kind} {kind}
{justify} {justify}
allowDeselect
{width} {width}
bind:space={value} bind:space={value}
on:change={(e) => onChange(e.detail)} on:change={(e) => onChange(e.detail)}

View File

@ -77,6 +77,9 @@
UsersPopup, UsersPopup,
{ {
_class: contact.class.Employee, _class: contact.class.Employee,
docQuery: {
active: true
},
ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id) ignoreUsers: employees.filter((p) => p.department === objectId).map((p) => p._id)
}, },
eventToHTMLElement(e), eventToHTMLElement(e),

View File

@ -434,3 +434,34 @@ export async function changePassword (oldPassword: string, password: string): Pr
body: serialize(request) body: serialize(request)
}) })
} }
export async function leaveWorkspace (email: string): Promise<void> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return
}
}
const token = fetchMetadataLocalStorage(login.metadata.LoginToken) as string
const request: Request<[string]> = {
method: 'leaveWorkspace',
params: [email]
}
await fetch(accountsUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: serialize(request)
})
}

View File

@ -17,7 +17,7 @@
import contact from '@anticrm/contact' import contact from '@anticrm/contact'
import { Account, Class, Client, Doc, generateId, Ref, SortingOrder, Space } from '@anticrm/core' import { Account, Class, Client, Doc, generateId, Ref, SortingOrder, Space } from '@anticrm/core'
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform' import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
import { Card, createQuery, getClient, SpaceSelector, UserBox } from '@anticrm/presentation' import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import type { Applicant, Candidate } from '@anticrm/recruit' import type { Applicant, Candidate } from '@anticrm/recruit'
import task, { calcRank, SpaceWithStates, State } from '@anticrm/task' import task, { calcRank, SpaceWithStates, State } from '@anticrm/task'
import ui, { import ui, {
@ -269,16 +269,13 @@
create={{ component: recruit.component.CreateCandidate, label: recruit.string.CreateTalent }} create={{ component: recruit.component.CreateCandidate, label: recruit.string.CreateTalent }}
/> />
{/if} {/if}
<UserBox <EmployeeBox
focusIndex={2} focusIndex={2}
_class={contact.class.Employee}
label={recruit.string.AssignRecruiter} label={recruit.string.AssignRecruiter}
placeholder={recruit.string.Recruiters} placeholder={recruit.string.Recruiters}
bind:value={doc.assignee} bind:value={doc.assignee}
allowDeselect allowDeselect
titleDeselect={recruit.string.UnAssignRecruiter} titleDeselect={recruit.string.UnAssignRecruiter}
kind={'no-border'}
size={'small'}
/> />
{#if states.length > 0} {#if states.length > 0}
<Button <Button

View File

@ -180,6 +180,6 @@
on:change={updateStart} on:change={updateStart}
/> />
<DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable /> <DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable />
<UserBoxList _class={contact.class.Employee} bind:items={doc.participants} label={calendar.string.Participants} /> <UserBoxList bind:items={doc.participants} label={calendar.string.Participants} />
</svelte:fragment> </svelte:fragment>
</Card> </Card>

View File

@ -43,6 +43,8 @@
"EditAttribute": "Edit attribute", "EditAttribute": "Edit attribute",
"CreateEnum": "Create enum", "CreateEnum": "Create enum",
"Enums": "Enums", "Enums": "Enums",
"NewValue": "New value" "NewValue": "New value",
"Leave": "Leave workspace",
"LeaveDescr": "Are you sure you want to leave the workspace? This action cannot be undone."
} }
} }

View File

@ -43,6 +43,8 @@
"EditAttribute": "Редактирование атрибута", "EditAttribute": "Редактирование атрибута",
"CreateEnum": "Создать справочник", "CreateEnum": "Создать справочник",
"Enums": "Справочники", "Enums": "Справочники",
"NewValue": "Новое значение" "NewValue": "Новое значение",
"Leave": "Покинуть рабочее пространство",
"LeaveDescr": "Вы действительно хотите покинуть рабочее пространство? Отменить это действие невозможно"
} }
} }

View File

@ -15,15 +15,16 @@
<script lang="ts"> <script lang="ts">
import { AttributeEditor, createQuery, EditableAvatar, getClient } from '@anticrm/presentation' import { AttributeEditor, createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
import setting from '@anticrm/setting' import setting from '../plugin'
import { EditBox, Icon, Label, createFocusManager, FocusHandler } from '@anticrm/ui' import { EditBox, Icon, Label, createFocusManager, FocusHandler, Button, showPopup } from '@anticrm/ui'
import contact, { Employee, EmployeeAccount, getFirstName, getLastName } from '@anticrm/contact' import contact, { Employee, EmployeeAccount, getFirstName, getLastName } from '@anticrm/contact'
import contactRes from '@anticrm/contact-resources/src/plugin' import contactRes from '@anticrm/contact-resources/src/plugin'
import { getCurrentAccount } from '@anticrm/core' import { getCurrentAccount } from '@anticrm/core'
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import attachment from '@anticrm/attachment' import attachment from '@anticrm/attachment'
import { changeName } from '@anticrm/login-resources' import { changeName, leaveWorkspace } from '@anticrm/login-resources'
import { ChannelsEditor } from '@anticrm/contact-resources' import { ChannelsEditor } from '@anticrm/contact-resources'
import MessageBox from '@anticrm/presentation/src/components/MessageBox.svelte'
const client = getClient() const client = getClient()
let employee: Employee | undefined let employee: Employee | undefined
@ -71,6 +72,22 @@
} }
const manager = createFocusManager() const manager = createFocusManager()
async function leave (): Promise<void> {
showPopup(
MessageBox,
{
label: setting.string.Leave,
message: setting.string.LeaveDescr
},
undefined,
async (res?: boolean) => {
if (res === true) {
await leaveWorkspace(getCurrentAccount().email)
}
}
)
}
</script> </script>
<FocusHandler {manager} /> <FocusHandler {manager} />
@ -80,51 +97,62 @@
<div class="ac-header__icon"><Icon icon={setting.icon.EditProfile} size={'medium'} /></div> <div class="ac-header__icon"><Icon icon={setting.icon.EditProfile} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.EditProfile} /></div> <div class="ac-header__title"><Label label={setting.string.EditProfile} /></div>
</div> </div>
{#if employee} <div class="ac-body p-10">
<div class="ac-body columns p-10"> {#if employee}
<div class="mr-8"> <div class="flex flex-grow">
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} /> <div class="mr-8">
</div> <EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
<div class="flex-grow flex-col"> </div>
<div class="flex-col"> <div class="flex-grow flex-col">
<div class="name"> <div class="flex-col">
<EditBox <div class="name">
placeholder={contactRes.string.PersonFirstNamePlaceholder} <EditBox
maxWidth="20rem" placeholder={contactRes.string.PersonFirstNamePlaceholder}
bind:value={firstName} maxWidth="20rem"
focus bind:value={firstName}
focusIndex={1} focus
on:change={() => { focusIndex={1}
changeName(firstName, lastName) on:change={() => {
}} changeName(firstName, lastName)
/> }}
</div> />
<div class="name"> </div>
<EditBox <div class="name">
placeholder={contactRes.string.PersonLastNamePlaceholder} <EditBox
maxWidth="20rem" placeholder={contactRes.string.PersonLastNamePlaceholder}
bind:value={lastName} maxWidth="20rem"
focusIndex={2} bind:value={lastName}
on:change={() => { focusIndex={2}
changeName(firstName, lastName) on:change={() => {
}} changeName(firstName, lastName)
/> }}
</div> />
<div class="location"> </div>
<AttributeEditor <div class="location">
maxWidth="20rem" <AttributeEditor
_class={contact.class.Person} maxWidth="20rem"
object={employee} _class={contact.class.Person}
focusIndex={3} object={employee}
key="city" focusIndex={3}
/> key="city"
</div> />
</div>
</div>
<div class="separator" />
<ChannelsEditor attachedTo={employee._id} attachedClass={employee._class} focusIndex={10} allowOpen={false} />
</div> </div>
<div class="separator" />
<ChannelsEditor attachedTo={employee._id} attachedClass={employee._class} focusIndex={10} allowOpen={false} />
</div> </div>
{/if}
<div class="footer">
<Button
icon={setting.icon.Signout}
label={setting.string.Leave}
on:click={() => {
leave()
}}
/>
</div> </div>
{/if} </div>
</div> </div>
<style lang="scss"> <style lang="scss">
@ -144,4 +172,8 @@
height: 1px; height: 1px;
background-color: var(--theme-card-divider); background-color: var(--theme-card-divider);
} }
.footer {
align-self: flex-end;
}
</style> </style>

View File

@ -14,19 +14,12 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Class, Doc, DocumentQuery, Enum, Ref } from '@anticrm/core' import type { Class, Doc, DocumentQuery, Enum, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform' import { ObjectCreate, ObjectPopup } from '@anticrm/presentation'
import { ObjectPopup } from '@anticrm/presentation'
import { AnyComponent } from '@anticrm/ui'
export let _class: Ref<Class<Enum>> export let _class: Ref<Class<Enum>>
export let selected: Ref<Enum> | undefined export let selected: Ref<Enum> | undefined
export let query: DocumentQuery<Enum> | undefined export let query: DocumentQuery<Enum> | undefined
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
$: _create = $: _create =
create !== undefined create !== undefined

View File

@ -21,24 +21,19 @@
Button, Button,
eventToHTMLElement, eventToHTMLElement,
getFocusManager, getFocusManager,
AnyComponent,
Tooltip, Tooltip,
TooltipAlignment TooltipAlignment
} from '@anticrm/ui' } from '@anticrm/ui'
import EnumPopup from './EnumPopup.svelte' import EnumPopup from './EnumPopup.svelte'
import core, { Ref, Class, DocumentQuery, Enum } from '@anticrm/core' import core, { Ref, Class, DocumentQuery, Enum } from '@anticrm/core'
import { ObjectCreate } from '@anticrm/presentation'
export let label: IntlString export let label: IntlString
export let value: Enum | undefined export let value: Enum | undefined
export let focusIndex = -1 export let focusIndex = -1
export let focus = false export let focus = false
export let create: export let create: ObjectCreate | undefined = undefined
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined export let labelDirection: TooltipAlignment | undefined = undefined
const _class: Ref<Class<Enum>> = core.class.Enum const _class: Ref<Class<Enum>> = core.class.Enum

View File

@ -39,6 +39,8 @@ export default mergeIds(settingId, setting, {
EditAttribute: '' as IntlString, EditAttribute: '' as IntlString,
CreateEnum: '' as IntlString, CreateEnum: '' as IntlString,
Enums: '' as IntlString, Enums: '' as IntlString,
NewValue: '' as IntlString NewValue: '' as IntlString,
Leave: '' as IntlString,
LeaveDescr: '' as IntlString
} }
}) })

View File

@ -83,6 +83,9 @@
{ {
_class: contact.class.Employee, _class: contact.class.Employee,
selected: value?._id, selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true, allowDeselect: true,
placeholder: task.string.AssignThisTask placeholder: task.string.AssignThisTask
}, },

View File

@ -16,10 +16,9 @@
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import { AttachedData, Ref } from '@anticrm/core' import { AttachedData, Ref } from '@anticrm/core'
import { getClient, UserBox } from '@anticrm/presentation' import { getClient, EmployeeBox } from '@anticrm/presentation'
import { Issue } from '@anticrm/tracker' import { Issue } from '@anticrm/tracker'
import { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui' import { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import contact from '@anticrm/contact'
import tracker from '../../plugin' import tracker from '../../plugin'
export let value: Issue | AttachedData<Issue> export let value: Issue | AttachedData<Issue>
@ -45,8 +44,7 @@
</script> </script>
{#if value} {#if value}
<UserBox <EmployeeBox
_class={contact.class.Employee}
label={tracker.string.Assignee} label={tracker.string.Assignee}
placeholder={tracker.string.Assignee} placeholder={tracker.string.Assignee}
value={value.assignee} value={value.assignee}

View File

@ -83,6 +83,9 @@
{ {
_class: contact.class.Employee, _class: contact.class.Employee,
selected: value?._id, selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true, allowDeselect: true,
placeholder: tracker.string.AssignTo placeholder: tracker.string.AssignTo
}, },

View File

@ -72,6 +72,9 @@
{ {
_class: contact.class.Employee, _class: contact.class.Employee,
selected: value?._id, selected: value?._id,
docQuery: {
active: true
},
allowDeselect: true, allowDeselect: true,
placeholder: tracker.string.ProjectLeadSearchPlaceholder placeholder: tracker.string.ProjectLeadSearchPlaceholder
}, },

View File

@ -13,10 +13,9 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact from '@anticrm/contact'
import { Data, Ref } from '@anticrm/core' import { Data, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform' import { IntlString } from '@anticrm/platform'
import { Card, getClient, SpaceSelector, UserBox, UserBoxList } from '@anticrm/presentation' import { Card, getClient, SpaceSelector, EmployeeBox, UserBoxList } from '@anticrm/presentation'
import { Project, ProjectStatus, Team } from '@anticrm/tracker' import { Project, ProjectStatus, Team } from '@anticrm/tracker'
import { DatePresenter, EditBox } from '@anticrm/ui' import { DatePresenter, EditBox } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
@ -83,19 +82,14 @@
</div> </div>
<div slot="pool" class="flex-row-center text-sm gap-1-5"> <div slot="pool" class="flex-row-center text-sm gap-1-5">
<ProjectStatusSelector selectedProjectStatus={object.status} onProjectStatusChange={handleProjectStatusChanged} /> <ProjectStatusSelector selectedProjectStatus={object.status} onProjectStatusChange={handleProjectStatusChanged} />
<UserBox <EmployeeBox
_class={contact.class.Employee}
label={tracker.string.ProjectLead} label={tracker.string.ProjectLead}
placeholder={tracker.string.AssignTo} placeholder={tracker.string.AssignTo}
bind:value={object.lead} bind:value={object.lead}
allowDeselect allowDeselect
titleDeselect={tracker.string.Unassigned} titleDeselect={tracker.string.Unassigned}
/> />
<UserBoxList <UserBoxList bind:items={object.members} label={tracker.string.ProjectStatusPlaceholder} />
_class={contact.class.Employee}
bind:items={object.members}
label={tracker.string.ProjectStatusPlaceholder}
/>
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate --> <!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
<DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable /> <DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable />
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable /> <DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />

View File

@ -61,6 +61,9 @@
selectedUsers: value.members, selectedUsers: value.members,
allowDeselect: true, allowDeselect: true,
multiSelect: true, multiSelect: true,
docQuery: {
active: true
},
placeholder: tracker.string.ProjectMembersSearchPlaceholder placeholder: tracker.string.ProjectMembersSearchPlaceholder
}, },
eventToHTMLElement(event), eventToHTMLElement(event),

View File

@ -14,6 +14,8 @@
"Leave": "Leave", "Leave": "Leave",
"Joined": "Joined", "Joined": "Joined",
"Join": "Join", "Join": "Join",
"BrowseSpaces": "Browse spaces" "BrowseSpaces": "Browse spaces",
"AccountDisabled": "Account is disabled",
"AccountDisabledDescr": "Please contact the workspace administrator"
} }
} }

View File

@ -14,6 +14,8 @@
"Leave": "Покинуть", "Leave": "Покинуть",
"Joined": "Вы присоеденились", "Joined": "Вы присоеденились",
"Join": "Присоедениться", "Join": "Присоедениться",
"BrowseSpaces": "Обзор пространств" "BrowseSpaces": "Обзор пространств",
"AccountDisabled": "Аккаунт отключен",
"AccountDisabledDescr": "Пожалуйста свяжитесь с администратором"
} }
} }

View File

@ -28,6 +28,7 @@
DatePickerPopup, DatePickerPopup,
fetchMetadataLocalStorage, fetchMetadataLocalStorage,
getCurrentLocation, getCurrentLocation,
Label,
location, location,
Location, Location,
navigate, navigate,
@ -358,7 +359,7 @@
</script> </script>
<svelte:window on:resize={windowResize} /> <svelte:window on:resize={windowResize} />
{#if client} {#if employee?.active === true}
<ActionHandler /> <ActionHandler />
<svg class="svg-mask"> <svg class="svg-mask">
<clipPath id="notify-normal"> <clipPath id="notify-normal">
@ -426,9 +427,7 @@
showPopup(AccountPopup, {}, 'account') showPopup(AccountPopup, {}, 'account')
}} }}
> >
{#if employee} <Avatar avatar={employee.avatar} size={'medium'} />
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
</div> </div>
</div> </div>
</div> </div>
@ -507,7 +506,10 @@
</Popup> </Popup>
<DatePickerPopup /> <DatePickerPopup />
{:else} {:else}
No client <div class="flex-col-center justify-center h-full flex-grow">
<h1><Label label={workbench.string.AccountDisabled} /></h1>
<Label label={workbench.string.AccountDisabledDescr} />
</div>
{/if} {/if}
<style lang="scss"> <style lang="scss">

View File

@ -34,7 +34,9 @@ export default mergeIds(workbenchId, workbench, {
Leave: '' as IntlString, Leave: '' as IntlString,
Joined: '' as IntlString, Joined: '' as IntlString,
Join: '' as IntlString, Join: '' as IntlString,
BrowseSpaces: '' as IntlString BrowseSpaces: '' as IntlString,
AccountDisabled: '' as IntlString,
AccountDisabledDescr: '' as IntlString
}, },
component: { component: {
SpacePanel: '' as AnyComponent SpacePanel: '' as AnyComponent

View File

@ -14,12 +14,12 @@
// //
import contact, { Employee } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core' import core, { Ref, SortingOrder, Tx, TxFactory, TxMixin, TxUpdateDoc } from '@anticrm/core'
import hr, { Department, DepartmentMember, Staff } from '@anticrm/hr' import hr, { Department, DepartmentMember, Staff } from '@anticrm/hr'
import { extractTx, TriggerControl } from '@anticrm/server-core' import { extractTx, TriggerControl } from '@anticrm/server-core'
async function getOldDepartment ( async function getOldDepartment (
currentTx: TxMixin<Employee, Staff>, currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
control: TriggerControl control: TriggerControl
): Promise<Ref<Department> | undefined> { ): Promise<Ref<Department> | undefined> {
const txes = await control.findAll<TxMixin<Employee, Staff>>( const txes = await control.findAll<TxMixin<Employee, Staff>>(
@ -109,6 +109,12 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
const lastDepartment = await getOldDepartment(ctx, control) const lastDepartment = await getOldDepartment(ctx, control)
const departmentId = ctx.attributes.department const departmentId = ctx.attributes.department
if (departmentId === null) {
if (lastDepartment !== undefined) {
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(control.txFactory, targetAccount._id, [], removed)
}
}
const push = await buildHierarchy(departmentId, control) const push = await buildHierarchy(departmentId, control)
if (lastDepartment === undefined) { if (lastDepartment === undefined) {
@ -124,9 +130,36 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
return [] return []
} }
/**
* @public
*/
export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = extractTx(tx)
if (core.class.TxUpdateDoc !== actualTx._class) {
return []
}
const ctx = actualTx as TxUpdateDoc<Employee>
if (ctx.objectClass !== contact.class.Employee || ctx.operations.active !== false) {
return []
}
const targetAccount = (
await control.modelDb.findAll(contact.class.EmployeeAccount, {
employee: ctx.objectId
})
)[0]
if (targetAccount === undefined) return []
const lastDepartment = await getOldDepartment(ctx, control)
if (lastDepartment === undefined) return []
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(control.txFactory, targetAccount._id, [], removed)
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({ export default async () => ({
trigger: { trigger: {
OnDepartmentStaff OnDepartmentStaff,
OnEmployeeDeactivate
} }
}) })

View File

@ -13,8 +13,8 @@
// limitations under the f. // limitations under the f.
// //
import contact, { combineName } from '@anticrm/contact' import contact, { combineName, Employee } from '@anticrm/contact'
import core, { TxOperations } from '@anticrm/core' import core, { Ref, TxOperations } from '@anticrm/core'
import platform, { import platform, {
getMetadata, getMetadata,
PlatformError, PlatformError,
@ -461,6 +461,14 @@ export async function assignWorkspace (db: Db, email: string, workspace: string)
if (account !== null) await createEmployeeAccount(account, workspace) if (account !== null) await createEmployeeAccount(account, workspace)
} }
async function createEmployee (ops: TxOperations, name: string): Promise<Ref<Employee>> {
return await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name,
city: '',
active: true
})
}
async function createEmployeeAccount (account: Account, workspace: string): Promise<void> { async function createEmployeeAccount (account: Account, workspace: string): Promise<void> {
const connection = await connect(getTransactor(), workspace, account.email) const connection = await connect(getTransactor(), workspace, account.email)
try { try {
@ -472,10 +480,7 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email }) const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email })
if (existingAccount === undefined) { if (existingAccount === undefined) {
const employee = await ops.createDoc(contact.class.Employee, contact.space.Employee, { const employee = await createEmployee(ops, name)
name,
city: ''
})
await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, { await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, {
email: account.email, email: account.email,
@ -486,13 +491,15 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee }) const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee })
if (employee === undefined) { if (employee === undefined) {
// Employee was deleted, let's restore it. // Employee was deleted, let's restore it.
const employeeId = await ops.createDoc(contact.class.Employee, contact.space.Employee, { const employeeId = await createEmployee(ops, name)
name,
city: ''
})
await ops.updateDoc(contact.class.EmployeeAccount, existingAccount.space, existingAccount._id, { await ops.updateDoc(contact.class.EmployeeAccount, existingAccount.space, existingAccount._id, {
employee: employeeId employee: employeeId
}) })
} else if (!employee.active) {
await ops.update(employee, {
active: true
})
} }
} }
} finally { } finally {
@ -599,12 +606,81 @@ export async function dropAccount (db: Db, email: string): Promise<void> {
if (account === null) { if (account === null) {
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email })) throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
} }
const workspaces = await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.find({ _id: { $in: account.workspaces } })
.toArray()
await Promise.all(
workspaces.map(async (ws) => {
return await deactivateEmployeeAccount(account, ws.workspace)
})
)
await db.collection(ACCOUNT_COLLECTION).deleteOne({ _id: account._id }) await db.collection(ACCOUNT_COLLECTION).deleteOne({ _id: account._id })
await db await db
.collection<Workspace>(WORKSPACE_COLLECTION) .collection<Workspace>(WORKSPACE_COLLECTION)
.updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } }) .updateMany({ _id: { $in: account.workspaces } }, { $pull: { accounts: account._id } })
} }
/**
* @public
*/
export async function leaveWorkspace (db: Db, token: string, email: string): Promise<void> {
const tokenData = decodeToken(token)
const account = await getAccount(db, email)
if (account === null) {
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
}
if (tokenData.email !== email) {
const currentAccount = await getAccount(db, tokenData.email)
if (currentAccount === null) {
throw new PlatformError(
new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: tokenData.email })
)
}
}
const workspace = await getWorkspace(db, tokenData.workspace)
if (workspace === null) {
throw new PlatformError(
new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace: tokenData.workspace })
)
}
await deactivateEmployeeAccount(account, workspace.workspace)
await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.updateOne({ _id: workspace._id }, { $pull: { accounts: account._id } })
await db
.collection<Account>(ACCOUNT_COLLECTION)
.updateOne({ _id: account._id }, { $pull: { workspaces: workspace._id } })
}
async function deactivateEmployeeAccount (account: Account, workspace: string): Promise<void> {
const connection = await connect(getTransactor(), workspace, account.email)
try {
const ops = new TxOperations(connection, core.account.System)
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email })
if (existingAccount !== undefined) {
const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee })
if (employee !== undefined) {
await ops.update(employee, {
active: false
})
}
}
} finally {
await connection.close()
}
}
function wrap (f: (db: Db, ...args: any[]) => Promise<any>) { function wrap (f: (db: Db, ...args: any[]) => Promise<any>) {
return async function (db: Db, request: Request<any[]>, token?: string): Promise<Response<any>> { return async function (db: Db, request: Request<any[]>, token?: string): Promise<Response<any>> {
if (token !== undefined) request.params.unshift(token) if (token !== undefined) request.params.unshift(token)
@ -634,6 +710,7 @@ export const methods = {
createWorkspace: wrap(createUserWorkspace), createWorkspace: wrap(createUserWorkspace),
assignWorkspace: wrap(assignWorkspace), assignWorkspace: wrap(assignWorkspace),
removeWorkspace: wrap(removeWorkspace), removeWorkspace: wrap(removeWorkspace),
leaveWorkspace: wrap(leaveWorkspace),
listWorkspaces: wrap(listWorkspaces), listWorkspaces: wrap(listWorkspaces),
changeName: wrap(changeName), changeName: wrap(changeName),
changePassword: wrap(changePassword) changePassword: wrap(changePassword)