mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-15 12:55:59 +00:00
State editor (#411)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
9f9123c546
commit
4ae06a7677
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { Account, Arr, Ref, Space, Domain, State } 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 { TDoc } from './core'
|
||||
|
||||
@ -49,7 +49,7 @@ export class TState extends TDoc implements State {
|
||||
|
||||
@Model(core.class.DocWithState, core.class.AttachedDoc)
|
||||
export class TDocWithState extends TDoc {
|
||||
@Prop(TypeString(), 'State' as IntlString)
|
||||
@Prop(TypeState(), 'State' as IntlString)
|
||||
state!: Ref<State>
|
||||
|
||||
@Prop(TypeString(), 'No.' as IntlString)
|
||||
|
@ -19,6 +19,7 @@ import type { Ref, FindOptions, Doc, Domain, Class } from '@anticrm/core'
|
||||
import core, { TSpace, TSpaceWithStates, TDocWithState } from '@anticrm/model-core'
|
||||
import type { Vacancy, Candidates, Candidate, Applicant } from '@anticrm/recruit'
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
|
||||
@ -73,6 +74,9 @@ export class TApplicant extends TDocWithState implements Applicant {
|
||||
|
||||
@Prop(TypeString(), 'Attachments' as IntlString)
|
||||
attachments?: number
|
||||
|
||||
@Prop(TypeString(), 'Assigned recruiter' as IntlString)
|
||||
employee!: Ref<Employee>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
|
@ -109,6 +109,10 @@ export function createModel (builder: Builder): void {
|
||||
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, {
|
||||
presenter: view.component.StatePresenter
|
||||
})
|
||||
|
@ -32,6 +32,7 @@ export default mergeIds(viewId, view, {
|
||||
BooleanPresenter: '' as AnyComponent,
|
||||
BooleanEditor: '' as AnyComponent,
|
||||
StatePresenter: '' as AnyComponent,
|
||||
StateEditor: '' as AnyComponent,
|
||||
TimestampPresenter: '' as AnyComponent,
|
||||
|
||||
TableView: '' as AnyComponent,
|
||||
|
@ -280,6 +280,13 @@ export function TypeTimestamp (): Type<string> {
|
||||
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
|
||||
*/
|
||||
|
@ -25,7 +25,7 @@
|
||||
export let title: string
|
||||
export let icon: Asset | AnySvelteComponent
|
||||
export let fullSize: boolean = false
|
||||
export let rightSection: AnyComponent | undefined
|
||||
export let rightSection: AnyComponent | undefined = undefined
|
||||
export let object: Doc
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -28,6 +28,7 @@
|
||||
export let maxWidth: string
|
||||
export let focus: boolean = false
|
||||
export let minimize: boolean = false
|
||||
export let showHeader: boolean = true
|
||||
|
||||
const _class = object._class
|
||||
const client = getClient()
|
||||
@ -59,14 +60,18 @@
|
||||
<CircleButton icon={attribute.icon} size={'large'} />
|
||||
{#if !minimize}
|
||||
<div class="flex-col with-icon">
|
||||
<Label label={attribute.label} />
|
||||
{#if showHeader}
|
||||
<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>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-col">
|
||||
<Label label={attribute.label} />
|
||||
{#if showHeader}
|
||||
<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>
|
||||
{/if}
|
||||
|
@ -24,12 +24,14 @@
|
||||
|
||||
import type { Ref, Class } from '@anticrm/core'
|
||||
import { formatName, Person } from '@anticrm/contact'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let _class: Ref<Class<Person>>
|
||||
export let title: IntlString
|
||||
export let caption: IntlString
|
||||
export let value: Ref<Person>
|
||||
export let show: boolean = false
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let selected: Person | undefined
|
||||
let btn: HTMLElement
|
||||
@ -58,7 +60,10 @@
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(UsersPopup, { _class, title, caption }, container, (result) => {
|
||||
if (result) { value = result._id }
|
||||
if (result) {
|
||||
value = result._id
|
||||
dispatch('change', value)
|
||||
}
|
||||
opened = false
|
||||
})
|
||||
}
|
||||
|
@ -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>
|
@ -26,7 +26,7 @@ export let value: Applicant
|
||||
const client = getClient()
|
||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
||||
|
||||
function show() {
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditApplication, { _id: value._id }, 'full')
|
||||
}
|
||||
|
@ -43,11 +43,11 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
export function canClose(): boolean {
|
||||
export function canClose (): boolean {
|
||||
return candidate === undefined && employee === undefined
|
||||
}
|
||||
|
||||
async function createApplication() {
|
||||
async function createApplication () {
|
||||
const state = await client.findOne(core.class.State, { space: _space })
|
||||
if (state === undefined) {
|
||||
throw new Error('create application: state not found')
|
||||
@ -62,19 +62,20 @@
|
||||
const id = await client.addCollection(recruit.class.Applicant, _space, candidate, recruit.class.Candidate, 'applications', {
|
||||
state: state._id,
|
||||
number: incResult.object.sequence,
|
||||
employee: employee
|
||||
})
|
||||
}
|
||||
|
||||
async function validate(candidate: Ref<Candidate>, space: Ref<Space>) {
|
||||
async function validate (candidate: Ref<Candidate>, space: Ref<Space>) {
|
||||
if (candidate === undefined) {
|
||||
status = new Status(Severity.INFO, recruit.status.CandidateRequired, {})
|
||||
} else {
|
||||
if (space === undefined) {
|
||||
status = new Status(Severity.INFO, recruit.status.VacancyRequired, {})
|
||||
} else {
|
||||
const applicants = await client.findAll(recruit.class.Applicant, { space, attachedTo: candidate})
|
||||
const applicants = await client.findAll(recruit.class.Applicant, { space, attachedTo: candidate })
|
||||
if (applicants.length > 0) {
|
||||
status = new Status(Severity.ERROR, recruit.status.ApplicationExists, {})
|
||||
status = new Status(Severity.ERROR, recruit.status.ApplicationExists, {})
|
||||
} else {
|
||||
status = OK
|
||||
}
|
||||
@ -94,7 +95,6 @@
|
||||
spacePlaceholder={'Select vacancy'}
|
||||
bind:space={_space}
|
||||
on:close={() => { dispatch('close') }}>
|
||||
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
{#if !preserveCandidate}
|
||||
|
@ -15,33 +15,24 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Ref, Space, Doc, Class } from '@anticrm/core'
|
||||
import { CircleButton, EditBox, Link, showPopup, IconFile as FileIcon } from '@anticrm/ui'
|
||||
import type { Attachment } from '@anticrm/chunter'
|
||||
import FileUpload from './icons/FileUpload.svelte'
|
||||
import { getClient, createQuery, Channels, AttributeEditor, PDFViewer } from '@anticrm/presentation'
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import type { Candidate, Applicant, Vacancy } from '@anticrm/recruit'
|
||||
import Contact from './icons/Contact.svelte'
|
||||
import Avatar from './icons/Avatar.svelte'
|
||||
import Attachments from './Attachments.svelte'
|
||||
import Edit from './icons/Edit.svelte'
|
||||
import SocialEditor from './SocialEditor.svelte'
|
||||
import CandidateCard from './CandidateCard.svelte'
|
||||
import VacancyCard from './VacancyCard.svelte'
|
||||
|
||||
import chunter from '@anticrm/chunter'
|
||||
|
||||
import recruit from '../plugin'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import ApplicantHeader from './ApplicantHeader.svelte'
|
||||
|
||||
export let _id: Ref<Applicant>
|
||||
let object: Applicant
|
||||
let candidate: Candidate
|
||||
let vacancy: Vacancy
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const query = createQuery()
|
||||
$: 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] })
|
||||
|
||||
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>
|
||||
|
||||
{#if object !== undefined && candidate !== undefined}
|
||||
<Panel icon={Contact} title={formatName(candidate.name)} {object} on:close={() => { dispatch('close') }}>
|
||||
<!-- <svelte:fragment 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> -->
|
||||
<ApplicantHeader {object} slot="subtitle" />
|
||||
|
||||
<div class="grid-cards">
|
||||
<CandidateCard {candidate}/>
|
||||
|
@ -24,7 +24,6 @@ import Attachments from './components/Attachments.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
|
||||
import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte'
|
||||
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { Plugin, Asset } from '@anticrm/platform'
|
||||
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
|
||||
@ -46,6 +46,7 @@ export interface Candidate extends Person {
|
||||
*/
|
||||
export interface Applicant extends DocWithState, AttachedDoc {
|
||||
attachments?: number
|
||||
employee: Ref<Employee>
|
||||
}
|
||||
|
||||
/**
|
||||
|
52
plugins/view-resources/src/components/StateEditor.svelte
Normal file
52
plugins/view-resources/src/components/StateEditor.svelte
Normal 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}
|
65
plugins/view-resources/src/components/StatesPopup.svelte
Normal file
65
plugins/view-resources/src/components/StatesPopup.svelte
Normal 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>
|
@ -21,6 +21,7 @@ import StringPresenter from './components/StringPresenter.svelte'
|
||||
import BooleanEditor from './components/BooleanEditor.svelte'
|
||||
import BooleanPresenter from './components/BooleanPresenter.svelte'
|
||||
import StatePresenter from './components/StatePresenter.svelte'
|
||||
import StateEditor from './components/StateEditor.svelte'
|
||||
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||
import TableView from './components/TableView.svelte'
|
||||
import Table from './components/Table.svelte'
|
||||
@ -60,6 +61,7 @@ export default async () => ({
|
||||
BooleanPresenter,
|
||||
BooleanEditor,
|
||||
StatePresenter,
|
||||
StateEditor,
|
||||
TableView,
|
||||
KanbanView,
|
||||
TimestampPresenter
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user