Requests improve (#2470)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2022-12-25 22:40:13 +06:00 committed by GitHub
parent 11045a0737
commit 46ff06c800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 421 additions and 65 deletions

View File

@ -7801,6 +7801,10 @@ packages:
engines: {node: '>=10.0'}
dev: false
/just-clone/6.2.0:
resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==}
dev: false
/jwt-simple/0.5.6:
resolution: {integrity: sha512-40aUybvhH9t2h71ncA1/1SbtTNCVZHgsTsTgqPUxGWDmUDrXyDf2wMNQKEbdBjbf4AI+fQhbECNTV6lWxQKUzg==}
engines: {node: '>= 0.4.0'}
@ -12009,7 +12013,7 @@ packages:
dev: false
file:projects/core.tgz:
resolution: {integrity: sha512-/8paC6cB+GVr1y2iTGWYaFWHwL5h7PZn9yW23M84E4UVXWzDfPf0j/3YTqvcZSmplp2qXonIKYYtXkChb9Sh0A==, tarball: file:projects/core.tgz}
resolution: {integrity: sha512-jv3EZ1FSpLSg/KKEnDnoykFyMyvWYASwuyV35oCEZBxOTvnrSYhc7qsVHL+jM7hO2XkYd87H4gj0p2nO1nyQlg==, tarball: file:projects/core.tgz}
name: '@rush-temp/core'
version: 0.0.0
dependencies:
@ -12022,6 +12026,7 @@ packages:
eslint-plugin-import: 2.26.0_eslint@8.27.0
eslint-plugin-n: 15.5.1_eslint@8.27.0
eslint-plugin-promise: 6.1.1_eslint@8.27.0
just-clone: 6.2.0
prettier: 2.7.1
simplytyped: 3.3.0_typescript@4.8.4
typescript: 4.8.4
@ -13299,7 +13304,7 @@ packages:
dev: false
file:projects/model-request.tgz_typescript@4.8.4:
resolution: {integrity: sha512-cp8ab34sOYVYsvFyXgzRZ2A/7jfSHe0uk+Js0LXlYUPBcRfcKtXTIZKawIiFbrlxHs+q0ilCO1d2vHUjZcjYuQ==, tarball: file:projects/model-request.tgz}
resolution: {integrity: sha512-5xaCx+siFlB/YFV1TWY6xWoH7xtK4aLB6Y0Y+thZ5ULGlOnmI7a9JmMcDTzrbUIzUm7YWs0wv5ZUC3B3RmaoTA==, tarball: file:projects/model-request.tgz}
id: file:projects/model-request.tgz
name: '@rush-temp/model-request'
version: 0.0.0
@ -13570,7 +13575,7 @@ packages:
dev: false
file:projects/model-server-request.tgz_typescript@4.8.4:
resolution: {integrity: sha512-ZLE63KwWhyNqO5JIr0fkBadQlPFdM1SPNM8qmu2aQtIIyQNnuPcqA+AGfofJ0WFJZR3KWoPwPpw6hvpihWGLAw==, tarball: file:projects/model-server-request.tgz}
resolution: {integrity: sha512-h8GVLvirh4qRWAe7ya4kVXfSNNT1E3n9pibJu58zN58lrWjFMIdM2txKYS20bsUlW3fOpjX2LnP8FwQscFLTCg==, tarball: file:projects/model-server-request.tgz}
id: file:projects/model-server-request.tgz
name: '@rush-temp/model-server-request'
version: 0.0.0
@ -14252,7 +14257,7 @@ packages:
dev: false
file:projects/pod-server.tgz:
resolution: {integrity: sha512-KnNoX19wnXrre+vuE3qtWIsZ/f0JEyteWcacwOg8dCLDQlAlBTi7JEmwnNgxZMegc3J4aZHhqgnSeVPTs/zqTQ==, tarball: file:projects/pod-server.tgz}
resolution: {integrity: sha512-Csor3mNB2iTYjldWJKRq1x4NE7cCleOhwH0oTgD6Djbp10R9J6UGT9VbjPNTJFXZkdTHI02xQ6h5M3gm4yo6FA==, tarball: file:projects/pod-server.tgz}
name: '@rush-temp/pod-server'
version: 0.0.0
dependencies:
@ -14359,7 +14364,7 @@ packages:
dev: false
file:projects/prod.tgz_b3a81ceaadec606c0eb174aef12a6049:
resolution: {integrity: sha512-HF6aMWOyjHsQ6STxaZz0/dFY3zb6o4J+tz/NZYcSTdspxgHb+yuyqvfm+/RPItDiTTDO6GHVogJ8i/WYIkSyhQ==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-a7YZuQQMLWOqDrsnIrkhuWopJvLNjRr3Wqy/F5GsCnV6OGlkDfJyg/miaWqHrp8xo3meF+9D/p499QIH9LBc9w==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -14551,7 +14556,7 @@ packages:
dev: false
file:projects/request-resources.tgz_49b4785992daa3b61a639b2b31601e76:
resolution: {integrity: sha512-rOmhKA3gIrctWaK8BfIPPZOfFaFfzelAR4C8ZJ6zCKLCQUa6fET27PbSRL2GLOmUmpMntsXqcNKXpE8oYxcsfw==, tarball: file:projects/request-resources.tgz}
resolution: {integrity: sha512-oal7ZypMLSmjjX0dNV1LKx8/9uebdcARq49ytNx+tz70vFzlI/s0vRShvmY/3r5g9mWa0sJ+bKHFCYXSleHm4A==, tarball: file:projects/request-resources.tgz}
id: file:projects/request-resources.tgz
name: '@rush-temp/request-resources'
version: 0.0.0
@ -15086,7 +15091,7 @@ packages:
dev: false
file:projects/server-request-resources.tgz:
resolution: {integrity: sha512-MzGrK/qc4ZpT94I7pz0EzDxAbT6YWBzGFr4peZHuHwvmKIkLJ3lqyE6NNvOQCW6qcVB70uhwaLyj5UrSaN3akw==, tarball: file:projects/server-request-resources.tgz}
resolution: {integrity: sha512-D3xIUVe9bNn3utITFwWPt6NR9gtgzYoRZdtxu9oR/7NrSixFsn9VE0Q/KaE/TgnSEpY4M2DgVgVVM019kj7fLQ==, tarball: file:projects/server-request-resources.tgz}
name: '@rush-temp/server-request-resources'
version: 0.0.0
dependencies:
@ -15948,7 +15953,7 @@ packages:
dev: false
file:projects/tool.tgz:
resolution: {integrity: sha512-eV5BJ5aZRslw3lgvX7imSH6m5EBU8wCOPmZUl4tuKxZAnpjSJA9pHyUivn6EFaJ3r+pdu+ZVSjZ3D/DEbwzAKw==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-qfBzLOvB8cbvmaEWnpaA+gqF5xPAyLXRPh8dMDDoAfzZZPmaclaE890OKk6ngLBpbv2gPXD5E+BRizqerlxk8g==, tarball: file:projects/tool.tgz}
name: '@rush-temp/tool'
version: 0.0.0
dependencies:
@ -16209,7 +16214,7 @@ packages:
dev: false
file:projects/workbench-resources.tgz_49b4785992daa3b61a639b2b31601e76:
resolution: {integrity: sha512-hyxyGITfGafwcakPTdjJ7iQkwz25QiBIWF/QK0/sdPYTqSlxDHxkzvwBh2bKNXGuAPuqKS7aykvhrhHXqZsFXQ==, tarball: file:projects/workbench-resources.tgz}
resolution: {integrity: sha512-+9P4u+lojqOPl49qVXcc6fLQ4go9Q5lDuG/Yu5I9wdpA3wtTT+CBEj7jO/MvylBltsYyhZZ+lg2jZnVbCBiNGg==, tarball: file:projects/workbench-resources.tgz}
id: file:projects/workbench-resources.tgz
name: '@rush-temp/workbench-resources'
version: 0.0.0

View File

@ -409,6 +409,10 @@ export function createModel (builder: Builder): void {
inlineEditor: contact.component.AccountArrayEditor
})
builder.mixin(contact.class.EmployeeAccount, core.class.Class, view.mixin.ArrayEditor, {
inlineEditor: contact.component.AccountArrayEditor
})
builder.mixin(core.class.Account, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.EmployeeAccountPresenter
})

View File

@ -18,21 +18,23 @@ import chunter from '@hcengineering/chunter'
import type { EmployeeAccount } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import { Doc, Domain, IndexKind, Ref, TxCUD } from '@hcengineering/core'
import { ArrOf, Builder, Collection, Index, Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
import { ArrOf, Builder, Collection, Index, Model, Prop, ReadOnly, TypeRef, TypeString, UX } from '@hcengineering/model'
import core, { TAttachedDoc } from '@hcengineering/model-core'
import { Request, RequestStatus } from '@hcengineering/request'
import request from './plugin'
import view from '@hcengineering/model-view'
export const DOMAIN_REQUEST = 'request' as Domain
@Model(request.class.Request, core.class.AttachedDoc, DOMAIN_REQUEST)
@UX(request.string.Request)
@UX(request.string.Request, request.icon.Requests)
export class TRequest extends TAttachedDoc implements Request {
@Prop(ArrOf(TypeRef(contact.class.EmployeeAccount)), request.string.Requested)
@Index(IndexKind.Indexed)
requested!: Ref<EmployeeAccount>[]
@Prop(ArrOf(TypeRef(contact.class.EmployeeAccount)), request.string.Approved)
@ReadOnly()
approved!: Ref<EmployeeAccount>[]
requiredApprovesCount!: number
@ -50,6 +52,10 @@ export class TRequest extends TAttachedDoc implements Request {
export function createModel (builder: Builder): void {
builder.createModel(TRequest)
builder.mixin(request.class.Request, core.class.Class, view.mixin.ObjectEditor, {
editor: request.component.EditRequest
})
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
@ -59,6 +65,7 @@ export function createModel (builder: Builder): void {
txClass: core.class.TxCreateDoc,
component: request.activity.TxCreateRequest,
label: request.string.CreatedRequest,
labelComponent: request.activity.RequestLabel,
display: 'emphasized'
},
request.ids.TxRequestCreate

View File

@ -23,15 +23,17 @@ import type { TxViewlet } from '@hcengineering/activity'
export default mergeIds(requestId, request, {
activity: {
TxCreateRequest: '' as AnyComponent
TxCreateRequest: '' as AnyComponent,
RequestLabel: '' as AnyComponent
},
component: {
EditRequest: '' as AnyComponent
},
ids: {
TxRequestCreate: '' as Ref<TxViewlet>
},
string: {
Request: '' as IntlString,
Status: '' as IntlString,
Approved: '' as IntlString,
Requested: '' as IntlString
}
})

View File

@ -28,7 +28,8 @@
"typescript": "^4.3.5"
},
"dependencies": {
"@hcengineering/platform": "^0.6.8"
"@hcengineering/platform": "^0.6.8",
"just-clone": "~6.2.0"
},
"repository": "https://github.com/hcengineering/anticrm",
"publishConfig": {

View File

@ -34,6 +34,7 @@ import { _getOperator } from './operator'
import { _toDoc } from './proxy'
import type { DocumentQuery, TxResult } from './storage'
import { generateId } from './utils'
import justClone from 'just-clone'
/**
* @public
@ -292,7 +293,7 @@ export abstract class TxProcessor implements WithTx {
static createDoc2Doc<T extends Doc>(tx: TxCreateDoc<T>): T {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...tx.attributes,
...justClone(tx.attributes),
_id: tx.objectId,
_class: tx.objectClass,
space: tx.objectSpace,

View File

@ -326,13 +326,13 @@ class ActivityImpl implements Activity {
}
let firstTx = parents.get(tx.objectId)
const result: DisplayTx = newDisplayTx(tx, this.hierarchy)
result.collectionAttribute = collectionAttribute
result.doc = firstTx?.doc ?? result.doc
firstTx = firstTx ?? result
parents.set(tx.objectId, firstTx)
// If we have updates also apply them all.
const isUpdated = this.checkUpdateState(result, firstTx)
const isMixin = this.checkMixinState(result, firstTx)

View File

@ -202,9 +202,7 @@
{#if viewlet === undefined && model.length > 0 && tx.updateTx}
{#each model as m, i}
{#await getValue(client, m, tx) then value}
{#if value.set === null || value.set === undefined}
<span class="lower"><Label label={activity.string.Unset} /> <Label label={m.label} /></span>
{:else if value.added.length}
{#if value.added.length}
<span class="lower" class:flex-grow={hasMessageType}>
<Label label={activity.string.Added} />
<Label label={activity.string.To} />
@ -239,6 +237,8 @@
{/each}
</div>
</div>
{:else if value.set === null || value.set === undefined}
<span class="lower"><Label label={activity.string.Unset} /> <Label label={m.label} /></span>
{:else}
<span class="lower" class:flex-grow={hasMessageType}>
<Label label={activity.string.Changed} />

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { Comment } from '@hcengineering/chunter'
import type { TxCreateDoc } from '@hcengineering/core'
import type { AttachedData, TxCreateDoc } from '@hcengineering/core'
import { getClient, MessageViewer } from '@hcengineering/presentation'
import { AttachmentDocList } from '@hcengineering/attachment-resources'
import { Button } from '@hcengineering/ui'
@ -32,7 +32,7 @@
const editing = false
async function onMessage (event: CustomEvent) {
async function onMessage (event: CustomEvent<AttachedData<Comment>>) {
const { message, attachments } = event.detail
await client.updateCollection(
tx.objectClass,
@ -47,7 +47,7 @@
}
)
// We need to update backlinks before and after.
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, event.detail)
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
dispatch('close', false)
}

View File

@ -7,6 +7,7 @@
export let label: IntlString
export let value: Ref<Account>[]
export let onChange: (refs: Ref<Account>[]) => void
export let readonly = false
let timer: any
const client = getClient()
@ -33,6 +34,7 @@
<UserBoxList
items={employess}
{label}
{readonly}
on:update={onUpdate}
kind={'link'}
size={'medium'}

View File

@ -8,6 +8,12 @@
"For": "For",
"Change": "Change",
"Add": "Add",
"Remove": "Remove"
"Remove": "Remove",
"Status": "Status",
"Requested": "Requested",
"Completed": "Completed",
"Reject": "Reject",
"Rejected": "Rejected",
"Comment": "Comment"
}
}

View File

@ -3,11 +3,17 @@
"Requests": "Запросы",
"Request": "Запрос",
"Approve": "Одобрить",
"Approved": "Одобрил",
"Approved": "Одобрено",
"CreatedRequest": "Создал запрос",
"For": "Для",
"Change": "Изменить",
"Add": "Добавить",
"Remove": "Удалить"
"Remove": "Удалить",
"Status": "Статус",
"Requested": "Запрошено",
"Completed": "Выполнен",
"Reject": "Отклонить",
"Rejected": "Отклонен",
"Comment": "Комментировать"
}
}

View File

@ -40,6 +40,10 @@
"@hcengineering/activity": "~0.6.0",
"@hcengineering/activity-resources": "~0.6.1",
"@hcengineering/contact": "~0.6.8",
"@hcengineering/chunter": "~0.6.1",
"@hcengineering/chunter-resources": "~0.6.0",
"@hcengineering/attachment-resources": "~0.6.0",
"@hcengineering/view": "^0.6.1",
"@hcengineering/view-resources": "~0.6.0"
}
}

View File

@ -0,0 +1,42 @@
<!--
// 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 { Request } from '@hcengineering/request'
import { Label } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import request from '../plugin'
import RequestActions from './RequestActions.svelte'
import RequestLabel from './RequestLabel.svelte'
import TxView from './TxView.svelte'
export let object: Request
const dispatch = createEventDispatcher()
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'status'] })
})
</script>
{#if object !== undefined}
<div class="flex gap-1 mb-2">
<span class="mr-1"><Label label={request.string.For} /></span>
<ObjectPresenter objectId={object.tx.objectId} _class={object.tx.objectClass} />
<TxView tx={object.tx} />
</div>
<RequestLabel value={object} />
<RequestActions value={object} />
{/if}

View File

@ -0,0 +1,107 @@
<!--
// 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 { AttachmentRefInput } from '@hcengineering/attachment-resources'
import chunter, { Comment } from '@hcengineering/chunter'
import { updateBacklinks } from '@hcengineering/chunter-resources/src/backlinks'
import contact, { EmployeeAccount } from '@hcengineering/contact'
import { AttachedData, getCurrentAccount, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Request, RequestStatus } from '@hcengineering/request'
import { Button } from '@hcengineering/ui'
import request from '../plugin'
export let value: Request
let employee: EmployeeAccount | undefined
const query = createQuery()
const client = getClient()
const me = getCurrentAccount()._id as Ref<EmployeeAccount>
$: query.query(
contact.class.EmployeeAccount,
{ _id: value.tx.modifiedBy as Ref<EmployeeAccount> },
(account) => {
;[employee] = account
},
{ limit: 1 }
)
const approvable = value.requested.includes(me) && !value.approved.includes(me)
async function approve () {
await saveComment()
await client.update(value, {
$push: {
approved: me
}
})
}
$: disabled = commentIsEmpty(message, attachments)
async function reject () {
await saveComment()
await client.update(value, {
status: RequestStatus.Rejected
})
}
let message: string = ''
let attachments: number | undefined = 0
async function onUpdate (event: CustomEvent<AttachedData<Comment>>) {
message = event.detail.message
attachments = event.detail.attachments
}
async function saveComment () {
await client.addCollection(chunter.class.Comment, value.space, value._id, value._class, 'comments', {
message,
attachments
})
// We need to update backlinks before and after.
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
refInput.submit()
}
function commentIsEmpty (message: string, attachments: number | undefined): boolean {
return (message === '<p></p>' || message.trim().length === 0) && !((attachments ?? 0) > 0)
}
let refInput: AttachmentRefInput
</script>
{#if value.status === RequestStatus.Active}
<div class="mt-2">
<AttachmentRefInput
bind:this={refInput}
space={value.space}
_class={value._class}
objectId={value._id}
showSend={false}
on:update={onUpdate}
/>
</div>
<div class="mt-2 flex gap-2">
<Button label={request.string.Comment} {disabled} on:click={saveComment} />
{#if approvable}
<Button label={request.string.Approve} {disabled} on:click={approve} />
<Button label={request.string.Reject} {disabled} on:click={reject} />
{/if}
</div>
{/if}

View File

@ -0,0 +1,30 @@
<!--
// 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 { Request, RequestStatus } from '@hcengineering/request'
import { Label } from '@hcengineering/ui'
import RequestStatusPresenter from './RequestStatusPresenter.svelte'
import request from '../plugin'
export let value: Request
</script>
<div class="flex gap-2">
{#if value.status !== RequestStatus.Active}
<RequestStatusPresenter value={value.status === RequestStatus.Completed} />
{:else}
<Label label={request.string.Approved} />: {value.approved.length}/{value.requiredApprovesCount}
{/if}
</div>

View File

@ -13,10 +13,28 @@
// limitations under the License.
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { Request } from '@hcengineering/request'
import { getPanelURI, Label } from '@hcengineering/ui'
import view from '@hcengineering/view'
import TxView from './TxView.svelte'
export let value: Request
export let inline: boolean = false
const client = getClient()
$: label = client.getHierarchy().getClass(value._class).label
</script>
<TxView tx={value.tx} />
<div class="flex">
<a
class="flex-presenter mr-1"
class:inline-presenter={inline}
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
>
<span class="label nowrap">
<Label {label} />
</span>
</a>
<TxView tx={value.tx} />
</div>

View File

@ -0,0 +1,71 @@
<!--
// 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 { IntlString } from '@hcengineering/platform'
import { Label } from '@hcengineering/ui'
import request from '../plugin'
export let value: boolean
function getBooleanLabel (value: boolean): IntlString {
if (value) return request.string.Completed
return request.string.Rejected
}
</script>
<div
class="flex-row-center yesno-container"
class:yes={value === true}
class:no={value === false}
class:unknown={value === undefined}
>
<svg class="svg-small" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle class:yes={value === true} class:no={value === false} cx="8" cy="8" r="6" />
{#if value === true}
<polygon fill="#fff" points="7.4,10.9 4.9,8.4 5.7,7.6 7.3,9.1 10.2,5.6 11.1,6.4 " />
{:else if value === false}
<polygon fill="#fff" points="10.7,6 10,5.3 8,7.3 6,5.3 5.3,6 7.3,8 5.3,10 6,10.7 8,8.7 10,10.7 10.7,10 8.7,8 " />
{:else}
<path
fill="#fff"
d="M7.3,9.3h1.3V9c0.1-0.5,0.6-0.9,1.1-1.4c0.4-0.4,0.8-0.9,0.8-1.6c0-1.1-0.8-1.8-2.2-1.8c-1.4,0-2.4,0.8-2.5,2.2 h1.4c0.1-0.6,0.4-1,1-1C8.8,5.4,9,5.7,9,6.2c0,0.4-0.3,0.7-0.7,1.1c-0.5,0.5-1,0.9-1,1.7V9.3z M8,11.6c0.5,0,0.9-0.4,0.9-0.9 c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9C7.1,11.2,7.5,11.6,8,11.6z"
/>
{/if}
</svg>
<span><Label label={getBooleanLabel(value)} /></span>
</div>
<style lang="scss">
.yesno-container {
max-width: fit-content;
user-select: none;
fill: #77818e;
&.yes {
fill: #77c07b;
}
&.no {
fill: #f96e50;
}
span {
margin-left: 0.25rem;
white-space: nowrap;
text-transform: capitalize;
font-weight: 500;
color: var(--theme-caption-color);
}
}
</style>

View File

@ -1,11 +1,26 @@
<!--
// 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 contact, { EmployeeAccount, formatName } from '@hcengineering/contact'
import { getCurrentAccount, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Request, RequestStatus } from '@hcengineering/request'
import { Button, Label, TimeSince } from '@hcengineering/ui'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Request } from '@hcengineering/request'
import { Label, TimeSince } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import request from '../plugin'
import RequestActions from './RequestActions.svelte'
import RequestPresenter from './RequestPresenter.svelte'
export let value: Request
@ -13,8 +28,6 @@
let employee: EmployeeAccount | undefined
const query = createQuery()
const client = getClient()
const me = getCurrentAccount()._id as Ref<EmployeeAccount>
$: query.query(
contact.class.EmployeeAccount,
@ -24,17 +37,6 @@
},
{ limit: 1 }
)
const approvable =
value.requested.includes(me) && !value.approved.includes(me) && value.status === RequestStatus.Active
async function approve () {
await client.update(value, {
$push: {
approved: me
}
})
}
</script>
<div class="container">
@ -54,11 +56,8 @@
<div class="time"><TimeSince value={value.tx.modifiedOn} /></div>
</div>
<RequestPresenter {value} />
{#if approvable}
<div class="mt-2">
<Button label={request.string.Approve} on:click={approve} />
</div>
{/if}
<RequestActions {value} />
</div>
<style lang="scss">
@ -76,7 +75,7 @@
flex-wrap: wrap;
& > * {
margin-right: 0.5rem;
margin-right: 0.25rem;
}
& > *:last-child {
margin-right: 0;

View File

@ -13,8 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import activity, { TxViewlet } from '@hcengineering/activity'
import { TxViewlet } from '@hcengineering/activity'
import { ActivityKey, DisplayTx, newDisplayTx, getValue, updateViewlet } from '@hcengineering/activity-resources'
import activity from '@hcengineering/activity-resources/src/plugin'
import contact, { EmployeeAccount } from '@hcengineering/contact'
import core, { AnyAttribute, Ref, Tx } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
@ -91,7 +92,7 @@
<div class="flex-grow flex-col clear-mins" class:mention={isMessageType(model[0]?.attribute)}>
<div class="flex-between">
<div class="flex-row-center flex-grow label">
{#if ptx.updateTx}
{#if ptx?.updateTx}
{#each model as m}
{#await getValue(client, m, ptx) then value}
{#if value.set === null || value.set === undefined}
@ -140,7 +141,7 @@
{/if}
{/await}
{/each}
{:else if ptx.mixinTx}
{:else if ptx?.mixinTx}
{#each model as m}
{#await getValue(client, m, ptx) then value}
{#if value.set === null}
@ -221,10 +222,7 @@
& > *:last-child {
margin-right: 0;
}
.bold {
font-weight: 500;
color: var(--caption-color);
}
.strong {
font-weight: 500;
color: var(--accent-color);

View File

@ -13,11 +13,10 @@
// limitations under the License.
-->
<script lang="ts">
import { TxCreateDoc, TxProcessor } from '@hcengineering/core'
import { Request } from '@hcengineering/request'
import RequestPresenter from '../RequestPresenter.svelte'
export let tx: TxCreateDoc<Request>
export let value: Request
</script>
<RequestPresenter value={TxProcessor.createDoc2Doc(tx)} />
<RequestPresenter {value} />

View File

@ -0,0 +1,31 @@
<!--
// 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 { createQuery } from '@hcengineering/presentation'
import { Request } from '@hcengineering/request'
import RequestLabel from '../RequestLabel.svelte'
export let value: Request
let request: Request | undefined = undefined
const query = createQuery()
query.query(value._class, { _id: value._id }, (res) => ([request] = res))
</script>
{#if request}
<div class="mr-2">
<RequestLabel value={request} />
</div>
{/if}

View File

@ -16,12 +16,16 @@
import { Resources } from '@hcengineering/platform'
import RequestsPopup from './components/RequestsPopup.svelte'
import TxCreateRequest from './components/activity/TxCreateRequest.svelte'
import RequestLabel from './components/activity/TxRequestLabel.svelte'
import EditRequest from './components/EditRequest.svelte'
export default async (): Promise<Resources> => ({
activity: {
RequestLabel,
TxCreateRequest
},
component: {
RequestsPopup
RequestsPopup,
EditRequest
}
})

View File

@ -20,10 +20,16 @@ import request, { requestId } from '@hcengineering/request'
export default mergeIds(requestId, request, {
string: {
Approve: '' as IntlString,
Approved: '' as IntlString,
CreatedRequest: '' as IntlString,
For: '' as IntlString,
Change: '' as IntlString,
Add: '' as IntlString,
Remove: '' as IntlString
Remove: '' as IntlString,
Completed: '' as IntlString,
Reject: '' as IntlString,
Request: '' as IntlString,
Rejected: '' as IntlString,
Comment: '' as IntlString
}
})

View File

@ -36,7 +36,8 @@ export interface Request extends AttachedDoc {
*/
export enum RequestStatus {
Active = 'Active',
Completed = 'Completed'
Completed = 'Completed',
Rejected = 'Rejected'
}
/**

View File

@ -17,7 +17,7 @@
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
import core, { Class, Client, Doc, getCurrentAccount, Ref, setCurrentAccount, Space } from '@hcengineering/core'
import notification, { NotificationStatus } from '@hcengineering/notification'
import request from '@hcengineering/request'
import request, { RequestStatus } from '@hcengineering/request'
import { BrowserNotificatator, NotificationClientImpl } from '@hcengineering/notification-resources'
import { getMetadata, getResource, IntlString } from '@hcengineering/platform'
import { Avatar, createQuery, setClient } from '@hcengineering/presentation'
@ -138,6 +138,18 @@
}
)
let hasRequests = false
const requestQuery = createQuery()
$: requestQuery.query(
request.class.Request,
{
requested: account._id,
status: RequestStatus.Active
},
(res) => (hasRequests = res.filter((p) => !p.approved.includes(account._id)).length > 0)
)
onDestroy(
location.subscribe(async (loc) => {
closeTooltip()
@ -452,7 +464,7 @@
action={async () => {
showPopup(request.component.RequestsPopup, {}, popupPosition)
}}
notify={false}
notify={hasRequests}
/>
<AppItem
icon={calendar.icon.Reminder}