State editor (#411)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2021-11-29 17:32:17 +06:00 committed by GitHub
parent 9f9123c546
commit 4ae06a7677
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1398 additions and 1138 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import type { Account, Arr, Ref, Space, Domain, State } from '@anticrm/core' import type { Account, Arr, Ref, Space, Domain, State } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core' import { DOMAIN_MODEL } from '@anticrm/core'
import { Model, Prop, TypeString } from '@anticrm/model' import { Model, Prop, TypeState, TypeString } from '@anticrm/model'
import core from './component' import core from './component'
import { TDoc } from './core' import { TDoc } from './core'
@ -49,7 +49,7 @@ export class TState extends TDoc implements State {
@Model(core.class.DocWithState, core.class.AttachedDoc) @Model(core.class.DocWithState, core.class.AttachedDoc)
export class TDocWithState extends TDoc { export class TDocWithState extends TDoc {
@Prop(TypeString(), 'State' as IntlString) @Prop(TypeState(), 'State' as IntlString)
state!: Ref<State> state!: Ref<State>
@Prop(TypeString(), 'No.' as IntlString) @Prop(TypeString(), 'No.' as IntlString)

View File

@ -19,6 +19,7 @@ import type { Ref, FindOptions, Doc, Domain, Class } from '@anticrm/core'
import core, { TSpace, TSpaceWithStates, TDocWithState } from '@anticrm/model-core' import core, { TSpace, TSpaceWithStates, TDocWithState } from '@anticrm/model-core'
import type { Vacancy, Candidates, Candidate, Applicant } from '@anticrm/recruit' import type { Vacancy, Candidates, Candidate, Applicant } from '@anticrm/recruit'
import activity from '@anticrm/activity' import activity from '@anticrm/activity'
import type { Employee } from '@anticrm/contact'
import workbench from '@anticrm/model-workbench' import workbench from '@anticrm/model-workbench'
@ -73,6 +74,9 @@ export class TApplicant extends TDocWithState implements Applicant {
@Prop(TypeString(), 'Attachments' as IntlString) @Prop(TypeString(), 'Attachments' as IntlString)
attachments?: number attachments?: number
@Prop(TypeString(), 'Assigned recruiter' as IntlString)
employee!: Ref<Employee>
} }
export function createModel (builder: Builder): void { export function createModel (builder: Builder): void {

View File

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

View File

@ -32,6 +32,7 @@ export default mergeIds(viewId, view, {
BooleanPresenter: '' as AnyComponent, BooleanPresenter: '' as AnyComponent,
BooleanEditor: '' as AnyComponent, BooleanEditor: '' as AnyComponent,
StatePresenter: '' as AnyComponent, StatePresenter: '' as AnyComponent,
StateEditor: '' as AnyComponent,
TimestampPresenter: '' as AnyComponent, TimestampPresenter: '' as AnyComponent,
TableView: '' as AnyComponent, TableView: '' as AnyComponent,

View File

@ -280,6 +280,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 TypeState (): Type<string> {
return { _class: core.class.State, label: 'TypeState' as IntlString }
}
/** /**
* @public * @public
*/ */

View File

@ -25,7 +25,7 @@
export let title: string export let title: string
export let icon: Asset | AnySvelteComponent export let icon: Asset | AnySvelteComponent
export let fullSize: boolean = false export let fullSize: boolean = false
export let rightSection: AnyComponent | undefined export let rightSection: AnyComponent | undefined = undefined
export let object: Doc export let object: Doc
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()

View File

@ -28,6 +28,7 @@
export let maxWidth: string export let maxWidth: string
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
const _class = object._class const _class = object._class
const client = getClient() const client = getClient()
@ -59,14 +60,18 @@
<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">
{#if showHeader}
<Label label={attribute.label} /> <Label label={attribute.label} />
{/if}
<div class="value"><svelte:component this={instance} label={attribute?.label} placeholder={attribute?.label} {maxWidth} bind:value={object[key]} {onChange} {focus}/></div> <div class="value"><svelte:component this={instance} label={attribute?.label} placeholder={attribute?.label} {maxWidth} bind:value={object[key]} {onChange} {focus}/></div>
</div> </div>
{/if} {/if}
</div> </div>
{:else} {:else}
<div class="flex-col"> <div class="flex-col">
{#if showHeader}
<Label label={attribute.label} /> <Label label={attribute.label} />
{/if}
<div class="value"><svelte:component this={instance} label={attribute?.label} placeholder={attribute?.label} {maxWidth} bind:value={object[key]} {onChange} {focus}/></div> <div class="value"><svelte:component this={instance} label={attribute?.label} placeholder={attribute?.label} {maxWidth} bind:value={object[key]} {onChange} {focus}/></div>
</div> </div>
{/if} {/if}

View File

@ -24,12 +24,14 @@
import type { Ref, Class } from '@anticrm/core' import type { Ref, Class } from '@anticrm/core'
import { formatName, Person } from '@anticrm/contact' import { formatName, Person } from '@anticrm/contact'
import { createEventDispatcher } from 'svelte'
export let _class: Ref<Class<Person>> export let _class: Ref<Class<Person>>
export let title: IntlString export let title: IntlString
export let caption: IntlString export let caption: IntlString
export let value: Ref<Person> export let value: Ref<Person>
export let show: boolean = false export let show: boolean = false
const dispatch = createEventDispatcher()
let selected: Person | undefined let selected: Person | undefined
let btn: HTMLElement let btn: HTMLElement
@ -58,7 +60,10 @@
if (!opened) { if (!opened) {
opened = true opened = true
showPopup(UsersPopup, { _class, title, caption }, container, (result) => { showPopup(UsersPopup, { _class, title, caption }, container, (result) => {
if (result) { value = result._id } if (result) {
value = result._id
dispatch('change', value)
}
opened = false opened = false
}) })
} }

View File

@ -0,0 +1,40 @@
<!--
// 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 { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
import { Applicant } from '@anticrm/recruit'
import contact from '@anticrm/contact'
export let object: Applicant
const client = getClient()
function change () {
client.updateDoc(object._class, object.space, object._id, { employee: object.employee })
}
</script>
<div class="flex-between header">
<UserBox _class={contact.class.Employee} title='Assigned recruiter' caption='Recruiters' bind:value={object.employee} on:change={change} />
<AttributeBarEditor key={'state'} {object} showHeader={false} />
</div>
<style lang="scss">
.header {
width: 100%;
padding: 0 .5rem;
}
</style>

View File

@ -62,6 +62,7 @@
const id = await client.addCollection(recruit.class.Applicant, _space, candidate, recruit.class.Candidate, 'applications', { const id = await client.addCollection(recruit.class.Applicant, _space, candidate, recruit.class.Candidate, 'applications', {
state: state._id, state: state._id,
number: incResult.object.sequence, number: incResult.object.sequence,
employee: employee
}) })
} }
@ -94,7 +95,6 @@
spacePlaceholder={'Select vacancy'} spacePlaceholder={'Select vacancy'}
bind:space={_space} bind:space={_space}
on:close={() => { dispatch('close') }}> on:close={() => { dispatch('close') }}>
<StatusControl slot="error" {status} /> <StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.75}> <Grid column={1} rowGap={1.75}>
{#if !preserveCandidate} {#if !preserveCandidate}

View File

@ -15,33 +15,24 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import type { Ref, Space, Doc, Class } from '@anticrm/core' import type { Ref } from '@anticrm/core'
import { CircleButton, EditBox, Link, showPopup, IconFile as FileIcon } from '@anticrm/ui' import { createQuery } from '@anticrm/presentation'
import type { Attachment } from '@anticrm/chunter'
import FileUpload from './icons/FileUpload.svelte'
import { getClient, createQuery, Channels, AttributeEditor, PDFViewer } from '@anticrm/presentation'
import { Panel } from '@anticrm/panel' import { Panel } from '@anticrm/panel'
import type { Candidate, Applicant, Vacancy } from '@anticrm/recruit' import type { Candidate, Applicant, Vacancy } from '@anticrm/recruit'
import Contact from './icons/Contact.svelte' import Contact from './icons/Contact.svelte'
import Avatar from './icons/Avatar.svelte'
import Attachments from './Attachments.svelte' import Attachments from './Attachments.svelte'
import Edit from './icons/Edit.svelte'
import SocialEditor from './SocialEditor.svelte'
import CandidateCard from './CandidateCard.svelte' import CandidateCard from './CandidateCard.svelte'
import VacancyCard from './VacancyCard.svelte' import VacancyCard from './VacancyCard.svelte'
import chunter from '@anticrm/chunter'
import recruit from '../plugin' import recruit from '../plugin'
import { formatName } from '@anticrm/contact' import { formatName } from '@anticrm/contact'
import ApplicantHeader from './ApplicantHeader.svelte'
export let _id: Ref<Applicant> export let _id: Ref<Applicant>
let object: Applicant let object: Applicant
let candidate: Candidate let candidate: Candidate
let vacancy: Vacancy let vacancy: Vacancy
const client = getClient()
const query = createQuery() const query = createQuery()
$: query.query(recruit.class.Applicant, { _id }, result => { object = result[0] }) $: query.query(recruit.class.Applicant, { _id }, result => { object = result[0] })
@ -52,26 +43,11 @@
$: if (object !== undefined) vacancyQuery.query(recruit.class.Vacancy, { _id: object.space }, result => { vacancy = result[0] }) $: if (object !== undefined) vacancyQuery.query(recruit.class.Vacancy, { _id: object.space }, result => { vacancy = result[0] })
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
// function saveChannels(result: any) {
// object.channels = result
// client.updateDoc(recruit.class.Candidate, object.space, object._id, { channels: result })
// }
function getVacancyName() {
return client.getModel().getObject(object.space).name
}
</script> </script>
{#if object !== undefined && candidate !== undefined} {#if object !== undefined && candidate !== undefined}
<Panel icon={Contact} title={formatName(candidate.name)} {object} on:close={() => { dispatch('close') }}> <Panel icon={Contact} title={formatName(candidate.name)} {object} on:close={() => { dispatch('close') }}>
<!-- <svelte:fragment slot="subtitle"> <ApplicantHeader {object} slot="subtitle" />
<div class="flex-between flex-reverse" style="width: 100%">
<Channels value={object.channels}/>
<CircleButton icon={Edit} label={'Edit'} on:click={(ev) => showPopup(SocialEditor, { values: object.channels ?? [] }, ev.target, (result) => { saveChannels(result) })} />
</div>
</svelte:fragment> -->
<div class="grid-cards"> <div class="grid-cards">
<CandidateCard {candidate}/> <CandidateCard {candidate}/>

View File

@ -24,7 +24,6 @@ import Attachments from './components/Attachments.svelte'
import KanbanCard from './components/KanbanCard.svelte' import KanbanCard from './components/KanbanCard.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'
import { showPopup } from '@anticrm/ui' import { showPopup } from '@anticrm/ui'

View File

@ -16,7 +16,7 @@
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 } from '@anticrm/core'
import type { Person } from '@anticrm/contact' import type { Employee, Person } from '@anticrm/contact'
/** /**
* @public * @public
@ -46,6 +46,7 @@ export interface Candidate extends Person {
*/ */
export interface Applicant extends DocWithState, AttachedDoc { export interface Applicant extends DocWithState, AttachedDoc {
attachments?: number attachments?: number
employee: Ref<Employee>
} }
/** /**

View File

@ -0,0 +1,52 @@
<!--
// 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 core, { Ref, State } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import StatePresenter from './StatePresenter.svelte'
import StatesPopup from './StatesPopup.svelte'
export let value: Ref<State>
export let onChange: (value: any) => void
let state: State
let container: HTMLElement
let opened: boolean = false
const query = createQuery()
$: query.query(core.class.State, { _id: value }, (res) => {
state = res[0]
}, { limit: 1 })
</script>
{#if state}
<div class="flex-row-center" bind:this={container}
on:click|preventDefault={() => {
if (!opened) {
opened = true
showPopup(StatesPopup, { space: state.space }, container, (result) => {
if (result) {
value = result._id
onChange(value)
}
opened = false
})
}
}} >
<StatePresenter value={state} />
</div>
{/if}

View File

@ -0,0 +1,65 @@
<!--
// 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 core, { Ref, SpaceWithStates, State } from "@anticrm/core"
import { createQuery } from "@anticrm/presentation"
import { createEventDispatcher } from "svelte"
export let space: Ref<SpaceWithStates>
let states: State[] = []
const dispatch = createEventDispatcher()
const statesQuery = createQuery()
statesQuery.query(core.class.State, { space }, (res) => states = res)
</script>
<div class="container">
{#each states as state}
<div class="state flex" on:click={() => { dispatch('close', state) }}>
<div class="color" style="background-color: {state.color}"></div>
{state.title}
</div>
{/each}
</div>
<style lang="scss">
.container {
display: flex;
flex-direction: column;
padding: 1rem;
max-height: 100%;
min-width: 10rem;
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));
.state {
margin: 1rem;
cursor: pointer;
.color {
margin-right: .75rem;
width: 1rem;
height: 1rem;
border-radius: .25rem;
}
}
}
</style>

View File

@ -21,6 +21,7 @@ import StringPresenter from './components/StringPresenter.svelte'
import BooleanEditor from './components/BooleanEditor.svelte' import BooleanEditor from './components/BooleanEditor.svelte'
import BooleanPresenter from './components/BooleanPresenter.svelte' 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 TimestampPresenter from './components/TimestampPresenter.svelte' import TimestampPresenter from './components/TimestampPresenter.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'
@ -60,6 +61,7 @@ export default async () => ({
BooleanPresenter, BooleanPresenter,
BooleanEditor, BooleanEditor,
StatePresenter, StatePresenter,
StateEditor,
TableView, TableView,
KanbanView, KanbanView,
TimestampPresenter TimestampPresenter

File diff suppressed because it is too large Load Diff