Create Customer (#1689)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-05-07 01:07:43 +07:00 committed by GitHub
parent 8e1667b9fd
commit 6c8afa1fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 224 additions and 23 deletions

View File

@ -215,6 +215,10 @@ export function createModel (builder: Builder): void {
mode: ['workbench', 'browser', 'editor', 'panel', 'popup']
}
})
builder.mixin(lead.mixin.Customer, core.class.Mixin, view.mixin.ObjectFactory, {
component: lead.component.CreateCustomer
})
}
export { leadOperation } from './migration'

View File

@ -60,14 +60,16 @@
</ScrollBox>
</div>
</div>
<div class="buttons" class:shown={showButtons}>
<div class="tool"><TextStyle size={'large'} /></div>
<div class="tool"><Emoji size={'large'} /></div>
<div class="tool"><GIF size={'large'} /></div>
<div class="flex-grow">
<slot />
{#if showButtons}
<div class="buttons">
<div class="tool"><TextStyle size={'large'} /></div>
<div class="tool"><Emoji size={'large'} /></div>
<div class="tool"><GIF size={'large'} /></div>
<div class="flex-grow">
<slot />
</div>
</div>
</div>
{/if}
</div>
<style lang="scss">
@ -77,14 +79,6 @@
flex-direction: column;
min-height: 4.5rem;
.buttons {
visibility: hidden;
}
.shown {
visibility: visible;
}
.textInput {
flex-grow: 1;
display: flex;

View File

@ -22,6 +22,9 @@
"LeadPlaceholder": "The simple lead",
"ManageFunnelStatuses": "Manage funnel statuses",
"FunnelBrowser": "Funnel browser",
"GotoLeadApplication": "Switch to Lead Application"
"GotoLeadApplication": "Switch to Lead Application",
"IssueDescriptionPlaceholder": "Add description...",
"CreateCustomer": "Create Customer",
"CreateCustomerLabel": "Customer"
}
}

View File

@ -22,6 +22,9 @@
"LeadPlaceholder": "Простая сделка",
"ManageFunnelStatuses": "Управление статусами воронки",
"FunnelBrowser": "Браузер воронок",
"GotoLeadApplication": "Открыть приложение Сделки"
"GotoLeadApplication": "Открыть приложение Сделки",
"IssueDescriptionPlaceholder": "Добавить описание...",
"CreateCustomer": "Добавить Клиента",
"CreateCustomerLabel": "Клиент"
}
}

View File

@ -46,6 +46,8 @@
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/notification": "~0.6.0"
"@anticrm/notification": "~0.6.0",
"@anticrm/attachment": "~0.6.1",
"@anticrm/text-editor": "~0.6.0"
}
}

View File

@ -0,0 +1,178 @@
<!--
// Copyright © 2022 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 attachment from '@anticrm/attachment'
import { Channel, combineName, findPerson, Person } from '@anticrm/contact'
import { ChannelsDropdown } from '@anticrm/contact-resources'
import PersonPresenter from '@anticrm/contact-resources/src/components/PersonPresenter.svelte'
import contact from '@anticrm/contact-resources/src/plugin'
import { AttachedData, Data, generateId, MixinData, Ref } from '@anticrm/core'
import type { Customer } from '@anticrm/lead'
import { getResource } from '@anticrm/platform'
import { Card, EditableAvatar, getClient } from '@anticrm/presentation'
import { EditBox, IconInfo, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import lead from '../plugin'
let firstName = ''
let lastName = ''
export function canClose (): boolean {
return firstName === '' && lastName === ''
}
const object: Customer = {
_class: contact.class.Person
} as Customer
const dispatch = createEventDispatcher()
const client = getClient()
const customerId = generateId()
let channels: AttachedData<Channel>[] = []
let avatar: File | undefined
async function createCustomer () {
const candidate: Data<Person> = {
name: combineName(firstName, lastName),
city: object.city
}
if (avatar !== undefined) {
const uploadFile = await getResource(attachment.helper.UploadFile)
candidate.avatar = await uploadFile(avatar)
}
const candidateData: MixinData<Person, Customer> = {
description: object.description
}
const id = await client.createDoc(contact.class.Person, contact.space.Contacts, candidate, customerId)
await client.createMixin(
id as Ref<Person>,
contact.class.Person,
contact.space.Contacts,
lead.mixin.Customer,
candidateData
)
for (const channel of channels) {
await client.addCollection(
contact.class.Channel,
contact.space.Contacts,
customerId,
contact.class.Person,
'channels',
{
value: channel.value,
provider: channel.provider
}
)
}
dispatch('close')
}
function onAvatarDone (e: any) {
const { file } = e.detail
avatar = file
}
let matches: Person[] = []
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
matches = p
})
function removeAvatar (): void {
avatar = undefined
}
</script>
<Card
label={lead.string.CreateCustomer}
okAction={createCustomer}
canSave={firstName.length > 0 && lastName.length > 0 && matches.length === 0}
space={contact.space.Contacts}
on:close={() => {
dispatch('close')
}}
>
<div class="flex-between flex-row-top">
<div class="flex-col flex-grow">
<EditBox
placeholder={contact.string.PersonFirstNamePlaceholder}
bind:value={firstName}
kind={'large-style'}
maxWidth={'32rem'}
focus
/>
<EditBox
placeholder={contact.string.PersonLastNamePlaceholder}
bind:value={lastName}
kind={'large-style'}
maxWidth={'32rem'}
/>
<div class="mt-1">
<EditBox
placeholder={contact.string.PersonLocationPlaceholder}
bind:value={object.city}
kind={'small-style'}
maxWidth={'32rem'}
/>
</div>
<EditBox
placeholder={lead.string.IssueDescriptionPlaceholder}
bind:value={object.description}
kind={'small-style'}
maxWidth={'32rem'}
/>
</div>
<div class="ml-4 flex">
<EditableAvatar
bind:direct={avatar}
avatar={object.avatar}
size={'large'}
on:remove={removeAvatar}
on:done={onAvatarDone}
/>
</div>
</div>
<svelte:fragment slot="pool">
<ChannelsDropdown bind:value={channels} editable />
</svelte:fragment>
<svelte:fragment slot="footer">
{#if matches.length > 0}
<div class="flex-row-center error-color">
<IconInfo size={'small'} />
<span class="text-sm overflow-label ml-2">
<Label label={contact.string.PersonAlreadyExists} />
</span>
<div class="ml-4"><PersonPresenter value={matches[0]} /></div>
</div>
{/if}
</svelte:fragment>
</Card>
<style lang="scss">
.resume {
padding: 0.5rem 0.75rem;
background: var(--accent-bg-color);
border: 1px dashed var(--divider-color);
border-radius: 0.5rem;
&.solid {
border-style: solid;
}
}
</style>

View File

@ -16,10 +16,11 @@
<script lang="ts">
import { Doc, DocumentQuery } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Icon, Label, Scroller, SearchEdit } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import { Button, Icon, IconAdd, Label, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet } from '@anticrm/view'
import { Table } from '@anticrm/view-resources'
import lead from '../plugin'
import CreateCustomer from './CreateCustomer.svelte'
let search = ''
let resultQuery: DocumentQuery<Doc> = {}
@ -33,6 +34,10 @@
function updateResultQuery (search: string): void {
resultQuery = search === '' ? {} : { $search: search }
}
function showCreateDialog (ev: Event) {
showPopup(CreateCustomer, {}, 'top')
}
</script>
<div class="ac-header full">
@ -47,6 +52,13 @@
updateResultQuery(search)
}}
/>
<Button
icon={IconAdd}
label={lead.string.CreateCustomerLabel}
kind={'primary'}
on:click={(ev) => showCreateDialog(ev)}
/>
</div>
<Scroller tableFade>

View File

@ -24,6 +24,7 @@ import LeadPresenter from './components/LeadPresenter.svelte'
import Leads from './components/Leads.svelte'
import LeadsPresenter from './components/LeadsPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import CreateCustomer from './components/CreateCustomer.svelte'
export default async (): Promise<Resources> => ({
component: {
@ -35,6 +36,7 @@ export default async (): Promise<Resources> => ({
TemplatesIcon,
Customers,
LeadsPresenter,
Leads
Leads,
CreateCustomer
}
})

View File

@ -33,7 +33,10 @@ export default mergeIds(leadId, lead, {
Customers: '' as IntlString,
Leads: '' as IntlString,
NoLeadsForDocument: '' as IntlString,
LeadPlaceholder: '' as IntlString
LeadPlaceholder: '' as IntlString,
CreateCustomer: '' as IntlString,
IssueDescriptionPlaceholder: '' as IntlString,
CreateCustomerLabel: '' as IntlString
},
component: {
CreateCustomer: '' as AnyComponent,

View File

@ -21,7 +21,7 @@
export let placeholder: IntlString
export let value: any
export let focus: boolean
export let maxWidth: string
export let maxWidth: string = '10rem'
export let onChange: (value: string) => void
function _onchange (ev: Event) {