mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-19 23:00:13 +00:00
kanban take cards implementation from extensions
Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
parent
951fd2ae10
commit
a26c10ad8c
File diff suppressed because it is too large
Load Diff
@ -138,6 +138,10 @@ export function createModel (builder: Builder): void {
|
||||
} as FindOptions<Doc>, // TODO: fix
|
||||
config: ['$lookup.candidate', '$lookup.state', '$lookup.candidate.city', '$lookup.candidate.channels']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.KanbanCard, {
|
||||
card: recruit.component.KanbanCard
|
||||
})
|
||||
}
|
||||
|
||||
export { default } from './plugin'
|
||||
|
@ -32,7 +32,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
CreateCandidates: '' as AnyComponent,
|
||||
CreateCandidate: '' as AnyComponent,
|
||||
CreateApplication: '' as AnyComponent,
|
||||
EditCandidate: '' as AnyComponent
|
||||
EditCandidate: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
|
@ -18,7 +18,7 @@ import type { Ref, Class, Space } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Model, Mixin, Builder } from '@anticrm/model'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { ViewletDescriptor, Viewlet, AttributeEditor, AttributePresenter } from '@anticrm/view'
|
||||
import type { ViewletDescriptor, Viewlet, AttributeEditor, AttributePresenter, KanbanCard } from '@anticrm/view'
|
||||
|
||||
import core, { TDoc, TClass } from '@anticrm/model-core'
|
||||
|
||||
@ -34,6 +34,11 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.KanbanCard, core.class.Class)
|
||||
export class TKanbanCard extends TClass implements KanbanCard {
|
||||
card!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(view.class.ViewletDescriptor, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
|
||||
component!: AnyComponent
|
||||
@ -48,7 +53,7 @@ export class TViewlet extends TDoc implements Viewlet {
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TAttributeEditor, TAttributePresenter, TViewletDescriptor, TViewlet)
|
||||
builder.createModel(TAttributeEditor, TAttributePresenter, TKanbanCard, TViewletDescriptor, TViewlet)
|
||||
|
||||
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.AttributeEditor, {
|
||||
editor: view.component.StringEditor
|
||||
|
@ -14,10 +14,10 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { UserInfo } from '@anticrm/presentation'
|
||||
import { ActionIcon, IconMoreH, IconFile } from '@anticrm/ui'
|
||||
import IconWithLabel from './IconWithLabel.svelte'
|
||||
import Tag from './Tag.svelte'
|
||||
import { UserInfo, Avatar } from '@anticrm/presentation'
|
||||
import { Icon, Label, IconThread, IconAttachment } from '@anticrm/ui'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
|
||||
interface ICard {
|
||||
_id: number
|
||||
@ -27,63 +27,139 @@
|
||||
state: number
|
||||
}
|
||||
|
||||
export let card: ICard
|
||||
export let object: WithLookup<Applicant>
|
||||
export let draggable: boolean
|
||||
|
||||
</script>
|
||||
|
||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||
<div class="header">
|
||||
<UserInfo value={{firstName: card.firstName, lastName: card.lastName }} subtitle={'Candidate'} size={'small'} />
|
||||
<ActionIcon icon={IconMoreH} label={'More..'} direction={'left'} />
|
||||
</div>
|
||||
<div class="card-bg" />
|
||||
<div class="content">
|
||||
<IconWithLabel icon={IconFile} label={'Team Interview'} />
|
||||
<div class="description">{card.description}</div>
|
||||
<div class="flex-center">
|
||||
<div class="avatar">
|
||||
<Avatar size={'large'} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col">
|
||||
<div class="name">{object.$lookup?.candidate?.firstName} {object.$lookup?.candidate?.lastName}</div>
|
||||
<div class="city">{object.$lookup?.candidate?.city}</div>
|
||||
<div class="tags">
|
||||
<Tag icon={IconFile} label={'Application'} />
|
||||
<div class="tag"><Label label={'Application'} /></div>
|
||||
<div class="tag"><Label label={'Resume'} /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Avatar size={'small'} />
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-row-center caption-color tool">
|
||||
<span class="icon"><IconAttachment size={'small'} /></span>
|
||||
4
|
||||
</div>
|
||||
<div class="flex-row-center caption-color tool">
|
||||
<span class="icon"><IconThread size={'small'} /></span>
|
||||
5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../../../packages/theme/styles/mixins.scss';
|
||||
|
||||
.card-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: var(--theme-button-bg-hovered);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
background-color: var(--theme-button-bg-focused);
|
||||
border-radius: 11px 11px 0 0;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
padding: 1.25rem;
|
||||
|
||||
.description {
|
||||
margin-top: 8px;
|
||||
.avatar {
|
||||
position: relative;
|
||||
margin-right: 1rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 50%;
|
||||
filter: drop-shadow(0px 24px 94px rgba(50, 53, 47, 1));
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(transparent, .1);
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: .25rem 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.city {
|
||||
font-weight: 500;
|
||||
font-size: .75rem;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
margin-top: .5rem;
|
||||
|
||||
.tag {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .375rem .5rem;
|
||||
font-weight: 500;
|
||||
font-size: .625rem;
|
||||
text-align: center;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(#fff, .04);
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
.tag + .tag { margin-left: .5rem; }
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .75rem 1.25rem;
|
||||
width: 100%;
|
||||
min-height: 3rem;
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(#fff, .04);
|
||||
}
|
||||
|
||||
.tool .icon {
|
||||
margin-right: .25rem;
|
||||
opacity: .4;
|
||||
}
|
||||
.tool + .tool {
|
||||
margin-left: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
@include bg-layer(var(--theme-card-bg), .06);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.card-container + .card-container) { margin-top: .75rem; }
|
||||
</style>
|
||||
|
@ -20,6 +20,7 @@ import CreateApplication from './components/CreateApplication.svelte'
|
||||
import EditCandidate from './components/EditCandidate.svelte'
|
||||
import CandidateGeneral from './components/CandidateGeneral.svelte'
|
||||
import Attachments from './components/Attachments.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
|
||||
export default async () => ({
|
||||
component: {
|
||||
@ -29,6 +30,7 @@ export default async () => ({
|
||||
CreateApplication,
|
||||
EditCandidate,
|
||||
CandidateGeneral,
|
||||
Attachments
|
||||
Attachments,
|
||||
KanbanCard
|
||||
},
|
||||
})
|
||||
|
@ -1,163 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 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 { UserInfo, Avatar } from '@anticrm/presentation'
|
||||
import { Icon, Label, IconThread, IconAttachment } from '@anticrm/ui'
|
||||
|
||||
interface ICard {
|
||||
_id: number
|
||||
firstName: string
|
||||
lastName: string
|
||||
description: string
|
||||
state: number
|
||||
}
|
||||
|
||||
export let card: ICard
|
||||
export let draggable: boolean
|
||||
|
||||
</script>
|
||||
|
||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||
<div class="card-bg" />
|
||||
<div class="content">
|
||||
<div class="flex-center">
|
||||
<div class="avatar">
|
||||
<Avatar size={'large'} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col">
|
||||
<div class="name">{card.firstName} {card.lastName}</div>
|
||||
<div class="city">{card.description}</div>
|
||||
<div class="tags">
|
||||
<div class="tag"><Label label={'Application'} /></div>
|
||||
<div class="tag"><Label label={'Resume'} /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Avatar size={'small'} />
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-row-center caption-color tool">
|
||||
<span class="icon"><IconAttachment size={'small'} /></span>
|
||||
4
|
||||
</div>
|
||||
<div class="flex-row-center caption-color tool">
|
||||
<span class="icon"><IconThread size={'small'} /></span>
|
||||
5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../../../packages/theme/styles/mixins.scss';
|
||||
|
||||
.card-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border-radius: .75rem;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1.25rem;
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
margin-right: 1rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 50%;
|
||||
filter: drop-shadow(0px 24px 94px rgba(50, 53, 47, 1));
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(transparent, .1);
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: .25rem 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.city {
|
||||
font-weight: 500;
|
||||
font-size: .75rem;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
margin-top: .5rem;
|
||||
|
||||
.tag {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .375rem .5rem;
|
||||
font-weight: 500;
|
||||
font-size: .625rem;
|
||||
text-align: center;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(#fff, .04);
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
.tag + .tag { margin-left: .5rem; }
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .75rem 1.25rem;
|
||||
width: 100%;
|
||||
min-height: 3rem;
|
||||
&::after {
|
||||
content: '';
|
||||
@include bg-layer(#fff, .04);
|
||||
}
|
||||
|
||||
.tool .icon {
|
||||
margin-right: .25rem;
|
||||
opacity: .4;
|
||||
}
|
||||
.tool + .tool {
|
||||
margin-left: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
@include bg-layer(var(--theme-card-bg), .06);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.card-container + .card-container) { margin-top: .75rem; }
|
||||
</style>
|
@ -17,20 +17,20 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Ref, Class, Doc, Space, FindOptions, State } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { buildModel } from '../utils'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Label, showPopup, Loading, ScrollBox } from '@anticrm/ui'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Label, showPopup, Loading, ScrollBox, AnyComponent } from '@anticrm/ui'
|
||||
import type { AnySvelteComponent } from '@anticrm/ui'
|
||||
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
|
||||
import KanbanPanel from './KanbanPanel.svelte'
|
||||
import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
|
||||
import KanbanCard from './KanbanCard.svelte'
|
||||
import KanbanCardEmpty from './KanbanCardEmpty.svelte'
|
||||
|
||||
import core from '@anticrm/core'
|
||||
import { _ID_SEPARATOR } from '@anticrm/platform';
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let _class: Ref<Class<(Doc & { state: Ref<State> })>>
|
||||
export let space: Ref<Space>
|
||||
@ -75,11 +75,17 @@ import { _ID_SEPARATOR } from '@anticrm/platform';
|
||||
|
||||
let dragCard: (Doc & { state: Ref<State>}) | undefined
|
||||
|
||||
async function cardPresenter(_class: Ref<Class<Doc>>): Promise<AnySvelteComponent> {
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.KanbanCard)
|
||||
return await getResource(presenterMixin.card)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#await buildModel(client, _class, config, options)}
|
||||
{#await cardPresenter(_class)}
|
||||
<Loading/>
|
||||
{:then model}
|
||||
{:then presenter}
|
||||
<div class="kanban-container">
|
||||
<ScrollBox>
|
||||
<div class="kanban-content">
|
||||
@ -97,15 +103,14 @@ import { _ID_SEPARATOR } from '@anticrm/platform';
|
||||
}}
|
||||
>
|
||||
<KanbanCardEmpty label={'Create new application'} />
|
||||
{#each objects.filter((c) => c.state === state._id) as card}
|
||||
<KanbanCard {card} draggable={true}
|
||||
{#each objects.filter((c) => c.state === state._id) as object}
|
||||
<svelte:component this={presenter} {object} draggable={true}
|
||||
on:dragstart={() => {
|
||||
dragCard = card
|
||||
dragCard = object
|
||||
}}
|
||||
on:dragend={() => {
|
||||
dragCard = undefined
|
||||
}}
|
||||
/>
|
||||
}}/>
|
||||
{/each}
|
||||
</KanbanPanel>
|
||||
{/each}
|
||||
|
@ -34,6 +34,13 @@ export interface AttributePresenter extends Class<Doc> {
|
||||
presenter: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KanbanCard extends Class<Doc> {
|
||||
card: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -60,7 +67,8 @@ export const viewId = 'view' as Plugin
|
||||
export default plugin(viewId, {
|
||||
mixin: {
|
||||
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
|
||||
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>
|
||||
AttributePresenter: '' as Ref<Mixin<AttributePresenter>>,
|
||||
KanbanCard: '' as Ref<Mixin<KanbanCard>>
|
||||
},
|
||||
class: {
|
||||
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user