mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-23 12:05:36 +00:00
EZQMS-1109: Add signature details for reviews/approvals (#6111)
* ezqms-1109: add signature details for reviews/approvals Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
parent
93b798c99b
commit
8163a30b77
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { Label, Scroller, getUserTimezone } from '@hcengineering/ui'
|
||||
import { Label, Scroller } from '@hcengineering/ui'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import documents, { DocumentApprovalRequest, DocumentReviewRequest } from '@hcengineering/controlled-documents'
|
||||
import { employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
import documentsRes from '../../plugin'
|
||||
import { $controlledDocument as controlledDocument } from '../../stores/editors/document/editor'
|
||||
import { formatSignatureDate } from '../../utils'
|
||||
|
||||
interface Signer {
|
||||
id?: Ref<Person>
|
||||
@ -38,7 +39,6 @@
|
||||
|
||||
const reviewQuery = createQuery()
|
||||
const approvalQuery = createQuery()
|
||||
const timeZone: string = getUserTimezone()
|
||||
|
||||
$: if ($controlledDocument !== undefined) {
|
||||
reviewQuery.query(
|
||||
@ -92,7 +92,7 @@
|
||||
id: $controlledDocument.author,
|
||||
role: 'author',
|
||||
name: getNameByEmployeeId($controlledDocument.author),
|
||||
date: $controlledDocument.createdOn !== undefined ? formatDate($controlledDocument.createdOn) : ''
|
||||
date: $controlledDocument.createdOn !== undefined ? formatSignatureDate($controlledDocument.createdOn) : ''
|
||||
}
|
||||
]
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
id: rAcc?.person,
|
||||
role: 'reviewer',
|
||||
name: getNameByEmployeeId(rAcc?.person),
|
||||
date: formatDate(date ?? reviewRequest.modifiedOn)
|
||||
date: formatSignatureDate(date ?? reviewRequest.modifiedOn)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -119,25 +119,12 @@
|
||||
id: aAcc?.person,
|
||||
role: 'approver',
|
||||
name: getNameByEmployeeId(aAcc?.person),
|
||||
date: formatDate(date ?? approvalRequest.modifiedOn)
|
||||
date: formatSignatureDate(date ?? approvalRequest.modifiedOn)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate (date: number): string {
|
||||
return new Date(date).toLocaleDateString('default', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
timeZone,
|
||||
timeZoneName: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
function getSignerLabel (role: 'author' | 'reviewer' | 'approver'): IntlString {
|
||||
switch (role) {
|
||||
case 'author':
|
||||
|
@ -2,11 +2,11 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import documents, { DocumentRequest } from '@hcengineering/controlled-documents'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { PersonAccountRefPresenter } from '@hcengineering/contact-resources'
|
||||
import { type Person, type PersonAccount } from '@hcengineering/contact'
|
||||
import { PersonAccountRefPresenter, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Chevron, Label } from '@hcengineering/ui'
|
||||
import { Chevron, Label, tooltip } from '@hcengineering/ui'
|
||||
|
||||
import { $documentSnapshots as documentSnapshots } from '../../../stores/editors/document'
|
||||
import documentsRes from '../../../plugin'
|
||||
@ -14,13 +14,16 @@
|
||||
import RejectedIcon from '../../icons/Rejected.svelte'
|
||||
import CancelledIcon from '../../icons/Cancelled.svelte'
|
||||
import WaitingIcon from '../../icons/Waiting.svelte'
|
||||
import SignatureInfo from './SignatureInfo.svelte'
|
||||
|
||||
export let request: DocumentRequest
|
||||
export let initiallyExpanded: boolean = false
|
||||
|
||||
interface PersonalApproval {
|
||||
account: Ref<PersonAccount>
|
||||
person?: Ref<Person>
|
||||
approved: 'approved' | 'rejected' | 'cancelled' | 'waiting'
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
@ -31,38 +34,45 @@
|
||||
let rejectingMessage: string | undefined
|
||||
let approvals: PersonalApproval[] = []
|
||||
|
||||
$: if (request != null) {
|
||||
void getRequestData()
|
||||
}
|
||||
$: void getRequestData(request)
|
||||
|
||||
$: type = hierarchy.isDerived(request._class, documents.class.DocumentApprovalRequest)
|
||||
? documents.string.Approval
|
||||
: documents.string.Review
|
||||
|
||||
async function getRequestData (): Promise<void> {
|
||||
if (request !== undefined) {
|
||||
approvals = await getApprovals(request)
|
||||
const rejectingComment = await client.findOne(chunter.class.ChatMessage, {
|
||||
attachedTo: request?._id,
|
||||
attachedToClass: request?._class
|
||||
})
|
||||
rejectingMessage = rejectingComment?.message
|
||||
async function getRequestData (req: DocumentRequest): Promise<void> {
|
||||
if (req == null) {
|
||||
return
|
||||
}
|
||||
|
||||
approvals = await getApprovals(req, $personAccountByIdStore)
|
||||
const rejectingComment = await client.findOne(chunter.class.ChatMessage, {
|
||||
attachedTo: req?._id,
|
||||
attachedToClass: req?._class
|
||||
})
|
||||
rejectingMessage = rejectingComment?.message
|
||||
}
|
||||
|
||||
async function getApprovals (req: DocumentRequest): Promise<PersonalApproval[]> {
|
||||
async function getApprovals (
|
||||
req: DocumentRequest,
|
||||
accountById: typeof $personAccountByIdStore
|
||||
): Promise<PersonalApproval[]> {
|
||||
const rejectedBy: PersonalApproval[] =
|
||||
req.rejected !== undefined
|
||||
? [
|
||||
{
|
||||
account: req.rejected,
|
||||
approved: 'rejected'
|
||||
person: accountById.get(req.rejected)?.person,
|
||||
approved: 'rejected',
|
||||
timestamp: req.modifiedOn
|
||||
}
|
||||
]
|
||||
: []
|
||||
const approvedBy: PersonalApproval[] = req.approved.map((id) => ({
|
||||
const approvedBy: PersonalApproval[] = req.approved.map((id, idx) => ({
|
||||
account: id,
|
||||
approved: 'approved'
|
||||
person: accountById.get(id)?.person,
|
||||
approved: 'approved',
|
||||
timestamp: req.approvedDates?.[idx] ?? req.modifiedOn
|
||||
}))
|
||||
const ignoredBy = req.requested
|
||||
.filter((p) => p !== req?.rejected)
|
||||
@ -70,6 +80,7 @@
|
||||
.map(
|
||||
(id): PersonalApproval => ({
|
||||
account: id,
|
||||
person: accountById.get(id)?.person,
|
||||
approved: req?.rejected !== undefined ? 'cancelled' : 'waiting'
|
||||
})
|
||||
)
|
||||
@ -115,15 +126,30 @@
|
||||
{#each approvals as approver}
|
||||
<div class="approver">
|
||||
<PersonAccountRefPresenter value={approver.account} avatarSize="x-small" />
|
||||
{#if approver.approved === 'approved'}
|
||||
<ApprovedIcon size="medium" fill={'var(--theme-docs-accepted-color)'} />
|
||||
{:else if approver.approved === 'rejected'}
|
||||
<RejectedIcon size="medium" fill={'var(--negative-button-default)'} />
|
||||
{:else if approver.approved === 'cancelled'}
|
||||
<CancelledIcon size="medium" />
|
||||
{:else if approver.approved === 'waiting'}
|
||||
<WaitingIcon size="medium" />
|
||||
{/if}
|
||||
{#key approver.timestamp}
|
||||
<!-- For some reason tooltip is not interactive w/o remount -->
|
||||
<span
|
||||
use:tooltip={approver.timestamp !== undefined
|
||||
? {
|
||||
component: SignatureInfo,
|
||||
props: {
|
||||
id: approver.person,
|
||||
timestamp: approver.timestamp
|
||||
}
|
||||
}
|
||||
: undefined}
|
||||
>
|
||||
{#if approver.approved === 'approved'}
|
||||
<ApprovedIcon size="medium" fill={'var(--theme-docs-accepted-color)'} />
|
||||
{:else if approver.approved === 'rejected'}
|
||||
<RejectedIcon size="medium" fill={'var(--negative-button-default)'} />
|
||||
{:else if approver.approved === 'cancelled'}
|
||||
<CancelledIcon size="medium" />
|
||||
{:else if approver.approved === 'waiting'}
|
||||
<WaitingIcon size="medium" />
|
||||
{/if}
|
||||
</span>
|
||||
{/key}
|
||||
</div>
|
||||
{#if rejectingMessage !== undefined && approver.approved === 'rejected'}
|
||||
<div class="reject-message">{@html rejectingMessage}</div>
|
||||
|
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2022, 2023, 2024 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 { formatSignatureDate } from '../../../utils'
|
||||
|
||||
export let id: string
|
||||
export let timestamp: number
|
||||
</script>
|
||||
|
||||
<div>ID: {id}</div>
|
||||
<div>{formatSignatureDate(timestamp)}</div>
|
@ -35,7 +35,7 @@ import contact, { type Employee, type PersonAccount } from '@hcengineering/conta
|
||||
import request, { RequestStatus } from '@hcengineering/request'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
import { getEventPositionElement, showPopup, type Location } from '@hcengineering/ui'
|
||||
import { getEventPositionElement, showPopup, getUserTimezone, type Location } from '@hcengineering/ui'
|
||||
import { type KeyFilter } from '@hcengineering/view'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import documents, {
|
||||
@ -835,3 +835,18 @@ export async function createTemplate (space: OrgSpace): Promise<void> {
|
||||
wizardOpened({ $$currentStep: 'info', location: { space: space._id, project: project ?? documents.ids.NoProject } })
|
||||
showPopup(documents.component.QmsTemplateWizard, {})
|
||||
}
|
||||
|
||||
export function formatSignatureDate (date: number): string {
|
||||
const timeZone: string = getUserTimezone()
|
||||
|
||||
return new Date(date).toLocaleDateString('default', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
timeZone,
|
||||
timeZoneName: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
})
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class DocumentApprovalsPage extends DocumentCommonPage {
|
||||
}
|
||||
|
||||
async checkSuccessApproval (approvalName: string): Promise<void> {
|
||||
await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=..').locator('span.ap-label')).toHaveText(
|
||||
await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=../..').locator('span.ap-label')).toHaveText(
|
||||
approvalName
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user