Universal UI for Tasks (#659)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2021-12-17 15:04:49 +06:00 committed by GitHub
parent b31202b34b
commit 531343d17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 509 additions and 347 deletions

View File

@ -152,7 +152,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: contact.class.Person, attachTo: contact.class.Person,
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
open: contact.component.EditPerson, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {}, options: {},
config: [ config: [
@ -167,7 +167,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: contact.class.Organization, attachTo: contact.class.Organization,
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
open: contact.component.EditOrganization, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {}, options: {},
config: ['', { presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, 'modifiedOn', 'channels'] config: ['', { presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, 'modifiedOn', 'channels']

View File

@ -27,6 +27,7 @@ export const ids = mergeIds(contactId, contact, {
ChannelsPresenter: '' as AnyComponent, ChannelsPresenter: '' as AnyComponent,
CreatePerson: '' as AnyComponent, CreatePerson: '' as AnyComponent,
EditPerson: '' as AnyComponent, EditPerson: '' as AnyComponent,
EditContact: '' as AnyComponent,
EditOrganization: '' as AnyComponent, EditOrganization: '' as AnyComponent,
CreateOrganization: '' as AnyComponent, CreateOrganization: '' as AnyComponent,
CreatePersons: '' as AnyComponent, CreatePersons: '' as AnyComponent,

View File

@ -28,7 +28,7 @@ import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view from '@anticrm/model-view' import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench' import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import type { } from '@anticrm/view' import type {} from '@anticrm/view'
import lead from './plugin' import lead from './plugin'
@Model(lead.class.Funnel, task.class.SpaceWithStates) @Model(lead.class.Funnel, task.class.SpaceWithStates)
@ -36,7 +36,7 @@ import lead from './plugin'
export class TFunnel extends TSpaceWithStates implements Funnel {} export class TFunnel extends TSpaceWithStates implements Funnel {}
@Model(lead.class.Lead, task.class.Task) @Model(lead.class.Lead, task.class.Task)
@UX('Lead' as IntlString) @UX('Lead' as IntlString, lead.icon.Lead)
export class TLead extends TTask implements Lead { export class TLead extends TTask implements Lead {
@Prop(TypeString(), 'Title' as IntlString) @Prop(TypeString(), 'Title' as IntlString)
title!: string title!: string
@ -61,32 +61,42 @@ export function createModel (builder: Builder): void {
} }
}) })
builder.createDoc(workbench.class.Application, core.space.Model, { builder.createDoc(
label: lead.string.LeadApplication, workbench.class.Application,
icon: lead.icon.LeadApplication, core.space.Model,
hidden: false, {
navigatorModel: { label: lead.string.LeadApplication,
spaces: [ icon: lead.icon.LeadApplication,
{ hidden: false,
label: lead.string.Funnels, navigatorModel: {
spaceClass: lead.class.Funnel, spaces: [
addSpaceLabel: lead.string.CreateFunnel, {
createComponent: lead.component.CreateFunnel label: lead.string.Funnels,
} spaceClass: lead.class.Funnel,
] addSpaceLabel: lead.string.CreateFunnel,
} createComponent: lead.component.CreateFunnel
}, lead.app.Lead) }
builder.createDoc(lead.class.Funnel, core.space.Model, { ]
name: 'Funnel', }
description: 'Default funnel', },
private: false, lead.app.Lead
members: [] )
}, lead.space.DefaultFunnel) builder.createDoc(
lead.class.Funnel,
core.space.Model,
{
name: 'Funnel',
description: 'Default funnel',
private: false,
members: []
},
lead.space.DefaultFunnel
)
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead, attachTo: lead.class.Lead,
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
open: lead.component.EditLead, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: { options: {
lookup: { lookup: {
@ -101,13 +111,14 @@ export function createModel (builder: Builder): void {
{ presenter: attachment.component.AttachmentsPresenter, label: 'Files' }, { presenter: attachment.component.AttachmentsPresenter, label: 'Files' },
{ presenter: chunter.component.CommentsPresenter, label: 'Comments' }, { presenter: chunter.component.CommentsPresenter, label: 'Comments' },
'modifiedOn', 'modifiedOn',
'$lookup.customer.channels'] '$lookup.customer.channels'
]
}) })
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead, attachTo: lead.class.Lead,
descriptor: task.viewlet.Kanban, descriptor: task.viewlet.Kanban,
open: lead.component.EditLead, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: { options: {
lookup: { lookup: {
@ -135,13 +146,18 @@ export function createModel (builder: Builder): void {
sequence: 0 sequence: 0
}) })
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, { builder.createDoc(
name: 'Funnels', task.class.KanbanTemplateSpace,
description: 'Manage funnel statuses', core.space.Model,
members: [], {
private: false, name: 'Funnels',
icon: lead.component.TemplatesIcon description: 'Manage funnel statuses',
}, lead.space.FunnelTemplates) members: [],
private: false,
icon: lead.component.TemplatesIcon
},
lead.space.FunnelTemplates
)
createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => { createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => {
builder.createDoc(_class, space, data, id) builder.createDoc(_class, space, data, id)

View File

@ -70,7 +70,7 @@ export class TCandidate extends TPerson implements Candidate {
} }
@Model(recruit.class.Applicant, task.class.Task) @Model(recruit.class.Applicant, task.class.Task)
@UX('Application' as IntlString, recruit.icon.RecruitApplication, 'APP' as IntlString) @UX('Application' as IntlString, recruit.icon.Application, 'APP' as IntlString)
export class TApplicant extends TTask implements Applicant { export class TApplicant extends TTask implements Applicant {
// We need to declare, to provide property with label // We need to declare, to provide property with label
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString) @Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
@ -107,28 +107,33 @@ export function createModel (builder: Builder): void {
editor: recruit.component.Applications editor: recruit.component.Applications
}) })
builder.createDoc(workbench.class.Application, core.space.Model, { builder.createDoc(
label: recruit.string.RecruitApplication, workbench.class.Application,
icon: recruit.icon.RecruitApplication, core.space.Model,
hidden: false, {
navigatorModel: { label: recruit.string.RecruitApplication,
spaces: [ icon: recruit.icon.RecruitApplication,
{ hidden: false,
label: recruit.string.Vacancies, navigatorModel: {
spaceClass: recruit.class.Vacancy, spaces: [
addSpaceLabel: recruit.string.CreateVacancy, {
createComponent: recruit.component.CreateVacancy, label: recruit.string.Vacancies,
component: recruit.component.EditVacancy spaceClass: recruit.class.Vacancy,
}, addSpaceLabel: recruit.string.CreateVacancy,
{ createComponent: recruit.component.CreateVacancy,
label: recruit.string.Candidates, component: recruit.component.EditVacancy
spaceClass: recruit.class.Candidates, },
addSpaceLabel: recruit.string.CreateCandidates, {
createComponent: recruit.component.CreateCandidates label: recruit.string.Candidates,
} spaceClass: recruit.class.Candidates,
] addSpaceLabel: recruit.string.CreateCandidates,
} createComponent: recruit.component.CreateCandidates
}, recruit.app.Recruit) }
]
}
},
recruit.app.Recruit
)
builder.createDoc( builder.createDoc(
recruit.class.Candidates, recruit.class.Candidates,
core.space.Model, core.space.Model,
@ -144,7 +149,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Candidate, attachTo: recruit.class.Candidate,
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
open: recruit.component.EditCandidate, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: { options: {
// lookup: { // lookup: {
@ -166,7 +171,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant, attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
open: recruit.component.EditCandidate, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: { options: {
lookup: { lookup: {
@ -189,7 +194,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant, attachTo: recruit.class.Applicant,
descriptor: task.viewlet.Kanban, descriptor: task.viewlet.Kanban,
open: recruit.component.EditCandidate, open: contact.component.EditContact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: { options: {
lookup: { lookup: {
@ -208,6 +213,10 @@ export function createModel (builder: Builder): void {
editor: recruit.component.EditCandidate editor: recruit.component.EditCandidate
}) })
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectEditor, {
editor: recruit.component.EditApplication
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributePresenter, {
presenter: recruit.component.ApplicationPresenter presenter: recruit.component.ApplicationPresenter
}) })
@ -238,13 +247,18 @@ export function createModel (builder: Builder): void {
sequence: 0 sequence: 0
}) })
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, { builder.createDoc(
name: 'Vacancies', task.class.KanbanTemplateSpace,
description: 'Manage vacancy statuses', core.space.Model,
members: [], {
private: false, name: 'Vacancies',
icon: recruit.component.TemplatesIcon description: 'Manage vacancy statuses',
}, recruit.space.VacancyTemplates) members: [],
private: false,
icon: recruit.component.TemplatesIcon
},
recruit.space.VacancyTemplates
)
} }
export { recruitOperation } from './migration' export { recruitOperation } from './migration'

View File

@ -50,6 +50,7 @@ export default mergeIds(recruitId, recruit, {
ApplicationPresenter: '' as AnyComponent, ApplicationPresenter: '' as AnyComponent,
ApplicationsPresenter: '' as AnyComponent, ApplicationsPresenter: '' as AnyComponent,
EditVacancy: '' as AnyComponent, EditVacancy: '' as AnyComponent,
EditApplication: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent, TemplatesIcon: '' as AnyComponent,
Applications: '' as AnyComponent Applications: '' as AnyComponent
}, },

View File

@ -26,7 +26,24 @@ import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
import view from '@anticrm/model-view' import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench' import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform' import type { IntlString } from '@anticrm/platform'
import type { Kanban, KanbanCard, Project, State, Issue, Sequence, DoneState, WonState, LostState, KanbanTemplateSpace, StateTemplate, DoneStateTemplate, WonStateTemplate, LostStateTemplate, KanbanTemplate, Task } from '@anticrm/task' import type {
Kanban,
KanbanCard,
Project,
State,
Issue,
Sequence,
DoneState,
WonState,
LostState,
KanbanTemplateSpace,
StateTemplate,
DoneStateTemplate,
WonStateTemplate,
LostStateTemplate,
KanbanTemplate,
Task
} from '@anticrm/task'
import { createProjectKanban } from '@anticrm/task' import { createProjectKanban } from '@anticrm/task'
import task from './plugin' import task from './plugin'
import { AnyComponent } from '@anticrm/ui' import { AnyComponent } from '@anticrm/ui'
@ -77,8 +94,7 @@ export class TTask extends TAttachedDoc implements Task {
} }
@Model(task.class.SpaceWithStates, core.class.Space) @Model(task.class.SpaceWithStates, core.class.Space)
export class TSpaceWithStates extends TSpace { export class TSpaceWithStates extends TSpace {}
}
@Model(task.class.Project, task.class.SpaceWithStates) @Model(task.class.Project, task.class.SpaceWithStates)
@UX('Project' as IntlString, task.icon.Task) @UX('Project' as IntlString, task.icon.Task)
@ -188,7 +204,8 @@ export function createModel (builder: Builder): void {
TTask, TTask,
TSpaceWithStates, TSpaceWithStates,
TProject, TProject,
TIssue) TIssue
)
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, { builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
view: { view: {
class: task.class.Issue, class: task.class.Issue,
@ -196,21 +213,26 @@ export function createModel (builder: Builder): void {
} }
}) })
builder.createDoc(workbench.class.Application, core.space.Model, { builder.createDoc(
label: task.string.ApplicationLabelTask, workbench.class.Application,
icon: task.icon.Task, core.space.Model,
hidden: false, {
navigatorModel: { label: task.string.ApplicationLabelTask,
spaces: [ icon: task.icon.Task,
{ hidden: false,
label: task.string.Projects, navigatorModel: {
spaceClass: task.class.Project, spaces: [
addSpaceLabel: task.string.CreateProject, {
createComponent: task.component.CreateProject label: task.string.Projects,
} spaceClass: task.class.Project,
] addSpaceLabel: task.string.CreateProject,
} createComponent: task.component.CreateProject
}, task.app.Tasks) }
]
}
},
task.app.Tasks
)
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Issue, attachTo: task.class.Issue,
@ -237,7 +259,7 @@ export function createModel (builder: Builder): void {
}) })
builder.mixin(task.class.Issue, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(task.class.Issue, core.class.Class, view.mixin.ObjectEditor, {
editor: task.component.EditTask editor: task.component.EditIssue
}) })
builder.createDoc(task.class.Sequence, task.space.Sequence, { builder.createDoc(task.class.Sequence, task.space.Sequence, {
@ -260,44 +282,65 @@ export function createModel (builder: Builder): void {
config: [ config: [
// '$lookup.attachedTo', // '$lookup.attachedTo',
'$lookup.state', '$lookup.state',
'$lookup.assignee'] '$lookup.assignee'
]
}) })
builder.mixin(task.class.Issue, core.class.Class, task.mixin.KanbanCard, { builder.mixin(task.class.Issue, core.class.Class, task.mixin.KanbanCard, {
card: task.component.KanbanCard card: task.component.KanbanCard
}) })
builder.createDoc(task.class.Project, core.space.Model, { builder.createDoc(
name: 'public', task.class.Project,
description: 'Public tasks', core.space.Model,
private: false, {
members: [] name: 'public',
}, task.space.TasksPublic) description: 'Public tasks',
private: false,
members: []
},
task.space.TasksPublic
)
builder.createDoc(task.class.KanbanTemplateSpace, core.space.Model, { builder.createDoc(
name: 'Projects', task.class.KanbanTemplateSpace,
description: 'Manage project statuses', core.space.Model,
members: [], {
private: false, name: 'Projects',
icon: task.component.TemplatesIcon description: 'Manage project statuses',
}, task.space.ProjectTemplates) members: [],
private: false,
icon: task.component.TemplatesIcon
},
task.space.ProjectTemplates
)
createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => { createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
builder.createDoc(_class, space, data, id) builder.createDoc(_class, space, data, id)
return await Promise.resolve() return await Promise.resolve()
}).catch((err) => console.error(err)) }).catch((err) => console.error(err))
builder.createDoc(view.class.Action, core.space.Model, { builder.createDoc(
label: 'Create task' as IntlString, view.class.Action,
icon: task.icon.Task, core.space.Model,
action: task.actionImpl.CreateTask {
}, task.action.CreateTask) label: 'Create task' as IntlString,
icon: task.icon.Task,
action: task.actionImpl.CreateTask
},
task.action.CreateTask
)
builder.createDoc(view.class.Action, core.space.Model, { builder.createDoc(
label: 'Edit Statuses' as IntlString, view.class.Action,
icon: view.icon.MoreH, core.space.Model,
action: task.actionImpl.EditStatuses {
}, task.action.EditStatuses) label: 'Edit Statuses' as IntlString,
icon: view.icon.MoreH,
action: task.actionImpl.EditStatuses
},
task.action.EditStatuses
)
builder.createDoc(view.class.ActionTarget, core.space.Model, { builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: task.class.SpaceWithStates, target: task.class.SpaceWithStates,
@ -312,18 +355,28 @@ export function createModel (builder: Builder): void {
presenter: task.component.StatePresenter presenter: task.component.StatePresenter
}) })
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, { builder.createDoc(
label: 'Kanban' as IntlString, view.class.ViewletDescriptor,
icon: task.icon.Kanban, core.space.Model,
component: task.component.KanbanView {
}, task.viewlet.Kanban) label: 'Kanban' as IntlString,
icon: task.icon.Kanban,
component: task.component.KanbanView
},
task.viewlet.Kanban
)
builder.createDoc(core.class.Space, core.space.Model, { builder.createDoc(
name: 'Sequences', core.class.Space,
description: 'Internal space to store sequence numbers', core.space.Model,
members: [], {
private: false name: 'Sequences',
}, task.space.Sequence) description: 'Internal space to store sequence numbers',
members: [],
private: false
},
task.space.Sequence
)
} }
export { taskOperation } from './migration' export { taskOperation } from './migration'

View File

@ -39,6 +39,7 @@ export default mergeIds(taskId, task, {
CreateProject: '' as AnyComponent, CreateProject: '' as AnyComponent,
CreateTask: '' as AnyComponent, CreateTask: '' as AnyComponent,
EditTask: '' as AnyComponent, EditTask: '' as AnyComponent,
EditIssue: '' as AnyComponent,
TaskPresenter: '' as AnyComponent, TaskPresenter: '' as AnyComponent,
KanbanCard: '' as AnyComponent, KanbanCard: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent, TemplatesIcon: '' as AnyComponent,

View File

@ -1,7 +1,7 @@
{ {
"string": { "string": {
"UploadDropFilesHere": "Upload or drop files here", "UploadDropFilesHere": "Upload or drop files here",
"NoAttachments": "There are no attachments", "NoAttachments": "There are no attachments for this",
"AddAttachment": "uploaded an attachment" "AddAttachment": "uploaded an attachment"
} }
} }

View File

@ -1,14 +1,15 @@
<!-- <!--
// Copyright © 2020 Anticrm Platform Contributors. // Copyright © 2020, 2021 Anticrm Platform Contributors.
// // Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // 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 // 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 // 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 // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
@ -16,7 +17,7 @@
import attachment from '../plugin' import attachment from '../plugin'
import type { Attachment } from '@anticrm/attachment' import type { Attachment } from '@anticrm/attachment'
import type { Class, Doc, Ref, Space } from '@anticrm/core' import type { Class, Doc, Ref, Space } from '@anticrm/core'
import { IntlString, setPlatformStatus, unknownError } from '@anticrm/platform' import { setPlatformStatus, unknownError } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { CircleButton, IconAdd, Label, Spinner } from '@anticrm/ui' import { CircleButton, IconAdd, Label, Spinner } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources' import { Table } from '@anticrm/view-resources'
@ -26,7 +27,6 @@
export let objectId: Ref<Doc> export let objectId: Ref<Doc>
export let space: Ref<Space> export let space: Ref<Space>
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let noLabel: IntlString = attachment.string.NoAttachments
let attachments: Attachment[] = [] let attachments: Attachment[] = []
@ -39,6 +39,7 @@
let loading = 0 let loading = 0
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy()
async function createAttachment (file: File) { async function createAttachment (file: File) {
loading++ loading++
@ -78,6 +79,7 @@
} }
let dragover = false let dragover = false
$: classLabel = hierarchy.getClass(_class).label
</script> </script>
<div class="attachments-container"> <div class="attachments-container">
@ -120,7 +122,10 @@
> >
<UploadDuo size={'large'} /> <UploadDuo size={'large'} />
<div class="small-text content-dark-color mt-2"> <div class="small-text content-dark-color mt-2">
<Label label={noLabel} /> <Label label={attachment.string.NoAttachments} />
<span class="lower">
<Label label={classLabel} />
</span>
</div> </div>
<div class="small-text"> <div class="small-text">
<a href={'#'} on:click={() => inputFile.click()}><Label label={attachment.string.UploadDropFilesHere} /></a> <a href={'#'} on:click={() => inputFile.click()}><Label label={attachment.string.UploadDropFilesHere} /></a>
@ -156,4 +161,8 @@
border: 1px dashed var(--theme-zone-border-lite); border: 1px dashed var(--theme-zone-border-lite);
border-radius: 0.75rem; border-radius: 0.75rem;
} }
.lower {
text-transform: lowercase;
}
</style> </style>

View File

@ -14,23 +14,14 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { Ref } from '@anticrm/core' import { getClient, UserBox } from '@anticrm/presentation'
import { Panel } from '@anticrm/panel'
import { createQuery, getClient, UserBox } from '@anticrm/presentation'
import { Attachments } from '@anticrm/attachment-resources'
import type { Lead } from '@anticrm/lead' import type { Lead } from '@anticrm/lead'
import { EditBox, Grid } from '@anticrm/ui' import { EditBox, Grid } from '@anticrm/ui'
import contact from '@anticrm/contact' import contact from '@anticrm/contact'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import lead from '../plugin' import lead from '../plugin'
export let _id: Ref<Lead> export let object: Lead
let object: Lead
const query = createQuery()
$: query.query(lead.class.Lead, { _id }, (result) => {
object = result[0]
})
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
@ -38,34 +29,31 @@
function change (field: string, value: any) { function change (field: string, value: any) {
client.updateDoc(object._class, object.space, object._id, { [field]: value }) client.updateDoc(object._class, object.space, object._id, { [field]: value })
} }
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'number', 'title', 'customer'] })
})
</script> </script>
{#if object !== undefined} {#if object !== undefined}
<Panel <Grid column={1} rowGap={1.5}>
icon={lead.icon.Lead} <EditBox
title={object.title} label={lead.string.LeadName}
{object} bind:value={object.title}
on:close={() => { icon={lead.icon.Lead}
dispatch('close') placeholder="The simple lead"
}} maxWidth="39rem"
> focus
<Grid column={1} rowGap={1.5}> on:change={(evt) => change('title', object.title)}
<EditBox />
label={lead.string.LeadName} <UserBox
bind:value={object.title} _class={contact.class.Contact}
icon={lead.icon.Lead} title="Customer"
placeholder="The simple lead" caption="Select customer"
maxWidth="39rem" bind:value={object.customer}
focus on:change={() => {
on:change={(evt) => change('title', object.title)}
/>
<UserBox _class={contact.class.Contact} title="Customer" caption="Select customer" bind:value={object.customer} on:change={() => {
change('customer', object.customer) change('customer', object.customer)
}} /> }}
</Grid> />
</Grid>
<div class="mt-14">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
</Panel>
{/if} {/if}

View File

@ -22,7 +22,7 @@
import { ActionIcon, IconMoreH, showPopup } from '@anticrm/ui' import { ActionIcon, IconMoreH, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources' import { ContextMenu } from '@anticrm/view-resources'
import lead from '../plugin' import lead from '../plugin'
import EditLead from './EditLead.svelte' import { EditTask } from '@anticrm/task-resources'
export let object: WithLookup<Lead> export let object: WithLookup<Lead>
export let draggable: boolean export let draggable: boolean
@ -32,7 +32,7 @@
} }
function showLead () { function showLead () {
showPopup(EditLead, { _id: object._id }, 'full') showPopup(EditTask, { _id: object._id }, 'full')
} }
</script> </script>
@ -63,7 +63,7 @@
}} }}
icon={IconMoreH} icon={IconMoreH}
size={'small'} size={'small'}
/> />
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,14 +16,14 @@
<script lang="ts"> <script lang="ts">
import type { Lead } from '@anticrm/lead' import type { Lead } from '@anticrm/lead'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui' import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditLead from './EditLead.svelte'
import lead from '../plugin' import lead from '../plugin'
import { EditTask } from '@anticrm/task-resources'
export let value: Lead export let value: Lead
function show () { function show () {
closeTooltip() closeTooltip()
showPopup(EditLead, { _id: value._id }, 'full') showPopup(EditTask, { _id: value._id }, 'full')
} }
</script> </script>

View File

@ -1,8 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="chunter" viewBox="0 0 32 32">
<path d="M25.9,14.6C25.7,9.8,21.9,6,17,5.7h-0.5c0,0,0,0,0,0c-1.4,0-2.9,0.3-4.2,1c-3.2,1.6-5.2,4.8-5.2,8.4c0,1.4,0.3,2.7,0.9,4 l-1.9,5.7c-0.1,0.2,0,0.5,0.1,0.6c0.1,0.1,0.3,0.2,0.4,0.2c0.1,0,0.1,0,0.2,0l5.7-1.9c1.2,0.6,2.6,0.9,4,0.9c0,0,0,0,0,0 c3.6,0,6.8-2,8.4-5.2c0.7-1.3,1-2.8,1-4.2L25.9,14.6z M24.7,15.1C24.7,15.1,24.7,15.1,24.7,15.1c0,1.3-0.3,2.5-0.9,3.7 c-1.4,2.8-4.2,4.5-7.3,4.5c0,0,0,0,0,0c-1.3,0-2.5-0.3-3.6-0.9c-0.1-0.1-0.3-0.1-0.5,0l-4.8,1.6l1.6-4.8c0.1-0.2,0-0.3,0-0.5 c-0.6-1.1-0.9-2.4-0.9-3.7c0-3.1,1.7-5.9,4.5-7.3c1.1-0.6,2.4-0.9,3.6-0.9c0,0,0,0,0,0l0.5,0c4.2,0.2,7.5,3.6,7.7,7.7V15.1z"/>
</symbol>
<symbol id="recruitment" viewBox="0 0 24 24"> <symbol id="recruitment" viewBox="0 0 24 24">
<g> <g>
<path d="M9.6,14.4c-1.8,0-7.4,0-7.4,3.4c0,3,4.2,3.4,7.4,3.4c1.8,0,7.4,0,7.4-3.4C17.1,14.7,12.8,14.4,9.6,14.4z M9.6,20c-4.1,0-6.2-0.7-6.2-2.2c0-1.5,2.1-2.2,6.2-2.2s6.2,0.7,6.2,2.2C15.9,19.2,13.8,20,9.6,20z"/> <path d="M9.6,14.4c-1.8,0-7.4,0-7.4,3.4c0,3,4.2,3.4,7.4,3.4c1.8,0,7.4,0,7.4-3.4C17.1,14.7,12.8,14.4,9.6,14.4z M9.6,20c-4.1,0-6.2-0.7-6.2-2.2c0-1.5,2.1-2.2,6.2-2.2s6.2,0.7,6.2,2.2C15.9,19.2,13.8,20,9.6,20z"/>
@ -35,4 +31,7 @@
<path d="M9.8,10.2H5.5c-0.3,0-0.6,0.3-0.6,0.6s0.3,0.6,0.6,0.6h4.4c0.3,0,0.6-0.3,0.6-0.6S10.1,10.2,9.8,10.2z"/> <path d="M9.8,10.2H5.5c-0.3,0-0.6,0.3-0.6,0.6s0.3,0.6,0.6,0.6h4.4c0.3,0,0.6-0.3,0.6-0.6S10.1,10.2,9.8,10.2z"/>
<path d="M5.5,8.3h2.7c0.3,0,0.6-0.3,0.6-0.6S8.5,7.2,8.2,7.2H5.5c-0.3,0-0.6,0.3-0.6,0.6S5.1,8.3,5.5,8.3z"/> <path d="M5.5,8.3h2.7c0.3,0,0.6-0.3,0.6-0.6S8.5,7.2,8.2,7.2H5.5c-0.3,0-0.6,0.3-0.6,0.6S5.1,8.3,5.5,8.3z"/>
</symbol> </symbol>
<symbol id="application" viewBox="0 0 16 16">
<path d="M11.2,0C11.2,0,11.2,0,11.2,0H6C6,0,6,0,6,0s0,0-0.1,0H5.8C5.7,0,5.5,0.1,5.4,0.2L1.3,4.5C1.2,4.6,1.2,4.7,1.2,4.8v7.5 c0,2,1.5,3.6,3.5,3.7h6.5c0,0,0.1,0,0.1,0c2,0,3.6-1.7,3.5-3.7V3.5C14.8,1.6,13.2,0,11.2,0z M5.5,1.4v1.6c0,1-0.7,1.8-1.6,1.8H2.2 L5.5,1.4z M13.9,12.4c0,1.5-1.1,2.7-2.7,2.7H4.7c-1.5-0.1-2.6-1.2-2.6-2.7V5.8h1.8c1.4,0,2.6-1.2,2.6-2.8V1h4.8c0,0,0,0,0,0 c1.4,0,2.6,1.2,2.7,2.6V12.4z"/>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -14,8 +14,7 @@
"CandidatesName": "Pool name *", "CandidatesName": "Pool name *",
"MakePrivateDescription": "Only members can see it", "MakePrivateDescription": "Only members can see it",
"CreateAnApplication": "Create an application", "CreateAnApplication": "Create an application",
"NoApplicationsForCandidate": "There are no applications for this candidate.", "NoApplicationsForCandidate": "There are no applications for this candidate."
"NoAttachmentsForCandidate": "There are no attachments for this candidate."
}, },
"status": { "status": {
"CandidateRequired": "Please select candidate" "CandidateRequired": "Please select candidate"

View File

@ -22,7 +22,8 @@ loadMetadata(recruit.icon, {
Vacancy: `${icons}#vacancy`, Vacancy: `${icons}#vacancy`,
Location: `${icons}#location`, Location: `${icons}#location`,
Calendar: `${icons}#calendar`, Calendar: `${icons}#calendar`,
Create: `${icons}#create` Create: `${icons}#create`,
Application: `${icons}#application`
}) })
addStringsLoader(recruitId, async (lang: string) => await import(`../lang/${lang}.json`)) addStringsLoader(recruitId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

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

View File

@ -13,26 +13,23 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { Applicant } from '@anticrm/recruit'
import { closeTooltip, IconFile, showPopup } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import { EditTask } from '@anticrm/task-resources'
import type { Applicant } from '@anticrm/recruit' export let value: Applicant
import { closeTooltip, IconFile, showPopup } from '@anticrm/ui'
import EditApplication from './EditApplication.svelte'
import { getClient } from '@anticrm/presentation'
export let value: Applicant const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
function show () {
closeTooltip()
showPopup(EditApplication, { _id: value._id }, 'full')
}
function show () {
closeTooltip()
showPopup(EditTask, { _id: value._id }, 'full')
}
</script> </script>
<div class="sm-tool-icon" on:click={show}> <div class="sm-tool-icon" on:click={show}>
<span class="icon"><IconFile size={'small'}/></span>&nbsp;{shortLabel}-{value.number} <span class="icon"><IconFile size={'small'} /></span>&nbsp;{shortLabel}-{value.number}
</div> </div>

View File

@ -1,70 +1,58 @@
<!-- <!--
// Copyright © 2020 Anticrm Platform Contributors. // Copyright © 2020, 2021 Anticrm Platform Contributors.
// // Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // 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 // 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 // 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 // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import type { Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation' import { createQuery } from '@anticrm/presentation'
import { Panel } from '@anticrm/panel'
import type { Candidate, Applicant, Vacancy } from '@anticrm/recruit' import type { Candidate, Applicant, Vacancy } from '@anticrm/recruit'
import { Attachments } from '@anticrm/attachment-resources'
import Contact from './icons/Contact.svelte'
import CandidateCard from './CandidateCard.svelte' import CandidateCard from './CandidateCard.svelte'
import VacancyCard from './VacancyCard.svelte' import VacancyCard from './VacancyCard.svelte'
import recruit from '../plugin' import recruit from '../plugin'
import { formatName } from '@anticrm/contact'
import ApplicantHeader from './ApplicantHeader.svelte'
export let _id: Ref<Applicant> export let object: Applicant
let object: Applicant
let candidate: Candidate let candidate: Candidate
let vacancy: Vacancy let vacancy: Vacancy
const query = createQuery()
$: query.query(recruit.class.Applicant, { _id }, result => { object = result[0] })
const candidateQuery = createQuery() const candidateQuery = createQuery()
$: if (object !== undefined) candidateQuery.query(recruit.class.Candidate, { _id: object.attachedTo }, result => { candidate = result[0] }) $: if (object !== undefined)
{candidateQuery.query(recruit.class.Candidate, { _id: object.attachedTo }, (result) => {
candidate = result[0]
})}
const vacancyQuery = createQuery() const vacancyQuery = createQuery()
$: 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()
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'number'] })
})
</script> </script>
{#if object !== undefined && candidate !== undefined} {#if object !== undefined && candidate !== undefined}
<Panel icon={Contact} title={formatName(candidate.name)} {object} on:close={() => { dispatch('close') }}>
<ApplicantHeader {object} slot="subtitle" />
<div class="grid-cards"> <div class="grid-cards">
<CandidateCard {candidate}/> <CandidateCard {candidate} />
<VacancyCard {vacancy}/> <VacancyCard {vacancy} />
</div> </div>
<div class="attachments">
<Attachments objectId={object._id} _class={object._class} space={object.space} />
</div>
</Panel>
{/if} {/if}
<style lang="scss"> <style lang="scss">
.attachments {
margin-top: 3.5rem;
}
.grid-cards { .grid-cards {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;

View File

@ -26,6 +26,7 @@ import ApplicationPresenter from './components/ApplicationPresenter.svelte'
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte' import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte' import TemplatesIcon from './components/TemplatesIcon.svelte'
import Applications from './components/Applications.svelte' import Applications from './components/Applications.svelte'
import EditApplication from './components/EditApplication.svelte'
import { showPopup } from '@anticrm/ui' import { showPopup } from '@anticrm/ui'
import { Resources } from '@anticrm/platform' import { Resources } from '@anticrm/platform'
@ -44,6 +45,7 @@ export default async (): Promise<Resources> => ({
CreateCandidate, CreateCandidate,
CreateApplication, CreateApplication,
EditCandidate, EditCandidate,
EditApplication,
KanbanCard, KanbanCard,
ApplicationPresenter, ApplicationPresenter,
ApplicationsPresenter, ApplicationsPresenter,

View File

@ -37,8 +37,6 @@ export default mergeIds(recruitId, recruit, {
CreateCandidate: '' as IntlString, CreateCandidate: '' as IntlString,
CreateAnApplication: '' as IntlString, CreateAnApplication: '' as IntlString,
NoApplicationsForCandidate: '' as IntlString, NoApplicationsForCandidate: '' as IntlString,
NoAttachmentsForCandidate: '' as IntlString,
FirstName: '' as IntlString, FirstName: '' as IntlString,
LastName: '' as IntlString LastName: '' as IntlString
}, },

View File

@ -71,6 +71,7 @@ export default plugin(recruitId, {
Vacancy: '' as Asset, Vacancy: '' as Asset,
Location: '' as Asset, Location: '' as Asset,
Calendar: '' as Asset, Calendar: '' as Asset,
Create: '' as Asset Create: '' as Asset,
Application: '' as Asset
} }
}) })

View File

@ -13,7 +13,6 @@
"TaskName": "Task name *", "TaskName": "Task name *",
"TaskAssignee": "Assignee", "TaskAssignee": "Assignee",
"TaskDescription": "Description", "TaskDescription": "Description",
"NoAttachmentsForTask": "There are no attachments for this task.",
"AssigneeRequired": "Assignee is required", "AssigneeRequired": "Assignee is required",
"More": "Options", "More": "Options",
"TaskUnAssign": "Unassign", "TaskUnAssign": "Unassign",

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 { getClient } from '@anticrm/presentation'
import type { Issue } from '@anticrm/task'
import { EditBox, Grid } from '@anticrm/ui'
import { createEventDispatcher, onMount } from 'svelte'
import task from '../plugin'
export let object: Issue
const dispatch = createEventDispatcher()
const client = getClient()
function change (field: string, value: any) {
client.updateCollection(
object._class,
object.space,
object._id,
object.attachedTo,
object.attachedToClass,
object.collection,
{ [field]: value }
)
}
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'name', 'description', 'number'] })
})
</script>
{#if object !== undefined}
<Grid column={1} rowGap={1.5}>
<EditBox
label={task.string.TaskName}
bind:value={object.name}
icon={task.icon.Task}
placeholder="The boring task"
maxWidth="39rem"
focus
on:change={(evt) => change('name', object.name)}
/>
<EditBox
label={task.string.TaskDescription}
bind:value={object.description}
icon={task.icon.Task}
placeholder="Description"
maxWidth="39rem"
on:change={(evt) => change('description', object.description)}
/>
</Grid>
{/if}

View File

@ -1,78 +1,118 @@
<!-- <!--
// Copyright © 2020 Anticrm Platform Contributors. // Copyright © 2020, 2021 Anticrm Platform Contributors.
// // Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // 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 // 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 // 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 // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { Ref } from '@anticrm/core' import core, { Class, Doc, Ref } from '@anticrm/core'
import { Panel } from '@anticrm/panel' import { Panel } from '@anticrm/panel'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getAttributePresenterClass, getClient } from '@anticrm/presentation'
import type { Issue } from '@anticrm/task' import type { Task } from '@anticrm/task'
import { EditBox, Grid } from '@anticrm/ui' import { AnyComponent, Component } from '@anticrm/ui'
import view from '@anticrm/view' import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import task from '../plugin' import task from '../plugin'
import { Attachments } from '@anticrm/attachment-resources'
import TaskHeader from './TaskHeader.svelte' import TaskHeader from './TaskHeader.svelte'
import { Asset } from '@anticrm/platform'
export let _id: Ref<Issue> export let _id: Ref<Task>
let object: Issue let object: Task
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
let keys: string[] = []
let collectionKeys: string[] = []
const query = createQuery() const query = createQuery()
$: query.query(task.class.Issue, { _id }, (result) => { $: _id &&
object = result[0] query.query(task.class.Task, { _id }, (result) => {
}) object = result[0]
})
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient()
function change (field: string, value: any) { function getFiltredKeys (ignoreKeys: string[]): string[] {
client.updateCollection(object._class, object.space, object._id, object.attachedTo, object.attachedToClass, object.collection, { [field]: value }) let keys = Array.from(hierarchy.getAllAttributes(object._class).keys())
keys = keys.filter((k) => !docKeys.has(k))
keys = keys.filter((k) => !ignoreKeys.includes(k))
return keys
} }
function getKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(ignoreKeys)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: string[], get: boolean): string[] {
const result: string[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: string): boolean {
const attribute = hierarchy.getAttribute(object._class, key)
return hierarchy.isDerived(attribute.type._class, core.class.Collection)
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
if (editorMixin?.editor == null && clazz.extends != null) return getEditor(clazz.extends)
return editorMixin.editor
}
async function getCollectionEditor (key: string): Promise<AnyComponent> {
const attribute = hierarchy.getAttribute(object._class, key)
const attrClass = getAttributePresenterClass(attribute)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
$: icon = object && (hierarchy.getClass(object._class).icon as Asset)
$: title = object && hierarchy.getClass(object._class).label
</script> </script>
{#if object !== undefined} {#if object !== undefined}
<Panel <Panel
icon={view.icon.Table} {icon}
title={object.name} {title}
{object} {object}
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}
> >
<TaskHeader {object} slot="subtitle" /> <TaskHeader {object} {keys} slot="subtitle" />
<Grid column={1} rowGap={1.5}> {#await getEditor(object._class) then is}
<EditBox <Component
label={task.string.TaskName} {is}
bind:value={object.name} props={{ object }}
icon={task.icon.Task} on:open={(ev) => {
placeholder="The boring task" getKeys(ev.detail.ignoreKeys)
maxWidth="39rem" }}
focus
on:change={(evt) => change('name', object.name)}
/> />
<EditBox {/await}
label={task.string.TaskDescription} {#each collectionKeys as collection}
bind:value={object.description} <div class="mt-14">
icon={task.icon.Task} {#await getCollectionEditor(collection) then is}
placeholder="Description" <Component {is} props={{ objectId: object._id, _class: object._class, space: object.space }} />
maxWidth="39rem" {/await}
on:change={(evt) => change('description', object.description)} </div>
/> {/each}
</Grid>
<div class="mt-14">
<Attachments objectId={object._id} _class={object._class} space={object.space} noLabel={task.string.NoAttachmentsForTask} />
</div>
</Panel> </Panel>
{/if} {/if}

View File

@ -1,42 +1,71 @@
<!-- <!--
// Copyright © 2020, 2021 Anticrm Platform Contributors. // Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc. // Copyright © 2021 Hardcore Engineering Inc.
// //
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // 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 // 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 // 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 // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import contact from '@anticrm/contact' import contact from '@anticrm/contact'
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation' import core, { Class, Doc, Ref, RefTo } from '@anticrm/core'
import { Issue } from '@anticrm/task' import { AttributeBarEditor, AttributesBar, getClient, UserBox } from '@anticrm/presentation'
import { Task } from '@anticrm/task'
import task from '../plugin' import task from '../plugin'
export let object: Issue export let object: Task
export let keys: string[]
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy()
function change () { function change () {
client.updateCollection(object._class, object.space, object._id, object.attachedTo, object.attachedToClass, object.collection, { assignee: object.assignee }) client.updateCollection(
object._class,
object.space,
object._id,
object.attachedTo,
object.attachedToClass,
object.collection,
{ assignee: object.assignee }
)
} }
$: assigneeTitle = hierarchy.getAttribute(object._class, 'assignee').label
function getAssigneeClass (object: Task): Ref<Class<Doc>> {
const attribute = hierarchy.getAttribute(object._class, 'assignee')
const attrClass = attribute.type._class
if (attrClass === core.class.RefTo) {
return (attribute.type as RefTo<Doc>).to
}
return contact.class.Employee
}
$: filtredKeys = keys.filter((p) => p !== 'state' && p !== 'assignee' && p !== 'doneState') // todo
</script> </script>
<div class="flex-between header"> <div class="flex-between header">
<UserBox <div class="flex-center">
_class={contact.class.Employee} <UserBox
title={task.string.TaskAssignee} _class={getAssigneeClass(object)}
caption="Assignee" title={assigneeTitle}
bind:value={object.assignee} caption={assigneeTitle}
on:change={change} bind:value={object.assignee}
allowDeselect on:change={change}
titleDeselect={task.string.TaskUnAssign} allowDeselect
/> titleDeselect={task.string.TaskUnAssign}
/>
<div class="column">
<AttributesBar {object} keys={filtredKeys} />
</div>
</div>
<AttributeBarEditor key={'state'} {object} showHeader={false} /> <AttributeBarEditor key={'state'} {object} showHeader={false} />
</div> </div>
@ -44,5 +73,19 @@
.header { .header {
width: 100%; width: 100%;
padding: 0 0.5rem; padding: 0 0.5rem;
.column {
position: relative;
margin-left: 3rem;
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: -1.5rem;
width: 1px;
background-color: var(--theme-bg-accent-hover);
}
}
} }
</style> </style>

View File

@ -21,6 +21,7 @@ import CreateProject from './components/CreateProject.svelte'
import TaskPresenter from './components/TaskPresenter.svelte' import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanCard from './components/KanbanCard.svelte' import KanbanCard from './components/KanbanCard.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte' import TemplatesIcon from './components/TemplatesIcon.svelte'
import EditIssue from './components/EditIssue.svelte'
import { Doc } from '@anticrm/core' import { Doc } from '@anticrm/core'
import { showPopup } from '@anticrm/ui' import { showPopup } from '@anticrm/ui'
@ -34,6 +35,7 @@ export { default as KanbanTemplateEditor } from './components/kanban/KanbanTempl
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte' export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
export { default as Tasks } from './components/Tasks.svelte' export { default as Tasks } from './components/Tasks.svelte'
export { default as EditTask } from './components/EditTask.svelte'
async function createTask (object: Doc): Promise<void> { async function createTask (object: Doc): Promise<void> {
showPopup(CreateTask, { parent: object._id, space: object.space }) showPopup(CreateTask, { parent: object._id, space: object.space })
@ -48,6 +50,7 @@ export default async (): Promise<Resources> => ({
CreateTask, CreateTask,
CreateProject, CreateProject,
TaskPresenter, TaskPresenter,
EditIssue,
KanbanCard, KanbanCard,
TemplatesIcon, TemplatesIcon,
KanbanView, KanbanView,

View File

@ -32,7 +32,6 @@ export default mergeIds(taskId, task, {
TaskAssignee: '' as IntlString, TaskAssignee: '' as IntlString,
TaskUnAssign: '' as IntlString, TaskUnAssign: '' as IntlString,
TaskDescription: '' as IntlString, TaskDescription: '' as IntlString,
NoAttachmentsForTask: '' as IntlString,
More: '' as IntlString, More: '' as IntlString,
UploadDropFilesHere: '' as IntlString, UploadDropFilesHere: '' as IntlString,
NoTaskForObject: '' as IntlString, NoTaskForObject: '' as IntlString,