Vacancy company organization (#1155)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-03-17 15:39:57 +06:00 committed by GitHub
parent 338901ab51
commit 294d6c5732
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 213 additions and 88 deletions

View File

@ -86,7 +86,6 @@ async function genVacansyApplicants (
description: faker.lorem.sentences(2),
fullDescription: faker.lorem.sentences(10),
location: faker.address.city(),
company: faker.company.companyName(),
members: accountIds,
private: false,
archived: false

View File

@ -237,7 +237,6 @@ async function createUpdateVacancy (client: TxOperations, statuses: any): Promis
description: '',
fullDescription: '',
location: '',
company: '',
members: [],
archived: false,
private: false

View File

@ -165,6 +165,10 @@ export function createModel (builder: Builder): void {
editor: contact.component.EditOrganization
})
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.AttributeEditor, {
editor: contact.component.OrganizationEditor
})
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.ChannelsPresenter
})

View File

@ -34,7 +34,8 @@ export default mergeIds(contactId, contact, {
CreateOrganizations: '' as AnyComponent,
OrganizationPresenter: '' as AnyComponent,
Contacts: '' as AnyComponent,
EmployeeAccountPresenter: '' as AnyComponent
EmployeeAccountPresenter: '' as AnyComponent,
OrganizationEditor: '' as AnyComponent
},
string: {
Persons: '' as IntlString,
@ -47,7 +48,6 @@ export default mergeIds(contactId, contact, {
Channel: '' as IntlString,
ChannelProvider: '' as IntlString,
Person: '' as IntlString,
Organization: '' as IntlString,
Employee: '' as IntlString,
Value: '' as IntlString,
Phone: '' as IntlString,

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { Employee } from '@anticrm/contact'
import type { Employee, Organization } from '@anticrm/contact'
import { Doc, FindOptions, IndexKind, Lookup, Ref, Timestamp } from '@anticrm/core'
import {
Builder,
@ -60,9 +60,8 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
@Index(IndexKind.FullText)
location?: string
@Prop(TypeString(), recruit.string.Company, contact.icon.Company)
@Index(IndexKind.FullText)
company?: string
@Prop(TypeRef(contact.class.Organization), recruit.string.Company, contact.icon.Company)
company?: Ref<Organization>
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number

View File

@ -1,4 +1,4 @@
import { Employee } from '@anticrm/contact'
import { Employee, Organization } from '@anticrm/contact'
import { Domain, IndexKind, Ref, Timestamp } from '@anticrm/core'
import { Collection, Index, Model, Prop, TypeDate, TypeMarkup, TypeRef, TypeString, UX } from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
@ -14,6 +14,9 @@ import recruit from './plugin'
export class TReviewCategory extends TSpaceWithStates implements ReviewCategory {
@Prop(TypeString(), recruit.string.FullDescription)
fullDescription?: string
@Prop(TypeRef(contact.class.Organization), recruit.string.Company, contact.icon.Company)
company?: Ref<Organization>
}
@Model(recruit.class.Review, task.class.Task)

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#B82C2E;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#6FA94C;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#687EB9;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#E6C12D;}
</style>
<g>
<path class="st0" d="M4.9,11.3c0.6-1.3,1.5-2.5,2.5-3.6c2.3-2.4,5.1-3.9,8.4-4.5c4.6-0.7,8.7,0.4,12.3,3.4c0.2,0.2,0.3,0.3,0,0.5
c-1.3,1.2-2.5,2.5-3.7,3.7c-0.1,0.1-0.2,0.3-0.4,0.1c-3.1-2.9-8.2-2.8-11.6,0.3c-1.1,1.1-2,2.3-2.5,3.8c-0.1-0.1-0.2-0.1-0.2-0.2
C8,13.7,6.5,12.5,4.9,11.3z"/>
<path class="st1" d="M9.8,20.9c0.4,1.1,1,2.2,1.8,3.1c2.1,2.3,4.7,3.3,7.9,3c1.5-0.2,2.8-0.6,4-1.4c0.1,0.1,0.2,0.2,0.4,0.3
c1.5,1.1,2.9,2.3,4.4,3.4c-1.6,1.5-3.5,2.5-5.6,3.1c-5,1.3-9.6,0.5-13.7-2.8c-1.7-1.3-3-3-4-4.9C6.5,23.4,8.2,22.2,9.8,20.9z"/>
<path class="st2" d="M28.2,29.4c-1.5-1.1-2.9-2.3-4.4-3.4c-0.1-0.1-0.2-0.2-0.4-0.3c1-0.8,1.8-1.6,2.3-2.8c0.2-0.4,0.4-0.9,0.5-1.4
c0.1-0.3,0.1-0.5-0.3-0.5c-2.4,0-4.8,0-7.2,0c-0.5,0-0.5,0-0.5-0.5c0-1.6,0-3.3,0-4.9c0-0.3,0.1-0.4,0.4-0.4c4.5,0,8.9,0,13.4,0
c0.2,0,0.4,0,0.4,0.3c0.6,3.9,0.1,7.6-1.9,11.1C29.9,27.6,29.2,28.6,28.2,29.4z"/>
<path class="st3" d="M9.8,20.9c-1.6,1.3-3.3,2.5-4.9,3.8c-0.8-1.5-1.3-3.1-1.5-4.7c-0.4-2.9,0-5.6,1.3-8.3c0.1-0.1,0.2-0.3,0.2-0.4
c1.6,1.2,3.1,2.4,4.7,3.6C9.7,14.9,9.8,15,9.8,15C9.2,17,9.2,18.9,9.8,20.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="14.3791" y1="-2.5354" x2="21.6209" y2="38.5354">
<stop offset="5.076140e-03" style="stop-color:#C52229"/>
<stop offset="1" style="stop-color:#802022"/>
</linearGradient>
<rect class="st0" width="36" height="36"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="18" y1="6.2743" x2="18" y2="29.7257">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.5066" style="stop-color:#FDFDFD"/>
<stop offset="0.7696" style="stop-color:#F5F5F5"/>
<stop offset="0.9763" style="stop-color:#E8E8E8"/>
<stop offset="1" style="stop-color:#E6E6E6"/>
</linearGradient>
<path class="st1" d="M7.3,9.8c2.8-1.1,7.4-1.3,9.4-1.2l1.3,1.6l1.3-1.6c2-0.1,6.6,0.1,9.4,1.2c-0.4,0.7-2.1,1.8-3,2
c-0.1-1.4-2.1-1.7-4-1.7L18,29.7l-3.7-19.7c-2,0-4,0.3-4,1.7C9.4,11.6,7.7,10.5,7.3,9.8L7.3,9.8z M6,7.8l0.7,1.1
c2.7-1,5.9-1.4,9.3-1.5c1.3,0,2.6,0,3.9,0c3.4,0.1,6.6,0.6,9.3,1.5L30,7.8c-3.2-1.1-6.6-1.4-10-1.5c-1.3,0-2.6,0-4,0
C12.6,6.4,9.2,6.7,6,7.8L6,7.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -24,13 +24,10 @@
import DropdownPopup from './DropdownPopup.svelte'
import Add from './icons/Add.svelte'
import tesla from '../../img/tesla.svg'
import google from '../../img/google.svg'
export let icon: Asset | AnySvelteComponent = Add
export let label: IntlString
export let placeholder: string
export let items: ListItem[] = [{ item: tesla, label: 'Tesla' }, { item: google, label: 'Google' }]
export let placeholder: IntlString
export let items: ListItem[] = []
export let selected: ListItem | undefined = undefined
export let show: boolean = false
@ -51,7 +48,7 @@
btn.focus()
if (!opened) {
opened = true
showPopup(DropdownPopup, { title: label, items }, container, (result) => {
showPopup(DropdownPopup, { title: label, items, icon }, container, (result) => {
if (result) selected = result
opened = false
})
@ -59,8 +56,8 @@
}}
>
<div class="flex-center focused-button btn" class:selected bind:this={btn} tabindex={0} on:focus={() => container.click()}>
{#if selected}
<img src={selected.item} alt={selected.label} />
{#if selected && selected.image}
<img src={selected.image} alt={selected.label} />
{:else}
{#if typeof (icon) === 'string'}
<Icon {icon} size={'small'} />
@ -73,7 +70,7 @@
<div class="selectUser">
<div class="title"><Label {label} /></div>
<div class="caption-color" class:empty={selected ? false : true}>
{#if selected}{selected.label}{:else}{placeholder}{/if}
{#if selected}{selected.label}{:else}<Label label={placeholder} />{/if}
</div>
</div>
</div>

View File

@ -13,16 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import type { Asset, IntlString } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import Label from './Label.svelte'
import EditWithIcon from './EditWithIcon.svelte'
import IconSearch from './icons/Search.svelte'
import type { ListItem } from '../types'
import type { AnySvelteComponent, ListItem } from '../types'
import plugin from '../plugin'
import Icon from './Icon.svelte'
export let title: IntlString | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let caption: IntlString = plugin.string.Suggested
export let items: ListItem[]
export let header: boolean = true
@ -52,8 +54,16 @@
<div class="ap-box">
{#each items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase())) as item}
<button class="ap-menuItem" on:click={() => { dispatch('close', item) }}>
<div class="flex-center img">
<img src={item.item} alt={item.label} />
<div class="flex-center img" class:image={item.image}>
{#if item.image}
<img src={item.image} alt={item.label} />
{:else}
{#if typeof (icon) === 'string'}
<Icon {icon} size={'small'} />
{:else}
<svelte:component this={icon} size={'small'} />
{/if}
{/if}
</div>
<div class="flex-grow caption-color">{item.label}</div>
</button>
@ -66,12 +76,18 @@
<style lang="scss">
.img {
margin-right: .75rem;
flex-shrink: 0;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
background-color: transparent;
border: 1px solid var(--theme-card-divider);
border-radius: .5rem;
outline: none;
overflow: hidden;
}
.image {
border-color: transparent;
img { max-width: fit-content; }
}
</style>

View File

@ -61,6 +61,7 @@ export { default as CircleButton } from './components/CircleButton.svelte'
export { default as Link } from './components/Link.svelte'
export { default as TimeSince } from './components/TimeSince.svelte'
export { default as Dropdown } from './components/Dropdown.svelte'
export { default as DropdownPopup } from './components/DropdownPopup.svelte'
export { default as DropdownLabels } from './components/DropdownLabels.svelte'
export { default as ShowMore } from './components/ShowMore.svelte'
export { default as Menu } from './components/Menu.svelte'

View File

@ -77,8 +77,9 @@ export interface LabelAndProps {
}
export interface ListItem {
item: any | undefined
_id: string
label: string
image?: string
}
export interface DropdownTextItem {

View File

@ -0,0 +1,78 @@
<!--
// 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 { Organization } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { DropdownPopup, Label, showPopup } from '@anticrm/ui'
import { ListItem } from '@anticrm/ui/src/types'
import contact from '../plugin'
import Company from './icons/Company.svelte'
export let value: Ref<Organization> | undefined
export let label: IntlString = contact.string.Organization
export let onChange: (value: any) => void
const query = createQuery()
query.query(contact.class.Organization, {}, (res) => {
items = res.map((org) => {
return {
_id: org._id,
label: org.name,
image: org.avatar
}
})
if (value !== undefined) {
selected = items.find((p) => p._id === value)
}
})
let items: ListItem[] = []
let selected: ListItem | undefined
function setValue (res: ListItem | undefined): void {
selected = res
if (selected === undefined) {
value = undefined
} else {
value = selected._id as Ref<Organization>
}
onChange(value)
}
let opened: boolean = false
const icon = Company
let container: HTMLElement
</script>
<div class="caption-color cursor-pointer" bind:this={container} class:empty={selected === undefined} on:click|preventDefault={() => {
if (!opened) {
opened = true
showPopup(DropdownPopup, { title: label, items, icon }, container, (result) => {
if (result) setValue(result)
opened = false
})
}
}}>
{#if selected}{selected.label}{:else}<Label {label} />{/if}
</div>
<style lang="scss">
.empty { color: var(--theme-content-trans-color); }
</style>

View File

@ -0,0 +1,58 @@
<!--
// 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 { Organization } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { Dropdown } from '@anticrm/ui'
import { ListItem } from '@anticrm/ui/src/types'
import contact from '../plugin'
import Company from './icons/Company.svelte'
export let value: Ref<Organization> | undefined
export let label: IntlString = contact.string.Organization
const query = createQuery()
query.query(contact.class.Organization, {}, (res) => {
items = res.map((org) => {
return {
_id: org._id,
label: org.name,
image: org.avatar
}
})
if (value !== undefined) {
selected = items.find((p) => p._id === value)
}
})
let items: ListItem[] = []
let selected: ListItem | undefined
$: setValue(selected)
function setValue (selected: ListItem | undefined): void {
if (selected === undefined) {
value = undefined
} else {
value = selected._id as Ref<Organization>
}
}
</script>
<Dropdown icon={Company} label={label} placeholder={label} {items} bind:selected />

View File

@ -35,8 +35,10 @@ import PersonPresenter from './components/PersonPresenter.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import contact from './plugin'
import EmployeeAccountPresenter from './components/EmployeeAccountPresenter.svelte'
import OrganizationEditor from './components/OrganizationEditor.svelte'
import OrganizationSelector from './components/OrganizationSelector.svelte'
export { Channels, ChannelsEditor, ContactPresenter, ChannelsView }
export { Channels, ChannelsEditor, ContactPresenter, ChannelsView, OrganizationSelector }
async function queryContact (_class: Ref<Class<Contact>>, client: Client, search: string): Promise<ObjectSearchResult[]> {
return (await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => ({
@ -51,6 +53,7 @@ async function queryContact (_class: Ref<Class<Contact>>, client: Client, search
export default async (): Promise<Resources> => ({
component: {
OrganizationEditor,
ContactPresenter,
PersonPresenter,
OrganizationPresenter,

View File

@ -32,6 +32,7 @@ export default mergeIds(contactId, contact, {
PersonsNamePlaceholder: '' as IntlString,
CreateOrganizations: '' as IntlString,
Organizations: '' as IntlString,
Organization: '' as IntlString,
SelectFolder: '' as IntlString,
OrganizationsFolder: '' as IntlString,
PersonsFolder: '' as IntlString,

View File

@ -14,13 +14,14 @@
-->
<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, Dropdown, EditBox, Grid } from '@anticrm/ui'
import { getClient,SpaceCreateCard } from '@anticrm/presentation'
import task, { createKanban,KanbanTemplate } from '@anticrm/task'
import { Component,EditBox,Grid } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import Company from './icons/Company.svelte'
import { OrganizationSelector } from '@anticrm/contact-resources'
import Vacancy from './icons/Vacancy.svelte'
const dispatch = createEventDispatcher()
@ -28,6 +29,7 @@
let name: string = ''
const description: string = ''
let templateId: Ref<KanbanTemplate> | undefined
let company: Ref<Organization> | undefined
export function canClose (): boolean {
return name === '' && templateId !== undefined
@ -45,6 +47,7 @@
description,
private: false,
archived: false,
company,
members: []
})
@ -60,7 +63,7 @@
>
<Grid column={1} rowGap={1.5}>
<EditBox label={recruit.string.VacancyName} bind:value={name} icon={Vacancy} placeholder={recruit.string.VacancyPlaceholder} maxWidth={'16rem'} focus/>
<Dropdown icon={Company} label={recruit.string.Company} placeholder={'Company'} />
<OrganizationSelector bind:value={company} label={recruit.string.Company} />
<Component is={task.component.KanbanTemplateSelector} props={{
folders: [recruit.space.VacancyTemplates],

View File

@ -13,7 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Doc, DocumentQuery, Ref } from '@anticrm/core'
import contact from '@anticrm/contact'
import core, { Doc, DocumentQuery, Lookup, Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { Applicant, Vacancy } from '@anticrm/recruit'
import { Button, getCurrentLocation, Icon, Label, navigate, Scroller, showPopup, IconAdd } from '@anticrm/ui'
@ -88,6 +89,10 @@
)
}
const lookup = {
company: contact.class.Organization
} as Lookup<Doc>
function showCreateDialog (ev: Event) {
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, ev.target as HTMLElement)
}
@ -127,7 +132,7 @@
sortingKey: '@applications',
sortingFunction: applicationSorting
},
'company',
'$lookup.company',
'location',
'description',
{
@ -139,7 +144,9 @@
sortingFunction: modifiedSorting
}
]}
options={{}}
options={{
lookup
}}
query={{
...vacancyQuery,
archived: false

View File

@ -15,19 +15,21 @@
<script lang="ts">
import core, { Ref } from '@anticrm/core'
import { getClient, SpaceCreateCard } from '@anticrm/presentation'
import task, { createKanban, KanbanTemplate } from '@anticrm/task'
import { Component, Dropdown, EditBox, Grid } from '@anticrm/ui'
import { getClient,SpaceCreateCard } from '@anticrm/presentation'
import task,{ createKanban,KanbanTemplate } from '@anticrm/task'
import { Component,EditBox,Grid } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../../plugin'
import Company from '../icons/Company.svelte'
import Review from '../icons/Review.svelte'
import { Organization } from '@anticrm/contact'
import { OrganizationSelector } from '@anticrm/contact-resources'
const dispatch = createEventDispatcher()
let name: string = ''
const description: string = ''
let templateId: Ref<KanbanTemplate> | undefined
let company: Ref<Organization> | undefined
export function canClose (): boolean {
return name === '' && templateId !== undefined
@ -45,6 +47,7 @@
description,
private: false,
archived: false,
company,
members: []
})
@ -60,7 +63,7 @@
>
<Grid column={1} rowGap={1.5}>
<EditBox label={recruit.string.ReviewCategoryName} bind:value={name} icon={Review} placeholder={recruit.string.ReviewCategoryPlaceholder} maxWidth={'16rem'} focus/>
<Dropdown icon={Company} label={recruit.string.Company} placeholder={'Company'} />
<OrganizationSelector bind:value={company} label={recruit.string.Company} />
<Component is={task.component.KanbanTemplateSelector} props={{
folders: [recruit.space.ReviewTemplates],

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { Employee, Person } from '@anticrm/contact'
import type { Employee, Organization, Person } from '@anticrm/contact'
import type { AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
@ -28,7 +28,7 @@ export interface Vacancy extends SpaceWithStates {
attachments?: number
dueTo?: Timestamp
location?: string
company?: string
company?: Ref<Organization>
}
/**
@ -37,6 +37,7 @@ export interface Vacancy extends SpaceWithStates {
export interface ReviewCategory extends SpaceWithStates {
fullDescription?: string
attachments?: number
company?: Ref<Organization>
}
/**