mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
HR: Fixes to Vacancy/Application creation (#1753)
This commit is contained in:
parent
84844b65a9
commit
2defbc434b
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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[] = []
|
||||
|
@ -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()
|
||||
|
203
packages/presentation/src/components/ObjectPopup.svelte
Normal file
203
packages/presentation/src/components/ObjectPopup.svelte
Normal 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>
|
@ -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>
|
||||
|
34
packages/presentation/src/components/SpaceSelector.svelte
Normal file
34
packages/presentation/src/components/SpaceSelector.svelte
Normal 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} />
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 />
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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'}"
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -58,7 +58,6 @@
|
||||
<Card
|
||||
label={inventory.string.CreateCategory}
|
||||
okAction={create}
|
||||
space={inventory.space.Category}
|
||||
canSave={name.length > 0}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -169,7 +169,6 @@
|
||||
label={lead.string.CreateCustomer}
|
||||
okAction={createCustomer}
|
||||
{canSave}
|
||||
space={contact.space.Contacts}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -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}
|
||||
|
@ -22,6 +22,7 @@
|
||||
"CreateCandidate": "New Candidate",
|
||||
"AssignRecruiter": "Assign recruiter",
|
||||
"UnAssignRecruiter": "Unassign recruiter",
|
||||
"UnAssignCompany": "Unassign Company",
|
||||
"Recruiters": "Recruiters",
|
||||
"Create": "Create",
|
||||
"Applications": "Applications",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"CreateCandidate": "Новый Кандидат",
|
||||
"AssignRecruiter": "Назначить рекрутера",
|
||||
"UnAssignRecruiter": "Отменить назначение рекрутера",
|
||||
"UnAssignCompany": "Отменить назначение компании",
|
||||
"Recruiters": "Рекрутеры",
|
||||
"Create": "Создать",
|
||||
"Applications": "Претенденты",
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
}}
|
||||
|
@ -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
|
||||
|
@ -83,7 +83,6 @@
|
||||
label={recruit.string.CreateOpinion}
|
||||
okAction={createOpinion}
|
||||
canSave={(doc.value ?? '').trim().length > 0}
|
||||
bind:space={doc.space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -62,7 +62,6 @@
|
||||
label={recruit.string.Opinion}
|
||||
okAction={editOpinion}
|
||||
canSave={value.length > 0}
|
||||
space={item.space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
@ -96,7 +96,6 @@
|
||||
labelProps={{ word: keyTitle }}
|
||||
okAction={createTagElenent}
|
||||
canSave={title.length > 0}
|
||||
space={tags.space.Tags}
|
||||
createMore={false}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
|
@ -94,7 +94,6 @@
|
||||
labelProps={{ word: keyTitle }}
|
||||
okAction={updateElement}
|
||||
canSave={value.title.length > 0}
|
||||
space={tags.space.Tags}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -52,7 +52,6 @@
|
||||
label={plugin.string.TodoCreate}
|
||||
okAction={createTodo}
|
||||
canSave={name?.length > 0}
|
||||
bind:space={_space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -66,7 +66,6 @@
|
||||
label={plugin.string.TodoEdit}
|
||||
okAction={editTodo}
|
||||
canSave={name.length > 0}
|
||||
space={item.space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
@ -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}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user