mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
parent
63b0e63866
commit
5dde89f503
@ -17,7 +17,14 @@ import { ArrOf, Builder, Index, Model, Prop, TypeNumber, TypeRef, TypeString, UX
|
||||
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import view from '@hcengineering/model-view'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import type { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||
import type {
|
||||
ExpertKnowledge,
|
||||
InitialKnowledge,
|
||||
MeaningfullKnowledge,
|
||||
TagCategory,
|
||||
TagElement,
|
||||
TagReference
|
||||
} from '@hcengineering/tags'
|
||||
import tags from './plugin'
|
||||
|
||||
export { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||
@ -63,6 +70,9 @@ export class TTagReference extends TAttachedDoc implements TagReference {
|
||||
|
||||
@Prop(TypeString(), tags.string.ColorLabel)
|
||||
color!: number
|
||||
|
||||
@Prop(TypeNumber(), tags.string.Weight)
|
||||
weight!: InitialKnowledge | MeaningfullKnowledge | ExpertKnowledge
|
||||
}
|
||||
|
||||
@Model(tags.class.TagCategory, core.class.Doc, DOMAIN_TAGS)
|
||||
|
@ -34,8 +34,7 @@
|
||||
const config: (BuildModelKey | string)[] = [
|
||||
'',
|
||||
'$lookup.space.name',
|
||||
'comments',
|
||||
'attachments',
|
||||
'$lookup.space.$lookup.company',
|
||||
'$lookup.state',
|
||||
'$lookup.doneState'
|
||||
]
|
||||
|
@ -197,7 +197,8 @@
|
||||
await client.addCollection(skill._class, skill.space, candidateId, recruit.mixin.Candidate, 'skills', {
|
||||
title: skill.title,
|
||||
color: skill.color,
|
||||
tag: skill.tag
|
||||
tag: skill.tag,
|
||||
weight: skill.weight
|
||||
})
|
||||
}
|
||||
|
||||
@ -404,7 +405,7 @@
|
||||
<Card
|
||||
label={recruit.string.CreateTalent}
|
||||
okAction={createCandidate}
|
||||
canSave={!loading && firstName.length > 0 && lastName.length > 0}
|
||||
canSave={!loading && (firstName.length > 0 || lastName.length > 0 || channels.length > 0)}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
@ -470,47 +471,80 @@
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="pool">
|
||||
<ChannelsDropdown
|
||||
editable={!loading}
|
||||
focusIndex={10}
|
||||
bind:value={channels}
|
||||
highlighted={matchedChannels.map((it) => it.provider)}
|
||||
/>
|
||||
<YesNo
|
||||
disabled={loading}
|
||||
focusIndex={100}
|
||||
label={recruit.string.Onsite}
|
||||
tooltip={recruit.string.WorkLocationPreferences}
|
||||
bind:value={object.onsite}
|
||||
/>
|
||||
<YesNo
|
||||
disabled={loading}
|
||||
focusIndex={101}
|
||||
label={recruit.string.Remote}
|
||||
tooltip={recruit.string.WorkLocationPreferences}
|
||||
bind:value={object.remote}
|
||||
/>
|
||||
<Component
|
||||
is={tags.component.TagsDropdownEditor}
|
||||
props={{
|
||||
disabled: loading,
|
||||
focusIndex: 102,
|
||||
items: skills,
|
||||
key,
|
||||
targetClass: recruit.mixin.Candidate,
|
||||
showTitle: false,
|
||||
elements,
|
||||
newElements,
|
||||
countLabel: recruit.string.NumberSkills
|
||||
}}
|
||||
on:open={(evt) => {
|
||||
addTagRef(evt.detail)
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
skills = skills.filter((it) => it._id !== evt.detail)
|
||||
}}
|
||||
/>
|
||||
<div class="flex-col flex-grow">
|
||||
<div class="flex flex-wrap">
|
||||
<ChannelsDropdown
|
||||
editable={!loading}
|
||||
focusIndex={10}
|
||||
bind:value={channels}
|
||||
highlighted={matchedChannels.map((it) => it.provider)}
|
||||
/>
|
||||
<YesNo
|
||||
disabled={loading}
|
||||
focusIndex={100}
|
||||
label={recruit.string.Onsite}
|
||||
tooltip={recruit.string.WorkLocationPreferences}
|
||||
bind:value={object.onsite}
|
||||
/>
|
||||
<YesNo
|
||||
disabled={loading}
|
||||
focusIndex={101}
|
||||
label={recruit.string.Remote}
|
||||
tooltip={recruit.string.WorkLocationPreferences}
|
||||
bind:value={object.remote}
|
||||
/>
|
||||
<Component
|
||||
is={tags.component.TagsDropdownEditor}
|
||||
props={{
|
||||
disabled: loading,
|
||||
focusIndex: 102,
|
||||
items: skills,
|
||||
key,
|
||||
targetClass: recruit.mixin.Candidate,
|
||||
showTitle: false,
|
||||
elements,
|
||||
newElements,
|
||||
countLabel: recruit.string.NumberSkills
|
||||
}}
|
||||
on:open={(evt) => {
|
||||
addTagRef(evt.detail)
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
skills = skills.filter((it) => it._id !== evt.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if skills.length > 0}
|
||||
<div class="skills-box p-1 mt-2">
|
||||
<Component
|
||||
is={tags.component.TagsEditor}
|
||||
props={{
|
||||
disabled: loading,
|
||||
focusIndex: 102,
|
||||
items: skills,
|
||||
key,
|
||||
targetClass: recruit.mixin.Candidate,
|
||||
showTitle: false,
|
||||
elements,
|
||||
newElements,
|
||||
countLabel: recruit.string.NumberSkills
|
||||
}}
|
||||
on:open={(evt) => {
|
||||
addTagRef(evt.detail)
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
skills = skills.filter((it) => it._id !== evt.detail)
|
||||
}}
|
||||
on:change={(evt) => {
|
||||
evt.detail.tag.weight = evt.detail.weight
|
||||
skills = skills
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<div
|
||||
class="flex-center resume"
|
||||
@ -581,4 +615,10 @@
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
.skills-box {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--accent-bg-color);
|
||||
border: 1px dashed var(--divider-color);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,4 +2,19 @@
|
||||
<symbol id="tags" viewBox="0 0 16 16">
|
||||
<path d="M2.5,9.8l3.7,3.7c0,0,0,0,0,0c0.6,0.6,1.6,0.6,2.2,0l5.5-5.5C13.9,8,14,7.8,14,7.7V2.5C14,2.2,13.8,2,13.5,2H8.3 C8.1,2,8,2.1,7.9,2.1L2.5,7.6C2.2,7.9,2,8.3,2,8.7C2,9.1,2.2,9.5,2.5,9.8z M3.2,8.3L8.5,3H13v4.5l-5.3,5.3c-0.2,0.2-0.6,0.2-0.8,0 L3.2,9.1C3.1,9,3,8.9,3,8.7S3.1,8.4,3.2,8.3z"/>
|
||||
</symbol>
|
||||
<symbol id="level-3" viewBox="0 0 16 16">
|
||||
<rect x="1" y="8" width="3" height="6" rx="1" />
|
||||
<rect x="6" y="5" width="3" height="9" rx="1" />
|
||||
<rect x="11" y="2" width="3" height="12" rx="1" />
|
||||
</symbol>
|
||||
<symbol id="level-2" viewBox="0 0 16 16">
|
||||
<rect x="1" y="8" width="3" height="6" rx="1" />
|
||||
<rect x="6" y="5" width="3" height="9" rx="1" />
|
||||
<rect x="11" y="2" width="3" height="12" rx="1" fill-opacity=".4" />
|
||||
</symbol>
|
||||
<symbol id="level-1" viewBox="0 0 16 16">
|
||||
<rect x="1" y="8" width="3" height="6" rx="1" />
|
||||
<rect x="6" y="5" width="3" height="9" rx="1" fill-opacity=".4" />
|
||||
<rect x="11" y="2" width="3" height="12" rx="1" fill-opacity=".4" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 1.1 KiB |
@ -33,6 +33,10 @@
|
||||
"DefaultLabel": "Default category",
|
||||
"SelectAll": "Select all",
|
||||
"SelectNone": "Select none",
|
||||
"ApplyTags": "Apply"
|
||||
"ApplyTags": "Apply",
|
||||
"Weight": "Weight",
|
||||
"Expert": "Expert",
|
||||
"Meaningfull": "Meaningfull",
|
||||
"Initial": "Initial"
|
||||
}
|
||||
}
|
@ -33,6 +33,10 @@
|
||||
"DefaultLabel": "Категория по умолчанию",
|
||||
"SelectAll": "Выбрать все",
|
||||
"SelectNone": "Выбрать ничего",
|
||||
"ApplyTags": "Применить"
|
||||
"ApplyTags": "Применить",
|
||||
"Weight": "Вес",
|
||||
"Expert": "Эксперт",
|
||||
"Meaningfull": "Значимый",
|
||||
"Initial": "Начальный"
|
||||
}
|
||||
}
|
@ -19,7 +19,10 @@ import tags, { tagsId } from '@hcengineering/tags'
|
||||
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(tags.icon, {
|
||||
Tags: `${icons}#tags`
|
||||
Tags: `${icons}#tags`,
|
||||
Level1: `${icons}#level-1`,
|
||||
Level2: `${icons}#level-2`,
|
||||
Level3: `${icons}#level-3`
|
||||
})
|
||||
|
||||
addStringsLoader(tagsId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
@ -15,10 +15,10 @@
|
||||
<script lang="ts">
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { ActionIcon, AnySvelteComponent, getPlatformColor, tooltip } from '@hcengineering/ui'
|
||||
import { ActionIcon, AnySvelteComponent, getPlatformColor, Icon, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tags from '../plugin'
|
||||
import { getTagStyle } from '../utils'
|
||||
import { getTagStyle, tagLevel } from '../utils'
|
||||
|
||||
export let tag: TagReference | undefined = undefined
|
||||
export let element: TagElement | undefined = undefined
|
||||
@ -28,11 +28,15 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: name = element?.title ?? tag?.title ?? 'New item'
|
||||
|
||||
$: tagIcon = tagLevel[(((tag?.weight ?? 0) % 3) + 1) as 1 | 2 | 3]
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="text-sm flex flex-between tag-item"
|
||||
style={`${getTagStyle(getPlatformColor(tag?.color ?? element?.color ?? 0), selected)}`}
|
||||
on:click
|
||||
on:keydown
|
||||
use:tooltip={{
|
||||
label: element?.description ? tags.string.TagTooltip : undefined,
|
||||
props: { text: element?.description },
|
||||
@ -40,11 +44,16 @@
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<span class="ml-1">
|
||||
{#if tag}
|
||||
<Icon icon={tagIcon} size={'small'} />
|
||||
{/if}
|
||||
</span>
|
||||
{#if action}
|
||||
<div class="ml-1">
|
||||
<ActionIcon
|
||||
icon={action}
|
||||
size={'small'}
|
||||
size={'medium'}
|
||||
action={() => {
|
||||
dispatch('action')
|
||||
}}
|
||||
@ -61,7 +70,7 @@
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
|
@ -14,9 +14,10 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { TagReference } from '@hcengineering/tags'
|
||||
import { getPlatformColor, IconClose, Icon, resizeObserver } from '@hcengineering/ui'
|
||||
import TagItem from './TagItem.svelte'
|
||||
import { getPlatformColor, Icon, IconClose, resizeObserver } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { tagLevel } from '../utils'
|
||||
import TagItem from './TagItem.svelte'
|
||||
|
||||
export let value: TagReference
|
||||
export let isEditable: boolean = false
|
||||
@ -24,6 +25,7 @@
|
||||
export let realWidth: number | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
$: tagIcon = tagLevel[(((value?.weight ?? 0) % 3) + 1) as 1 | 2 | 3]
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
@ -48,7 +50,10 @@
|
||||
}}
|
||||
>
|
||||
<div class="color" style:background-color={getPlatformColor(value.color ?? 0)} />
|
||||
<span class="overflow-label ml-1-5 caption-color">{value.title}</span>
|
||||
<span class="overflow-label ml-1-5 caption-color"
|
||||
>{value.title}-
|
||||
<Icon icon={tagIcon} size={'small'} />
|
||||
</span>
|
||||
{#if isEditable}
|
||||
<button class="btn-close" on:click|stopPropagation={() => dispatch('remove', value.tag)}>
|
||||
<Icon icon={IconClose} size={'x-small'} />
|
||||
|
@ -34,7 +34,7 @@
|
||||
(result) => {
|
||||
items = result
|
||||
},
|
||||
{ sort: { title: 1 } }
|
||||
{ sort: { weight: -1, title: 1 } }
|
||||
)
|
||||
|
||||
async function addRef (tag: TagElement): Promise<void> {
|
||||
@ -49,6 +49,10 @@
|
||||
await client.removeCollection(tags.class.TagReference, object.space, id, object._id, _class, key.key)
|
||||
}
|
||||
|
||||
async function updateWeight (tag: TagReference, weight: TagReference['weight']): Promise<void> {
|
||||
await client.update(tag, { weight })
|
||||
}
|
||||
|
||||
let elements: Map<Ref<TagElement>, TagElement> = new Map()
|
||||
const elementQuery = createQuery()
|
||||
$: elementQuery.query(tags.class.TagElement, {}, (result) => {
|
||||
@ -63,4 +67,5 @@
|
||||
targetClass={_class}
|
||||
on:open={(evt) => addRef(evt.detail)}
|
||||
on:delete={(evt) => removeTag(evt.detail)}
|
||||
on:change={(evt) => updateWeight(evt.detail.tag, evt.detail.weight)}
|
||||
/>
|
||||
|
@ -17,11 +17,20 @@
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { KeyedAttribute } from '@hcengineering/presentation'
|
||||
import { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { Button, IconAdd, IconClose, Label, ShowMore, showPopup } from '@hcengineering/ui'
|
||||
import {
|
||||
Button,
|
||||
getEventPopupPositionElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
Label,
|
||||
ShowMore,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tags from '../plugin'
|
||||
import TagItem from './TagItem.svelte'
|
||||
import TagsPopup from './TagsPopup.svelte'
|
||||
import WeightPopup from './WeightPopup.svelte'
|
||||
|
||||
export let items: TagReference[] = []
|
||||
export let targetClass: Ref<Class<Doc>>
|
||||
@ -39,6 +48,16 @@
|
||||
keyLabel = v
|
||||
})
|
||||
|
||||
$: expert = items.filter((it) => (it.weight ?? 0) >= 6 && (it.weight ?? 0) <= 8)
|
||||
$: meaningfull = items.filter((it) => (it.weight ?? 0) >= 3 && (it.weight ?? 0) <= 5)
|
||||
$: initial = items.filter((it) => (it.weight ?? 1) >= 0 && (it.weight ?? 0) <= 2)
|
||||
|
||||
$: categories = [
|
||||
{ items: expert, label: tags.string.Expert },
|
||||
{ items: meaningfull, label: tags.string.Meaningfull },
|
||||
{ items: initial, label: tags.string.Initial }
|
||||
]
|
||||
|
||||
async function addRef (tag: TagElement): Promise<void> {
|
||||
dispatch('open', tag)
|
||||
}
|
||||
@ -87,7 +106,7 @@
|
||||
{/if}
|
||||
<ShowMore ignore={!showTitle}>
|
||||
<div class:tags-container={showTitle} class:mt-3={showTitle} class:empty={items.length === 0}>
|
||||
<div class="tag-items" class:tag-items-scroll={!showTitle}>
|
||||
<div class="tag-items-container" class:tag-items-scroll={!showTitle}>
|
||||
{#if items.length === 0}
|
||||
{#if keyLabel}
|
||||
<div class="text-sm dark-color w-full flex-center">
|
||||
@ -95,15 +114,38 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#each items as tag}
|
||||
<TagItem
|
||||
{tag}
|
||||
element={elements.get(tag.tag)}
|
||||
action={IconClose}
|
||||
on:action={() => {
|
||||
removeTag(tag._id)
|
||||
}}
|
||||
/>
|
||||
{#each categories as cat, ci}
|
||||
{#if cat.items.length > 0}
|
||||
<div class="text-sm mb-1" class:mt-2={ci > 0 && categories[ci - 1].items.length > 0}>
|
||||
<Label label={cat.label} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="tag-items">
|
||||
{#each cat.items as tag}
|
||||
<TagItem
|
||||
{tag}
|
||||
element={elements.get(tag.tag)}
|
||||
action={IconClose}
|
||||
on:action={() => {
|
||||
removeTag(tag._id)
|
||||
}}
|
||||
on:click={(evt) => {
|
||||
showPopup(
|
||||
WeightPopup,
|
||||
{ value: tag.weight ?? 1, format: 'number' },
|
||||
getEventPopupPositionElement(evt),
|
||||
(res) => {
|
||||
if (Number.isFinite(res) && res >= 0 && res <= 8) {
|
||||
if (res != null) {
|
||||
dispatch('change', { tag, weight: res })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@ -122,6 +164,11 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.tag-items-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tag-items {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
@ -129,6 +176,6 @@
|
||||
}
|
||||
.tag-items-scroll {
|
||||
overflow-y: scroll;
|
||||
max-height: 10rem;
|
||||
max-height: 20rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -16,13 +16,24 @@
|
||||
import { Class, Doc, FindResult, Ref } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { Button, CheckBox, getPlatformColor, Loading, resizeObserver } from '@hcengineering/ui'
|
||||
import { TagCategory, TagElement } from '@hcengineering/tags'
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
getEventPopupPositionElement,
|
||||
getPlatformColor,
|
||||
Label,
|
||||
Loading,
|
||||
resizeObserver,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import tags from '../plugin'
|
||||
import { TagCategory, TagElement } from '@hcengineering/tags'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
import { tagLevel } from '../utils'
|
||||
import WeightPopup from './WeightPopup.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let filter: Filter
|
||||
@ -32,6 +43,7 @@
|
||||
}
|
||||
const client = getClient()
|
||||
let selected: Ref<TagElement>[] = filter.value
|
||||
let level: number = filter.props?.level ?? 0
|
||||
|
||||
filter.modes = [tags.filter.FilterTagsIn, tags.filter.FilterTagsNin]
|
||||
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
|
||||
@ -100,6 +112,9 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
getValues(search)
|
||||
|
||||
$: tagLevelIcon = tagLevel[((level % 3) + 1) as 1 | 2 | 3]
|
||||
$: tagLevelLabel = [tags.string.Initial, tags.string.Meaningfull, tags.string.Expert][Math.floor(level / 3)]
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
@ -113,6 +128,22 @@
|
||||
}}
|
||||
placeholder={phTraslate}
|
||||
/>
|
||||
<div class="flex-row-center flex-between flex-grow p-1">
|
||||
<Label label={tags.string.Weight} />
|
||||
<Button
|
||||
label={tagLevelLabel}
|
||||
icon={tagLevelIcon}
|
||||
on:click={(evt) => {
|
||||
showPopup(WeightPopup, { value: level }, getEventPopupPositionElement(evt), (res) => {
|
||||
if (Number.isFinite(res) && res >= 0 && res <= 8) {
|
||||
if (res != null) {
|
||||
level = res
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
@ -169,7 +200,9 @@
|
||||
shape={'round'}
|
||||
label={view.string.Apply}
|
||||
on:click={async () => {
|
||||
filter.value = selected
|
||||
filter.value = [...selected]
|
||||
// Replace last one with value with level
|
||||
filter.props = { level }
|
||||
onChange(filter)
|
||||
dispatch('close')
|
||||
}}
|
||||
|
@ -123,6 +123,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="buttons-group small-gap">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="clear-btn"
|
||||
class:show={search !== ''}
|
||||
|
@ -19,6 +19,7 @@
|
||||
import tags from '../plugin'
|
||||
import TagItem from './TagItem.svelte'
|
||||
import { selectedTagElements } from '@hcengineering/tags'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
export let object: Doc
|
||||
export let _class: Ref<Class<Doc>>
|
||||
@ -39,13 +40,32 @@
|
||||
(res) => {
|
||||
items = res
|
||||
},
|
||||
{ sort: { title: 1 } }
|
||||
{ sort: { weight: -1, title: 1 } }
|
||||
)
|
||||
|
||||
$: expert = items.filter((it) => (it.weight ?? 0) >= 6 && (it.weight ?? 0) <= 8)
|
||||
$: meaningfull = items.filter((it) => (it.weight ?? 0) >= 3 && (it.weight ?? 0) <= 5)
|
||||
$: initial = items.filter((it) => (it.weight ?? 1) >= 0 && (it.weight ?? 0) <= 2)
|
||||
|
||||
$: categories = [
|
||||
{ items: expert, label: tags.string.Expert },
|
||||
{ items: meaningfull, label: tags.string.Meaningfull },
|
||||
{ items: initial, label: tags.string.Initial }
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="tags flex flex-wrap">
|
||||
{#each items as tag}
|
||||
<TagItem {tag} element={elements.get(tag.tag)} selected={$selectedTagElements.includes(tag.tag)} />
|
||||
<div class="tags flex flex-col">
|
||||
{#each categories as cat, ci}
|
||||
{#if cat.items.length > 0}
|
||||
<div class="text-xs mb-1" class:mt-2={ci > 0 && categories[ci - 1].items.length > 0}>
|
||||
<Label label={cat.label} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-center flex-wrap tags">
|
||||
{#each cat.items as tag}
|
||||
<TagItem {tag} element={elements.get(tag.tag)} selected={$selectedTagElements.includes(tag.tag)} />
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
47
plugins/tags-resources/src/components/WeightPopup.svelte
Normal file
47
plugins/tags-resources/src/components/WeightPopup.svelte
Normal file
@ -0,0 +1,47 @@
|
||||
<!--
|
||||
// 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 { Button, resizeObserver } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tags from '../plugin'
|
||||
import { tagLevel } from '../utils'
|
||||
|
||||
export let value: number | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const labels = [tags.string.Initial, tags.string.Meaningfull, tags.string.Expert]
|
||||
</script>
|
||||
|
||||
<div class="selectPopup max-width-40" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="header no-border p-3">
|
||||
{#each labels as l, i}
|
||||
<div class="flex gap-2 p-1">
|
||||
{#each Object.entries(tagLevel) as k, j}
|
||||
{@const valueK = i * 3 + j}
|
||||
<Button
|
||||
label={l}
|
||||
icon={k[1]}
|
||||
size={'small'}
|
||||
justify={'left'}
|
||||
selected={value === valueK}
|
||||
on:click={() => dispatch('close', valueK)}
|
||||
width={'8rem'}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
@ -46,7 +46,12 @@ export default mergeIds(tagsId, tags, {
|
||||
AllCategories: '' as IntlString,
|
||||
SelectAll: '' as IntlString,
|
||||
SelectNone: '' as IntlString,
|
||||
ApplyTags: '' as IntlString
|
||||
ApplyTags: '' as IntlString,
|
||||
|
||||
Weight: '' as IntlString,
|
||||
Expert: '' as IntlString,
|
||||
Meaningfull: '' as IntlString,
|
||||
Initial: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
FilterTagsInResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
||||
|
@ -1,10 +1,11 @@
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
|
||||
import { Doc, FindResult, Ref } from '@hcengineering/core'
|
||||
import { TagReference } from '@hcengineering/tags'
|
||||
import tags from './plugin'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
import { Doc, DocumentQuery, FindResult, Ref } from '@hcengineering/core'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { InitialKnowledge, TagReference } from '@hcengineering/tags'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
import tags from './plugin'
|
||||
|
||||
export function getTagStyle (color: string, selected = false): string {
|
||||
return `
|
||||
@ -16,21 +17,29 @@ export function getTagStyle (color: string, selected = false): string {
|
||||
export async function getRefs (filter: Filter, onUpdate: () => void): Promise<Array<Ref<Doc>>> {
|
||||
const lq = FilterQuery.getLiveQuery(filter.index)
|
||||
const promise = new Promise<Array<Ref<Doc>>>((resolve, reject) => {
|
||||
const refresh = lq.query(
|
||||
tags.class.TagReference,
|
||||
{
|
||||
tag: { $in: filter.value }
|
||||
},
|
||||
(refs: FindResult<TagReference>) => {
|
||||
const result = Array.from(new Set(refs.map((p) => p.attachedTo)))
|
||||
FilterQuery.results.set(filter.index, result)
|
||||
resolve(result)
|
||||
onUpdate()
|
||||
}
|
||||
)
|
||||
const level = filter.props?.level ?? 0
|
||||
const q: DocumentQuery<TagReference> = {
|
||||
tag: { $in: filter.value },
|
||||
weight:
|
||||
level === 0
|
||||
? { $in: [null as unknown as InitialKnowledge, 0, 1, 2, 3, 4, 5, 6, 7, 8] }
|
||||
: { $gte: level as TagReference['weight'] }
|
||||
}
|
||||
const refresh = lq.query(tags.class.TagReference, q, (refs: FindResult<TagReference>) => {
|
||||
const result = Array.from(new Set(refs.map((p) => p.attachedTo)))
|
||||
FilterQuery.results.set(filter.index, result)
|
||||
resolve(result)
|
||||
onUpdate()
|
||||
})
|
||||
if (!refresh) {
|
||||
resolve(FilterQuery.results.get(filter.index) ?? [])
|
||||
}
|
||||
})
|
||||
return await promise
|
||||
}
|
||||
|
||||
export const tagLevel: Record<1 | 2 | 3, Asset> = {
|
||||
3: tags.icon.Level3,
|
||||
2: tags.icon.Level2,
|
||||
1: tags.icon.Level1
|
||||
}
|
||||
|
@ -32,6 +32,21 @@ export interface TagElement extends Doc {
|
||||
refCount?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type InitialKnowledge = 0 | 1 | 2
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type MeaningfullKnowledge = 3 | 4 | 5
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ExpertKnowledge = 6 | 7 | 8
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -39,6 +54,9 @@ export interface TagReference extends AttachedDoc {
|
||||
tag: Ref<TagElement>
|
||||
title: string // Copy of title for full text search, updated with trigger.
|
||||
color: number // Copy of color from tagElement. Updated with trigger.
|
||||
|
||||
// If set in [8-10] it is speciality, [5-7] - meaningfull, [1-4] - initial
|
||||
weight?: InitialKnowledge | MeaningfullKnowledge | ExpertKnowledge
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +94,10 @@ const tagsPlugin = plugin(tagsId, {
|
||||
Tags: '' as Ref<Space>
|
||||
},
|
||||
icon: {
|
||||
Tags: '' as Asset
|
||||
Tags: '' as Asset,
|
||||
Level1: '' as Asset,
|
||||
Level2: '' as Asset,
|
||||
Level3: '' as Asset
|
||||
},
|
||||
component: {
|
||||
TagsView: '' as AnyComponent,
|
||||
|
@ -21,7 +21,7 @@
|
||||
import Lost from '../icons/Lost.svelte'
|
||||
|
||||
export let value: DoneState
|
||||
export let showTitle: boolean = false
|
||||
export let showTitle: boolean = true
|
||||
|
||||
$: color = value._class === task.class.WonState ? getPlatformColor(0) : getPlatformColor(11)
|
||||
</script>
|
||||
|
@ -47,11 +47,9 @@
|
||||
onMount(() => {
|
||||
;(document.activeElement as HTMLElement)?.blur()
|
||||
})
|
||||
|
||||
let docWidth: number
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={docWidth} />
|
||||
<svelte:window />
|
||||
|
||||
<ActionContext
|
||||
context={{
|
||||
@ -61,7 +59,7 @@
|
||||
{#if showFilterBar}
|
||||
<FilterBar {_class} {query} on:change={(e) => (resultQuery = e.detail)} />
|
||||
{/if}
|
||||
<Scroller fade={tableSP} horizontal={docWidth < 1024}>
|
||||
<Scroller fade={tableSP} horizontal={true}>
|
||||
<Table
|
||||
bind:this={table}
|
||||
{_class}
|
||||
|
@ -63,6 +63,7 @@ export interface Filter {
|
||||
mode: Ref<FilterMode>
|
||||
modes: Ref<FilterMode>[]
|
||||
value: any[]
|
||||
props?: Record<string, any>
|
||||
index: number
|
||||
onRemove?: () => void
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user