Update dialogs layout (#1385)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-04-13 07:25:59 +03:00 committed by GitHub
parent 8dcdafb37d
commit 595fbf487a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 731 additions and 622 deletions

View File

@ -13,6 +13,7 @@
"Remove": "Remove",
"Members": "Members",
"Search": "Search...",
"Unassigned": "Unassigned"
"Unassigned": "Unassigned",
"CreateMore": "Create more"
}
}

View File

@ -13,6 +13,7 @@
"Remove": "Удалить",
"Members": "Участники",
"Search": "Поиск...",
"Unassigned": "Не назначен"
"Unassigned": "Не назначен",
"CreateMore": "Создать еще"
}
}

View File

@ -20,7 +20,7 @@
import { createEventDispatcher } from 'svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { Button, Label } from '@anticrm/ui'
import { Button, Label, IconClose, MiniToggle } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
import presentation from '..'
@ -34,31 +34,50 @@
export let okAction: () => void
export let canSave: boolean = false
export let size: 'small'| 'medium' = 'small'
export let createMore: boolean | undefined = undefined
export let okLabel: IntlString = presentation.string.Create
export let cancelLabel: IntlString = presentation.string.Cancel
const dispatch = createEventDispatcher()
</script>
<form class="antiCard" class:w-85={size === 'small'} class:w-165={size === 'medium'} on:submit|preventDefault={ () => {} }>
<form class="antiCard dialog" on:submit|preventDefault={ () => {} }>
<div class="antiCard-header">
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
{#if $$slots.error}
<div class="antiCard-header__error">
<slot name="error" />
</div>
{/if}
<div class="antiCard-header__title-wrap">
{#if spaceClass && spaceLabel && spacePlaceholder}
{#if $$slots.space}
<slot name="space" />
{:else}
<SpaceSelect _class={spaceClass} spaceQuery={spaceQuery} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
{/if}
<span class="antiCard-header__divider"></span>
{/if}
<span class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></span>
</div>
<div class="buttons-group small-gap">
<Button icon={IconClose} kind={'transparent'} on:click={() => { dispatch('close') }} />
</div>
</div>
<div class="antiCard-content"><slot /></div>
{#if spaceClass && spaceLabel && spacePlaceholder}
{#if (spaceClass && spaceLabel && spacePlaceholder) || $$slots.pool}
<div class="antiCard-pool">
<div class="antiCard-pool__separator" />
<SpaceSelect _class={spaceClass} spaceQuery={spaceQuery} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
<slot name="pool" />
</div>
{/if}
<div class="antiCard-footer">
<Button disabled={!canSave} label={okLabel} kind={'primary'} on:click={() => { okAction(); dispatch('close') }} />
<Button label={cancelLabel} on:click={() => { dispatch('close') }} />
<div class="antiCard-footer reverse">
<div class="buttons-group text-sm flex-no-shrink">
{#if createMore !== undefined}
<MiniToggle label={presentation.string.CreateMore} bind:on={createMore} />
{/if}
<Button disabled={!canSave} label={okLabel} kind={'primary'} on:click={() => { okAction(); dispatch('close') }} />
</div>
<div class="buttons-group small-gap text-sm">
<slot name="footer" />
{#if $$slots.error}
<div class="antiCard-footer__error">
<slot name="error" />
</div>
{/if}
</div>
</div>
</form>

View File

@ -17,7 +17,7 @@
import type { IntlString } from '@anticrm/platform'
import { getClient } from '../utils'
import { Label, showPopup, IconFolder } from '@anticrm/ui'
import { Label, showPopup, IconFolder, Button } from '@anticrm/ui'
import SpacesPopup from './SpacesPopup.svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
@ -27,10 +27,8 @@
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()
@ -39,43 +37,21 @@
}
$: 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) => {
<Button
icon={IconFolder}
size={'small'}
kind={'no-border'}
on:click={(ev) => {
showPopup(SpacesPopup, { _class, spaceQuery }, ev.target, (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: .125rem;
font-weight: 500;
font-size: .75rem;
color: var(--theme-content-accent-color);
}
</style>
<span slot="content" class="text-sm">
{#if selected}{selected.name}{:else}<Label {label} />{/if}
</span>
</Button>

View File

@ -42,7 +42,8 @@ export default plugin(presentationId, {
Remove: '' as IntlString,
Members: '' as IntlString,
Search: '' as IntlString,
Unassigned: '' as IntlString
Unassigned: '' as IntlString,
CreateMore: '' as IntlString
},
metadata: {
RequiredVersion: '' as Metadata<string>

View File

@ -244,8 +244,7 @@ p:last-child { margin-block-end: 0; }
}
.gap-1, .gap-1-5, .gap-2 {
& > * { margin-right: .25rem; }
& > *:last-child { margin-right: 0; }
& > *:not(:last-child) { margin-right: .25rem; }
&.reverse {
flex-direction: row-reverse;
& > :last-child { margin-right: .25rem; }
@ -293,6 +292,8 @@ p:last-child { margin-block-end: 0; }
.ml-6 { margin-left: 1.5rem; }
.ml-8 { margin-left: 2rem; }
.ml-10 { margin-left: 2.5rem; }
.ml-12 { margin-left: 3rem; }
.ml-22 { margin-left: 5.5rem; }
.mr-1 { margin-right: .25rem; }
.mr-2 { margin-right: .5rem; }
.mr-3 { margin-right: .75rem; }
@ -300,6 +301,7 @@ p:last-child { margin-block-end: 0; }
.mr-6 { margin-right: 1.5rem; }
.mr-8 { margin-right: 2rem; }
.mr-10 { margin-right: 2.5rem; }
.mt-0-5 { margin-top: .125rem; }
.mt-1 { margin-top: .25rem; }
.mt-2 { margin-top: .5rem; }
.mt-3 { margin-top: .75rem; }
@ -317,6 +319,7 @@ p:last-child { margin-block-end: 0; }
.mx-1 { margin: 0 .25rem; }
.mx-2 { margin: 0 .5rem; }
.mx-3 { margin: 0 .75rem; }
.my-4 { margin: 1rem 0; }
.pr-1 { padding-right: .25rem; }
.pr-4 { padding-right: 1rem; }
@ -377,6 +380,7 @@ p:last-child { margin-block-end: 0; }
.min-w-4 { min-width: 1rem; }
.min-w-9 { min-width: 2.25rem; }
.min-h-0 { min-height: 0; }
.min-w-min { min-width: min-content; }
.clear-mins {
min-width: 0;
min-height: 0;
@ -540,13 +544,14 @@ a.no-line {
.background-bg-accent { background-color: var(--theme-bg-accent-color); }
.background-bg-accent-normal { background-color: var(--theme-bg-accent-normal); }
.content-color { color: var(--theme-content-color); }
.content-color { color: var(--content-color); }
.content-trans-color { color: var(--theme-content-trans-color); }
.content-accent-color { color: var(--theme-content-accent-color); }
.content-accent-color { color: var(--accent-color); }
.content-dark-color { color: var(--theme-content-dark-color); }
.caption-color { color: var(--theme-caption-color); }
.caption-color { color: var(--caption-color); }
.red-color { color: var(--highlight-red); }
.error-color { color: var(--error-color); }
.border-radius-4 { border-radius: 1rem; }
.border-radius-3 { border-radius: 0.75rem; }

View File

@ -111,10 +111,14 @@
}
.antiCard-content {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 1;
margin: 0 1rem;
height: fit-content;
& > *:not(:last-child) { margin-bottom: 1rem; }
}
.antiCard-pool {
@ -157,20 +161,25 @@
display: flex;
align-items: center;
min-width: 0;
& > *:not(:first-child) { margin-left: .375rem; }
}
&__divider, &__title {
margin-left: .375rem;
font-weight: 400;
font-size: .8125rem;
}
&__divider { color: var(--content-color); }
&__title { color: var(--accent-color); }
}
.antiCard-content { margin: 0 1.125rem; }
.antiCard-content { margin: .5rem 1.125rem 1rem; }
.antiCard-pool {
flex-direction: row;
margin: .375rem .75rem .75rem;
align-items: center;
margin: 0 .75rem .75rem;
font-size: .75rem;
& > *:not(:last-child) { margin-right: .375rem; }
}
.antiCard-footer {
direction: ltr;
@ -182,6 +191,16 @@
border-top: 1px solid var(--button-bg-color);
&.reverse { flex-direction: row-reverse; }
&__error {
flex-grow: 1;
display: flex;
margin-left: .375rem;
min-width: 0;
font-weight: 500;
font-size: .75rem;
color: var(--system-error-color);
&:empty { visibility: hidden; }
}
}
}
}

View File

@ -64,6 +64,7 @@
scrollbar-color: var(--theme-menu-color) var(--theme-bg-color);
scrollbar-width: thin;
--font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
--timing-shadow: cubic-bezier(0,.65,.35,1);
}
::-webkit-scrollbar {

View File

@ -70,6 +70,13 @@
width: 1rem;
height: 1rem;
}
.color {
margin-right: .75rem;
width: .875rem;
height: .875rem;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: .25rem;
}
.label {
flex-grow: 1;
min-width: 0;

View File

@ -22,7 +22,7 @@
export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'secondary'
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'link-bordered' | 'dangerous' = 'secondary'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'medium'
export let shape: 'circle' | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
@ -55,10 +55,11 @@
disabled={disabled || loading}
style={width ? 'width: ' + width : ''}
{title}
type={kind === 'primary' ? 'submit' : 'button'}
on:click
>
{#if icon && !loading}
<div class="btn-icon"
<div class="btn-icon pointer-events-none"
class:mr-1={!iconOnly && kind === 'no-border'}
class:mr-2={!iconOnly && kind !== 'no-border'}
class:resetIconSize
@ -69,11 +70,13 @@
{#if loading}
<Spinner />
{:else}
{#if label}
<Label {label} params={labelParams}/>
{:else if $$slots.content}
<slot name="content" />
{/if}
<span class="overflow-label pointer-events-none">
{#if label}
<Label {label} params={labelParams} />
{:else if $$slots.content}
<slot name="content" />
{/if}
</span>
{/if}
</button>
@ -176,6 +179,16 @@
.btn-icon { color: var(--content-color); }
}
}
&.link-bordered {
padding: 0 .375rem;
color: var(--acctent-color);
border-color: var(--button-border-color);
&:hover {
color: var(--acctent-color);
border-color: var(--button-border-hover);
.btn-icon { color: var(--accent-color); }
}
}
&.primary {
padding: 0 1rem;
color: var(--white-color);

View File

@ -0,0 +1,50 @@
<!--
// 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 { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import { Icon, Label, getPlatformColor } from '..'
export let placeholder: IntlString | undefined = undefined
export let placeholderParam: any | undefined = undefined
export let searchable: boolean = false
export let value: Array<{id: number | string, color: number, label: string}>
let search: string = ''
let phTraslate: string = ''
$: if (placeholder) translate(placeholder, placeholderParam ?? {}).then(res => { phTraslate = res })
const dispatch = createEventDispatcher()
</script>
<div class="selectPopup">
{#if searchable}
<div class="header">
<input type='text' bind:value={search} placeholder={phTraslate} on:input={(ev) => { }} on:change/>
</div>
{/if}
<div class="scroll">
<div class="box">
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as item}
<button class="menu-item" on:click={() => { dispatch('close', item) }}>
<div class="color" style="background-color: {getPlatformColor(item.color)}" />
<span class="label">{item.label}</span>
</button>
{/each}
</div>
</div>
</div>

View File

@ -21,9 +21,8 @@
import Icon from './Icon.svelte'
import { showPopup, Button, Tooltip, DropdownPopup } from '..'
import type { AnySvelteComponent, ListItem, TooltipAligment } from '../types'
import Add from './icons/Add.svelte'
export let icon: Asset | AnySvelteComponent = Add
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let label: IntlString
export let placeholder: IntlString
export let items: ListItem[] = []

View File

@ -14,24 +14,26 @@
-->
<script lang="ts">
import { IntlString } from '@anticrm/platform'
import { IntlString, Asset } from '@anticrm/platform'
import DropdownLabelsPopup from './DropdownLabelsPopup.svelte'
import Label from './Label.svelte'
import IconUp from './icons/Up.svelte'
import IconDown from './icons/Down.svelte'
import type { DropdownTextItem } from '../types'
import { showPopup } from '..'
import type { AnySvelteComponent, DropdownTextItem, TooltipAligment } from '../types'
import { showPopup, Tooltip, Button, Label } from '..'
import { createEventDispatcher } from 'svelte'
import ui from '../plugin'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let label: IntlString
export let placeholder: IntlString | undefined = undefined
export let placeholder: IntlString | undefined = ui.string.SearchDots
export let items: DropdownTextItem[]
export let selected: DropdownTextItem['id'] | undefined = undefined
let btn: HTMLElement
export let kind: 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'dangerous' = 'no-border'
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
let container: HTMLElement
let opened: boolean = false
let isDisabled = false
$: isDisabled = items.length === 0
@ -46,7 +48,33 @@
const none = ui.string.None
</script>
<div class="flex-col cursor-pointer"
<div bind:this={container} class="min-w-0">
<Tooltip label={label} fill={width === '100%'} direction={labelDirection}>
<Button
{icon}
width={width ?? 'min-content'}
{size} {kind} {justify}
on:click={() => {
if (!opened) {
opened = true
showPopup(DropdownLabelsPopup, { placeholder, items, selected }, container, (result) => {
if (result) {
selected = result
dispatch('selected', result)
}
opened = false
})
}
}}
>
<span slot="content" style="overflow: hidden">
{#if selectedItem}{selectedItem.label}{:else}<Label label={label ?? ui.string.NotSelected} />{/if}
</span>
</Button>
</Tooltip>
</div>
<!-- <div class="flex-col cursor-pointer"
bind:this={btn}
on:click|preventDefault={() => {
if (!opened) {
@ -87,4 +115,4 @@
font-size: .75rem;
color: var(--theme-content-accent-color);
}
</style>
</style> -->

View File

@ -29,7 +29,7 @@
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
export let placeholderParam: any | undefined = undefined
export let format: 'text'| 'password' | 'number' = 'text'
export let kind: 'editbox' | 'large-style' = 'editbox'
export let kind: 'editbox' | 'large-style' | 'small-style' = 'editbox'
export let focus: boolean = false
const dispatch = createEventDispatcher()
@ -105,6 +105,10 @@
font-weight: 500;
font-size: 1.125rem;
}
.small-style {
font-weight: 400;
font-size: .75rem;
}
input {
margin: 0;

View File

@ -340,8 +340,8 @@
height: 2.5rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-dark-color);
background-color: var(--theme-bg-color);
color: var(--dark-color);
background-color: var(--board-bg-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
}

View File

@ -16,14 +16,12 @@
import type { Asset, IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import { Icon, Label } from '..'
import { Icon, Label, getPlatformColor } from '..'
// export let _class: Ref<Class<Space>>
// export let spaceQuery: DocumentQuery<Space> | undefined
export let placeholder: IntlString | undefined = undefined
export let placeholderParam: any | undefined = undefined
export let searchable: boolean = false
export let value: Array<{id: number | string, icon: Asset, label: IntlString}>
export let value: Array<{id: number | string, color: number, label: IntlString}>
let search: string = ''
@ -31,9 +29,6 @@
$: if (placeholder) translate(placeholder, placeholderParam ?? {}).then(res => { phTraslate = res })
const dispatch = createEventDispatcher()
// const query = createQuery()
// $: query.query(_class, { ...(spaceQuery ?? {}), name: { $like: '%' + search + '%' } }, result => { objects = result })
// afterUpdate(() => { dispatch('update', Date.now()) })
</script>
<div class="selectPopup">
@ -44,10 +39,10 @@
{/if}
<div class="scroll">
<div class="box">
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as space}
<button class="menu-item" on:click={() => { dispatch('close', space.id) }}>
<div class="icon"><Icon icon={space.icon} size={'small'} /></div>
<span class="label"><Label label={space.label} /></span>
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as item}
<button class="menu-item" on:click={() => { dispatch('close', item.id) }}>
<div class="color" style="background-color: {getPlatformColor(item.color)}" />
<span class="label"><Label label={item.label} /></span>
</button>
{/each}
</div>

View File

@ -43,6 +43,7 @@ export { default as Tabs } from './components/Tabs.svelte'
export { default as ScrollBox } from './components/ScrollBox.svelte'
export { default as PopupMenu } from './components/PopupMenu.svelte'
export { default as SelectPopup } from './components/SelectPopup.svelte'
export { default as ColorPopup } from './components/ColorPopup.svelte'
export { default as TextArea } from './components/TextArea.svelte'
export { default as Section } from './components/Section.svelte'
export { default as DatePickerPopup } from './components/calendar/DatePickerPopup.svelte'

View File

@ -51,4 +51,8 @@
<path d="M12,0C5.4,0,0,5.4,0,12c0,6.6,5.4,12,12,12c6.6,0,12-5.4,12-12C24,5.4,18.6,0,12,0z M12,22.6 C6.2,22.6,1.4,17.8,1.4,12C1.4,6.2,6.2,1.4,12,1.4c5.8,0,10.6,4.7,10.6,10.6C22.6,17.8,17.8,22.6,12,22.6z"/>
<path d="M18.6,16.2c-0.3-0.9-0.8-1.6-1.5-2.2c-0.9-0.7-2-1.1-3.1-1.1h-1.5h-0.9h-1.5c-1.1,0-2.2,0.4-3.1,1.1 c-0.7,0.6-1.2,1.3-1.5,2.2l-0.2,0.6c-0.1,0.4,0.1,0.8,0.5,0.9c0.1,0,0.1,0,0.2,0c0.3,0,0.6-0.2,0.7-0.5L7,16.7 c0.2-0.6,0.5-1.1,1-1.5c0.6-0.5,1.4-0.8,2.2-0.8h1.5h0.9h1.5c0.8,0,1.6,0.3,2.2,0.8c0.5,0.4,0.8,0.9,1,1.5l0.2,0.6 c0.1,0.3,0.4,0.5,0.7,0.5c0.1,0,0.1,0,0.2,0c0.4-0.1,0.6-0.5,0.5-0.9L18.6,16.2z"/>
</symbol>
<symbol id="social-edit" viewBox="0 0 24 24">
<path d="M12.8,16.5c0-0.4-0.3-0.8-0.8-0.8c-1.2,0-2.3-0.1-3.5-0.2c-0.3-2.3-0.3-4.6,0-6.9c2.3-0.3,4.6-0.3,6.9,0 c0.1,1.1,0.2,2.3,0.2,3.5c0,0.4,0.3,0.8,0.8,0.8c0.4,0,0.8-0.3,0.8-0.8c0-1.1-0.1-2.2-0.2-3.2c1.3,0.2,2.5,0.5,3.8,0.9 c0.1,0,0.1,0,0.2,0c0.2,0.7,0.3,1.5,0.3,2.3c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-5.9-4.8-10.8-10.8-10.8S1.2,6.1,1.2,12 S6.1,22.8,12,22.8c0.4,0,0.8-0.3,0.8-0.8s-0.3-0.8-0.8-0.8c-0.8,0-1.5-0.1-2.3-0.3c0-0.1,0-0.1,0-0.2C9.3,19.5,9,18.3,8.8,17 c1.1,0.1,2.1,0.2,3.2,0.2C12.4,17.2,12.8,16.9,12.8,16.5z M7,15.2c-1.3-0.2-2.5-0.5-3.8-0.9c-0.1,0-0.1,0-0.2,0 c-0.2-0.7-0.3-1.5-0.3-2.3c0-0.8,0.1-1.5,0.3-2.3c0.1,0,0.1,0,0.2,0C4.5,9.3,5.7,9,7,8.8C6.8,10.9,6.8,13.1,7,15.2z M20.3,8 c-1.2-0.3-2.3-0.6-3.5-0.8C16.6,6,16.3,4.8,16,3.7C17.9,4.6,19.4,6.1,20.3,8z M14.3,3c0,0.1,0,0.1,0,0.2C14.7,4.5,15,5.7,15.2,7 c-2.1-0.2-4.3-0.2-6.5,0C9,5.7,9.3,4.5,9.7,3.2c0-0.1,0-0.1,0-0.2c0.7-0.2,1.5-0.3,2.3-0.3C12.8,2.8,13.5,2.9,14.3,3z M7.9,3.7 C7.9,3.7,7.9,3.7,7.9,3.7C7.6,4.9,7.4,6,7.2,7.2C6,7.4,4.8,7.7,3.7,8C4.5,6.2,6,4.6,7.9,3.7z M8,20.2C7.9,20.3,7.9,20.3,8,20.2 c-1.9-0.9-3.4-2.3-4.3-4.1c0,0,0-0.1,0-0.1c1.1,0.3,2.3,0.6,3.5,0.8C7.4,18,7.6,19.1,8,20.2z" />
<path d="M20.1,14.4c-0.5,0-1,0.3-1.5,0.8l-3.5,3.5c-0.2,0.2-0.5,0.7-0.5,1l-0.2,1.3c-0.1,0.5,0.1,0.9,0.4,1.2 c0.3,0.3,0.6,0.4,1,0.4c0.1,0,0.2,0,0.2,0l1.3-0.2c0.3,0,0.8-0.3,1-0.5l3.5-3.5c0.5-0.5,0.7-1,0.8-1.5c0.1-0.6-0.2-1.2-0.8-1.8 S20.8,14.4,20.1,14.4z M20.3,15.9C20.3,15.9,20.3,15.9,20.3,15.9c0.2,0,0.5,0.2,0.6,0.4c0.2,0.2,0.4,0.4,0.3,0.6 c0,0.1-0.1,0.3-0.3,0.5l-0.2,0.2c-0.5-0.2-0.9-0.7-1.1-1.2l0.2-0.2C19.9,16,20.1,15.9,20.3,15.9z M17.4,21c0,0-0.1,0.1-0.2,0.1 l-1.2,0.2l0.2-1.2c0,0,0-0.1,0.1-0.2l2.3-2.3c0.3,0.5,0.7,0.9,1.2,1.2L17.4,21z" />
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -32,6 +32,7 @@ loadMetadata(contact.icon, {
GitHub: `${icons}#github`,
Edit: `${icons}#edit`,
Person: `${icons}#person`,
Company: `${icons}#company`
Company: `${icons}#company`,
SocialEdit: `${icons}#social-edit`
})
addStringsLoader(contactId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -45,18 +45,20 @@
<span class="ml-2"><Label label={presentation.string.AddSocialLinks} /></span>
</div>
{:else}
<ChannelsView value={channels} size={'small'} {integrations} on:click />
<div id="channels-edit" class="ml-1">
<CircleButton
icon={contact.icon.Edit}
size={'small'}
selected
on:click={(ev) =>
showPopup(contact.component.SocialEditor, { values: channels }, ev.target, (result) => {
if (result !== undefined) {
dispatch('change', result)
}
})}
/>
<div class="flex-row-center min-w-min">
<ChannelsView value={channels} size={'small'} {integrations} on:click />
<div id="channels-edit" class="ml-1">
<CircleButton
icon={contact.icon.Edit}
size={'small'}
selected
on:click={(ev) =>
showPopup(contact.component.SocialEditor, { values: channels }, ev.target, (result) => {
if (result !== undefined) {
dispatch('change', result)
}
})}
/>
</div>
</div>
{/if}

View File

@ -124,6 +124,8 @@
<style lang="scss">
.channels {
display: grid;
width: min-content;
&.one { display: block; }
&.short {
grid-template-columns: repeat(4, min-content);

View File

@ -22,7 +22,7 @@
label: cl.label,
action: async () => {
closePopup()
showPopup(f.component, {}, targetElement)
showPopup(f.component, {}, 'top')
}
})
}

View File

@ -18,13 +18,14 @@
import { getClient, Card } from '@anticrm/presentation'
import { EditBox } from '@anticrm/ui'
import { EditBox, Button, showPopup, IconAdd } from '@anticrm/ui'
import { Channel, Organization } from '@anticrm/contact'
import contact from '../plugin'
import Company from './icons/Company.svelte'
import { AttachedData, generateId } from '@anticrm/core'
import Channels from './Channels.svelte'
import ChannelsView from './ChannelsView.svelte'
export function canClose (): boolean {
return object.name === ''
@ -63,31 +64,28 @@
dispatch('close')
}}
>
<div class="flex-row-center">
<div class="mr-4 flex-center logo">
<Company size={'large'} />
</div>
<div class="flex-col">
<div class="fs-title">
<EditBox placeholder={contact.string.OrganizationNamePlaceholder} maxWidth="11rem" bind:value={object.name} focus />
</div>
<div class="flex-row-center clear-mins">
<div class="mr-3">
<Button icon={Company} size={'medium'} kind={'link-bordered'} disabled />
</div>
<EditBox
placeholder={contact.string.OrganizationNamePlaceholder}
bind:value={object.name}
maxWidth={'37.5rem'} kind={'large-style'} focus
/>
</div>
<div class="flex-row-center channels">
<Channels bind:channels={channels} on:change={(e) => { channels = e.detail }} />
</div>
{#if channels.length > 0}
<ChannelsView value={channels} size={'small'} on:click />
{/if}
<svelte:fragment slot="footer">
<Button
icon={contact.icon.SocialEdit}
kind={'transparent'}
on:click={(ev) =>
showPopup(contact.component.SocialEditor, { values: channels }, ev.target, (result) => {
if (result !== undefined) channels = result
})
}
/>
</svelte:fragment>
</Card>
<style lang="scss">
.logo {
width: 5rem;
height: 5rem;
color: var(--primary-button-color);
background-color: var(--primary-button-enabled);
border-radius: 50%;
}
.channels {
margin-top: 1.25rem;
}
</style>

View File

@ -21,11 +21,11 @@
import { getClient, Card, EditableAvatar } from '@anticrm/presentation'
import attachment from '@anticrm/attachment'
import { EditBox, IconInfo, Label } from '@anticrm/ui'
import { EditBox, IconInfo, Label, Button, showPopup } from '@anticrm/ui'
import { Channel, combineName, findPerson, Person } from '@anticrm/contact'
import contact from '../plugin'
import Channels from './Channels.svelte'
import ChannelsView from './ChannelsView.svelte'
import PersonPresenter from './PersonPresenter.svelte'
let firstName = ''
@ -93,58 +93,41 @@
dispatch('close')
}}
>
{#if matches.length > 0}
<div class="flex-row update-container ERROR">
<div class="flex mb-2">
<svelte:fragment slot="error">
{#if matches.length > 0}
<div class="flex-row-center error-color">
<IconInfo size={'small'} />
<div class="text-sm ml-2 overflow-label">
<span class="text-sm overflow-label ml-2">
<Label label={contact.string.PersonAlreadyExists} />
</div>
</span>
<div class="ml-4"><PersonPresenter value={matches[0]} /></div>
</div>
<PersonPresenter value={matches[0]} />
</div>
{/if}
{/if}
</svelte:fragment>
<div class="flex-row-center">
<div class="mr-4">
<EditableAvatar avatar={object.avatar} size={'large'} on:done={onAvatarDone} on:remove={removeAvatar} />
</div>
<div class="flex-col">
<div class="fs-title">
<EditBox placeholder={contact.string.PersonFirstNamePlaceholder} maxWidth="12rem" bind:value={firstName} focus />
</div>
<div class="fs-title mb-1">
<EditBox placeholder={contact.string.PersonLastNamePlaceholder} maxWidth="12rem" bind:value={lastName} />
</div>
<div class="text-sm">
<EditBox placeholder={contact.string.PersonLocationPlaceholder} maxWidth="12rem" bind:value={object.city} />
<EditBox placeholder={contact.string.PersonFirstNamePlaceholder} bind:value={firstName} kind={'large-style'} maxWidth={'32rem'} focus />
<EditBox placeholder={contact.string.PersonLastNamePlaceholder} bind:value={lastName} kind={'large-style'} maxWidth={'32rem'} />
<div class="mt-1">
<EditBox placeholder={contact.string.PersonLocationPlaceholder} bind:value={object.city} kind={'small-style'} maxWidth={'32rem'} />
</div>
</div>
</div>
<div class="flex-row-center mt-5">
<Channels
bind:channels
on:change={(e) => {
channels = e.detail
}}
{#if channels.length > 0}
<div class="ml-22"><ChannelsView value={channels} size={'small'} on:click /></div>
{/if}
<svelte:fragment slot="footer">
<Button
icon={contact.icon.SocialEdit}
kind={'transparent'}
on:click={(ev) =>
showPopup(contact.component.SocialEditor, { values: channels }, ev.target, (result) => {
if (result !== undefined) channels = result
})
}
/>
</div>
</svelte:fragment>
</Card>
<style lang="scss">
.update-container {
margin-left: -1rem;
margin-right: -1rem;
padding: 1rem;
margin-bottom: 1rem;
user-select: none;
font-size: 14px;
color: var(--theme-content-color);
&.ERROR { color: var(--system-error-color); }
border: 1px dashed var(--theme-zone-border);
border-radius: 0.5rem;
backdrop-filter: blur(10px);
}
</style>

View File

@ -170,7 +170,8 @@ const contactPlugin = plugin(contactId, {
GitHub: '' as Asset,
Edit: '' as Asset,
Person: '' as Asset,
Company: '' as Asset
Company: '' as Asset,
SocialEdit: '' as Asset
},
space: {
Employee: '' as Ref<Space>,

View File

@ -75,7 +75,7 @@
"OpinionShortLabel": "OPE",
"ReviewShortLabel": "RVE",
"StartDate": "Дата начала",
"DueDate": "Дата конца",
"DueDate": "Дата окончания",
"ReviewCategoryTitle":"Категория",
"Verdict": "Вердикт",
"OpinionSave": "Сохранить",

View File

@ -24,8 +24,8 @@
import FileDuo from './icons/FileDuo.svelte'
import chunter from '@anticrm/chunter'
import attachment from '@anticrm/attachment'
import { Applicant } from '@anticrm/recruit'
import { BuildModelKey } from '@anticrm/view'
import { Applicant } from '@anticrm/recruit'
import { BuildModelKey } from '@anticrm/view'
export let objectId: Ref<Doc>
// export let space: Ref<Space>

View File

@ -60,7 +60,7 @@
}
function showCreateDialog (ev: Event) {
showPopup(CreateApplication, { }, ev.target as HTMLElement)
showPopup(CreateApplication, { }, 'top')
}
function updateResultQuery (search: string): void {

View File

@ -25,6 +25,7 @@
import recruit from '../plugin'
export let candidate: Candidate
export let disabled: boolean = false
let channels: Channel[] = []
const channelsQuery = createQuery()
@ -43,8 +44,8 @@
<div class="label uppercase"><Label label={recruit.string.Candidate} /></div>
<Avatar avatar={candidate.avatar} size={'large'} />
{#if candidate}
<div class="name lines-limit-2 over-underline" on:click={() => {
showPanel(view.component.EditDoc, candidate._id, candidate._class, 'full')
<div class="name lines-limit-2" class:over-underline={!disabled} on:click={() => {
if (!disabled) showPanel(view.component.EditDoc, candidate._id, candidate._class, 'full')
}}>{formatName(candidate.name)}</div>
<div class="description lines-limit-2">{candidate.title ?? ''}</div>
<div class="description overflow-label">{candidate.city ?? ''}</div>
@ -64,15 +65,16 @@
.card-container {
padding: 1rem 1.5rem 1.25rem;
background-color: var(--board-card-bg-color);
border: 1px solid var(--board-card-bg-color);
border: 1px solid var(--divider-color);
border-radius: .5rem;
transition-property: box-shadow, background-color;
transition-timing-function: ease-in-out;
transition-property: box-shadow, background-color, border-color;
transition-timing-function: var(--timing-shadow);
transition-duration: .15s;
&:hover {
background-color: var(--board-card-bg-hover);
box-shadow: var(--popup-shadow);
border-color: var(--button-border-color);
box-shadow: rgb(0 0 0 / 15%) 0px 4px 8px;
}
.label {

View File

@ -32,7 +32,7 @@
const tableDescriptor = client.findOne<Viewlet>(view.class.Viewlet, { attachTo: recruit.mixin.Candidate, descriptor: view.viewlet.Table })
function showCreateDialog (ev: Event) {
showPopup(CreateCandidate, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement)
showPopup(CreateCandidate, { space: recruit.space.CandidatesPublic }, 'top')
}
let category: Ref<TagCategory> | undefined = undefined

View File

@ -17,13 +17,16 @@
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, getClient, UserBox } from '@anticrm/presentation'
import type { Applicant, Candidate } from '@anticrm/recruit'
import { Card, getClient, UserBox, createQuery, AttributeEditor } from '@anticrm/presentation'
import type { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
import task, { calcRank, SpaceWithStates, State } from '@anticrm/task'
import { Grid, Status as StatusControl } from '@anticrm/ui'
import ui, { Status as StatusControl, Label, Button, ColorPopup, showPopup, getPlatformColor } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import CandidateCard from './CandidateCard.svelte'
import VacancyCard from './VacancyCard.svelte'
import ExpandRightDouble from './icons/ExpandRightDouble.svelte'
export let space: Ref<SpaceWithStates>
export let candidate: Ref<Candidate>
@ -58,7 +61,7 @@
}
async function createApplication () {
const state = await client.findOne(task.class.State, { space: doc.space })
const state = await client.findOne(task.class.State, { space: doc.space, _id: selectedState._id })
if (state === undefined) {
throw new Error(`create application: state not found space:${doc.space}`)
}
@ -115,6 +118,35 @@
}
$: validate(doc, doc._class)
let selectedVacancy: Vacancy
let selectedCandidate: Person
const vacancyQuery = createQuery()
$: if (doc.space !== undefined) {
vacancyQuery.query(recruit.class.Vacancy, { _id: doc.space }, (result) => {
selectedVacancy = result[0]
})
}
const candidateQuery = createQuery()
$: if (doc.attachedTo !== undefined) {
candidateQuery.query(contact.class.Person, { _id: doc.attachedTo as Ref<Person> }, (result) => {
selectedCandidate = result[0]
})
}
let states: Array<{id: number | string, color: number, label: string}> = []
let selectedState: State
const statesQuery = createQuery()
$: if (doc.space !== undefined) {
statesQuery.query(
task.class.State,
{ space: doc.space },
(res) => {
states = res.map(s => { return { id: s._id, label: s.title, color: s.color} })
selectedState = res.filter(s => s._id === doc.state)[0] ?? res[0]
},
{ sort: { rank: SortingOrder.Ascending } }
)
}
</script>
<Card
@ -125,20 +157,38 @@
spaceQuery={{ archived: false }}
spaceLabel={recruit.string.Vacancy}
spacePlaceholder={recruit.string.SelectVacancy}
createMore={false}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}
>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1}>
<!-- <div class="flex-between mt-2 mb-2">
<div class="card" class:empty={!selectedCandidate}>
{#if selectedCandidate}
<CandidateCard candidate={selectedCandidate} disabled />
{:else}
<Label label={recruit.status.CandidateRequired} />
{/if}
</div>
<div class="arrows"><ExpandRightDouble /></div>
<div class="card" class:empty={!selectedVacancy}>
{#if selectedVacancy}
<VacancyCard vacancy={selectedVacancy} disabled />
{:else}
<Label label={recruit.status.VacancyRequired} />
{/if}
</div>
</div> -->
<svelte:fragment slot="pool">
{#if !preserveCandidate}
<UserBox
_class={contact.class.Person}
label={recruit.string.Candidate}
placeholder={recruit.string.Candidates}
bind:value={doc.attachedTo}
kind={'link'} size={'x-large'} justify={'left'} width={'100%'} labelDirection={'left'}
kind={'no-border'} size={'small'}
/>
{/if}
<UserBox
@ -148,7 +198,67 @@
bind:value={doc.assignee}
allowDeselect
titleDeselect={recruit.string.UnAssignRecruiter}
kind={'link'} size={'x-large'} justify={'left'} width={'100%'} labelDirection={'left'}
kind={'no-border'} size={'small'}
/>
</Grid>
{#if states && doc.space}
<Button
width="min-content"
size="small"
kind="no-border"
on:click={(ev) => {
showPopup(
ColorPopup,
{ value: states, searchable: true, placeholder: ui.string.SearchDots },
ev.currentTarget,
(result) => {
if (result && result.id !== doc.state) {
doc.state = result.id
selectedState = result
}
}
)
}}
>
<div slot="content" class="flex-row-center">
{#if selectedState}
<div class="color" style="background-color: {getPlatformColor(selectedState.color)}" />
<span class="label overflow-label">{selectedState.title}</span>
{/if}
</div>
</Button>
{/if}
</svelte:fragment>
</Card>
<style lang="scss">
.card {
align-self: stretch;
width: calc(50% - 3rem);
min-height: 16rem;
&.empty {
display: flex;
justify-content: center;
align-items: center;
font-size: .75rem;
color: var(--dark-color);
border: 1px solid var(--divider-color);
border-radius: .25rem;
}
}
.arrows { width: 4rem; }
.color {
margin-right: .375rem;
width: .875rem;
height: .875rem;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: .25rem;
}
.label {
flex-grow: 1;
min-width: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -15,7 +15,7 @@
<script lang="ts">
import attachment from '@anticrm/attachment'
import contact, { Channel, ChannelProvider, combineName, findPerson, Person } from '@anticrm/contact'
import { Channels } from '@anticrm/contact-resources'
import { ChannelsView } from '@anticrm/contact-resources'
import PersonPresenter from '@anticrm/contact-resources/src/components/PersonPresenter.svelte'
import {
Account,
@ -50,7 +50,9 @@
Label,
Link,
showPopup,
Spinner
Spinner,
Button,
IconAttachment
} from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
@ -397,91 +399,24 @@
dispatch('close')
}}
>
{#if matches.length > 0}
<div class="flex-row update-container ERROR">
<div class="flex mb-2">
<IconInfo size={'small'} />
<div class="text-sm ml-2 overflow-label">
<Label label={contact.string.PersonAlreadyExists} />
</div>
</div>
<PersonPresenter value={matches[0]} />
</div>
{/if}
<div class="flex-row-center">
<div class="mr-4">
<EditableAvatar bind:direct={avatar} avatar={object.avatar} size={'large'} on:remove={removeAvatar} on:done={onAvatarDone} />
</div>
<div class="flex-col">
<div class="fs-title">
<EditBox placeholder={recruit.string.PersonFirstNamePlaceholder} maxWidth="10rem" bind:value={firstName} focus />
</div>
<div class="fs-title mb-1">
<EditBox placeholder={recruit.string.PersonLastNamePlaceholder} maxWidth="10rem" bind:value={lastName} />
</div>
<div class="text-sm">
<EditBox placeholder={recruit.string.Title} maxWidth="10rem" bind:value={object.title} />
</div>
<div class="text-sm">
<EditBox placeholder={recruit.string.Location} maxWidth="10rem" bind:value={object.city} />
<EditBox placeholder={recruit.string.PersonFirstNamePlaceholder} bind:value={firstName} kind={'large-style'} maxWidth={'32rem'} focus />
<EditBox placeholder={recruit.string.PersonLastNamePlaceholder} bind:value={lastName} kind={'large-style'} maxWidth={'32rem'} />
<div class="mt-1">
<EditBox placeholder={recruit.string.Title} bind:value={object.title} kind={'small-style'} maxWidth={'32rem'} />
</div>
<EditBox placeholder={recruit.string.Location} bind:value={object.city} kind={'small-style'} maxWidth={'32rem'} />
</div>
</div>
<div class="flex-row-center channels">
<Channels
bind:channels
on:change={(e) => {
channels = e.detail
}}
/>
</div>
<div
class="flex-center resume"
class:solid={dragover || resume.uuid}
on:dragover|preventDefault={() => {
dragover = true
}}
on:dragleave={() => {
dragover = false
}}
on:drop|preventDefault|stopPropagation={drop}
>
{#if resume.uuid}
<Link
label={resume.name}
icon={FileIcon}
maxLenght={16}
on:click={() => {
showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'right')
}}
/>
{:else}
{#if loading}
<Link label={'Uploading...'} icon={Spinner} disabled />
{:else}
<Link
label={'Add or drop resume'}
icon={FileUpload}
on:click={() => {
inputFile.click()
}}
/>
{/if}
<input bind:this={inputFile} type="file" name="file" id="file" style="display: none" on:change={fileSelected} />
{/if}
</div>
<div class="separator" />
<div class="flex-col locations">
<span><Label label={recruit.string.WorkLocationPreferences} /></span>
<div class="row"><Label label={recruit.string.Onsite} /><YesNo bind:value={object.onsite} /></div>
<div class="row"><Label label={recruit.string.Remote} /><YesNo bind:value={object.remote} /></div>
</div>
<div class="separator" />
<div class="flex-col locations">
<span><Label label={recruit.string.SkillsLabel} /></span>
{#if channels.length > 0}
<div class="ml-22"><ChannelsView value={channels} size={'small'} on:click /></div>
{/if}
<div class="flex-col">
<span class="text-sm fs-bold content-accent-color"><Label label={recruit.string.SkillsLabel} /></span>
<div class="flex-grow">
<Component
is={tags.component.TagsEditor}
@ -495,60 +430,48 @@
/>
</div>
</div>
<svelte:fragment slot="pool">
<div class="flex-between w-full">
<span class="ml-2 content-color overflow-label"><Label label={recruit.string.WorkLocationPreferences} /></span>
<div class="buttons-group small-gap">
<YesNo label={recruit.string.Onsite} bind:value={object.onsite} />
<YesNo label={recruit.string.Remote} bind:value={object.remote} />
</div>
</div>
</svelte:fragment>
<svelte:fragment slot="footer">
<Button
icon={contact.icon.SocialEdit}
kind={'transparent'}
on:click={(ev) =>
showPopup(contact.component.SocialEditor, { values: channels }, ev.target, (result) => {
if (result !== undefined) channels = result
})
}
/>
<Button
icon={!resume.uuid && loading ? Spinner : IconAttachment}
kind={'transparent'}
on:click={() => { inputFile.click() }}
/>
<input bind:this={inputFile} type="file" name="file" id="file" style="display: none" on:change={fileSelected} />
{#if resume.uuid}
<Button
icon={FileIcon}
kind={'link-bordered'}
on:click={() => {
showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'right')
}}
><svelte:fragment slot="content">{resume.name}</svelte:fragment></Button>
{/if}
{#if matches.length > 0}
<div class="flex-row-center error-color">
<IconInfo size={'small'} />
<span class="text-sm overflow-label ml-2">
<Label label={contact.string.PersonAlreadyExists} />
</span>
<div class="ml-4"><PersonPresenter value={matches[0]} /></div>
</div>
{/if}
</svelte:fragment>
</Card>
<style lang="scss">
.channels {
margin-top: 1.25rem;
}
.locations {
span {
margin-bottom: 0.125rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-accent-color);
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.75rem;
color: var(--theme-caption-color);
}
}
.separator {
margin: 1rem 0;
height: 1px;
background-color: var(--theme-card-divider);
}
.resume {
margin-top: 1rem;
padding: 0.75rem;
background: var(--theme-zone-bg);
border: 1px dashed var(--theme-zone-border);
border-radius: 0.5rem;
backdrop-filter: blur(10px);
&.solid {
border-style: solid;
}
}
.update-container {
margin-left: -1rem;
margin-right: -1rem;
padding: 1rem;
margin-bottom: 1rem;
user-select: none;
font-size: 14px;
color: var(--theme-content-color);
&.ERROR { color: var(--system-error-color); }
border: 1px dashed var(--theme-zone-border);
border-radius: 0.5rem;
backdrop-filter: blur(10px);
}
</style>

View File

@ -16,9 +16,9 @@
<script lang="ts">
import { Organization } from '@anticrm/contact'
import core, { Ref } from '@anticrm/core'
import { getClient,SpaceCreateCard } from '@anticrm/presentation'
import task, { createKanban,KanbanTemplate } from '@anticrm/task'
import { Component,EditBox,Grid } from '@anticrm/ui'
import { getClient, Card } from '@anticrm/presentation'
import task, { createKanban, KanbanTemplate } from '@anticrm/task'
import { Component, EditBox, Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import { OrganizationSelector } from '@anticrm/contact-resources'
@ -55,24 +55,32 @@
}
</script>
<SpaceCreateCard
<Card
label={recruit.string.CreateVacancy}
okAction={createVacancy}
canSave={!!name}
on:close={() => { dispatch('close') }}
>
<Grid column={1} rowGap={1.5}>
<EditBox label={recruit.string.VacancyName} bind:value={name} icon={Vacancy} placeholder={recruit.string.VacancyPlaceholder} maxWidth={'16rem'} focus/>
<div class="flex-row-center clear-mins">
<div class="mr-3">
<Button icon={Vacancy} size={'medium'} kind={'link-bordered'} disabled />
</div>
<EditBox
bind:value={name}
placeholder={recruit.string.VacancyPlaceholder}
maxWidth={'37.5rem'} kind={'large-style'} focus
/>
</div>
<svelte:fragment slot="pool">
<OrganizationSelector
bind:value={company} label={recruit.string.Company}
kind={'link'} size={'x-large'} justify={'left'} width={'100%'} labelDirection={'left'}
kind={'no-border'} size={'small'}
/>
<Component is={task.component.KanbanTemplateSelector} props={{
folders: [recruit.space.VacancyTemplates],
template: templateId
}} on:change={(evt) => {
templateId = evt.detail
}}/>
</Grid>
</SpaceCreateCard>
</svelte:fragment>
</Card>

View File

@ -64,7 +64,8 @@
<style lang="scss">
.card {
align-self: stretch;
width: calc(50% - 3.5rem);
width: calc(50% - 3rem);
min-height: 16rem;
}
.arrows { width: 4rem; }
</style>

View File

@ -94,7 +94,7 @@
}
function showCreateDialog (ev: Event) {
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement)
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, 'top')
}
const applicationSorting = (a:Doc, b:Doc) => ((applications?.get(b._id as Ref<Vacancy>)?.count ?? 0) - (applications?.get(a._id as Ref<Vacancy>)?.count ?? 0)) ?? 0
const modifiedSorting = (a:Doc, b:Doc) => ((applications?.get(b._id as Ref<Vacancy>)?.modifiedOn ?? 0) - (applications?.get(a._id as Ref<Vacancy>)?.modifiedOn ?? 0)) ?? 0

View File

@ -23,6 +23,7 @@
import { Ref } from '@anticrm/core'
export let vacancy: Vacancy
export let disabled: boolean = false
let company: Organization | undefined
$: getOrganization(vacancy?.company)
@ -43,14 +44,16 @@
<VacancyIcon size={'large'} />
</div>
{#if vacancy}
<div class="name lines-limit-2 over-underline" on:click={() => {
closeTooltip()
closePopup()
closePanel()
const loc = getCurrentLocation()
loc.path[2] = vacancy._id
loc.path.length = 3
navigate(loc)
<div class="name lines-limit-2" class:over-underline={!disabled} on:click={() => {
if (!disabled) {
closeTooltip()
closePopup()
closePanel()
const loc = getCurrentLocation()
loc.path[2] = vacancy._id
loc.path.length = 3
navigate(loc)
}
}}>{vacancy.name}</div>
{#if company}
<span class="label">{company.name}</span>
@ -63,15 +66,16 @@
.card-container {
padding: 1rem 1.5rem 1.25rem;
background-color: var(--board-card-bg-color);
border: 1px solid var(--board-card-bg-color);
border: 1px solid var(--divider-color);
border-radius: .5rem;
transition-property: box-shadow, background-color;
transition-timing-function: ease-in-out;
transition-property: box-shadow, background-color, border-color;
transition-timing-function: var(--timing-shadow);
transition-duration: .15s;
&:hover {
background-color: var(--board-card-bg-hover);
box-shadow: var(--popup-shadow);
border-color: var(--button-border-color);
box-shadow: rgb(0 0 0 / 15%) 0px 4px 8px;
}
.logo {

View File

@ -18,56 +18,82 @@
import { Label } from '@anticrm/ui'
import recruit from '../plugin'
export let label: IntlString
export let value: boolean | undefined
function getLabel(value: boolean | undefined): IntlString {
if (value === true) return recruit.string.Yes
if (value === false) return recruit.string.No
return recruit.string.NA
}
export let disabled: boolean = false
</script>
<div class="flex-row-center yesno-container" class:yes={value === true} class:no={value === false} on:click={() => {
<button class="yesno-container" {disabled} class:yes={value === true} class:no={value === false} on:click={() => {
if (value === true) value = false
else if (value === false) value = undefined
else value = true
}}>
<svg class="yesno-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle fill="#fff" cx="8" cy="8" r="6"/>
{#if value === true}
<polygon fill="#60B96E" points="7.4,10.9 4.9,8.4 5.7,7.6 7.3,9.1 10.2,5.6 11.1,6.4 "/>
{:else if value === false}
<polygon fill="#F06C63" points="10.7,6 10,5.3 8,7.3 6,5.3 5.3,6 7.3,8 5.3,10 6,10.7 8,8.7 10,10.7 10.7,10 8.7,8 "/>
{:else}
<path fill="#77818E" d="M7.3,9.3h1.3V9c0.1-0.5,0.6-0.9,1.1-1.4c0.4-0.4,0.8-0.9,0.8-1.6c0-1.1-0.8-1.8-2.2-1.8c-1.4,0-2.4,0.8-2.5,2.2 h1.4c0.1-0.6,0.4-1,1-1C8.8,5.4,9,5.7,9,6.2c0,0.4-0.3,0.7-0.7,1.1c-0.5,0.5-1,0.9-1,1.7V9.3z M8,11.6c0.5,0,0.9-0.4,0.9-0.9 c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9C7.1,11.2,7.5,11.6,8,11.6z"/>
{/if}
</svg>
<div class="label"><Label label={getLabel(value)} /></div>
</div>
<span class="overflow-label">
<Label {label} />
</span>
<div class="btn-icon ml-1">
<svg class="yesno-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle class="circle" class:yes={value === true} class:no={value === false} cx="8" cy="8" r="6"/>
{#if value === true}
<polygon fill="#fff" points="7.4,10.9 4.9,8.4 5.7,7.6 7.3,9.1 10.2,5.6 11.1,6.4 "/>
{:else if value === false}
<polygon fill="#fff" points="10.7,6 10,5.3 8,7.3 6,5.3 5.3,6 7.3,8 5.3,10 6,10.7 8,8.7 10,10.7 10.7,10 8.7,8 "/>
{:else}
<path fill="#fff" d="M7.3,9.3h1.3V9c0.1-0.5,0.6-0.9,1.1-1.4c0.4-0.4,0.8-0.9,0.8-1.6c0-1.1-0.8-1.8-2.2-1.8c-1.4,0-2.4,0.8-2.5,2.2 h1.4c0.1-0.6,0.4-1,1-1C8.8,5.4,9,5.7,9,6.2c0,0.4-0.3,0.7-0.7,1.1c-0.5,0.5-1,0.9-1,1.7V9.3z M8,11.6c0.5,0,0.9-0.4,0.9-0.9 c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9C7.1,11.2,7.5,11.6,8,11.6z"/>
{/if}
</svg>
</div>
</button>
<style lang="scss">
.yesno-container {
padding: .25rem .5rem .25rem .4rem;
max-width: fit-content;
border-radius: 1.25rem;
user-select: none;
cursor: pointer;
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0 .25rem 0 .5rem;
min-width: 1.5rem;
height: 1.5rem;
font-weight: 400;
line-height: 1.5rem;
white-space: nowrap;
color: var(--accent-color);
background-color: var(--button-bg-color);
border: 1px solid transparent;
border-radius: .25rem;
box-shadow: var(--button-shadow);
transition-property: border, background-color, color, box-shadow;
transition-duration: .15s;
background-color: var(--grayscale-grey-03);
&.yes { background-color: #60B96E; }
&.no { background-color: #F06C63; }
.btn-icon {
color: var(--content-color);
transition: color .15s;
pointer-events: none;
}
&:hover {
color: var(--caption-color);
background-color: var(--button-bg-hover);
transition-duration: 0;
.btn-icon { color: var(--caption-color); }
}
&:disabled {
color: var(--content-color);
background-color: #30323655;
.label {
width: 1.4rem;
margin-left: .25rem;
text-transform: uppercase;
font-weight: 500;
font-size: .625rem;
color: #FFF;
&:hover {
color: var(--content-color);
.btn-icon { color: var(--content-color); }
}
}
}
.yesno-svg {
width: 1rem;
height: 1rem;
.circle {
fill: var(--grayscale-grey-03);
&.yes { fill: #60B96E; }
&.no { fill: #F06C63; }
}
}
</style>

View File

@ -22,7 +22,7 @@
import type { Candidate, Review } from '@anticrm/recruit'
import task, { SpaceWithStates } from '@anticrm/task'
import { StyledTextBox } from '@anticrm/text-editor'
import { DateRangePicker, Grid, Status as StatusControl, EditBox, Row } from '@anticrm/ui'
import { DateRangePicker, Grid, Status as StatusControl, EditBox, Row, DateRangePresenter } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import recruit from '../../plugin'
@ -39,8 +39,8 @@
let title: string = ''
let description: string = ''
let startDate: number = Date.now()
let dueDate: number = Date.now()
let startDate: number | null = null
let dueDate: number | null = null
let location: string = ''
let company: Ref<Organization> | undefined = undefined
@ -103,8 +103,8 @@
await client.addCollection(recruit.class.Review, doc.space, doc.attachedTo, doc.attachedToClass, 'reviews', {
number: (incResult as any).object.sequence,
date: startDate ?? null,
dueDate: dueDate ?? null,
date: startDate ?? 0,
dueDate: dueDate ?? 0,
description,
verdict: '',
title,
@ -143,7 +143,6 @@
</script>
<Card
size={'medium'}
label={recruit.string.CreateReviewParams}
labelProps={{ label: spaceLabel }}
okAction={createReview}
@ -158,61 +157,47 @@
}}
>
<StatusControl slot="error" {status} />
<Grid column={2} rowGap={1.75}>
<EditBox label={recruit.string.Title} icon={recruit.icon.Review} bind:value={title} maxWidth={'13rem'} />
<EditBox
placeholder={recruit.string.Title} bind:value={title}
maxWidth={'37.5rem'} kind={'large-style'} focus
/>
<EditBox
placeholder={recruit.string.Location} bind:value={location}
maxWidth={'37.5rem'} kind={'small-style'}
/>
<UserBoxList
_class={contact.class.Employee}
items={doc.participants}
title={calendar.string.Participants}
on:open={(evt) => {
doc.participants = [...(doc.participants ?? []), evt.detail._id]
}}
on:delete={(evt) => {
doc.participants = doc.participants?.filter((it) => it !== evt.detail._id) ?? [currentUser.employee]
}}
noItems={calendar.string.NoParticipants}
/>
<StyledTextBox
emphasized
showButtons={false}
bind:content={description}
label={recruit.string.Description}
alwaysEdit
placeholder={recruit.string.AddDescription}
/>
<svelte:fragment slot="pool">
{#if !preserveCandidate}
<UserBox
_class={contact.class.Person}
label={recruit.string.Candidate}
placeholder={recruit.string.Candidates}
bind:value={doc.attachedTo}
kind={'link'}
size={'x-large'}
justify={'left'}
width={'100%'}
labelDirection={'left'}
_class={contact.class.Person} bind:value={doc.attachedTo}
label={recruit.string.Candidate} placeholder={recruit.string.Candidates}
kind={'no-border'} size={'small'}
/>
{:else}
<div />
{/if}
<EditBox label={recruit.string.Location} icon={recruit.icon.Location} bind:value={location} maxWidth={'13rem'} />
<OrganizationSelector
bind:value={company}
label={recruit.string.Company}
kind={'link'}
size={'x-large'}
justify={'left'}
width={'100%'}
labelDirection={'left'}
bind:value={company} label={recruit.string.Company}
kind={'no-border'} size={'small'}
/>
<DateRangePicker title={recruit.string.StartDate} bind:value={startDate} withTime on:change={updateStart} />
<DateRangePicker title={recruit.string.DueDate} bind:value={dueDate} withTime />
<Row>
<UserBoxList
_class={contact.class.Employee}
items={doc.participants}
title={calendar.string.Participants}
on:open={(evt) => {
doc.participants = [...(doc.participants ?? []), evt.detail._id]
}}
on:delete={(evt) => {
doc.participants = doc.participants?.filter((it) => it !== evt.detail._id) ?? [currentUser.employee]
}}
noItems={calendar.string.NoParticipants}
/>
</Row>
<Row>
<StyledTextBox
emphasized
showButtons={false}
bind:content={description}
label={recruit.string.Description}
alwaysEdit
placeholder={recruit.string.AddDescription}
/>
</Row>
</Grid>
<DateRangePresenter bind:value={startDate} labelNull={recruit.string.StartDate} withTime editable on:change={updateStart} />
<DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable />
</svelte:fragment>
</Card>

View File

@ -1,7 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="tags" viewBox="0 0 16 16">
<path d="M8,8.5c1.7,0,3.2-1.4,3.2-3.2S9.7,2.2,8,2.2c-1.7,0-3.2,1.4-3.2,3.2S6.3,8.5,8,8.5z M8,3.2c1.2,0,2.2,1,2.2,2.2 S9.2,7.5,8,7.5s-2.2-1-2.2-2.2S6.8,3.2,8,3.2z"/>
<path d="M4.2,11.2c-0.9,0.6-1.5,1.3-1.8,2.2c-0.1,0.3,0,0.5,0.3,0.6c0.3,0.1,0.5,0,0.6-0.3c0.2-0.6,0.7-1.3,1.4-1.7 c0.7-0.5,1.6-0.8,2.6-0.9c0.1,0,0.1,0,0.2,0c0.2-0.4,0.6-0.8,1-1c-0.4,0-0.8,0-1.2,0C6.1,10.3,5.1,10.7,4.2,11.2z"/>
<path d="M14.7,11.5h-2.2V9.3c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5v2.2H9.3c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h2.2v2.2 c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5v-2.2h2.2c0.3,0,0.5-0.2,0.5-0.5S14.9,11.5,14.7,11.5z"/>
<symbol id="tags" viewBox="0 0 24 24">
<path d="M22.8,8.7c0-0.3-0.2-0.6-0.5-0.7L11.8,3.8c-0.2-0.1-0.4-0.1-0.6,0L0.7,8C0.4,8.1,0.2,8.4,0.2,8.7 s0.2,0.6,0.5,0.7L5,11.1v6c0,0.2,0.1,0.4,0.2,0.5l0.5-0.5c-0.5,0.5-0.5,0.5-0.5,0.5l0,0l0,0l0,0l0,0c0,0,0.1,0.1,0.1,0.1 c0.1,0.1,0.2,0.2,0.3,0.3C6,18.3,6.5,18.6,7,19c1.1,0.6,2.7,1.3,4.5,1.3c1.8,0,3.4-0.7,4.5-1.3c0.5-0.3,1-0.6,1.3-0.9 c0.2-0.1,0.3-0.2,0.3-0.3c0,0,0.1-0.1,0.1-0.1l0,0l0,0l0,0l0,0l-0.5-0.5c0.5,0.5,0.5,0.5,0.5,0.5c0.1-0.1,0.2-0.3,0.2-0.5v-6 l3.3-1.3v3.6c0,0.4,0.3,0.8,0.8,0.8c0.4,0,0.8-0.3,0.8-0.8L22.8,8.7C22.8,8.7,22.8,8.7,22.8,8.7C22.8,8.7,22.8,8.7,22.8,8.7z M16.5,16.8c0,0-0.1,0.1-0.1,0.1c-0.3,0.2-0.6,0.5-1.1,0.8c-0.9,0.6-2.2,1.1-3.7,1.1c-1.5,0-2.8-0.5-3.7-1.1 c-0.5-0.3-0.8-0.5-1.1-0.8c-0.1,0-0.1-0.1-0.1-0.1v-5.1l4.7,1.9c0.2,0.1,0.4,0.1,0.6,0l4.7-1.9V16.8z M11.5,12.1L3,8.7l8.5-3.4 L20,8.7L11.5,12.1z" />
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 746 B

After

Width:  |  Height:  |  Size: 956 B

View File

@ -16,7 +16,7 @@
import { Class, Data, Doc, generateId, Ref } from '@anticrm/core'
import { Card, createQuery, getClient } from '@anticrm/presentation'
import { findTagCategory, TagCategory, TagElement } from '@anticrm/tags'
import { DropdownLabels, EditBox, getColorNumberByText, getPlatformColor, showPopup } from '@anticrm/ui'
import { DropdownLabels, EditBox, getColorNumberByText, getPlatformColor, showPopup, Button, IconFolder } from '@anticrm/ui'
import { DropdownTextItem } from '@anticrm/ui/src/types'
import { ColorsPopup } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
@ -88,58 +88,65 @@
okAction={createTagElenent}
canSave={title.length > 0}
space={tags.space.Tags}
createMore={false}
on:close={() => {
dispatch('close')
}}
>
<div class="flex-row-center">
<div class="flex-col">
<div class="fs-title flex-row-center">
<div
class="color"
style={getTagStyle(getPlatformColor(color))}
on:click={(evt) => {
showPopup(ColorsPopup, {}, evt.target, (col) => {
if (col != null) {
color = col
colorSet = true
}
})
}}
/>
<div class="flex-row-top clear-mins">
<div class="mr-3">
<Button
size={'medium'}
kind={'link-bordered'}
on:click={(evt) => {
showPopup(ColorsPopup, {}, evt.target, (col) => {
if (col != null) {
color = col
colorSet = true
}
})
}}
>
<svelte:fragment slot="content">
<div class="color" style={getTagStyle(getPlatformColor(color))} />
</svelte:fragment>
</Button>
</div>
<div class="flex-col mt-0-5">
<EditBox
bind:value={title}
placeholder={tags.string.TagName}
placeholderParam={{ word: keyTitle }}
maxWidth={'35rem'} kind={'large-style'} focus
/>
<div class="mt-2">
<EditBox
focus
placeholder={tags.string.TagName}
placeholderParam={{ word: keyTitle }}
maxWidth={'16rem'}
bind:value={title}
/>
</div>
<div class="text-sm mt-4">
<EditBox placeholder={tags.string.TagDescriptionPlaceholder} maxWidth={'17.75rem'} bind:value={description} />
</div>
<div class="text-sm mt-4">
<DropdownLabels
label={tags.string.CategoryLabel}
bind:selected={category}
items={categoryItems}
on:selected={() => {
categoryWasSet = true
}}
bind:value={description}
placeholder={tags.string.TagDescriptionPlaceholder}
maxWidth={'35rem'} kind={'small-style'}
/>
</div>
</div>
</div>
<svelte:fragment slot="pool">
<div class="ml-12">
<DropdownLabels
icon={IconFolder}
label={tags.string.CategoryLabel}
bind:selected={category}
items={categoryItems}
on:selected={() => {
categoryWasSet = true
}}
/>
</div>
</svelte:fragment>
</Card>
<style lang="scss">
.color {
margin-right: 0.75rem;
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
cursor: pointer;
border-radius: .25rem;
}
</style>

View File

@ -43,7 +43,7 @@
}
function showCreateDialog (ev: Event) {
showPopup(CreateTagElement, { targetClass, keyTitle }, ev.target as HTMLElement)
showPopup(CreateTagElement, { targetClass, keyTitle }, 'top')
}
const opt: FindOptions<TagElement> = {
lookup: {

View File

@ -41,4 +41,4 @@
}
</script>
<DropdownLabels {items} bind:selected={selectedItem} label={plugin.string.States} />
<DropdownLabels {items} icon={task.icon.ManageStatuses} bind:selected={selectedItem} label={plugin.string.States} />

View File

@ -18,7 +18,7 @@
import { Ref } from '@anticrm/core'
import task, { State } from '@anticrm/task'
import { createQuery } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import { showPopup, Button, SelectPopup } from '@anticrm/ui'
import StatePresenter from './StatePresenter.svelte'
import StatesPopup from './StatesPopup.svelte'
@ -35,19 +35,30 @@
</script>
{#if state}
<div class="flex-row-center cursor-pointer" bind:this={container}
on:click|preventDefault={() => {
<Button
width="min-content"
size="small"
kind="no-border"
on:click={(ev) => {
if (!opened) {
opened = true
showPopup(StatesPopup, { space: state.space }, container, (result) => {
if (result && result._id !== value) {
value = result._id
onChange(value)
}
opened = false
})
showPopup(
StatesPopup,
{ space: state.space },
ev.currentTarget,
(result) => {
if (result && result._id !== value) {
value = result._id
onChange(value)
}
opened = false
}
)
}
}} >
<StatePresenter value={state} />
</div>
}}
>
<svelte:fragment slot="content">
<StatePresenter value={state} />
</svelte:fragment>
</Button>
{/if}

View File

@ -19,22 +19,19 @@
export let value: State
</script>
{#if value}
<div class="overflow-label state-container" style="background-color: {getPlatformColor(value.color)}">
{value.title}
<div class="flex-row-center">
<div class="state-container" style="background-color: {getPlatformColor(value.color)}" />
<span class="overflow-label">{value.title ?? ''}</span>
</div>
{/if}
<style lang="scss">
.state-container {
padding: 0.25rem 0.5rem;
width: 6.25rem;
max-width: 6.25rem;
text-transform: uppercase;
text-align: center;
letter-spacing: 0.5px;
font-size: 0.625rem;
color: #fff;
margin-right: .5rem;
width: .875rem;
height: .875rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.25rem;
}

View File

@ -21,7 +21,6 @@
"AddIssue": "Add Issue",
"NewIssue": "New issue",
"SaveIssue": "Save issue",
"CreateMore": "Create more",
"Todo": "Todo",
"InProgress": "In Progress",
"Done": "Done",

View File

@ -17,7 +17,6 @@
"CreateTeam": "Create team",
"NewIssue": "Новая задача",
"SaveIssue": "Сохранить задачу",
"CreateMore": "Создать еще",
"Todo": "Todo",
"InProgress": "В работе",
"Done": "Выполнено",

View File

@ -1,73 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Asset, IntlString } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { Button, Label, IconAttachment, IconExpand, IconClose, MiniToggle } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
import presentation from '@anticrm/presentation'
import tracker from '../plugin'
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 icon: Asset | undefined = undefined
export let okAction: () => void
export let canSave: boolean = false
export let createMore: boolean | undefined = undefined
export let okLabel: IntlString = presentation.string.Create
export let cancelLabel: IntlString = presentation.string.Cancel
const dispatch = createEventDispatcher()
</script>
<form class="antiCard dialog" on:submit|preventDefault={ () => {} }>
<div class="antiCard-header">
<div class="antiCard-header__title-wrap">
<Button icon={icon} label={presentation.string.Save} size={'small'} kind={'no-border'} disabled on:click={() => { }} />
<span class="antiCard-header__divider"></span>
<span class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></span>
</div>
<div class="buttons-group small-gap">
<Button icon={IconExpand} kind={'transparent'} on:click={() => { }} />
<Button icon={IconClose} kind={'transparent'} on:click={() => { dispatch('close') }} />
</div>
</div>
<div class="antiCard-content"><slot /></div>
{#if spaceClass && spaceLabel && spacePlaceholder}
<div class="antiCard-pool">
<slot name="pool" />
</div>
{/if}
<div class="antiCard-footer reverse">
<div class="buttons-group text-sm">
{#if createMore !== undefined}
<MiniToggle label={tracker.string.CreateMore} bind:on={createMore} />
{/if}
<Button disabled={!canSave} label={okLabel} kind={'primary'} on:click={() => { okAction(); dispatch('close') }} />
</div>
<Button icon={IconAttachment} kind={'transparent'} on:click={() => { }} />
</div>
</form>

View File

@ -16,14 +16,13 @@
import contact, { Employee } from '@anticrm/contact'
import core, { Data, generateId, Ref, SortingOrder } from '@anticrm/core'
import { Asset, IntlString } from '@anticrm/platform'
import { getClient, UserBox } from '@anticrm/presentation'
import presentation, { getClient, UserBox, Card } from '@anticrm/presentation'
import { Issue, IssuePriority, IssueStatus, Team } from '@anticrm/tracker'
import { StyledTextBox } from '@anticrm/text-editor'
import { EditBox, Button, showPopup, DatePresenter, SelectPopup } from '@anticrm/ui'
import { EditBox, Button, showPopup, DatePresenter, SelectPopup, IconAttachment } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../plugin'
import { calcRank } from '../utils'
import Card from './Card.svelte'
import StatusSelector from './StatusSelector.svelte'
import PrioritySelector from './PrioritySelector.svelte'
@ -115,7 +114,6 @@
<Card
label={tracker.string.NewIssue}
okAction={createIssue}
icon={tracker.icon.Home}
canSave={true}
okLabel={tracker.string.SaveIssue}
spaceClass={tracker.class.Team}
@ -127,6 +125,9 @@
dispatch('close')
}}
>
<svelte:fragment slot="space">
<Button icon={tracker.icon.Home} label={presentation.string.Save} size={'small'} kind={'no-border'} disabled on:click={() => { }} />
</svelte:fragment>
<EditBox
bind:value={object.title}
placeholder={tracker.string.IssueTitlePlaceholder}
@ -134,17 +135,15 @@
kind={'large-style'}
focus
/>
<div class="mt-4">
<StyledTextBox
alwaysEdit
showButtons={false}
bind:content={object.description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>
</div>
<div slot="pool" class="flex-row-center text-sm gap-1-5">
<StatusSelector status={object.status} onStatusChange={handleStatusChanged} />
<PrioritySelector priority={object.priority} onPriorityChange={handlePriorityChanged} />
<StyledTextBox
alwaysEdit
showButtons={false}
bind:content={object.description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>
<svelte:fragment slot="pool">
<StatusSelector bind:status={object.status} onStatusChange={handleStatusChanged} />
<PrioritySelector bind:priority={object.priority} onPriorityChange={handlePriorityChanged} />
<UserBox
_class={contact.class.Employee}
label={tracker.string.Assignee}
@ -177,5 +176,8 @@
showPopup(SelectPopup, { value: moreActions }, ev.currentTarget)
}}
/>
</div>
</svelte:fragment>
<svelte:fragment slot="footer">
<Button icon={IconAttachment} kind={'transparent'} on:click={() => { }} />
</svelte:fragment>
</Card>

View File

@ -42,7 +42,6 @@ export default mergeIds(trackerId, tracker, {
Team: '' as IntlString,
SelectTeam: '' as IntlString,
SaveIssue: '' as IntlString,
CreateMore: '' as IntlString,
Todo: '' as IntlString,
InProgress: '' as IntlString,
Done: '' as IntlString,

View File

@ -44,7 +44,7 @@
$: query.query(core.class.Space, { _id: spaceId }, result => { space = result[0] })
function showCreateDialog (ev: Event) {
showPopup(createItemDialog as AnyComponent, { space: spaceId }, ev.target as HTMLElement)
showPopup(createItemDialog as AnyComponent, { space: spaceId }, 'top')
}
$: updateViewlets(viewlets)

View File

@ -58,7 +58,7 @@ test.describe('recruit tests', () => {
// Click on Add button
await page.click('.applications-container .flex-row-center .flex-center')
await page.click('span:has-text("Select vacancy")')
await page.click('button:has-text("Vacancy")')
await page.click(`button:has-text("${vacancyId}")`)
@ -142,13 +142,13 @@ test.describe('recruit tests', () => {
// Click button:has-text("Review")
await page.click('button:has-text("Review")')
// Click [placeholder="\ "]
await page.click('[placeholder="placeholder"]')
await page.click('[placeholder="Title"]')
// Fill [placeholder="\ "]
await page.fill('[placeholder="placeholder"]', 'Meet PEterson')
await page.fill('[placeholder="Title"]', 'Meet PEterson')
// Click text=Location Company Company >> [placeholder="\ "]
await page.click('text=placeholder Location >> [placeholder="placeholder"]')
await page.click('[placeholder="Location"]')
// Fill text=Location Company Company >> [placeholder="\ "]
await page.fill('text=placeholder Location >> [placeholder="placeholder"]', 'NSK')
await page.fill('[placeholder="Location"]', 'NSK')
// Click text=Company Company >> div
// await page.click('text=Company Company >> div')
// Click button:has-text("Apple")

View File

@ -30,11 +30,11 @@ test.describe('recruit tests', () => {
// Fill [placeholder="Please\ type\ Skill\ title"]
await page.fill('[placeholder="Please\\ type\\ Skill\\ title"]', 's1')
// Click text=Create Skill s1 Please type description here Category Other Create Cancel >> button
await page.click('text=Create Skill s1 Please type description here Category Other Create Cancel >> button')
await page.click('text=Create more Create >> button')
// Click text=s1
await page.click('text=s1')
// Click :nth-match(:text("Cancel"), 2)
await page.click(':nth-match(:text("Cancel"), 2)')
await page.click('button:has-text("Cancel")')
// Click button:has-text("Create")
await page.click('button:has-text("Create")')
})
@ -78,7 +78,7 @@ test.describe('recruit tests', () => {
// Click text=java
await page.click('text=java')
// Click :nth-match(:text("Cancel"), 2)
await page.click(':nth-match(:text("Cancel"), 2)')
await page.click('button:has-text("Cancel")')
// Click [placeholder="John"]
await page.click('[placeholder="John"]')
// Fill [placeholder="John"]

View File

@ -14,14 +14,14 @@ test.describe('workbench tests', () => {
await page.click('text=Applications')
await expect(page).toHaveURL('http://localhost:8083/workbench%3Acomponent%3AWorkbenchApp/recruit%3Aapp%3ARecruit/applicants')
// Click text=Applications Application >> span
await expect(page.locator('text=Applications Application >> span')).toBeVisible()
await expect(page.locator('text=Applications Application')).toBeVisible()
await expect(page.locator('text=APP-1')).toBeVisible()
// Click text=Candidates
await page.click('text=Candidates')
await expect(page).toHaveURL('http://localhost:8083/workbench%3Acomponent%3AWorkbenchApp/recruit%3Aapp%3ARecruit/candidates')
await expect(page.locator('text=Candidates Candidate >> span')).toBeVisible()
await expect(page.locator('text=Candidates Candidate')).toBeVisible()
await expect(page.locator('text=Andrey P.')).toBeVisible()
// Click text=Vacancies