Skip to content

Commit c6898cb

Browse files
authored
Merge pull request bcgov#2285 from eve-git/30749
30749 UI: Audit history component
2 parents 8b3fdbd + 595b17a commit c6898cb

6 files changed

Lines changed: 228 additions & 140 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<script setup lang="ts">
2+
import { computed, ref, watch } from 'vue'
3+
import { enumToLabel } from '@/utils/format-helper'
4+
import { ReviewStatusTypes } from '@/composables/analystQueue/enums'
5+
import { pacificDate } from '@/utils'
6+
import type { QueueReviewStepIF } from '@/composables/analystQueue/interfaces'
7+
8+
const props = withDefaults(defineProps<{
9+
count?: number
10+
expanded?: boolean
11+
title?: string
12+
steps?: QueueReviewStepIF[]
13+
}>(), {
14+
count: 0,
15+
expanded: false,
16+
title: 'Audit History',
17+
steps: () => []
18+
})
19+
20+
const emit = defineEmits<{
21+
(e: 'toggle', expanded: boolean): void
22+
}>()
23+
24+
const isExpanded = ref(props.expanded)
25+
26+
watch(() => props.expanded, (val) => {
27+
isExpanded.value = val
28+
})
29+
30+
const label = computed(() => `View History (${props.count})`)
31+
32+
const sortedSteps = computed(() => {
33+
return [...props.steps].sort((a, b) => {
34+
const aTime = a?.createDateTime ? new Date(a.createDateTime).getTime() : 0
35+
const bTime = b?.createDateTime ? new Date(b.createDateTime).getTime() : 0
36+
return bTime - aTime
37+
})
38+
})
39+
40+
const formatDateTimeParts = (dateTime?: string) => {
41+
if (!dateTime) return { date: '', time: '' }
42+
const pacific = pacificDate(dateTime, true).replace(' Pacific time', '')
43+
const parts = pacific.split(' at ')
44+
return {
45+
date: parts[0] || '',
46+
time: parts[1] || ''
47+
}
48+
}
49+
50+
const getStatusLabel = (statusType: ReviewStatusTypes) => {
51+
if (statusType === ReviewStatusTypes.DECLINED) return 'Rejected'
52+
return enumToLabel(statusType)
53+
}
54+
55+
const toggleExpanded = () => {
56+
isExpanded.value = !isExpanded.value
57+
emit('toggle', isExpanded.value)
58+
}
59+
</script>
60+
61+
<template>
62+
<div id="audit-history">
63+
<div class="font-bold text-gray-900 bg-bcGovColor-gray2 px-[1.4rem] py-[12px] rounded-t">
64+
<div class="flex justify-between">
65+
<div class="flex items-center">
66+
<UIcon name="i-mdi-history" class="w-6 h-6 text-blue-350" />
67+
<div class="ml-2">{{ title }}</div>
68+
</div>
69+
</div>
70+
</div>
71+
72+
<div class="w-full border-t bg-white pa-6">
73+
<button
74+
type="button"
75+
class="text-blue-600 hover:text-blue-700 flex items-center gap-1"
76+
:aria-expanded="isExpanded"
77+
aria-controls="audit-history-content"
78+
@click="toggleExpanded"
79+
>
80+
<span>{{ label }}</span>
81+
<UIcon :name="isExpanded ? 'i-mdi-chevron-up' : 'i-mdi-chevron-down'" class="w-5 h-5" />
82+
</button>
83+
84+
<div v-if="isExpanded" id="audit-history-content" class="mt-4">
85+
<div class="space-y-4">
86+
<div
87+
v-for="(step, index) in sortedSteps"
88+
:key="`${step.createDateTime}-${index}`"
89+
class="border-b border-bcGovGray-200 pb-4"
90+
>
91+
<v-row no-gutters>
92+
<v-col cols="12" sm="2" class="text-normal gray7 whitespace-nowrap">
93+
{{ formatDateTimeParts(step.createDateTime).date }}
94+
</v-col>
95+
<v-col cols="12" sm="1" class="text-normal gray7 whitespace-nowrap">
96+
{{ formatDateTimeParts(step.createDateTime).time }}
97+
</v-col>
98+
<v-col cols="12" sm="7" class="space-y-2">
99+
<div class="font-bold text-gray-900">
100+
Registration {{ getStatusLabel(step.statusType) }}
101+
<span v-if="step.username" class="font-normal gray7">
102+
(by {{ step.username }})
103+
</span>
104+
</div>
105+
<div v-if="step.statusType === ReviewStatusTypes.DECLINED" class="space-y-2">
106+
<div class="gray7">
107+
Rejected reason: {{ step.declinedReasonType ? enumToLabel(step.declinedReasonType) : '—' }}
108+
</div>
109+
<div class="gray7">
110+
Staff note: {{ step.staffNote || '—' }}
111+
</div>
112+
</div>
113+
</v-col>
114+
</v-row>
115+
</div>
116+
</div>
117+
</div>
118+
</div>
119+
</div>
120+
</template>

ppr-ui/src/components/queue/QueueWrapper.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { h, resolveComponent, ref, computed, onMounted } from 'vue'
33
import { storeToRefs } from 'pinia'
44
import { useAnalystQueueStore } from '@/store/analystQueue'
55
import { queueTableColumns } from '@/composables/analystQueue'
6-
import { enumToLabel } from '@/utils'
6+
import { enumToLabel } from '@/utils/format-helper'
77
88
const { setMhrInformation } = useStore()
99
const { goToRoute } = useNavigation()
Lines changed: 96 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
<script setup lang="ts">
2-
import { ReviewDecisionTypes, ReviewStatusTypes } from '@/composables';
3-
import { enumToLabel } from '@/utils';
42
import { computed, watch } from 'vue';
5-
import { useAnalystQueueStore } from '@/store/analystQueue';
3+
import { enumToLabel } from '@/utils/format-helper'
4+
import { ReviewDecisionTypes, ReviewStatusTypes } from '@/composables';
65
import { storeToRefs } from 'pinia';
6+
import { useAnalystQueueStore } from '@/store/analystQueue';
77
88
const { reviewDecision, validationErrors, isInReview } = storeToRefs(useAnalystQueueStore())
99
10-
11-
const isReadonly = computed(() => !isInReview.value)
12-
const isApproved = computed(() => reviewDecision.value.statusType === ReviewStatusTypes.APPROVED)
1310
const isDeclined = computed(() => reviewDecision.value.statusType === ReviewStatusTypes.DECLINED)
1411
15-
/// Options for the decline reason select dropdown
12+
// Options for the decline reason select dropdown
1613
const declineReasonItems = computed(() => {
1714
1815
return Object.values(ReviewDecisionTypes).map((value) => ({
@@ -21,14 +18,7 @@ const declineReasonItems = computed(() => {
2118
}))
2219
})
2320
24-
/// Label for the declined reason to show in readonly mode
25-
const declinedReasonLabel = computed(() => {
26-
return reviewDecision.value?.declinedReasonType
27-
? enumToLabel(reviewDecision.value.declinedReasonType)
28-
: ''
29-
})
30-
31-
/// Update the review decision status type when a button is clicked
21+
// Update the review decision status type when a button is clicked
3222
const updateReviewDecision = (statusType: ReviewStatusTypes) => {
3323
if (!isInReview.value) return
3424
reviewDecision.value = { ...reviewDecision.value, statusType }
@@ -55,127 +45,102 @@ watch(() => reviewDecision.value?.statusType, (val) => {
5545
<template>
5646
<div
5747
:class="validationErrors.general || validationErrors.declineReasonType ? 'border-error-left' : ''">
58-
<div :class="'font-bold text-gray-900 bg-bcGovColor-gray2 px-[1.4rem] py-[12px] rounded-t'">
59-
<div class="flex justify-between">
60-
<div class="flex">
61-
<UIcon name="i-mdi-stamper" class="w-6 h-6 text-blue-350" />
62-
<div class="ml-2">Review Decision</div>
63-
</div>
48+
<div :class="'font-bold text-gray-900 bg-bcGovColor-gray2 px-[1.4rem] py-[12px] rounded-t'">
49+
<div class="flex justify-between">
50+
<div class="flex">
51+
<UIcon name="i-mdi-stamper" class="w-6 h-6 text-blue-350" />
52+
<div class="ml-2">Review Decision</div>
6453
</div>
6554
</div>
66-
<div class="w-full border-t bg-white pa-6">
67-
<div class="grid grid-cols-12 gap-4">
68-
<div class="col-span-3 flex">
69-
<span class="text-gray-700 font-bold">Review Decision</span>
70-
</div>
71-
72-
<div class="col-span-9">
73-
<div v-if="!isReadonly" class="flex gap-[10px]">
74-
<UButton
75-
size="md"
76-
color="error"
77-
:class="[
78-
'rounded-sm flex-1 h-[44px] justify-center',
79-
reviewDecision.statusType === ReviewStatusTypes.DECLINED ? 'bg-error/10' : ''
80-
]"
81-
variant="outline"
82-
icon="i-mdi-close"
83-
@click="updateReviewDecision(ReviewStatusTypes.DECLINED)"
84-
>
85-
Decline
86-
</UButton>
87-
<UButton
88-
color="success"
89-
size="md"
90-
:class="[
91-
'rounded-sm flex-1 h-[44px] justify-center text-bcGovGray-900',
92-
reviewDecision.statusType === ReviewStatusTypes.APPROVED ? 'bg-success/10' : ''
93-
]"
94-
variant="outline"
95-
icon="i-mdi-check"
96-
@click="updateReviewDecision(ReviewStatusTypes.APPROVED)"
97-
>
98-
Approve
99-
</UButton>
100-
</div>
101-
102-
<div v-else class="text-gray-900">
103-
<div class="font-bold">{{ enumToLabel(reviewDecision.statusType) }}</div>
104-
</div>
105-
106-
<div v-if="validationErrors.general" class="text-red-600 text-sm mt-2">
107-
{{ validationErrors.general }}
108-
</div>
109-
110-
<div v-if="isDeclined" class="mt-4 rounded animate-slide-down">
111-
112-
<div class="space-y-4">
113-
<template v-if="!isReadonly">
114-
<div v-if="!isApproved" class="space-y-4">
115-
<USelect
116-
v-model="reviewDecision.declinedReasonType"
117-
:items="declineReasonItems"
118-
label-key="text"
119-
value-key="value"
120-
placeholder="Reason for decline (will be included in client email)"
121-
size="lg"
122-
:ui="{
123-
base: [
124-
'border-b-[1px] w-full h-[60px] focus:shadow-none data-[state=open]:shadow-none',
125-
'data-[state=open]:border-blue-350',
126-
validationErrors.declineReasonType ? 'border-error' : ''
127-
],
128-
item: 'hover:text-blue-500 hover:bg-bcGovGray-100',
129-
placeholder: validationErrors.declineReasonType ? 'text-error' : 'text-bcGovGray-700'
130-
}"
131-
/>
132-
<div v-if="validationErrors.declineReasonType" class="text-red-600 text-sm mt-1">
133-
{{ validationErrors.declineReasonType }}
134-
</div>
55+
</div>
56+
<div class="w-full border-t bg-white pa-6">
57+
<div class="grid grid-cols-12 gap-4">
58+
<div class="col-span-3 flex">
59+
<span class="text-gray-700 font-bold">Review Decision</span>
60+
</div>
61+
62+
<div class="col-span-9">
63+
<div class="flex gap-[10px]">
64+
<UButton
65+
size="md"
66+
color="error"
67+
:class="[
68+
'rounded-sm flex-1 h-[44px] justify-center',
69+
reviewDecision.statusType === ReviewStatusTypes.DECLINED ? 'bg-error/10' : ''
70+
]"
71+
variant="outline"
72+
icon="i-mdi-close"
73+
@click="updateReviewDecision(ReviewStatusTypes.DECLINED)"
74+
>
75+
Decline
76+
</UButton>
77+
<UButton
78+
color="success"
79+
size="md"
80+
:class="[
81+
'rounded-sm flex-1 h-[44px] justify-center text-bcGovGray-900',
82+
reviewDecision.statusType === ReviewStatusTypes.APPROVED ? 'bg-success/10' : ''
83+
]"
84+
variant="outline"
85+
icon="i-mdi-check"
86+
@click="updateReviewDecision(ReviewStatusTypes.APPROVED)"
87+
>
88+
Approve
89+
</UButton>
90+
</div>
91+
92+
<div v-if="validationErrors.general" class="text-red-600 text-sm mt-2">
93+
{{ validationErrors.general }}
94+
</div>
95+
96+
<div v-if="isDeclined" class="mt-4 rounded animate-slide-down">
97+
<div class="space-y-4">
98+
<USelect
99+
v-model="reviewDecision.declinedReasonType"
100+
:items="declineReasonItems"
101+
label-key="text"
102+
value-key="value"
103+
placeholder="Reason for decline (will be included in client email)"
104+
size="lg"
105+
:ui="{
106+
base: [
107+
'border-b-[1px] w-full h-[60px] focus:shadow-none data-[state=open]:shadow-none',
108+
'data-[state=open]:border-blue-350',
109+
validationErrors.declineReasonType ? 'border-error' : ''
110+
],
111+
item: 'hover:text-blue-500 hover:bg-bcGovGray-100',
112+
placeholder: validationErrors.declineReasonType ? 'text-error' : 'text-bcGovGray-700'
113+
}"
114+
/>
115+
<div v-if="validationErrors.declineReasonType" class="text-red-600 text-sm mt-1">
116+
{{ validationErrors.declineReasonType }}
135117
</div>
136118

137-
<div v-if="!isApproved">
138-
<UTextarea
139-
v-model="reviewDecision.staffNote"
140-
placeholder="Staff note (internal)"
141-
size="lg"
142-
:rows="4"
143-
:maxlength="2000"
144-
class="w-full"
145-
:ui="{
146-
slots:{
147-
base: [
148-
'border-bcGovGray-700 focus:shadow-none',
149-
'focus:border-blue-350 placeholder:text-bcGovGray-700'
150-
],
151-
},
152-
base: 'border-b-1 placeholder:text-bcGovGray-700'
153-
}"
154-
/>
155-
<div class="text-xs text-gray-500 mt-1 text-end">
156-
{{ reviewDecision.staffNote?.length }} / 2000
157-
</div>
158-
</div>
159-
</template>
160-
161-
<template v-else>
162-
<div v-if="isDeclined" class="space-y-4">
163-
<div>
164-
<div class="text-sm text-gray-500">Reason for decline</div>
165-
<div class="font-bold">{{ declinedReasonLabel || '—' }}</div>
166-
</div>
167-
168-
<div>
169-
<div class="text-sm text-gray-500">Staff note (internal)</div>
170-
<div class="whitespace-pre-wrap">{{ reviewDecision.staffNote || '—' }}</div>
171-
</div>
119+
<UTextarea
120+
v-model="reviewDecision.staffNote"
121+
placeholder="Staff note (internal)"
122+
size="lg"
123+
:rows="4"
124+
:maxlength="2000"
125+
class="w-full"
126+
:ui="{
127+
slots:{
128+
base: [
129+
'border-bcGovGray-700 focus:shadow-none',
130+
'focus:border-blue-350 placeholder:text-bcGovGray-700'
131+
],
132+
},
133+
base: 'border-b-1 placeholder:text-bcGovGray-700'
134+
}"
135+
/>
136+
<div class="text-xs text-gray-500 mt-1 text-end">
137+
{{ reviewDecision.staffNote?.length }} / 2000
172138
</div>
173-
</template>
174-
</div>
175-
</div>
176-
</div>
177-
</div>
178-
</div>
139+
</div>
140+
</div>
141+
</div>
142+
</div>
143+
</div>
179144
</div>
180145
</template>
181146

ppr-ui/src/composables/analystQueue/resources.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FilterTypes } from '@/enums'
22
import { ReviewStatusTypes, ReviewRegTypes } from '@/composables/analystQueue/enums'
3-
import { enumToLabel } from '@/utils'
3+
import { enumToLabel } from '@/utils/format-helper'
44

55
export const queueTableColumns = [
66
{ id: 'mhrNumber',

0 commit comments

Comments
 (0)