Vacancy details (#486)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2021-12-02 15:09:37 +06:00 committed by GitHub
parent 41f92c977a
commit 32d559a07b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 590 additions and 172 deletions

View File

@ -75,3 +75,6 @@ export class TTypeBoolean extends TType {}
@Model(core.class.TypeTimestamp, core.class.Type) @Model(core.class.TypeTimestamp, core.class.Type)
export class TTypeTimestamp extends TType {} export class TTypeTimestamp extends TType {}
@Model(core.class.TypeDate, core.class.Type)
export class TTypeDate extends TType {}

View File

@ -15,7 +15,7 @@
import { Builder } from '@anticrm/model' import { Builder } from '@anticrm/model'
import core from './component' import core from './component'
import { TAttribute, TClass, TDoc, TMixin, TObj, TType, TTypeString, TTypeBoolean, TTypeTimestamp, TAttachedDoc } from './core' import { TAttribute, TClass, TDoc, TMixin, TObj, TType, TTypeString, TTypeBoolean, TTypeTimestamp, TTypeDate, TAttachedDoc } from './core'
import { TSpace, TAccount, TState, TSpaceWithStates, TDocWithState } from './security' import { TSpace, TAccount, TState, TSpaceWithStates, TDocWithState } from './security'
import { TTx, TTxCreateDoc, TTxMixin, TTxUpdateDoc, TTxCUD, TTxPutBag, TTxRemoveDoc, TTxBulkWrite, TTxCollectionCUD } from './tx' import { TTx, TTxCreateDoc, TTxMixin, TTxUpdateDoc, TTxCUD, TTxPutBag, TTxRemoveDoc, TTxBulkWrite, TTxCollectionCUD } from './tx'
@ -49,6 +49,7 @@ export function createModel (builder: Builder): void {
TTypeString, TTypeString,
TTypeBoolean, TTypeBoolean,
TTypeTimestamp, TTypeTimestamp,
TTypeDate,
TState TState
) )
} }

View File

@ -15,7 +15,7 @@
import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core' import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core' import { DOMAIN_MODEL } from '@anticrm/core'
import { Implements, Model, Prop, TypeState, TypeString } from '@anticrm/model' import { Implements, Model, Prop, TypeBoolean, TypeState, TypeString } from '@anticrm/model'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import core from './component' import core from './component'
import { TDoc } from './core' import { TDoc } from './core'
@ -29,8 +29,12 @@ export class TSpace extends TDoc implements Space {
@Prop(TypeString(), 'Name' as IntlString) @Prop(TypeString(), 'Name' as IntlString)
name!: string name!: string
@Prop(TypeString(), 'Description' as IntlString)
description!: string description!: string
@Prop(TypeBoolean(), 'Private' as IntlString)
private!: boolean private!: boolean
members!: Arr<Ref<Account>> members!: Arr<Ref<Account>>
} }

View File

@ -15,8 +15,8 @@
import activity from '@anticrm/activity' import activity from '@anticrm/activity'
import type { Employee } from '@anticrm/contact' import type { Employee } from '@anticrm/contact'
import type { Doc, Domain, FindOptions, Ref } from '@anticrm/core' import type { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core'
import { Builder, Model, Prop, TypeBoolean, TypeString, UX } from '@anticrm/model' import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeString, UX } from '@anticrm/model'
import chunter from '@anticrm/model-chunter' import chunter from '@anticrm/model-chunter'
import contact, { TPerson } from '@anticrm/model-contact' import contact, { TPerson } from '@anticrm/model-contact'
import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core' import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core'
@ -30,7 +30,22 @@ export const DOMAIN_RECRUIT = 'recruit' as Domain
@Model(recruit.class.Vacancy, core.class.SpaceWithStates) @Model(recruit.class.Vacancy, core.class.SpaceWithStates)
@UX(recruit.string.Vacancy, recruit.icon.Vacancy) @UX(recruit.string.Vacancy, recruit.icon.Vacancy)
export class TVacancy extends TSpaceWithStates implements Vacancy {} export class TVacancy extends TSpaceWithStates implements Vacancy {
@Prop(TypeString(), 'Full description' as IntlString)
fullDescription?: string
@Prop(TypeString(), 'Attachments' as IntlString)
attachments?: number
@Prop(TypeDate(), 'Due date' as IntlString, recruit.icon.Calendar)
dueTo?: Timestamp
@Prop(TypeString(), 'Location' as IntlString, recruit.icon.Location)
location?: string
@Prop(TypeString(), 'Company' as IntlString, recruit.icon.Company)
company?: string
}
@Model(recruit.class.Candidates, core.class.Space) @Model(recruit.class.Candidates, core.class.Space)
@UX(recruit.string.CandidatePools, recruit.icon.RecruitApplication) @UX(recruit.string.CandidatePools, recruit.icon.RecruitApplication)
@ -109,7 +124,8 @@ export function createModel (builder: Builder): void {
label: recruit.string.Vacancies, label: recruit.string.Vacancies,
spaceClass: recruit.class.Vacancy, spaceClass: recruit.class.Vacancy,
addSpaceLabel: recruit.string.CreateVacancy, addSpaceLabel: recruit.string.CreateVacancy,
createComponent: recruit.component.CreateVacancy createComponent: recruit.component.CreateVacancy,
component: recruit.component.EditVacancy
}, },
{ {
label: recruit.string.CandidatePools, label: recruit.string.CandidatePools,

View File

@ -43,7 +43,8 @@ export default mergeIds(recruitId, recruit, {
EditCandidate: '' as AnyComponent, EditCandidate: '' as AnyComponent,
KanbanCard: '' as AnyComponent, KanbanCard: '' as AnyComponent,
ApplicationPresenter: '' as AnyComponent, ApplicationPresenter: '' as AnyComponent,
ApplicationsPresenter: '' as AnyComponent ApplicationsPresenter: '' as AnyComponent,
EditVacancy: '' as AnyComponent
}, },
space: { space: {
CandidatesPublic: '' as Ref<Space> CandidatesPublic: '' as Ref<Space>

View File

@ -109,6 +109,14 @@ export function createModel (builder: Builder): void {
presenter: view.component.TimestampPresenter presenter: view.component.TimestampPresenter
}) })
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.AttributePresenter, {
presenter: view.component.DatePresenter
})
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.DateEditor
})
builder.mixin(core.class.State, core.class.Class, view.mixin.AttributeEditor, { builder.mixin(core.class.State, core.class.Class, view.mixin.AttributeEditor, {
editor: view.component.StateEditor editor: view.component.StateEditor
}) })

View File

@ -34,7 +34,8 @@ export default mergeIds(viewId, view, {
StatePresenter: '' as AnyComponent, StatePresenter: '' as AnyComponent,
StateEditor: '' as AnyComponent, StateEditor: '' as AnyComponent,
TimestampPresenter: '' as AnyComponent, TimestampPresenter: '' as AnyComponent,
DateEditor: '' as AnyComponent,
DatePresenter: '' as AnyComponent,
TableView: '' as AnyComponent, TableView: '' as AnyComponent,
KanbanView: '' as AnyComponent KanbanView: '' as AnyComponent
} }

View File

@ -46,6 +46,7 @@ export default plugin(coreId, {
TypeString: '' as Ref<Class<Type<string>>>, TypeString: '' as Ref<Class<Type<string>>>,
TypeBoolean: '' as Ref<Class<Type<boolean>>>, TypeBoolean: '' as Ref<Class<Type<boolean>>>,
TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>, TypeTimestamp: '' as Ref<Class<Type<Timestamp>>>,
TypeDate: '' as Ref<Class<Type<Timestamp | Date>>>,
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>> Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>
}, },
interface: { interface: {

View File

@ -298,6 +298,13 @@ export function TypeTimestamp (): Type<string> {
return { _class: core.class.TypeTimestamp, label: 'TypeTimestamp' as IntlString } return { _class: core.class.TypeTimestamp, label: 'TypeTimestamp' as IntlString }
} }
/**
* @public
*/
export function TypeDate (): Type<string> {
return { _class: core.class.TypeDate, label: 'TypeDate' as IntlString }
}
/** /**
* @public * @public
*/ */

View File

@ -25,7 +25,7 @@
// export let _class: Ref<Class<Doc>> // export let _class: Ref<Class<Doc>>
export let key: string export let key: string
export let object: Doc export let object: Doc
export let maxWidth: string export let maxWidth: string | undefined = undefined
export let focus: boolean = false export let focus: boolean = false
export let minimize: boolean = false export let minimize: boolean = false
export let showHeader: boolean = true export let showHeader: boolean = true
@ -59,7 +59,7 @@
<div class="flex-row-center"> <div class="flex-row-center">
<CircleButton icon={attribute.icon} size={'large'} /> <CircleButton icon={attribute.icon} size={'large'} />
{#if !minimize} {#if !minimize}
<div class="flex-col with-icon"> <div class="flex-col with-icon ml-2">
{#if showHeader} {#if showHeader}
<Label label={attribute.label} /> <Label label={attribute.label} />
{/if} {/if}

View File

@ -37,10 +37,11 @@
<div class="ref-container"> <div class="ref-container">
<div class="textInput"> <div class="textInput">
<div class="inputMsg"> <div class="inputMsg">
<TextEditor content={content} bind:this={textEditor} on:content={ <TextEditor bind:content={content} bind:this={textEditor} on:content={
ev => { ev => {
dispatch('message', ev.detail) dispatch('message', ev.detail)
content = '' content = ''
textEditor.clear()
} }
}/> }/>
</div> </div>

View File

@ -32,6 +32,7 @@ import { getClient } from '@anticrm/presentation'
import contact from '@anticrm/contact' import contact from '@anticrm/contact'
export let content: string = '' export let content: string = ''
export let placeholder: string = 'Type something...'
let element: HTMLElement let element: HTMLElement
let editor: Editor let editor: Editor
@ -42,6 +43,9 @@ const client = getClient()
export function submit (): void { export function submit (): void {
content = editor.getHTML() content = editor.getHTML()
dispatch('content', content) dispatch('content', content)
}
export function clear (): void {
content = '' content = ''
editor.commands.clearContent(false) editor.commands.clearContent(false)
} }
@ -49,10 +53,10 @@ export function submit (): void {
const HandleEnter = Extension.create({ const HandleEnter = Extension.create({
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { return {
'Enter': () => { 'Enter': () => {
submit() submit()
return true return true
} }
} }
}, },
}) })
@ -66,7 +70,7 @@ onMount(() => {
StarterKit, StarterKit,
Highlight, Highlight,
// Typography, // we need to disable 1/2 -> ½ rule (https://github.com/hcengineering/anticrm/issues/345) // Typography, // we need to disable 1/2 -> ½ rule (https://github.com/hcengineering/anticrm/issues/345)
Placeholder.configure({placeholder: 'Type something...'}), Placeholder.configure({placeholder: placeholder}),
Mention.configure({ Mention.configure({
HTMLAttributes: { HTMLAttributes: {
class: 'mention', class: 'mention',
@ -97,11 +101,13 @@ onMount(() => {
}, },
}), }),
], ],
// content: 'dfgdfg',
onTransaction: () => { onTransaction: () => {
// force re-render so `editor.isActive` works as expected // force re-render so `editor.isActive` works as expected
editor = editor editor = editor
}, },
onBlur: () => {
dispatch('blur')
}
}) })
}) })

View File

@ -191,6 +191,7 @@ p:last-child { margin-block-end: 0; }
.mr-8 { margin-right: 2rem; } .mr-8 { margin-right: 2rem; }
.mt-2 { margin-top: .5rem; } .mt-2 { margin-top: .5rem; }
.mt-5 { margin-top: 1.25rem; } .mt-5 { margin-top: 1.25rem; }
.mt-10 { margin-top: 2.5rem; }
.mt-14 { margin-top: 3.5rem; } .mt-14 { margin-top: 3.5rem; }
.mb-1 { margin-bottom: .25rem; } .mb-1 { margin-bottom: .25rem; }

View File

@ -15,108 +15,36 @@
<script lang="ts"> <script lang="ts">
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte' import Label from './Label.svelte'
import PopupMenu from './PopupMenu.svelte'
import Calendar from './icons/Calendar.svelte' import Calendar from './icons/Calendar.svelte'
import Close from './icons/Close.svelte' import Close from './icons/Close.svelte'
import Back from './icons/Back.svelte' import { DatePopup, showPopup } from '..'
import Forward from './icons/Forward.svelte'
export let title: IntlString export let title: IntlString
export let selected: Date = new Date(Date.now()) export let selected: Date = new Date(Date.now())
export let show: boolean = false export let show: boolean = false
let container: HTMLElement
let view: Date = selected
const months: Array<string> = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
let monthYear: string
let days: Array<number>
const daysInMonth = (date: Date): number => {
return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate()
}
$: {
monthYear = months[view.getMonth()] + ' ' + view.getFullYear()
days = []
for (let i = 1; i <= daysInMonth(view); i++) {
days.push(new Date(view.getFullYear(), view.getMonth(), i).getDay())
}
}
</script> </script>
<div class="flex-row-center"> <div class="flex-row-center">
<PopupMenu bind:show={show}> <button
<button class="focused-button btn"
slot="trigger" class:selected={show}
class="focused-button btn" on:click|preventDefault={() => {
class:selected={show} show = true
on:click|preventDefault={() => { showPopup(DatePopup, { selected, title }, container, (result) => {
show = !show if (result) {
}} selected = result
> }
<div class="icon"> show = false
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if} })
</div> }}
</button> >
<div class="icon">
<div class="flex-col caption-color"> {#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
<div class="title"><Label label={title} /></div>
<div class="flex-between nav">
<button
class="focused-button arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() - 1)
view = view
}}><div class="icon"><Back size={'small'} /></div></button>
<div class="monthYear">
{monthYear}
</div>
<button
class="focused-button arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() + 1)
view = view
}}><div class="icon"><Forward size={'small'} /></div></button>
</div>
</div> </div>
</button>
<div class="calendar">
<div class="caption">Mo</div>
<div class="caption">Tu</div>
<div class="caption">We</div>
<div class="caption">Th</div>
<div class="caption">Fr</div>
<div class="caption">Sa</div>
<div class="caption">Su</div>
{#each days as day, i}
<div
class="day"
class:selected={i + 1 === selected.getDate() &&
view.getMonth() === selected.getMonth() &&
view.getFullYear() === selected.getFullYear()}
style="grid-column: {day + 1}/{day + 2};"
on:click={() => {
selected = new Date(view.getFullYear(), view.getMonth(), i + 1)
show = false
}}
>
{i + 1}
</div>
{/each}
</div>
</PopupMenu>
<div class="selectDate"> <div class="selectDate">
<div class="label"><Label label={title} /></div> <div class="label"><Label label={title} /></div>
<div class="date"> <div class="date">
@ -133,57 +61,6 @@
border: none; border: none;
} }
.arrow {
width: 2rem;
height: 2rem;
border: 1px solid var(--theme-bg-accent-color);
border-radius: .25rem;
}
.title {
margin-bottom: .75rem;
font-weight: 500;
text-align: left;
}
.nav {
min-width: 16.5rem;
.monthYear {
margin: 0 1rem;
line-height: 150%;
white-space: nowrap;
}
}
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: .125rem;
margin-top: .5rem;
.caption, .day {
display: flex;
justify-content: center;
align-items: center;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-content-dark-color);
}
.caption {
font-size: .75rem;
}
.day {
border-radius: .5rem;
cursor: pointer;
&.selected {
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-bg-accent-color);
color: var(--theme-caption-color);
}
}
}
.selectDate { .selectDate {
margin-left: .75rem; margin-left: .75rem;
.label { .label {

View File

@ -0,0 +1,169 @@
<!--
// 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 Label from './Label.svelte'
import Back from './icons/Back.svelte'
import Forward from './icons/Forward.svelte'
import { createEventDispatcher } from 'svelte'
export let title: IntlString
export let selected: Date = new Date(Date.now())
const dispatch = createEventDispatcher()
let view: Date = selected
const months: Array<string> = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
let monthYear: string
let days: Array<number>
const daysInMonth = (date: Date): number => {
return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate()
}
$: {
monthYear = months[view.getMonth()] + ' ' + view.getFullYear()
days = []
for (let i = 1; i <= daysInMonth(view); i++) {
days.push(new Date(view.getFullYear(), view.getMonth(), i).getDay())
}
}
</script>
<div class="popup">
<div class="flex-col caption-color">
<div class="title"><Label label={title} /></div>
<div class="flex-between nav">
<button
class="focused-button arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() - 1)
view = view
}}><div class="icon"><Back size={'small'} /></div></button>
<div class="monthYear">
{monthYear}
</div>
<button
class="focused-button arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() + 1)
view = view
}}><div class="icon"><Forward size={'small'} /></div></button>
</div>
</div>
<div class="calendar">
<div class="caption">Mo</div>
<div class="caption">Tu</div>
<div class="caption">We</div>
<div class="caption">Th</div>
<div class="caption">Fr</div>
<div class="caption">Sa</div>
<div class="caption">Su</div>
{#each days as day, i}
<div
class="day"
class:selected={i + 1 === selected.getDate() &&
view.getMonth() === selected.getMonth() &&
view.getFullYear() === selected.getFullYear()}
style="grid-column: {day + 1}/{day + 2};"
on:click={() => {
selected = new Date(view.getFullYear(), view.getMonth(), i + 1)
dispatch('close', selected)
}}
>
{i + 1}
</div>
{/each}
</div>
</div>
<style lang="scss">
.popup {
display: flex;
flex-direction: column;
padding: 1rem;
max-height: 100%;
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
user-select: none;
filter: drop-shadow(0 1.5rem 4rem rgba(0, 0, 0, .35));
}
.arrow {
width: 2rem;
height: 2rem;
border: 1px solid var(--theme-bg-accent-color);
border-radius: .25rem;
}
.title {
margin-bottom: .75rem;
font-weight: 500;
text-align: left;
}
.nav {
min-width: 16.5rem;
.monthYear {
margin: 0 1rem;
line-height: 150%;
white-space: nowrap;
}
}
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: .125rem;
margin-top: .5rem;
.caption, .day {
display: flex;
justify-content: center;
align-items: center;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-content-dark-color);
}
.caption {
font-size: .75rem;
}
.day {
border-radius: .5rem;
cursor: pointer;
&.selected {
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-bg-accent-color);
color: var(--theme-caption-color);
}
}
}
</style>

View File

@ -46,6 +46,7 @@ export { default as PopupMenu } from './components/PopupMenu.svelte'
export { default as TextArea } from './components/TextArea.svelte' export { default as TextArea } from './components/TextArea.svelte'
export { default as Section } from './components/Section.svelte' export { default as Section } from './components/Section.svelte'
export { default as DatePicker } from './components/DatePicker.svelte' export { default as DatePicker } from './components/DatePicker.svelte'
export { default as DatePopup } from './components/DatePopup.svelte'
export { default as StylishEdit } from './components/StylishEdit.svelte' export { default as StylishEdit } from './components/StylishEdit.svelte'
export { default as Grid } from './components/Grid.svelte' export { default as Grid } from './components/Grid.svelte'
export { default as Row } from './components/Row.svelte' export { default as Row } from './components/Row.svelte'

View File

@ -23,5 +23,20 @@
<path d="M16.9,12.2c-0.3,0-0.5,0.2-0.5,0.5l-0.1,1.9c-0.1,1-0.9,1.8-1.9,1.8H5.7c-1,0-1.8-0.8-1.9-1.8l-0.1-1.9 c0-0.3-0.3-0.5-0.5-0.5c-0.3,0-0.5,0.3-0.5,0.5l0.1,1.9c0.1,1.5,1.4,2.7,2.9,2.7h8.6c1.5,0,2.8-1.2,2.9-2.7l0.1-1.9 C17.4,12.5,17.2,12.2,16.9,12.2z"/> <path d="M16.9,12.2c-0.3,0-0.5,0.2-0.5,0.5l-0.1,1.9c-0.1,1-0.9,1.8-1.9,1.8H5.7c-1,0-1.8-0.8-1.9-1.8l-0.1-1.9 c0-0.3-0.3-0.5-0.5-0.5c-0.3,0-0.5,0.3-0.5,0.5l0.1,1.9c0.1,1.5,1.4,2.7,2.9,2.7h8.6c1.5,0,2.8-1.2,2.9-2.7l0.1-1.9 C17.4,12.5,17.2,12.2,16.9,12.2z"/>
</g> </g>
</symbol> </symbol>
<symbol id="location" viewBox="0 0 16 16">
<g>
<path
d="M8,3.7c-1.9,0-3.4,1.5-3.4,3.4s1.5,3.4,3.4,3.4s3.4-1.5,3.4-3.4S9.9,3.7,8,3.7z M8,9.3c-1.2,0-2.1-1-2.1-2.1 C5.9,6,6.8,5,8,5s2.1,1,2.1,2.1C10.1,8.3,9.2,9.3,8,9.3z"
/>
<path
d="M8,0C4.1,0,0.9,3.2,0.9,7.1c0,2.5,1.2,4.4,2.6,5.9c1.4,1.4,2.9,2.4,3.7,2.8c0.5,0.3,1.1,0.3,1.6,0 c0.8-0.4,2.4-1.4,3.7-2.8c1.4-1.4,2.6-3.4,2.6-5.9C15.1,3.2,11.9,0,8,0z M11.6,12.1c-1.2,1.3-2.7,2.1-3.4,2.5 c-0.1,0.1-0.3,0.1-0.4,0c-0.7-0.4-2.2-1.3-3.4-2.5c-1.2-1.3-2.2-2.9-2.2-5c0-3.2,2.6-5.8,5.8-5.8s5.8,2.6,5.8,5.8 C13.8,9.2,12.8,10.8,11.6,12.1z"
/>
</g>
</symbol>
<symbol id="company" viewBox="0 0 16 16">
<path d="M15.3,14.7h-0.7V4c0-0.3-0.2-0.6-0.5-0.6l-7.3-2c-0.2-0.1-0.4,0-0.6,0.1C6.1,1.6,6,1.8,6,2v3.3H2 C1.6,5.3,1.3,5.6,1.3,6v8.7H0.7C0.3,14.7,0,15,0,15.3C0,15.7,0.3,16,0.7,16h14.7c0.4,0,0.7-0.3,0.7-0.7C16,15,15.7,14.7,15.3,14.7z M2.7,14.7v-8h6v8H2.7z M9.3,5.3h-2V2.9l6,1.6v10.2H10V6C10,5.6,9.7,5.3,9.3,5.3z"/>
</symbol>
<symbol id="calendar" viewBox="0 0 24 24">
<path d="M19.5,5h-2.1V4.5c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5V5H8.1V4.5C8.1,4.2,7.9,4,7.6,4S7.1,4.2,7.1,4.5V5H5 C4.2,5,3.5,5.7,3.5,6.5V19c0,0.8,0.7,1.5,1.5,1.5h14.5c0.8,0,1.5-0.7,1.5-1.5V6.5C21,5.7,20.3,5,19.5,5z M5,6h2.1v0.5 c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V6h8.3v0.5c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V6h2.1C19.7,6,20,6.3,20,6.5v3.1H4.5V6.5 C4.5,6.3,4.7,6,5,6z M19.5,19.5H5c-0.3,0-0.5-0.2-0.5-0.5v-8.3H20V19C20,19.2,19.7,19.5,19.5,19.5z" />
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -19,7 +19,10 @@ import recruit, { recruitId } from '@anticrm/recruit'
const icons = require('../assets/icons.svg') const icons = require('../assets/icons.svg')
loadMetadata(recruit.icon, { loadMetadata(recruit.icon, {
RecruitApplication: `${icons}#recruitment`, RecruitApplication: `${icons}#recruitment`,
Vacancy: `${icons}#vacancy` Vacancy: `${icons}#vacancy`,
Location: `${icons}#location`,
Company: `${icons}#company`,
Calendar: `${icons}#calendar`,
}) })
addStringsLoader(recruitId, async (lang: string) => await import(`../lang/${lang}.json`)) addStringsLoader(recruitId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -43,6 +43,7 @@
"@anticrm/login": "~0.6.0", "@anticrm/login": "~0.6.0",
"deep-equal": "^2.0.5", "deep-equal": "^2.0.5",
"@anticrm/panel": "~0.6.0", "@anticrm/panel": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0", "@anticrm/chunter-resources": "~0.6.0",
"@anticrm/view": "~0.6.0", "@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0" "@anticrm/view-resources": "~0.6.0"

View File

@ -49,9 +49,7 @@
name, name,
description, description,
private: false, private: false,
members: [], members: []
states: [],
order: []
}) })
const s1 = await client.createDoc(core.class.State, id, { const s1 = await client.createDoc(core.class.State, id, {
title: 'Initial', title: 'Initial',

View File

@ -0,0 +1,219 @@
<!--
// 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 { IntlString } from '@anticrm/platform'
import type { Ref } from '@anticrm/core'
import { IconClose, Label, EditBox, ToggleWithLabel, Grid, Icon, Component } from '@anticrm/ui'
import { TextEditor } from '@anticrm/text-editor'
import { getClient, createQuery } from '@anticrm/presentation'
import { Vacancy } from '@anticrm/recruit'
import { createEventDispatcher } from 'svelte'
import AttributesBar from './AttributesBar.svelte'
import activity from '@anticrm/activity'
import recruit from '../plugin'
import Attachments from './Attachments.svelte'
export let _id: Ref<Vacancy>
let object: Vacancy
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
const clazz = client.getHierarchy().getClass(recruit.class.Vacancy)
$: query.query(recruit.class.Vacancy, { _id }, result => { object = result[0] })
const tabs: IntlString[] = ['General' as IntlString, 'Members' as IntlString, 'Activity' as IntlString]
let selected = 0
let textEditor: TextEditor
function onChange (key:string, value: any): void {
client.updateDoc(object._class, object.space, object._id, { [key]: value })
}
</script>
<div class="overlay" on:click={() => { dispatch('close') }}/>
<div class="dialog-container">
{#if object}
<div class="flex-row-center header">
<div class="flex-grow">
<div class="flex">
<Icon icon={clazz.icon} size={'medium'} />
<div class="flex-grow fs-title ml-2">
{object.name}
</div>
</div>
<div class="small-text">{object.description}</div>
</div>
<div class="tool" on:click={() => { dispatch('close') }}><IconClose size={'small'} /></div>
</div>
<div class="flex-row-center subtitle">
<AttributesBar {object} keys={['dueTo', 'location', 'company']} />
</div>
<div class="flex-stretch tab-container">
{#each tabs as tab, i}
<div class="flex-row-center tab" class:selected={i === selected}
on:click={() => { selected = i }}>
<Label label={tab}/>
</div>
{/each}
<div class="grow"/>
</div>
<div class="scroll">
<div class="flex-col box">
{#if selected === 0}
<Grid column={1} rowGap={1.5}>
<EditBox label={recruit.string.VacancyName} bind:value={object.name} placeholder="Software Engineer" maxWidth="39rem" focus on:change={() => {onChange('name', object.name)}}/>
<EditBox label='Description' bind:value={object.description} placeholder='Description' maxWidth="39rem" focus on:change={() => {onChange('description', object.description)}}/>
</Grid>
<div class="mt-10">
<span class="title">Description</span>
<div class="description-container">
<TextEditor bind:this={textEditor} bind:content={object.fullDescription} on:blur={textEditor.submit} on:content={() => {onChange('fullDescription', object.fullDescription)}} />
</div>
</div>
<div class="mt-14">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
{:else if selected === 1}
<ToggleWithLabel label={'This vacancy is private'} description={recruit.string.MakePrivateDescription}/>
{:else if selected === 2}
<Component is={activity.component.Activity} props={{object}} />
{/if}
</div>
</div>
{/if}
</div>
<style lang="scss">
.dialog-container {
overflow: hidden;
position: fixed;
top: 32px;
bottom: 1.25rem;
left: 50%;
right: 1rem;
display: flex;
flex-direction: column;
height: calc(100% - 32px - 1.25rem);
background: var(--theme-dialog-bg-spec);
border-radius: 1.25rem;
box-shadow: var(--theme-dialog-shadow);
backdrop-filter: blur(15px);
.header {
flex-shrink: 0;
padding: 0 2rem 0 2.5rem;
height: 4.5rem;
border-bottom: 1px solid var(--theme-dialog-divider);
.tool {
margin-left: .75rem;
transform-origin: center center;
transform: scale(.75);
color: var(--theme-content-accent-color);
cursor: pointer;
&:hover { color: var(--theme-caption-color); }
}
}
.subtitle {
flex-shrink: 0;
padding: 0 2.5rem;
height: 3.5rem;
border-bottom: 1px solid var(--theme-dialog-divider);
}
}
.tab-container {
flex-shrink: 0;
flex-wrap: nowrap;
margin: 0 2.5rem;
height: 4.5rem;
border-bottom: 1px solid var(--theme-menu-divider);
.tab {
height: 4.5rem;
font-weight: 500;
color: var(--theme-content-trans-color);
cursor: pointer;
user-select: none;
&.selected {
border-top: .125rem solid transparent;
border-bottom: .125rem solid var(--theme-caption-color);
color: var(--theme-caption-color);
cursor: default;
}
}
.tab + .tab {
margin-left: 2.5rem;
}
.grow {
min-width: 2.5rem;
flex-grow: 1;
}
}
.scroll {
flex-grow: 1;
overflow-x: hidden;
overflow-y: auto;
margin: 1rem 0;
padding: 1.5rem 2.5rem;
.box {
margin-right: 1px;
height: 100%;
}
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--theme-menu-color);
opacity: .6;
}
.title {
margin-right: .75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
.description-container {
display: flex;
justify-content: space-between;
overflow-y: auto;
height: 100px;
padding: 0px 16px;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-radius: .75rem;
margin-top: 1.5rem;
}
</style>

View File

@ -27,7 +27,7 @@
</div> </div>
{#if vacancy} {#if vacancy}
<div class="name">{vacancy.name}</div> <div class="name">{vacancy.name}</div>
<div class="description">Company</div> <div class="description">{vacancy.description}</div>
{/if} {/if}
</div> </div>

View File

@ -22,6 +22,7 @@ import CreateApplication from './components/CreateApplication.svelte'
import EditCandidate from './components/EditCandidate.svelte' import EditCandidate from './components/EditCandidate.svelte'
import Attachments from './components/Attachments.svelte' import Attachments from './components/Attachments.svelte'
import KanbanCard from './components/KanbanCard.svelte' import KanbanCard from './components/KanbanCard.svelte'
import EditVacancy from './components/EditVacancy.svelte'
import ApplicationPresenter from './components/ApplicationPresenter.svelte' import ApplicationPresenter from './components/ApplicationPresenter.svelte'
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte' import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte' import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte'
@ -46,7 +47,8 @@ export default async (): Promise<Resources> => ({
Attachments, Attachments,
KanbanCard, KanbanCard,
ApplicationPresenter, ApplicationPresenter,
ApplicationsPresenter ApplicationsPresenter,
EditVacancy
}, },
activity: { activity: {
TxApplicantUpdate TxApplicantUpdate

View File

@ -26,9 +26,6 @@ export default mergeIds(recruitId, recruit, {
CandidateRequired: '' as StatusCode, CandidateRequired: '' as StatusCode,
VacancyRequired: '' as StatusCode, VacancyRequired: '' as StatusCode,
}, },
class: {
Vacancy: '' as Ref<Class<Vacancy>>
},
string: { string: {
CreateVacancy: '' as IntlString, CreateVacancy: '' as IntlString,
VacancyName: '' as IntlString, VacancyName: '' as IntlString,

View File

@ -15,13 +15,19 @@
import { plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform'
import type { Plugin, Asset } from '@anticrm/platform' import type { Plugin, Asset } from '@anticrm/platform'
import type { Space, SpaceWithStates, DocWithState, Ref, Class, AttachedDoc } from '@anticrm/core' import type { Space, SpaceWithStates, DocWithState, Ref, Class, AttachedDoc, Timestamp } from '@anticrm/core'
import type { Employee, Person } from '@anticrm/contact' import type { Employee, Person } from '@anticrm/contact'
/** /**
* @public * @public
*/ */
export interface Vacancy extends SpaceWithStates {} export interface Vacancy extends SpaceWithStates {
fullDescription?: string
attachments?: number
dueTo?: Timestamp
location?: string
company?: string
}
/** /**
* @public * @public
@ -59,10 +65,14 @@ export default plugin(recruitId, {
class: { class: {
Applicant: '' as Ref<Class<Applicant>>, Applicant: '' as Ref<Class<Applicant>>,
Candidate: '' as Ref<Class<Candidate>>, Candidate: '' as Ref<Class<Candidate>>,
Candidates: '' as Ref<Class<Candidates>> Candidates: '' as Ref<Class<Candidates>>,
Vacancy: '' as Ref<Class<Vacancy>>
}, },
icon: { icon: {
RecruitApplication: '' as Asset, RecruitApplication: '' as Asset,
Vacancy: '' as Asset Vacancy: '' as Asset,
Company: '' as Asset,
Location: '' as Asset,
Calendar: '' as Asset
} }
}) })

View File

@ -0,0 +1,44 @@
<!--
// 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 { IntlString } from '@anticrm/platform'
import { DatePopup, showPopup } from '@anticrm/ui'
import DatePresenter from './DatePresenter.svelte'
export let value: number | Date | undefined
export let label: IntlString
export let onChange: (value: any) => void
$: date = value ? new Date(value) : new Date()
let container: HTMLElement
let opened: boolean = false
</script>
<div class="flex-row-center" bind:this={container}
on:click|preventDefault={() => {
if (!opened) {
opened = true
showPopup(DatePopup, { selected: date, title: label }, container, (result) => {
if (result) {
value = result.getTime()
onChange(value)
}
opened = false
})
}
}} >
<DatePresenter {value} />
</div>

View File

@ -0,0 +1,28 @@
<!--
// 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">
export let value: number | Date | undefined
$: date = value ? new Date(value) : undefined
</script>
<div class=".caption-color">
{#if date}
{date.getMonth() + 1} / {date.getDate()} / {date.getFullYear()}
{:else}
No date
{/if}
</div>

View File

@ -23,6 +23,8 @@ import BooleanPresenter from './components/BooleanPresenter.svelte'
import StatePresenter from './components/StatePresenter.svelte' import StatePresenter from './components/StatePresenter.svelte'
import StateEditor from './components/StateEditor.svelte' import StateEditor from './components/StateEditor.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte' import TimestampPresenter from './components/TimestampPresenter.svelte'
import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte'
import TableView from './components/TableView.svelte' import TableView from './components/TableView.svelte'
import Table from './components/Table.svelte' import Table from './components/Table.svelte'
import KanbanView from './components/KanbanView.svelte' import KanbanView from './components/KanbanView.svelte'
@ -64,6 +66,8 @@ export default async () => ({
StateEditor, StateEditor,
TableView, TableView,
KanbanView, KanbanView,
TimestampPresenter TimestampPresenter,
DateEditor,
DatePresenter
} }
}) })

View File

@ -61,7 +61,7 @@
label: 'Open' as IntlString, label: 'Open' as IntlString,
icon: IconEdit, icon: IconEdit,
action: async (_id: Ref<Doc>): Promise<void> => { action: async (_id: Ref<Doc>): Promise<void> => {
showPopup(SpacePanel, { _id, spaceClass: model.spaceClass }, 'right') showPopup(model.component ?? SpacePanel, { _id, spaceClass: model.spaceClass }, 'right')
} }
} }