HR: Fixes to Vacancy/Application creation (#1753)

This commit is contained in:
Andrey Sobolev 2022-05-16 18:41:22 +07:00 committed by GitHub
parent 84844b65a9
commit 2defbc434b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 745 additions and 501 deletions

View File

@ -29,7 +29,6 @@ export default mergeIds(contactId, contact, {
CreatePerson: '' as AnyComponent,
EditPerson: '' as AnyComponent,
EditOrganization: '' as AnyComponent,
CreateOrganization: '' as AnyComponent,
CreatePersons: '' as AnyComponent,
CreateOrganizations: '' as AnyComponent,
OrganizationPresenter: '' as AnyComponent,

View File

@ -390,6 +390,42 @@ export function createModel (builder: Builder): void {
}
})
createAction(builder, {
action: view.actionImpl.ShowPopup,
actionProps: {
component: recruit.component.CreateVacancy,
element: 'top'
},
label: recruit.string.CreateVacancy,
icon: recruit.icon.Create,
keyBinding: [],
input: 'none',
category: recruit.category.Recruit,
target: core.class.Doc,
context: {
mode: ['workbench', 'browser'],
application: recruit.app.Recruit
}
})
createAction(builder, {
action: view.actionImpl.ShowPopup,
actionProps: {
component: recruit.component.CreateApplication,
element: 'top'
},
label: recruit.string.CreateApplication,
icon: recruit.icon.Create,
keyBinding: [],
input: 'none',
category: recruit.category.Recruit,
target: core.class.Doc,
context: {
mode: ['workbench', 'browser'],
application: recruit.app.Recruit
}
})
builder.createDoc(
task.class.KanbanTemplateSpace,
core.space.Model,
@ -438,6 +474,17 @@ export function createModel (builder: Builder): void {
createReviewModel(builder)
// createAction(builder, { ...viewTemplates.open, target: recruit.class.Vacancy, context: { mode: ['browser', 'context'] } })
createAction(builder, {
...viewTemplates.open,
target: recruit.class.Vacancy,
context: { mode: ['browser', 'context'] },
action: workbench.actionImpl.Navigate,
actionProps: {
mode: 'space'
}
})
createAction(builder, {
...viewTemplates.open,
target: recruit.class.Applicant,

View File

@ -58,7 +58,6 @@ export default mergeIds(recruitId, recruit, {
ReviewValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
},
component: {
CreateVacancy: '' as AnyComponent,
CreateApplication: '' as AnyComponent,
KanbanCard: '' as AnyComponent,
ApplicationPresenter: '' as AnyComponent,
@ -68,7 +67,6 @@ export default mergeIds(recruitId, recruit, {
TemplatesIcon: '' as AnyComponent,
Applications: '' as AnyComponent,
Candidates: '' as AnyComponent,
CreateCandidate: '' as AnyComponent,
SkillsView: '' as AnyComponent,
Vacancies: '' as AnyComponent,

View File

@ -13,13 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import Avatar from './icons/Avatar.svelte'
import { Asset } from '@anticrm/platform'
import { AnySvelteComponent, Icon, IconSize } from '@anticrm/ui'
import { getBlobURL, getFileUrl } from '../utils'
import Avatar from './icons/Avatar.svelte'
export let avatar: string | null | undefined = undefined
export let direct: Blob | undefined = undefined
export let size: 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: IconSize
export let icon: Asset | AnySvelteComponent | undefined = undefined
let url: string | undefined
$: if (direct !== undefined) {
@ -40,7 +42,7 @@
{/if}
<img class="ava-{size} ava-mask" src={url} alt={''} />
{:else}
<Avatar {size} />
<Icon icon={icon ?? Avatar} {size} />
{/if}
</div>

View File

@ -15,19 +15,10 @@
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { Button, IconClose, Label, MiniToggle } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { Button, Label, IconClose, MiniToggle } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
import presentation from '..'
export let spaceClass: Ref<Class<Space>> | undefined = undefined
export let space: Ref<Space> | undefined = undefined
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let spaceLabel: IntlString | undefined = undefined
export let spacePlaceholder: IntlString | undefined = undefined
export let label: IntlString
export let labelProps: any | undefined = undefined
export let okAction: () => void
@ -41,12 +32,8 @@
<form id={label} class="antiCard dialog" on:submit|preventDefault={() => {}}>
<div class="antiCard-header">
<div class="antiCard-header__title-wrap">
{#if (spaceClass && spaceLabel && spacePlaceholder) || $$slots.space}
{#if $$slots.space}
<slot name="space" />
{:else if spaceClass && spaceLabel && spacePlaceholder}
<SpaceSelect focus focusIndex={-10} _class={spaceClass} {spaceQuery} label={spaceLabel} bind:value={space} />
{/if}
{#if $$slots.header}
<slot name="header" />
<span class="antiCard-header__divider"></span>
{/if}
<span class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></span>
@ -63,7 +50,7 @@
</div>
</div>
<div class="antiCard-content"><slot /></div>
{#if (spaceClass && spaceLabel && spacePlaceholder) || $$slots.pool}
{#if $$slots.header || $$slots.pool}
<div class="antiCard-pool">
<slot name="pool" />
</div>

View File

@ -17,10 +17,11 @@
import { createQuery } from '../utils'
import { Person } from '@anticrm/contact'
import Avatar from './Avatar.svelte'
import { IconSize } from '@anticrm/ui'
export let _class: Ref<Class<Doc>>
export let items: Ref<Person>[] = []
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: IconSize
export let limit: number = 3
let persons: Person[] = []

View File

@ -14,14 +14,14 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { showPopup } from '@anticrm/ui'
import { IconSize, showPopup } from '@anticrm/ui'
import Avatar from './Avatar.svelte'
import EditAvatarPopup from './EditAvatarPopup.svelte'
import { getFileUrl } from '../utils'
export let avatar: string | null | undefined = undefined
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: IconSize
export let direct: Blob | undefined = undefined
const dispatch = createEventDispatcher()

View File

@ -0,0 +1,203 @@
<!--
// 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 type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import {
AnyComponent,
Button,
CheckBox,
createFocusManager,
EditBox,
FocusHandler,
IconAdd,
ListView,
showPopup,
Tooltip
} from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { createQuery, getClient } from '../utils'
export let _class: Ref<Class<Doc>>
export let options: FindOptions<Doc> | undefined = undefined
export let selected: Ref<Doc> | undefined = undefined
export let docQuery: DocumentQuery<Doc> | undefined = undefined
export let multiSelect: boolean = false
export let allowDeselect: boolean = false
export let titleDeselect: IntlString | undefined = undefined
export let placeholder: IntlString = presentation.string.Search
export let selectedObjects: Ref<Doc>[] = []
export let ignoreObjects: Ref<Doc>[] = []
export let shadows: boolean = true
export let searchField: string = 'name'
export let create:
| {
component: AnyComponent
label: IntlString
update: (doc: Doc) => string
}
| undefined
let search: string = ''
let objects: Doc[] = []
const dispatch = createEventDispatcher()
const query = createQuery()
$: query.query<Doc>(
_class,
{
...(docQuery ?? {}),
[searchField]: { $like: '%' + search + '%' },
_id: { $nin: ignoreObjects }
},
(result) => {
objects = result
},
{ ...(options ?? {}), limit: 200 }
)
const isSelected = (person: Doc): boolean => {
if (selectedObjects.filter((p) => p === person._id).length > 0) return true
return false
}
const checkSelected = (person: Doc, objects: Doc[]): void => {
selectedObjects = isSelected(person)
? selectedObjects.filter((p) => p !== person._id)
: [...selectedObjects, person._id]
dispatch('update', selectedObjects)
}
const client = getClient()
let selection = 0
let list: ListView
async function handleSelection (evt: Event | undefined, objects: Doc[], selection: number): Promise<void> {
const person = objects[selection]
if (!multiSelect) {
selected = person._id === selected ? undefined : person._id
dispatch('close', selected !== undefined ? person : undefined)
} else {
checkSelected(person, objects)
}
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, objects, selection)
}
if (key.code === 'Escape') {
key.preventDefault()
key.stopPropagation()
dispatch('close')
}
}
const manager = createFocusManager()
function onCreate (): void {
if (create === undefined) {
return
}
const c = create
showPopup(c.component, {}, 'top', async (res) => {
if (res != null) {
// We expect reference to new object.
const newPerson = await client.findOne(_class, { _id: res })
if (newPerson !== undefined) {
search = c.update(newPerson)
}
}
})
}
</script>
<FocusHandler {manager} />
<div class="selectPopup" class:plainContainer={!shadows} on:keydown={onKeydown}>
<div class="header flex-row-center flex-bletween p-1">
<div class="flex-grow">
<EditBox kind={'search-style'} focusIndex={1} focus bind:value={search} {placeholder} />
</div>
{#if create !== undefined}
<Tooltip label={create.label}>
<Button focusIndex={2} kind={'transparent'} icon={IconAdd} on:click={onCreate} />
</Tooltip>
{/if}
</div>
<div class="scroll">
<div class="box">
<ListView bind:this={list} count={objects.length} bind:selection>
<svelte:fragment slot="item" let:item>
{@const obj = objects[item]}
<button
class="menu-item w-full"
on:click={() => {
handleSelection(undefined, objects, item)
}}
>
{#if multiSelect}
<div class="check pointer-events-none">
<CheckBox checked={isSelected(obj)} primary />
</div>
{/if}
<slot name="item" item={obj} />
{#if allowDeselect && obj._id === selected}
<div class="check-right pointer-events-none">
{#if titleDeselect}
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
<CheckBox checked circle primary />
</Tooltip>
{:else}
<CheckBox checked circle primary />
{/if}
</div>
{/if}
</button>
</svelte:fragment>
</ListView>
</div>
</div>
</div>
<style lang="scss">
.plainContainer {
color: var(--caption-color);
background-color: var(--body-color);
border: 1px solid var(--button-border-color);
border-radius: 0.25rem;
box-shadow: none;
}
</style>

View File

@ -16,7 +16,17 @@
import type { IntlString } from '@anticrm/platform'
import { getClient } from '../utils'
import { Label, showPopup, IconFolder, Button, eventToHTMLElement, getFocusManager } from '@anticrm/ui'
import {
Label,
showPopup,
IconFolder,
Button,
eventToHTMLElement,
getFocusManager,
AnyComponent,
Tooltip,
TooltipAlignment
} from '@anticrm/ui'
import SpacesPopup from './SpacesPopup.svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
@ -27,6 +37,13 @@
export let value: Ref<Space> | undefined
export let focusIndex = -1
export let focus = false
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let selected: Space | undefined
@ -40,22 +57,36 @@
$: updateSelected(value)
</script>
<Button
{focus}
{focusIndex}
icon={IconFolder}
size={'small'}
kind={'no-border'}
on:click={(ev) => {
showPopup(SpacesPopup, { _class, spaceQuery }, eventToHTMLElement(ev), (result) => {
if (result) {
value = result._id
mgr?.setFocusPos(focusIndex)
}
})
}}
>
<span slot="content" class="text-sm">
{#if selected}{selected.name}{:else}<Label {label} />{/if}
</span>
</Button>
<Tooltip {label} fill={false} direction={labelDirection}>
<Button
{focus}
{focusIndex}
icon={IconFolder}
size={'small'}
kind={'no-border'}
on:click={(ev) => {
showPopup(
SpacesPopup,
{
_class,
label,
options: { sort: { modifiedOn: -1 } },
selected,
spaceQuery,
create
},
eventToHTMLElement(ev),
(result) => {
if (result) {
value = result._id
mgr?.setFocusPos(focusIndex)
}
}
)
}}
>
<span slot="content" class="text-sm">
{#if selected}{selected.name}{:else}<Label {label} />{/if}
</span>
</Button>
</Tooltip>

View File

@ -0,0 +1,34 @@
<!--
// 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 type { Class, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
export let space: Ref<Space> | undefined = undefined
export let _class: Ref<Class<Space>>
export let query: DocumentQuery<Space> = { archived: false }
export let label: IntlString
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
</script>
<SpaceSelect {create} focus focusIndex={-10} {_class} spaceQuery={query} {label} bind:value={space} />

View File

@ -13,94 +13,43 @@
// limitations under the License.
-->
<script lang="ts">
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import { translate } from '@anticrm/platform'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { createQuery } from '../utils'
import type { Class, Doc, DocumentQuery, Ref, Space } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import ObjectPopup from './ObjectPopup.svelte'
import SpaceInfo from './SpaceInfo.svelte'
import presentation from '..'
import { ListView } from '@anticrm/ui'
export let _class: Ref<Class<Space>>
export let selected: Ref<Space> | undefined
export let spaceQuery: DocumentQuery<Space> | undefined
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
let search: string = ''
let objects: Space[] = []
let phTraslate: string = ''
$: translate(presentation.string.Search, {}).then((res) => {
phTraslate = res
})
let input: HTMLInputElement
const dispatch = createEventDispatcher()
const query = createQuery()
$: query.query(_class, { ...(spaceQuery ?? {}), name: { $like: '%' + search + '%' } }, (result) => {
objects = result
})
afterUpdate(() => {
dispatch('update', Date.now())
})
onMount(() => {
if (input) input.focus()
})
let selection = 0
let list: ListView
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
const space = objects[selection]
dispatch('close', space)
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, selection)
}
if (key.code === 'Escape') {
key.preventDefault()
key.stopPropagation()
dispatch('close')
}
}
$: _create =
create !== undefined
? {
...create,
update: (doc: Doc) => (doc as Space).name
}
: undefined
</script>
<div class="selectPopup" on:keydown={onKeydown}>
<div class="header">
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:input={() => {}} on:change />
</div>
<div class="scroll">
<div class="box">
<ListView
bind:this={list}
count={objects.length}
bind:selection
on:click={(evt) => handleSelection(evt, evt.detail)}
>
<svelte:fragment slot="item" let:item>
{@const space = objects[item]}
<button
class="menu-item flex-between"
on:click={() => {
handleSelection(undefined, item)
}}
>
<SpaceInfo size={'large'} value={space} />
</button>
</svelte:fragment>
</ListView>
</div>
</div>
</div>
<ObjectPopup
{_class}
{selected}
bind:docQuery={spaceQuery}
multiSelect={false}
allowDeselect={false}
shadows={true}
create={_create}
on:update
on:close
>
<svelte:fragment slot="item" let:item={space}>
<SpaceInfo size={'large'} value={space} />
</svelte:fragment>
</ObjectPopup>

View File

@ -15,10 +15,20 @@
-->
<script lang="ts">
import contact, { Contact, formatName } from '@anticrm/contact'
import type { Class, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { TooltipAlignment, ButtonKind, ButtonSize, getFocusManager } from '@anticrm/ui'
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
import type { Class, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform'
import {
AnyComponent,
AnySvelteComponent,
Button,
ButtonKind,
ButtonSize,
getFocusManager,
Label,
showPopup,
Tooltip,
TooltipAlignment
} from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { getClient } from '../utils'
@ -27,7 +37,10 @@
import UsersPopup from './UsersPopup.svelte'
export let _class: Ref<Class<Contact>>
export let excluded: Ref<Contact>[] | undefined = undefined
export let options: FindOptions<Contact> | undefined = undefined
export let label: IntlString
export let icon: Asset | AnySvelteComponent | undefined = IconPerson
export let placeholder: IntlString = presentation.string.Search
export let value: Ref<Contact> | null | undefined
export let allowDeselect = false
@ -40,6 +53,13 @@
export let labelDirection: TooltipAlignment | undefined = undefined
export let focusIndex = -1
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
const dispatch = createEventDispatcher()
let selected: Contact | undefined
@ -63,7 +83,17 @@
if (!readonly) {
showPopup(
UsersPopup,
{ _class, allowDeselect, selected: value, titleDeselect, placeholder },
{
_class,
options,
ignoreUsers: excluded ?? [],
icon,
allowDeselect,
selected: value,
titleDeselect,
placeholder,
create
},
container,
(result) => {
if (result === null) {
@ -86,7 +116,7 @@
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
<Button
{focusIndex}
icon={size === 'x-large' && selected ? undefined : IconPerson}
icon={size === 'x-large' && selected ? undefined : icon}
width={width ?? 'min-content'}
{size}
{kind}
@ -119,7 +149,7 @@
<span slot="content" style="overflow: hidden">
{#if selected}
{#if size === 'x-large'}
<UserInfo value={selected} size={'medium'} />
<UserInfo value={selected} size={'medium'} {icon} />
{:else}
{getName(selected)}
{/if}

View File

@ -16,14 +16,17 @@
import Avatar from './Avatar.svelte'
import { formatName, Person } from '@anticrm/contact'
import { Asset } from '@anticrm/platform'
import { AnySvelteComponent, IconSize } from '@anticrm/ui'
export let value: Person
export let subtitle: string | undefined = undefined
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let size: IconSize
export let icon: Asset | AnySvelteComponent | undefined = undefined
</script>
<div class="flex-row-center" on:click>
<Avatar avatar={value.avatar} {size} />
<Avatar avatar={value.avatar} {size} {icon} />
<div class="flex-col ml-2 min-w-0">
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
<div class="content-accent-color overflow-label text-left">{formatName(value.name)}</div>

View File

@ -13,19 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { createEventDispatcher, onMount } from 'svelte'
import { Tooltip, CheckBox, ListView } from '@anticrm/ui'
import { Contact, getFirstName, Person } from '@anticrm/contact'
import type { Class, Doc, FindOptions, Ref } from '@anticrm/core'
import type { Asset, IntlString } from '@anticrm/platform'
import { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
import presentation from '..'
import ObjectPopup from './ObjectPopup.svelte'
import UserInfo from './UserInfo.svelte'
import type { Ref, Class } from '@anticrm/core'
import type { Person } from '@anticrm/contact'
import { createQuery } from '../utils'
import presentation from '..'
export let _class: Ref<Class<Person>>
export let _class: Ref<Class<Contact>>
export let options: FindOptions<Contact> | undefined = undefined
export let selected: Ref<Person> | undefined
export let multiSelect: boolean = false
@ -35,125 +32,45 @@
export let selectedUsers: Ref<Person>[] = []
export let ignoreUsers: Ref<Person>[] = []
export let shadows: boolean = true
export let icon: Asset | AnySvelteComponent | undefined = undefined
let search: string = ''
let objects: Person[] = []
let input: HTMLInputElement
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
const dispatch = createEventDispatcher()
const query = createQuery()
$: query.query<Person>(
_class,
{ name: { $like: '%' + search + '%' }, _id: { $nin: ignoreUsers } },
(result) => {
objects = result
},
{ limit: 200 }
)
let phTraslate: string = ''
$: if (placeholder) {
translate(placeholder, {}).then((res) => {
phTraslate = res
})
}
const isSelected = (person: Person): boolean => {
if (selectedUsers.filter((p) => p === person._id).length > 0) return true
return false
}
const checkSelected = (person: Person): void => {
selectedUsers = isSelected(person) ? selectedUsers.filter((p) => p !== person._id) : [...selectedUsers, person._id]
objects = objects
dispatch('update', selectedUsers)
}
onMount(() => {
if (input) input.focus()
})
let selection = 0
let list: ListView
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
const person = objects[selection]
if (!multiSelect) {
selected = person._id === selected ? undefined : person._id
dispatch('close', selected !== undefined ? person : undefined)
} else {
checkSelected(person)
}
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, selection)
}
if (key.code === 'Escape') {
key.preventDefault()
key.stopPropagation()
dispatch('close')
}
}
$: _create =
create !== undefined
? {
...create,
update: (doc: Doc) => {
const name = getFirstName((doc as Contact).name)
return name.length > 0 ? name : (doc as Contact).name
}
}
: undefined
</script>
<div class="selectPopup" class:plainContainer={!shadows} on:keydown={onKeydown}>
<div class="header">
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} on:change />
</div>
<div class="scroll">
<div class="box">
<ListView bind:this={list} count={objects.length} bind:selection>
<svelte:fragment slot="item" let:item>
{@const person = objects[item]}
<button
class="menu-item w-full"
on:click={() => {
handleSelection(undefined, item)
}}
>
{#if multiSelect}
<div class="check pointer-events-none">
<CheckBox checked={isSelected(person)} primary />
</div>
{/if}
<UserInfo size={'x-small'} value={person} />
{#if allowDeselect && person._id === selected}
<div class="check-right pointer-events-none">
{#if titleDeselect}
<Tooltip label={titleDeselect ?? presentation.string.Deselect}>
<CheckBox checked circle primary />
</Tooltip>
{:else}
<CheckBox checked circle primary />
{/if}
</div>
{/if}
</button>
</svelte:fragment>
</ListView>
<ObjectPopup
{_class}
{options}
{selected}
{multiSelect}
{allowDeselect}
{titleDeselect}
{placeholder}
bind:selectedObjects={selectedUsers}
bind:ignoreObjects={ignoreUsers}
{shadows}
create={_create}
on:update
on:close
>
<svelte:fragment slot="item" let:item={person}>
<div class="flex flex-grow overflow-label">
<UserInfo size={'x-small'} value={person} {icon} />
</div>
</div>
</div>
<style lang="scss">
.plainContainer {
color: var(--caption-color);
background-color: var(--body-color);
border: 1px solid var(--button-border-color);
border-radius: 0.25rem;
box-shadow: none;
}
</style>
</svelte:fragment>
</ObjectPopup>

View File

@ -14,7 +14,9 @@
// limitations under the License.
-->
<script lang="ts">
export let size: 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
import { IconSize } from '@anticrm/ui'
export let size: IconSize
const fill: string = 'var(--theme-caption-color)'
</script>

View File

@ -21,17 +21,18 @@ export { default as AttributeBarEditor } from './components/AttributeBarEditor.s
export { default as AttributeEditor } from './components/AttributeEditor.svelte'
export { default as AttributesBar } from './components/AttributesBar.svelte'
export { default as Avatar } from './components/Avatar.svelte'
export { default as CombineAvatars } from './components/CombineAvatars.svelte'
export { default as Card } from './components/Card.svelte'
export { default as CombineAvatars } from './components/CombineAvatars.svelte'
export { default as EditableAvatar } from './components/EditableAvatar.svelte'
export { default as Members } from './components/Members.svelte'
export { default as MessageBox } from './components/MessageBox.svelte'
export { default as MessageViewer } from './components/MessageViewer.svelte'
export { default as PDFViewer } from './components/PDFViewer.svelte'
export { default as SpaceCreateCard } from './components/SpaceCreateCard.svelte'
export { default as SpacesMultiPopup } from './components/SpacesMultiPopup.svelte'
export { default as SpaceMultiBoxList } from './components/SpaceMultiBoxList.svelte'
export { default as SpaceSelect } from './components/SpaceSelect.svelte'
export { default as SpaceSelector } from './components/SpaceSelector.svelte'
export { default as SpacesMultiPopup } from './components/SpacesMultiPopup.svelte'
export { default as UserBox } from './components/UserBox.svelte'
export { default as UserBoxList } from './components/UserBoxList.svelte'
export { default as UserInfo } from './components/UserInfo.svelte'

View File

@ -15,6 +15,7 @@
<script lang="ts">
import type { Asset, IntlString } from '@anticrm/platform'
import { Button, DropdownPopup, showPopup, Tooltip } from '..'
import { getFocusManager } from '../focus'
import type { AnySvelteComponent, ButtonKind, ButtonSize, ListItem, TooltipAlignment } from '../types'
import Label from './Label.svelte'
@ -29,14 +30,18 @@
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
export let focusIndex = -1
let container: HTMLElement
let opened: boolean = false
const mgr = getFocusManager()
</script>
<div bind:this={container} class="min-w-0">
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
<Button
{focusIndex}
{icon}
width={width ?? 'min-content'}
{size}
@ -48,6 +53,7 @@
showPopup(DropdownPopup, { title: label, items, icon }, container, (result) => {
if (result) selected = result
opened = false
mgr?.setFocusPos(focusIndex)
})
}
}}

View File

@ -19,6 +19,7 @@
import { showPopup, Tooltip, Button, Label } from '..'
import { createEventDispatcher } from 'svelte'
import ui from '../plugin'
import { getFocusManager } from '../focus'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let label: IntlString
@ -31,6 +32,7 @@
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
export let focusIndex = -1
let container: HTMLElement
let opened: boolean = false
@ -44,11 +46,13 @@
}
const dispatch = createEventDispatcher()
const mgr = getFocusManager()
</script>
<div bind:this={container} class="min-w-0">
<Tooltip {label} fill={width === '100%'} direction={labelDirection}>
<Button
{focusIndex}
{icon}
width={width ?? 'min-content'}
{size}
@ -63,6 +67,7 @@
dispatch('selected', result)
}
opened = false
mgr?.setFocusPos(focusIndex)
})
}
}}

View File

@ -19,6 +19,7 @@
import CheckBox from './CheckBox.svelte'
import type { DropdownTextItem } from '../types'
import plugin from '../plugin'
import ListView from './ListView.svelte'
export let placeholder: IntlString = plugin.string.SearchDots
export let items: DropdownTextItem[]
@ -30,25 +31,48 @@
phTraslate = res
})
const dispatch = createEventDispatcher()
const btns: HTMLButtonElement[] = []
let searchInput: HTMLInputElement
const keyDown = (ev: KeyboardEvent, n: number): void => {
if (ev.key === 'ArrowDown') {
if (n === btns.length - 1) btns[0].focus()
else btns[n + 1].focus()
} else if (ev.key === 'ArrowUp') {
if (n === 0) btns[btns.length - 1].focus()
else btns[n - 1].focus()
} else searchInput.focus()
}
onMount(() => {
if (searchInput) searchInput.focus()
})
let selection = 0
let list: ListView
$: objects = items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase()))
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
const item = objects[selection]
dispatch('close', item.id)
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, selection)
}
if (key.code === 'Escape') {
key.preventDefault()
key.stopPropagation()
dispatch('close')
}
}
</script>
<div class="selectPopup">
<div class="selectPopup" on:keydown={onKeydown}>
<div class="header">
<input
bind:this={searchInput}
@ -61,22 +85,23 @@
</div>
<div class="scroll">
<div class="box">
{#each items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase())) as item, i}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<button
class="menu-item flex-between"
on:mouseover={(ev) => ev.currentTarget.focus()}
on:keydown={(ev) => keyDown(ev, i)}
on:click={() => {
dispatch('close', item.id)
}}
>
<div class="flex-grow caption-color lines-limit-2">{item.label}</div>
{#if item.id === selected}
<div class="check-right"><CheckBox checked primary /></div>
{/if}
</button>
{/each}
<ListView bind:this={list} count={objects.length} bind:selection>
<svelte:fragment slot="item" let:item={idx}>
{@const item = objects[idx]}
<button
class="menu-item flex-between w-full"
on:click={() => {
dispatch('close', item.id)
}}
>
<div class="flex-grow caption-color lines-limit-2">{item.label}</div>
{#if item.id === selected}
<div class="check-right"><CheckBox checked primary /></div>
{/if}
</button>
</svelte:fragment>
</ListView>
</div>
</div>
</div>

View File

@ -20,6 +20,7 @@
import type { AnySvelteComponent, ListItem } from '../types'
import plugin from '../plugin'
import Icon from './Icon.svelte'
import ListView from './ListView.svelte'
export let icon: Asset | AnySvelteComponent
export let placeholder: IntlString = plugin.string.SearchDots
@ -33,25 +34,50 @@
})
}
const dispatch = createEventDispatcher()
const btns: HTMLButtonElement[] = []
let searchInput: HTMLInputElement
const keyDown = (ev: KeyboardEvent, n: number): void => {
if (ev.key === 'ArrowDown') {
if (n === btns.length - 1) btns[0].focus()
else btns[n + 1].focus()
} else if (ev.key === 'ArrowUp') {
if (n === 0) btns[btns.length - 1].focus()
else btns[n - 1].focus()
} else searchInput.focus()
}
onMount(() => {
if (searchInput) searchInput.focus()
})
let selection = 0
let list: ListView
$: objects = items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase()))
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
const item = objects[selection]
if (item.isSelectable ?? true) {
dispatch('close', item)
}
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, selection)
}
if (key.code === 'Escape') {
key.preventDefault()
key.stopPropagation()
dispatch('close')
}
}
</script>
<div class="selectPopup">
<div class="selectPopup" on:keydown={onKeydown}>
<div class="header">
<input
bind:this={searchInput}
@ -64,34 +90,32 @@
</div>
<div class="scroll">
<div class="box">
{#each items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase())) as item, i}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<button
bind:this={btns[i]}
class="flex-between menu-item"
disabled={item.isSelectable === false}
on:mouseover={(ev) => ev.currentTarget.focus()}
on:keydown={(ev) => keyDown(ev, i)}
on:click={() => {
if (item.isSelectable ?? true) {
dispatch('close', item)
}
}}
>
{#if item.image || icon}
<div class="flex-center img" class:image={item.image}>
{#if item.image}
<img src={item.image} alt={item.label} />
{:else if typeof icon === 'string'}
<Icon {icon} size={'small'} />
{:else}
<svelte:component this={icon} size={'small'} />
{/if}
</div>
{/if}
<div class="flex-grow caption-color font-{item.fontWeight} pl-{item.paddingLeft}">{item.label}</div>
</button>
{/each}
<ListView bind:this={list} count={objects.length} bind:selection>
<svelte:fragment slot="item" let:item={idx}>
{@const item = objects[idx]}
<button
class="flex-between menu-item w-full"
disabled={item.isSelectable === false}
on:click={(evt) => {
handleSelection(evt, idx)
}}
>
{#if item.image || icon}
<div class="flex-center img" class:image={item.image}>
{#if item.image}
<img src={item.image} alt={item.label} />
{:else if typeof icon === 'string'}
<Icon {icon} size={'small'} />
{:else}
<svelte:component this={icon} size={'small'} />
{/if}
</div>
{/if}
<div class="flex-grow caption-color font-{item.fontWeight} pl-{item.paddingLeft}">{item.label}</div>
</button>
</svelte:fragment>
</ListView>
</div>
</div>
</div>

View File

@ -14,10 +14,10 @@
-->
<script lang="ts">
import { Asset, getMetadata } from '@anticrm/platform'
import { AnySvelteComponent } from '../types'
import { AnySvelteComponent, IconSize } from '../types'
export let icon: Asset | AnySvelteComponent
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'full'
export let size: IconSize
export let fill = 'currentColor'
export let filled: boolean = false

View File

@ -28,6 +28,8 @@
let triggerHTML: HTMLElement
let shown: boolean = false
$: shown = !!($tooltip.label || $tooltip.component)
let toHandler: number = -1
</script>
<div
@ -35,8 +37,16 @@
class:fill
name={`tooltip-${label}`}
bind:this={triggerHTML}
on:mouseleave={() => {
clearTimeout(toHandler)
}}
on:mousemove={() => {
if (!shown) showTooltip(label, triggerHTML, direction, component, props, anchor, onUpdate)
if (!shown) {
clearTimeout(toHandler)
toHandler = setTimeout(() => {
showTooltip(label, triggerHTML, direction, component, props, anchor, onUpdate)
}, 250)
}
}}
>
<slot />

View File

@ -32,7 +32,8 @@ export type {
PopupAlignment,
PopupPositionElement,
ButtonKind,
ButtonSize
ButtonSize,
IconSize
} from './types'
// export { applicationShortcutKey } from './utils'
export { getCurrentLocation, locationToUrl, navigate, location } from './location'

View File

@ -80,6 +80,8 @@ export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
export type VerticalAlignment = 'top' | 'bottom'
export type HorizontalAlignment = 'left' | 'right'
export type IconSize = 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'full'
export interface LabelAndProps {
label: IntlString | undefined
element: HTMLElement | undefined

View File

@ -1,11 +1,11 @@
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git.
{
"tsdocVersion": "0.12",
"toolPackages": [
{
"packageName": "@microsoft/api-extractor",
"packageVersion": "7.23.0"
}
]
}
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git.
{
"tsdocVersion": "0.12",
"toolPackages": [
{
"packageName": "@microsoft/api-extractor",
"packageVersion": "7.23.0"
}
]
}

View File

@ -73,7 +73,7 @@
<div class="p-10 select-text">
{#if txes}
<Grid column={1} rowGap={1.5}>
{#each txes as tx (tx.tx._id)}
{#each txes as tx}
<TxView {tx} {viewlets} />
{/each}
</Grid>

View File

@ -18,6 +18,7 @@
import { AttachedData, generateId, Ref, SortingOrder, Space } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient } from '@anticrm/presentation'
import SpaceSelector from '@anticrm/presentation/src/components/SpaceSelector.svelte'
import task, { calcRank } from '@anticrm/task'
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
@ -77,14 +78,13 @@
label={board.string.CreateCard}
okAction={createCard}
canSave={title.length > 0}
spaceClass={board.class.Board}
spaceLabel={board.string.BoardName}
spacePlaceholder={board.string.SelectBoard}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
>
<svelte:fragment slot="header">
<SpaceSelector _class={board.class.Board} label={board.string.BoardName} bind:space={_space} />
</svelte:fragment>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.5}>
<EditBox

View File

@ -59,7 +59,6 @@
label={calendar.string.CreateReminder}
okAction={saveReminder}
canSave={title !== undefined && title.trim().length > 0 && participants.length > 0}
{space}
on:close={() => {
dispatch('close')
}}

View File

@ -57,7 +57,6 @@
okAction={saveReminder}
okLabel={presentation.string.Save}
canSave={title.trim().length > 0 && startDate > 0 && participants.length > 0}
{space}
on:close={() => {
dispatch('close')
}}

View File

@ -121,7 +121,6 @@
let actions: Action[] = []
let addBtn: HTMLButtonElement
const btns: HTMLButtonElement[] = []
let anchor: HTMLElement
let opened: number | undefined = undefined
function filterUndefined (channels: AttachedData<Channel>[]): AttachedData<Channel>[] {
@ -215,7 +214,6 @@
</script>
<div
bind:this={anchor}
class="{displayItems.length === 0 ? 'clear-mins' : 'buttons-group'} {kind === 'no-border'
? 'xsmall-gap'
: 'xxsmall-gap'}"

View File

@ -51,7 +51,7 @@
)
}
dispatch('close')
dispatch('close', id)
}
let channels: AttachedData<Channel>[] = []
@ -65,7 +65,6 @@
label={contact.string.CreateOrganization}
okAction={createOrganization}
canSave={object.name.length > 0}
space={contact.space.Contacts}
on:close={() => {
dispatch('close')
}}

View File

@ -88,7 +88,6 @@
label={contact.string.CreatePerson}
okAction={createPerson}
canSave={firstName.length > 0 && lastName.length > 0 && matches.length === 0}
bind:space={contact.space.Contacts}
on:close={() => {
dispatch('close')
}}

View File

@ -17,8 +17,8 @@
import { Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import { Dropdown } from '@anticrm/ui'
import type { TooltipAlignment, ButtonKind, ButtonSize } from '@anticrm/ui'
import { ListItem } from '@anticrm/ui/src/types'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
@ -32,6 +32,7 @@
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
export let focusIndex = -1
const query = createQuery()
const dispatch = createEventDispatcher()
@ -69,6 +70,7 @@
</script>
<Dropdown
{focusIndex}
icon={Company}
{label}
placeholder={label}

View File

@ -23,7 +23,6 @@ export default mergeIds(contactId, contact, {
Contacts: '' as IntlString,
CreatePerson: '' as IntlString,
CreatePersons: '' as IntlString,
CreateOrganization: '' as IntlString,
OrganizationNamePlaceholder: '' as IntlString,
OrganizationsNamePlaceholder: '' as IntlString,
PersonFirstNamePlaceholder: '' as IntlString,

View File

@ -159,7 +159,8 @@ const contactPlugin = plugin(contactId, {
Status: '' as Ref<Class<Status>>
},
component: {
SocialEditor: '' as AnyComponent
SocialEditor: '' as AnyComponent,
CreateOrganization: '' as AnyComponent
},
channelProvider: {
Email: '' as Ref<ChannelProvider>,
@ -197,7 +198,8 @@ const contactPlugin = plugin(contactId, {
},
string: {
PersonAlreadyExists: '' as IntlString,
Person: '' as IntlString
Person: '' as IntlString,
CreateOrganization: '' as IntlString
}
})

View File

@ -58,7 +58,6 @@
<Card
label={inventory.string.CreateCategory}
okAction={create}
space={inventory.space.Category}
canSave={name.length > 0}
on:close={() => {
dispatch('close')

View File

@ -72,7 +72,6 @@
label={inventory.string.CreateProduct}
okAction={create}
canSave={doc.name.trim().length > 0 && doc.attachedTo.length > 0}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}

View File

@ -60,7 +60,6 @@
label={inventory.string.CreateVariant}
okAction={create}
canSave={doc.name.trim().length > 0 && doc.sku.trim().length > 0}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}

View File

@ -169,7 +169,6 @@
label={lead.string.CreateCustomer}
okAction={createCustomer}
{canSave}
space={contact.space.Contacts}
on:close={() => {
dispatch('close')
}}

View File

@ -18,7 +18,7 @@
import { AttachedData, generateId, Ref, SortingOrder, Space } from '@anticrm/core'
import type { Customer, Lead } from '@anticrm/lead'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import { Card, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import task, { calcRank } from '@anticrm/task'
import { EditBox, Status as StatusControl } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
@ -90,14 +90,13 @@
label={lead.string.CreateLead}
okAction={createLead}
canSave={title.length > 0 && customer !== null}
spaceClass={lead.class.Funnel}
spaceLabel={lead.string.FunnelName}
spacePlaceholder={lead.string.SelectFunnel}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
>
<svelte:fragment slot="header">
<SpaceSelector _class={lead.class.Funnel} label={lead.string.FunnelName} bind:space={_space} />
</svelte:fragment>
<StatusControl slot="error" {status} />
<EditBox
label={lead.string.LeadName}

View File

@ -22,6 +22,7 @@
"CreateCandidate": "New Candidate",
"AssignRecruiter": "Assign recruiter",
"UnAssignRecruiter": "Unassign recruiter",
"UnAssignCompany": "Unassign Company",
"Recruiters": "Recruiters",
"Create": "Create",
"Applications": "Applications",

View File

@ -21,6 +21,7 @@
"CreateCandidate": "Новый Кандидат",
"AssignRecruiter": "Назначить рекрутера",
"UnAssignRecruiter": "Отменить назначение рекрутера",
"UnAssignCompany": "Отменить назначение компании",
"Recruiters": "Рекрутеры",
"Create": "Создать",
"Applications": "Претенденты",

View File

@ -17,7 +17,7 @@
import contact from '@anticrm/contact'
import { Account, Class, Client, Doc, generateId, Ref, SortingOrder } from '@anticrm/core'
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
import { Card, createQuery, getClient, UserBox } from '@anticrm/presentation'
import { Card, createQuery, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import type { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
import task, { calcRank, SpaceWithStates, State } from '@anticrm/task'
import ui, {
@ -166,6 +166,24 @@
)
}
const manager = createFocusManager()
const existingApplicationsQuery = createQuery()
let existingApplicants: Ref<Contact>[] = []
$: existingApplicationsQuery.query(
recruit.class.Applicant,
{
space: doc.space
},
(result) => {
existingApplicants = result.map((it) => it.attachedTo)
},
{
projection: {
_id: 1,
attachedTo: 1
}
}
)
</script>
<FocusHandler {manager} />
@ -174,27 +192,37 @@
label={recruit.string.CreateApplication}
okAction={createApplication}
canSave={status.severity === Severity.OK}
spaceClass={recruit.class.Vacancy}
spaceQuery={{ archived: false }}
spaceLabel={recruit.string.Vacancy}
spacePlaceholder={recruit.string.SelectVacancy}
createMore={false}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}
>
<svelte:fragment slot="header">
<SpaceSelector
_class={recruit.class.Vacancy}
query={{ archived: false }}
label={recruit.string.Vacancy}
create={{
component: recruit.component.CreateVacancy,
label: recruit.string.CreateVacancy
}}
bind:space={doc.space}
/>
</svelte:fragment>
<StatusControl slot="error" {status} />
<svelte:fragment slot="pool">
{#if !preserveCandidate}
<UserBox
focusIndex={1}
_class={contact.class.Person}
options={{ sort: { modifiedOn: -1 } }}
excluded={existingApplicants}
label={recruit.string.Candidate}
placeholder={recruit.string.Candidates}
bind:value={doc.attachedTo}
kind={'no-border'}
size={'small'}
create={{ component: recruit.component.CreateCandidate, label: recruit.string.CreateCandidate }}
/>
{/if}
<UserBox

View File

@ -197,6 +197,8 @@
firstName = ''
lastName = ''
channels = []
} else {
dispatch('close', id)
}
}
@ -399,7 +401,6 @@
label={recruit.string.CreateCandidate}
okAction={createCandidate}
canSave={firstName.length > 0 && lastName.length > 0 && matches.length === 0}
space={contact.space.Contacts}
on:close={() => {
dispatch('close')
}}

View File

@ -13,14 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { Organization } from '@anticrm/contact'
import contact, { Organization } from '@anticrm/contact'
import core, { Ref } from '@anticrm/core'
import { getClient, Card } from '@anticrm/presentation'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import task, { createKanban, KanbanTemplate } from '@anticrm/task'
import { Component, EditBox, Button } from '@anticrm/ui'
import { Button, Component, createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import { OrganizationSelector } from '@anticrm/contact-resources'
import Company from './icons/Company.svelte'
import Vacancy from './icons/Vacancy.svelte'
const dispatch = createEventDispatcher()
@ -54,9 +54,12 @@
})
await createKanban(client, id, templateId)
dispatch('close', id)
}
const manager = createFocusManager()
</script>
<FocusHandler {manager} />
<Card
label={recruit.string.CreateVacancy}
okAction={createVacancy}
@ -67,9 +70,10 @@
>
<div class="flex-row-center clear-mins">
<div class="mr-3">
<Button icon={Vacancy} size={'medium'} kind={'link-bordered'} disabled />
<Button focusIndex={1} icon={Vacancy} size={'medium'} kind={'link-bordered'} disabled />
</div>
<EditBox
focusIndex={2}
bind:value={name}
placeholder={recruit.string.VacancyPlaceholder}
maxWidth={'37.5rem'}
@ -78,12 +82,25 @@
/>
</div>
<svelte:fragment slot="pool">
<OrganizationSelector bind:value={company} label={recruit.string.Company} kind={'no-border'} size={'small'} />
<UserBox
focusIndex={3}
_class={contact.class.Organization}
label={recruit.string.Company}
placeholder={recruit.string.Company}
bind:value={company}
allowDeselect
titleDeselect={recruit.string.UnAssignCompany}
kind={'no-border'}
size={'small'}
icon={Company}
create={{ component: contact.component.CreateOrganization, label: contact.string.CreateOrganization }}
/>
<Component
is={task.component.KanbanTemplateSelector}
props={{
folders: [recruit.space.VacancyTemplates],
template: templateId
template: templateId,
focusIndex: 4
}}
on:change={(evt) => {
templateId = evt.detail

View File

@ -83,7 +83,6 @@
label={recruit.string.CreateOpinion}
okAction={createOpinion}
canSave={(doc.value ?? '').trim().length > 0}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}

View File

@ -62,7 +62,6 @@
label={recruit.string.Opinion}
okAction={editOpinion}
canSave={value.length > 0}
space={item.space}
on:close={() => {
dispatch('close')
}}

View File

@ -51,6 +51,7 @@ export default mergeIds(recruitId, recruit, {
AssignRecruiter: '' as IntlString,
Recruiters: '' as IntlString,
UnAssignRecruiter: '' as IntlString,
UnAssignCompany: '' as IntlString,
Create: '' as IntlString,
Applications: '' as IntlString,
ThisVacancyIsPrivate: '' as IntlString,
@ -118,6 +119,8 @@ export default mergeIds(recruitId, recruit, {
VacancyCountPresenter: '' as AnyComponent,
OpinionsPresenter: '' as AnyComponent,
VacancyModifiedPresenter: '' as AnyComponent,
EditReviewCategory: '' as AnyComponent
EditReviewCategory: '' as AnyComponent,
CreateVacancy: '' as AnyComponent,
CreateCandidate: '' as AnyComponent
}
})

View File

@ -96,7 +96,6 @@
labelProps={{ word: keyTitle }}
okAction={createTagElenent}
canSave={title.length > 0}
space={tags.space.Tags}
createMore={false}
on:close={() => {
dispatch('close')

View File

@ -94,7 +94,6 @@
labelProps={{ word: keyTitle }}
okAction={updateElement}
canSave={value.title.length > 0}
space={tags.space.Tags}
on:close={() => {
dispatch('close')
}}

View File

@ -24,6 +24,7 @@
export let folders: Ref<KanbanTemplateSpace>[]
export let template: Ref<KanbanTemplate> | undefined = undefined
export let focusIndex = -1
let templates: KanbanTemplate[] = []
const templatesQ = createQuery()
@ -47,4 +48,10 @@
}
</script>
<DropdownLabels {items} icon={task.icon.ManageStatuses} bind:selected={selectedItem} label={plugin.string.States} />
<DropdownLabels
{focusIndex}
{items}
icon={task.icon.ManageStatuses}
bind:selected={selectedItem}
label={plugin.string.States}
/>

View File

@ -52,7 +52,6 @@
label={plugin.string.TodoCreate}
okAction={createTodo}
canSave={name?.length > 0}
bind:space={_space}
on:close={() => {
dispatch('close')
}}

View File

@ -66,7 +66,6 @@
label={plugin.string.TodoEdit}
okAction={editTodo}
canSave={name.length > 0}
space={item.space}
on:close={() => {
dispatch('close')
}}

View File

@ -16,23 +16,23 @@
import contact, { Employee } from '@anticrm/contact'
import core, { Data, generateId, Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Asset, IntlString } from '@anticrm/platform'
import presentation, { getClient, UserBox, Card, createQuery } from '@anticrm/presentation'
import { Issue, IssuePriority, IssueStatus, Team, calcRank, Project } from '@anticrm/tracker'
import presentation, { Card, createQuery, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import { StyledTextBox } from '@anticrm/text-editor'
import { calcRank, Issue, IssuePriority, IssueStatus, Project, Team } from '@anticrm/tracker'
import {
EditBox,
Button,
showPopup,
DatePresenter,
SelectPopup,
EditBox,
eventToHTMLElement,
IconAttachment,
eventToHTMLElement
SelectPopup,
showPopup
} from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../plugin'
import StatusSelector from './StatusSelector.svelte'
import PrioritySelector from './PrioritySelector.svelte'
import ProjectSelector from './ProjectSelector.svelte'
import StatusSelector from './StatusSelector.svelte'
export let space: Ref<Team>
export let parent: Ref<Issue> | undefined
@ -178,15 +178,14 @@
okAction={createIssue}
{canSave}
okLabel={tracker.string.SaveIssue}
spaceClass={tracker.class.Team}
spaceLabel={tracker.string.Team}
spacePlaceholder={tracker.string.SelectTeam}
createMore={false}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
createMore={false}
>
<svelte:fragment slot="header">
<SpaceSelector _class={tracker.class.Team} label={tracker.string.Team} bind:space={_space} />
</svelte:fragment>
<svelte:fragment slot="space">
<Button
icon={tracker.icon.Home}

View File

@ -1,80 +0,0 @@
<!--
// Copyright © 2020 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, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { IconFolder, Label, showPopup } from '@anticrm/ui'
import { onMount } from 'svelte'
import SpacesPopup from './SpacesPopup.svelte'
export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let label: IntlString
export let placeholder: IntlString
export let value: Ref<Space> | undefined
export let show: boolean = false
let selected: Space | undefined
let btn: HTMLElement
const client = getClient()
async function updateSelected (value: Ref<Space> | undefined) {
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
}
$: updateSelected(value)
onMount(() => {
if (btn && show) {
btn.click()
show = false
}
})
</script>
<div
class="flex-col cursor-pointer"
bind:this={btn}
on:click|preventDefault={() => {
showPopup(SpacesPopup, { _class, spaceQuery }, btn, (result) => {
if (result) {
value = result._id
}
})
}}
>
<div class="overflow-label label"><Label {label} /></div>
<div class="flex-row-center space">
<span class="mr-1"><IconFolder size={'small'} /></span>
<span class="overflow-label" class:caption-color={selected} class:content-dark-color={!selected}>
{#if selected}
{selected.name}
{:else}
<Label label={placeholder} />
{/if}
</span>
</div>
</div>
<style lang="scss">
.label {
margin-bottom: 0.125rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-accent-color);
}
</style>

View File

@ -13,12 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import { Data, Ref } from '@anticrm/core'
import { DatePresenter, EditBox } from '@anticrm/ui'
import { Card, getClient, UserBox, UserBoxList } from '@anticrm/presentation'
import { IntlString } from '@anticrm/platform'
import contact from '@anticrm/contact'
import { Data, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { Card, getClient, SpaceSelector, UserBox, UserBoxList } from '@anticrm/presentation'
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
import { DatePresenter, EditBox } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import ProjectStatusSelector from './ProjectStatusSelector.svelte'
@ -51,12 +51,11 @@
okAction={onSave}
canSave={object.label !== ''}
okLabel={tracker.string.CreateProject}
spaceClass={tracker.class.Team}
spaceLabel={tracker.string.Team}
spacePlaceholder={tracker.string.SelectTeam}
bind:space
on:close={() => dispatch('close')}
>
<svelte:fragment slot="header">
<SpaceSelector _class={tracker.class.Team} label={tracker.string.Team} bind:space />
</svelte:fragment>
<div class="label">
<EditBox
bind:value={object.label}

View File

@ -15,6 +15,7 @@
//
import type { Class, Client, Doc, Obj, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import type { Asset } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import { Application, NavigatorModel } from '@anticrm/workbench'
@ -89,6 +90,10 @@ export async function doNavigate (
case 'space': {
if (props.space !== undefined) {
loc.path[2] = props.space
} else {
if (doc !== undefined && !Array.isArray(doc) && client.getHierarchy().isDerived(doc._class, core.class.Space)) {
loc.path[2] = doc._id
}
}
if (props.spaceSpecial !== undefined) {
loc.path[3] = props.spaceSpecial