mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Show request comment (#2525)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
bea418d46c
commit
42e354f0ff
@ -32,6 +32,7 @@
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/request": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.2",
|
||||
|
@ -18,11 +18,24 @@ 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, ReadOnly, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
Collection,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||
import { Request, RequestStatus } from '@hcengineering/request'
|
||||
import { Request, RequestDecisionComment, RequestStatus } from '@hcengineering/request'
|
||||
import request from './plugin'
|
||||
import view from '@hcengineering/model-view'
|
||||
import { TComment } from '@hcengineering/model-chunter'
|
||||
|
||||
export const DOMAIN_REQUEST = 'request' as Domain
|
||||
|
||||
@ -45,12 +58,19 @@ export class TRequest extends TAttachedDoc implements Request {
|
||||
|
||||
tx!: TxCUD<Doc>
|
||||
|
||||
@Prop(TypeRef(contact.class.EmployeeAccount), request.string.Rejected)
|
||||
@ReadOnly()
|
||||
rejected?: Ref<EmployeeAccount>
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments?: number
|
||||
}
|
||||
|
||||
@Mixin(request.mixin.RequestDecisionComment, chunter.class.Comment)
|
||||
export class TRequestDecisionComment extends TComment implements RequestDecisionComment {}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TRequest)
|
||||
builder.createModel(TRequest, TRequestDecisionComment)
|
||||
|
||||
builder.mixin(request.class.Request, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: request.component.EditRequest
|
||||
|
31
packages/ui/src/components/BooleanIcon.svelte
Normal file
31
packages/ui/src/components/BooleanIcon.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
export let value: any
|
||||
</script>
|
||||
|
||||
<div class="container" class:yes={value === true} class:no={value === false}>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
fill: #77818e;
|
||||
&.yes {
|
||||
fill: #77c07b;
|
||||
}
|
||||
&.no {
|
||||
fill: #f96e50;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -154,6 +154,7 @@ export { default as FocusHandler } from './components/FocusHandler.svelte'
|
||||
export { default as ListView } from './components/ListView.svelte'
|
||||
export { default as ToggleButton } from './components/ToggleButton.svelte'
|
||||
export { default as ExpandCollapse } from './components/ExpandCollapse.svelte'
|
||||
export { default as BooleanIcon } from './components/BooleanIcon.svelte'
|
||||
export { default as Expandable } from './components/Expandable.svelte'
|
||||
export { default as BarDashboard } from './components/BarDashboard.svelte'
|
||||
export { default as Notifications } from './components/notifications/Notifications.svelte'
|
||||
|
@ -257,23 +257,17 @@
|
||||
{#if hasMessageType}
|
||||
<div class="time"><TimeSince value={tx.tx.modifiedOn} /></div>
|
||||
{/if}
|
||||
{#if isMessageType(m.attribute)}
|
||||
<div class="strong message emphasized">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="strong">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="strong"
|
||||
class:message={isMessageType(m.attribute)}
|
||||
class:emphasized={isMessageType(m.attribute)}
|
||||
>
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
@ -290,23 +284,17 @@
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
<Label label={activity.string.To} />
|
||||
</span>
|
||||
{#if isMessageType(m.attribute)}
|
||||
<div class="strong message emphasized">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="strong">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="strong"
|
||||
class:message={isMessageType(m.attribute)}
|
||||
class:emphasized={isMessageType(m.attribute)}
|
||||
>
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { Employee, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import { Account } from '@hcengineering/core'
|
||||
import { Avatar, getClient } from '@hcengineering/presentation'
|
||||
import { Avatar, createQuery } from '@hcengineering/presentation'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { EditDoc } from '@hcengineering/view-resources'
|
||||
import contact from '../plugin'
|
||||
@ -30,12 +30,10 @@
|
||||
showPopup(EditDoc, { _id: employee._id, _class: employee._class }, 'content')
|
||||
}
|
||||
}
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
|
||||
$: if (value._class === contact.class.EmployeeAccount) {
|
||||
client.findOne(contact.class.Employee, { _id: (value as EmployeeAccount).employee }).then((r) => {
|
||||
employee = r
|
||||
})
|
||||
query.query(contact.class.Employee, { _id: (value as EmployeeAccount).employee }, (r) => ([employee] = r))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
<!--
|
||||
// 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 { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import contact from '../plugin'
|
||||
import EmployeeAccountPresenter from './EmployeeAccountPresenter.svelte'
|
||||
|
||||
export let value: Ref<EmployeeAccount>
|
||||
|
||||
let account: EmployeeAccount | undefined
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query(contact.class.EmployeeAccount, { _id: value }, (r) => ([account] = r))
|
||||
</script>
|
||||
|
||||
{#if account}
|
||||
<EmployeeAccountPresenter value={account} />
|
||||
{/if}
|
@ -36,6 +36,7 @@ import EditMember from './components/EditMember.svelte'
|
||||
import EditOrganization from './components/EditOrganization.svelte'
|
||||
import EditPerson from './components/EditPerson.svelte'
|
||||
import EmployeeAccountPresenter from './components/EmployeeAccountPresenter.svelte'
|
||||
import EmployeeAccountRefPresenter from './components/EmployeeAccountRefPresenter.svelte'
|
||||
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
|
||||
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
|
||||
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
|
||||
@ -65,7 +66,8 @@ export {
|
||||
OrganizationPresenter,
|
||||
EmployeeBrowser,
|
||||
MemberPresenter,
|
||||
EmployeeEditor
|
||||
EmployeeEditor,
|
||||
EmployeeAccountRefPresenter
|
||||
}
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
"@hcengineering/contact": "^0.6.9",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.2",
|
||||
"@hcengineering/chunter-resources": "^0.6.0",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import request from '../plugin'
|
||||
import RequestActions from './RequestActions.svelte'
|
||||
import RequestLabel from './RequestLabel.svelte'
|
||||
import RequestDetail from './RequestDetail.svelte'
|
||||
import TxView from './TxView.svelte'
|
||||
|
||||
export let object: Request
|
||||
@ -27,16 +27,19 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['comments', 'status'], activityOptions: { enabled: true, showInput: false } })
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['comments', 'status', 'rejected', 'approved', 'requested'],
|
||||
activityOptions: { enabled: true, showInput: false }
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-center gap-1 mb-2">
|
||||
<span class="mr-1"><Label label={request.string.For} /></span>
|
||||
<ObjectPresenter objectId={object.tx.objectId} _class={object.tx.objectClass} />
|
||||
<span class="mr-1"><ObjectPresenter objectId={object.tx.objectId} _class={object.tx.objectClass} /></span>
|
||||
<TxView tx={object.tx} />
|
||||
</div>
|
||||
<RequestLabel value={object} />
|
||||
<RequestDetail value={object} />
|
||||
<RequestActions value={object} />
|
||||
{/if}
|
||||
|
@ -16,34 +16,22 @@
|
||||
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 { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { AttachedData, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Request, RequestStatus } from '@hcengineering/request'
|
||||
import request from '../plugin'
|
||||
import type { RefAction } from '@hcengineering/text-editor'
|
||||
import request from '../plugin'
|
||||
import Comments from './icons/Comments.svelte'
|
||||
import DocFail from './icons/DocFail.svelte'
|
||||
import DocSuccess from './icons/DocSuccess.svelte'
|
||||
import Comments from './icons/Comments.svelte'
|
||||
|
||||
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)
|
||||
const approvable = value.requested.filter((a) => a === me).length > value.approved.filter((a) => a === me).length
|
||||
|
||||
async function approve () {
|
||||
await saveComment()
|
||||
@ -59,6 +47,7 @@
|
||||
async function reject () {
|
||||
await saveComment()
|
||||
await client.update(value, {
|
||||
rejected: me,
|
||||
status: RequestStatus.Rejected
|
||||
})
|
||||
}
|
||||
@ -71,12 +60,14 @@
|
||||
attachments = event.detail.attachments
|
||||
}
|
||||
|
||||
async function saveComment () {
|
||||
await client.addCollection(chunter.class.Comment, value.space, value._id, value._class, 'comments', {
|
||||
async function saveComment (): Promise<void> {
|
||||
const _id = await client.addCollection(chunter.class.Comment, value.space, value._id, value._class, 'comments', {
|
||||
message,
|
||||
attachments
|
||||
})
|
||||
|
||||
await client.createMixin(_id, chunter.class.Comment, value.space, request.mixin.RequestDecisionComment, {})
|
||||
|
||||
// We need to update backlinks before and after.
|
||||
await updateBacklinks(client, value.attachedTo, value.attachedToClass, value._id, message)
|
||||
refInput.submit()
|
||||
|
@ -0,0 +1,93 @@
|
||||
<!--
|
||||
// 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 } from '@hcengineering/contact'
|
||||
import { EmployeeAccountRefPresenter } from '@hcengineering/contact-resources'
|
||||
import { Account, Ref } from '@hcengineering/core'
|
||||
import { createQuery, MessageViewer } from '@hcengineering/presentation'
|
||||
import { Request, RequestDecisionComment } from '@hcengineering/request'
|
||||
import { BooleanIcon, Label, ShowMore } from '@hcengineering/ui'
|
||||
import request from '../plugin'
|
||||
|
||||
export let value: Request
|
||||
let comments: Map<Ref<Account>, RequestDecisionComment> = new Map()
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(
|
||||
request.mixin.RequestDecisionComment,
|
||||
{ attachedTo: value._id },
|
||||
(res) => (comments = new Map(res.map((r) => [r.modifiedBy, r])))
|
||||
)
|
||||
|
||||
interface RequestDecision {
|
||||
employee: Ref<EmployeeAccount>
|
||||
decision?: boolean
|
||||
comment?: RequestDecisionComment
|
||||
}
|
||||
|
||||
function convert (value: Request, comments: Map<Ref<Account>, RequestDecisionComment>): RequestDecision[] {
|
||||
const res: RequestDecision[] = []
|
||||
for (const emp of value.requested) {
|
||||
const decision = value.rejected === emp ? false : value.approved.includes(emp) ? true : undefined
|
||||
const result: RequestDecision = {
|
||||
employee: emp,
|
||||
decision
|
||||
}
|
||||
if (decision !== undefined) {
|
||||
result.comment = comments.get(emp)
|
||||
}
|
||||
res.push(result)
|
||||
}
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class="antiTable">
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<th><Label label={contact.string.Employee} /></th>
|
||||
<th><Label label={request.string.Approved} /></th>
|
||||
<th><Label label={request.string.Comment} /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each convert(value, comments) as requested}
|
||||
<tr class="antiTable-body__row">
|
||||
<td><EmployeeAccountRefPresenter value={requested.employee} /></td>
|
||||
<td><BooleanIcon value={requested.decision} /></td>
|
||||
<td
|
||||
>{#if requested.comment}
|
||||
<ShowMore limit={126} fixed>
|
||||
<MessageViewer message={requested.comment.message} />
|
||||
</ShowMore>{/if}</td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<style lang="scss">
|
||||
.antiTable {
|
||||
th,
|
||||
td {
|
||||
&:first-child {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
// 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 RequestDetail from './RequestDetail.svelte'
|
||||
|
||||
export let value: Request
|
||||
</script>
|
||||
|
||||
<div class="antiPopup popup p-4">
|
||||
<RequestDetail {value} />
|
||||
</div>
|
@ -14,17 +14,31 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Request, RequestStatus } from '@hcengineering/request'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Button, eventToHTMLElement, ProgressCircle, showPopup } from '@hcengineering/ui'
|
||||
import RequestStatusPresenter from './RequestStatusPresenter.svelte'
|
||||
import request from '../plugin'
|
||||
import RequestDetailPopup from './RequestDetailPopup.svelte'
|
||||
|
||||
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}
|
||||
<Button
|
||||
on:click={(ev) => {
|
||||
ev.stopPropagation()
|
||||
showPopup(RequestDetailPopup, { value }, eventToHTMLElement(ev))
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if value.status !== RequestStatus.Active}
|
||||
<RequestStatusPresenter value={value.status === RequestStatus.Completed} />
|
||||
{:else}
|
||||
<div class="flex-row-center content-color text-sm pointer-events-none">
|
||||
<div class="mr-1">
|
||||
<ProgressCircle max={value.requiredApprovesCount} value={value.approved.length} size={'inline'} primary />
|
||||
</div>
|
||||
{value.approved.length}/{value.requiredApprovesCount}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -26,9 +26,6 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: cl = client.getHierarchy().getClass(value._class)
|
||||
$: label = cl.shortLabel
|
||||
|
||||
let accounts: WithLookup<EmployeeAccount>[] = []
|
||||
|
||||
$: client.findAll(contact.class.EmployeeAccount, { _id: { $in: value.requested } }).then((res) => {
|
||||
@ -39,7 +36,7 @@
|
||||
|
||||
<div class="flex">
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class="flex-presenter mr-1"
|
||||
class:inline-presenter={inline}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'content')}"
|
||||
>
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { BooleanIcon, Label } from '@hcengineering/ui'
|
||||
import request from '../plugin'
|
||||
|
||||
export let value: boolean
|
||||
@ -31,19 +31,7 @@
|
||||
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>
|
||||
<BooleanIcon {value} />
|
||||
<span><Label label={getBooleanLabel(value)} /></span>
|
||||
</div>
|
||||
|
||||
@ -52,14 +40,6 @@
|
||||
max-width: fit-content;
|
||||
user-select: none;
|
||||
|
||||
fill: #77818e;
|
||||
&.yes {
|
||||
fill: #77c07b;
|
||||
}
|
||||
&.no {
|
||||
fill: #f96e50;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0.25rem;
|
||||
white-space: nowrap;
|
||||
|
@ -30,7 +30,10 @@
|
||||
requested: me,
|
||||
status: RequestStatus.Active
|
||||
},
|
||||
(res) => (requests = res.filter((p) => !p.approved.includes(me)))
|
||||
(res) =>
|
||||
(requests = res.filter(
|
||||
(p) => p.requested.filter((a) => a === me).length > p.approved.filter((a) => a === me).length
|
||||
))
|
||||
)
|
||||
</script>
|
||||
|
||||
|
@ -139,7 +139,7 @@
|
||||
<Label label={activity.string.To} />
|
||||
</span>
|
||||
<div
|
||||
class="strong message emphasized"
|
||||
class="strong"
|
||||
class:message={isMessageType(m.attribute)}
|
||||
class:emphasized={isMessageType(m.attribute)}
|
||||
>
|
||||
@ -148,7 +148,6 @@
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
{/if}
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
@ -166,15 +165,13 @@
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
<Label label={activity.string.To} />
|
||||
</span>
|
||||
{#if isMessageType(m.attribute)}
|
||||
<div class="strong message emphasized">
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="strong">
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="strong"
|
||||
class:message={isMessageType(m.attribute)}
|
||||
class:emphasized={isMessageType(m.attribute)}
|
||||
>
|
||||
<svelte:component this={m.presenter} value={value.set} />
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
|
@ -25,7 +25,7 @@
|
||||
</script>
|
||||
|
||||
{#if request}
|
||||
<div class="mr-2">
|
||||
<div class="ml-1">
|
||||
<RequestLabel value={request} />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/core": "^0.6.20",
|
||||
"@hcengineering/chunter": "^0.6.2",
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/contact": "^0.6.9"
|
||||
}
|
||||
|
@ -14,10 +14,11 @@
|
||||
//
|
||||
|
||||
import { EmployeeAccount } from '@hcengineering/contact'
|
||||
import type { AttachedDoc, Class, Doc, Ref, TxCUD } from '@hcengineering/core'
|
||||
import type { AttachedDoc, Class, Doc, Mixin, Ref, TxCUD } from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -26,11 +27,17 @@ export interface Request extends AttachedDoc {
|
||||
requested: Ref<EmployeeAccount>[]
|
||||
approved: Ref<EmployeeAccount>[]
|
||||
requiredApprovesCount: number
|
||||
rejected?: Ref<EmployeeAccount>
|
||||
status: RequestStatus
|
||||
tx: TxCUD<Doc>
|
||||
comments?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface RequestDecisionComment extends Comment {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -52,6 +59,9 @@ const request = plugin(requestId, {
|
||||
class: {
|
||||
Request: '' as Ref<Class<Request>>
|
||||
},
|
||||
mixin: {
|
||||
RequestDecisionComment: '' as Ref<Mixin<RequestDecisionComment>>
|
||||
},
|
||||
component: {
|
||||
RequestsPopup: '' as AnyComponent
|
||||
},
|
||||
|
@ -14,31 +14,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { BooleanIcon, Label } from '@hcengineering/ui'
|
||||
import { getBooleanLabel } from '../utils'
|
||||
|
||||
export let value: any
|
||||
</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>
|
||||
<div class="flex-row-center yesno-container">
|
||||
<BooleanIcon {value} />
|
||||
<span><Label label={getBooleanLabel(value)} /></span>
|
||||
</div>
|
||||
|
||||
@ -47,14 +30,6 @@
|
||||
max-width: fit-content;
|
||||
user-select: none;
|
||||
|
||||
fill: #77818e;
|
||||
&.yes {
|
||||
fill: #77c07b;
|
||||
}
|
||||
&.no {
|
||||
fill: #f96e50;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0.25rem;
|
||||
width: 1.6rem;
|
||||
|
@ -152,7 +152,12 @@
|
||||
requested: account._id,
|
||||
status: RequestStatus.Active
|
||||
},
|
||||
(res) => (hasRequests = res.filter((p) => !p.approved.includes(account._id)).length > 0)
|
||||
(res) =>
|
||||
(hasRequests =
|
||||
res.filter(
|
||||
(p) =>
|
||||
p.requested.filter((a) => a === account._id).length > p.approved.filter((a) => a === account._id).length
|
||||
).length > 0)
|
||||
)
|
||||
|
||||
onDestroy(
|
||||
|
Loading…
Reference in New Issue
Block a user