@reqquest/ui 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/env.template +5 -0
- package/app/runtime/apply-env.sh +10 -0
- package/app/runtime/nginx.conf +37 -0
- package/app/src/app.css +21 -0
- package/app/src/app.d.ts +13 -0
- package/app/src/app.html +12 -0
- package/app/src/internal/api.ts +1707 -0
- package/app/src/internal/appreq-utils.ts +33 -0
- package/app/src/internal/components/AnnouncementBanner.svelte +47 -0
- package/app/src/internal/components/AppRequestCard.svelte +173 -0
- package/app/src/internal/components/ApplicantOptOutModal.svelte +78 -0
- package/app/src/internal/components/ApplicantProgramList.svelte +276 -0
- package/app/src/internal/components/ApplicantProgramListTooltip.svelte +44 -0
- package/app/src/internal/components/ApplicantPromptPage.svelte +136 -0
- package/app/src/internal/components/ApplicantPromptSkeleton.svelte +12 -0
- package/app/src/internal/components/ApplicationDetailsView.svelte +369 -0
- package/app/src/internal/components/ButtonLoadingIcon.svelte +28 -0
- package/{dist → app/src/internal}/components/DelayedSkeleton.svelte +2 -2
- package/app/src/internal/components/FieldNestedMultiselect.svelte +270 -0
- package/app/src/internal/components/IntroPanel.svelte +41 -0
- package/app/src/internal/components/MissingDefinitionNotification.svelte +20 -0
- package/app/src/internal/components/PeriodPanel.svelte +125 -0
- package/app/src/internal/components/ProgramStatusCell.svelte +134 -0
- package/app/src/internal/components/RenderDisplayComponent.svelte +42 -0
- package/app/src/internal/components/ReviewerActions.svelte +171 -0
- package/app/src/internal/components/ReviewerQuestions.svelte +508 -0
- package/app/src/internal/components/StatusMessageList.svelte +54 -0
- package/app/src/internal/components/SystemDeterminationNotice.svelte +37 -0
- package/app/src/internal/components/TabLinks.svelte +265 -0
- package/app/src/internal/components/WarningIconYellow.svelte +22 -0
- package/app/src/internal/components/index.ts +19 -0
- package/app/src/internal/csv.ts +30 -0
- package/app/src/internal/index.ts +8 -0
- package/app/src/internal/prompt-save-queue.ts +91 -0
- package/app/src/internal/prompt-utils.ts +40 -0
- package/app/src/internal/status-utils.ts +503 -0
- package/app/src/internal/stores/IStateStore.ts +5 -0
- package/app/src/internal/util.ts +27 -0
- package/app/src/routes/+error.svelte +4 -0
- package/app/src/routes/+layout.svelte +134 -0
- package/app/src/routes/+layout.ts +18 -0
- package/app/src/routes/+page.ts +14 -0
- package/app/src/routes/announcement/+page.svelte +164 -0
- package/app/src/routes/announcement/+page.ts +16 -0
- package/app/src/routes/dashboards/applicant/+page.svelte +494 -0
- package/app/src/routes/dashboards/applicant/+page.ts +95 -0
- package/app/src/routes/dashboards/reviewer/+page.svelte +143 -0
- package/app/src/routes/dashboards/reviewer/+page.ts +43 -0
- package/app/src/routes/periods/+page.svelte +158 -0
- package/app/src/routes/periods/+page.ts +13 -0
- package/app/src/routes/periods/[id]/configure/+page.svelte +150 -0
- package/app/src/routes/periods/[id]/configure/+page.ts +10 -0
- package/app/src/routes/requests/+page.svelte +246 -0
- package/app/src/routes/requests/+page.ts +24 -0
- package/app/src/routes/requests/[id]/+layout.ts +11 -0
- package/app/src/routes/requests/[id]/accept/+layout.svelte +80 -0
- package/app/src/routes/requests/[id]/accept/+layout.ts +12 -0
- package/app/src/routes/requests/[id]/accept/+page.ts +13 -0
- package/app/src/routes/requests/[id]/accept/[promptId]/+page.svelte +7 -0
- package/app/src/routes/requests/[id]/accept/[promptId]/+page.ts +10 -0
- package/app/src/routes/requests/[id]/accept/review/+page.svelte +87 -0
- package/app/src/routes/requests/[id]/apply/+layout.svelte +181 -0
- package/app/src/routes/requests/[id]/apply/+layout.ts +12 -0
- package/app/src/routes/requests/[id]/apply/+page.ts +14 -0
- package/app/src/routes/requests/[id]/apply/ProgressNavContainer.svelte +42 -0
- package/app/src/routes/requests/[id]/apply/[promptId]/+page.svelte +23 -0
- package/app/src/routes/requests/[id]/apply/[promptId]/+page.ts +11 -0
- package/app/src/routes/requests/[id]/apply/programs/+page.svelte +71 -0
- package/app/src/routes/requests/[id]/apply/review/+page.svelte +102 -0
- package/app/src/routes/requests/[id]/approve/+layout.svelte +114 -0
- package/app/src/routes/requests/[id]/approve/+page.ts +14 -0
- package/app/src/routes/requests/[id]/approve/ApproveLayout.svelte +164 -0
- package/app/src/routes/requests/[id]/approve/[programKey]/+page.svelte +396 -0
- package/app/src/routes/requests/[id]/approve/[programKey]/+page.ts +15 -0
- package/app/src/routes/requests/[id]/approve/activity/+page.svelte +66 -0
- package/app/src/routes/requests/[id]/approve/activity/+page.ts +12 -0
- package/app/src/routes/requests/[id]/approve/export/+page.ts +12 -0
- package/app/src/routes/requests/[id]/approve/export/+page@[id].svelte +41 -0
- package/app/src/routes/requests/[id]/export/+page.svelte +32 -0
- package/app/src/routes/requests/[id]/export/+page.ts +12 -0
- package/app/src/routes/roles/+layout.svelte +16 -0
- package/app/src/routes/roles/+layout.ts +14 -0
- package/app/src/routes/roles/+page.svelte +225 -0
- package/app/src/routes/roles/+page.ts +13 -0
- package/app/src/routes/roles/[id]/+layout.ts +10 -0
- package/app/src/routes/roles/[id]/+page.svelte +335 -0
- package/app/src/routes/roles/[id]/+page.ts +10 -0
- package/app/src/routes/roles/[id]/ControlWithTooltip.svelte +15 -0
- package/app/src/routes/roles/[id]/TagsWithTooltip.svelte +42 -0
- package/app/src/routes/roles/users/+layout.ts +13 -0
- package/app/src/routes/roles/users/+page.svelte +46 -0
- package/app/src/routes/roles/users/+page.ts +90 -0
- package/app/static/favicon.png +0 -0
- package/bin/reqquest-ui.js +117 -0
- package/cli/config.js +105 -0
- package/cli/sync.js +296 -0
- package/cli/util.js +29 -0
- package/dist/components.md +5 -4
- package/dist/registry.d.ts +17 -0
- package/dist/registry.js +3 -0
- package/package.json +29 -15
- package/dist/components/DelayedSkeleton.svelte.d.ts +0 -21
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { toasts } from '@txstate-mws/svelte-components'
|
|
3
|
+
import { Loading } from "carbon-components-svelte"
|
|
4
|
+
import { Button, Select, SelectItem } from 'carbon-components-svelte'
|
|
5
|
+
import { InfoCard, translateMutations } from '@reqquest/ui'
|
|
6
|
+
import { phaseChangeMutations, type PhaseChangeMutations } from '@reqquest/ui'
|
|
7
|
+
import { invalidateAll } from '$app/navigation'
|
|
8
|
+
import { api, type BasicRequestData } from '$internal/api'
|
|
9
|
+
import type { PageData } from '../../routes/requests/[id]/approve/[programKey]/$types'
|
|
10
|
+
import { uiRegistry } from '$local'
|
|
11
|
+
import DocumentExport from 'carbon-icons-svelte/lib/DocumentExport.svelte'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export let application: PageData['appRequest']['applications'][0]
|
|
15
|
+
export let basicRequestData: BasicRequestData
|
|
16
|
+
export let requestId: string
|
|
17
|
+
|
|
18
|
+
$: loading = false
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
let appRequestAction: '' | PhaseChangeMutations | 'reopen' | 'close' = ''
|
|
23
|
+
async function onAppRequestAction () {
|
|
24
|
+
loading = true
|
|
25
|
+
if ((phaseChangeMutations as readonly string[]).includes(appRequestAction)) {
|
|
26
|
+
await appRequestPhaseChange(appRequestAction as PhaseChangeMutations)
|
|
27
|
+
} else if (appRequestAction === 'close') {
|
|
28
|
+
await closeRequest()
|
|
29
|
+
} else if (appRequestAction === 'reopen') {
|
|
30
|
+
await reopenRequest()
|
|
31
|
+
}
|
|
32
|
+
appRequestAction = ''
|
|
33
|
+
loading = false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function appRequestPhaseChange (action: PhaseChangeMutations) {
|
|
37
|
+
const response = await api.appRequestPhaseChange(requestId, action)
|
|
38
|
+
await invalidateAll()
|
|
39
|
+
loading = false
|
|
40
|
+
if (!response.success) {
|
|
41
|
+
toasts.add({
|
|
42
|
+
type: 'error',
|
|
43
|
+
title: 'Action Failed',
|
|
44
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
45
|
+
})
|
|
46
|
+
} else {
|
|
47
|
+
toasts.add({
|
|
48
|
+
type: 'success',
|
|
49
|
+
message: `Successfully ${translateMutations[action]}.`
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
await invalidateAll()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function closeRequest () {
|
|
56
|
+
const response = await api.closeAppRequest(requestId)
|
|
57
|
+
await invalidateAll()
|
|
58
|
+
if (!response.success) {
|
|
59
|
+
toasts.add({
|
|
60
|
+
type: 'error',
|
|
61
|
+
title: `Could not close ${uiRegistry.getWord('appRequest').toLowerCase()}`,
|
|
62
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
63
|
+
})
|
|
64
|
+
} else {
|
|
65
|
+
toasts.add({
|
|
66
|
+
type: 'success',
|
|
67
|
+
message: `${uiRegistry.getWord('appRequest')} closed.`
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function reopenRequest () {
|
|
74
|
+
const response = await api.reopenAppRequest(requestId)
|
|
75
|
+
await invalidateAll()
|
|
76
|
+
if (!response.success) {
|
|
77
|
+
toasts.add({
|
|
78
|
+
type: 'error',
|
|
79
|
+
title: `Could not reopen ${uiRegistry.getWord('appRequest').toLowerCase()}`,
|
|
80
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
81
|
+
})
|
|
82
|
+
} else {
|
|
83
|
+
toasts.add({
|
|
84
|
+
type: 'success',
|
|
85
|
+
message: `${uiRegistry.getWord('appRequest')} reopened.`
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let appAction: '' | 'advanceWorkflow' | 'reverseWorkflow' = ''
|
|
91
|
+
async function onAppAction () {
|
|
92
|
+
if (appAction === 'advanceWorkflow') {
|
|
93
|
+
await advanceWorkflow()
|
|
94
|
+
} else if (appAction === 'reverseWorkflow') {
|
|
95
|
+
await reverseWorkflow()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function advanceWorkflow () {
|
|
100
|
+
loading = true
|
|
101
|
+
const response = await api.advanceWorkflow(application.id)
|
|
102
|
+
await invalidateAll()
|
|
103
|
+
loading = false
|
|
104
|
+
if (!response.success) {
|
|
105
|
+
toasts.add({
|
|
106
|
+
type: 'error',
|
|
107
|
+
title: 'Could not advance application',
|
|
108
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
109
|
+
})
|
|
110
|
+
} else {
|
|
111
|
+
toasts.add({
|
|
112
|
+
type: 'success',
|
|
113
|
+
message: 'Application advanced.'
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function reverseWorkflow () {
|
|
119
|
+
loading = true
|
|
120
|
+
const response = await api.reverseWorkflow(application.id)
|
|
121
|
+
await invalidateAll()
|
|
122
|
+
loading = false
|
|
123
|
+
if (!response.success) {
|
|
124
|
+
toasts.add({
|
|
125
|
+
type: 'error',
|
|
126
|
+
title: 'Could not reverse application workflow',
|
|
127
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
128
|
+
})
|
|
129
|
+
} else {
|
|
130
|
+
toasts.add({
|
|
131
|
+
type: 'success',
|
|
132
|
+
message: 'Application workflow reversed.'
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
{#if loading}
|
|
139
|
+
<Loading />
|
|
140
|
+
{/if}
|
|
141
|
+
|
|
142
|
+
<InfoCard title='Reviewer Actions'>
|
|
143
|
+
<div slot='header-right'>
|
|
144
|
+
<Button aria-label='export' href={`/requests/${requestId}/approve/export`} kind="secondary" size="small" icon={DocumentExport} class="ml-[32px]" />
|
|
145
|
+
</div>
|
|
146
|
+
<div class="flow">
|
|
147
|
+
<!-- <Select bind:selected={appAction} labelText="Next step" size="sm">
|
|
148
|
+
<SelectItem value="" text="Choose one" />
|
|
149
|
+
<SelectItem disabled={!application.actions.advanceWorkflow} value="advanceWorkflow" text={'Send to ' + (application.nextWorkflowStage?.title ?? (!application.workflowStage?.blocking ? 'Complete' : 'Review Complete'))} />
|
|
150
|
+
<SelectItem disabled={!application.actions.reverseWorkflow} value="reverseWorkflow" text={'Return to ' + (application.previousWorkflowStage?.title ?? 'Review')} />
|
|
151
|
+
</Select>
|
|
152
|
+
<Button disabled={!appAction} on:click={onAppAction} size="small" class="[ w-full ]">Update program</Button> -->
|
|
153
|
+
|
|
154
|
+
{#if basicRequestData}
|
|
155
|
+
<Select bind:selected={appRequestAction} labelText={`${uiRegistry.getWord('appRequest')} action`} size="sm">
|
|
156
|
+
<SelectItem value="" text="Choose one" />
|
|
157
|
+
<SelectItem disabled={!basicRequestData.actions.completeReview} value="completeReview" text="Complete Review" />
|
|
158
|
+
<SelectItem disabled={!basicRequestData.actions.completeRequest} value="completeRequest" text="Complete Request" />
|
|
159
|
+
<SelectItem disabled={!basicRequestData.actions.reopen} value="reopen" text="Reopen Request" />
|
|
160
|
+
<SelectItem disabled={!basicRequestData.actions.close} value="close" text="Close Request" />
|
|
161
|
+
<SelectItem disabled={!basicRequestData.actions.returnToApplicant} value="returnToApplicant" text="Return To Applicant" />
|
|
162
|
+
<SelectItem disabled={!basicRequestData.actions.returnToOffer} value="returnToOffer" text="Return To Offer" />
|
|
163
|
+
<SelectItem disabled={!basicRequestData.actions.returnToReview} value="returnToReview" text="Return To Review" />
|
|
164
|
+
<SelectItem disabled={!basicRequestData.actions.returnToNonBlocking} value="returnToNonBlocking" text="Return To Final Workflow Tasks" />
|
|
165
|
+
<SelectItem disabled={!basicRequestData.actions.submit} value="submitAppRequest" text="Submit On Behalf Of Applicant" />
|
|
166
|
+
<SelectItem disabled={!basicRequestData.actions.acceptOffer} value="acceptOffer" text="Accept Offer On Behalf Of Applicant" />
|
|
167
|
+
</Select>
|
|
168
|
+
<Button disabled={!appRequestAction} on:click={onAppRequestAction} size="small" class="[ w-full ]">Update application</Button>
|
|
169
|
+
{/if}
|
|
170
|
+
</div>
|
|
171
|
+
</InfoCard>
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Form } from '@txstate-mws/svelte-forms'
|
|
3
|
+
import MachineLearning from 'carbon-icons-svelte/lib/MachineLearning.svelte'
|
|
4
|
+
import WarningFilled from 'carbon-icons-svelte/lib/WarningFilled.svelte'
|
|
5
|
+
import Edit from 'carbon-icons-svelte/lib/Edit.svelte'
|
|
6
|
+
import SidePanelOpenFilled from 'carbon-icons-svelte/lib/SidePanelOpenFilled.svelte'
|
|
7
|
+
import { enumRequirementStatus, enumRequirementType, PromptIndicators, statusReasonsToFeedback, translateMutations, type PhaseChangeMutations, type StatusReason } from '@reqquest/ui'
|
|
8
|
+
import { isInlineReviewerEditPrompt, isDisplayablePrompt, hasDisplayablePrompts, RenderDisplayComponent, applicantRequirementTypes, reviewerRequirementTypes, api, PromptSaveQueue, type BasicRequestData } from '$internal'
|
|
9
|
+
import { FormInlineNotification, Panel, PanelFormDialog, TagSet } from '@txstate-mws/carbon-svelte'
|
|
10
|
+
import { Tooltip } from 'carbon-components-svelte'
|
|
11
|
+
import { uiRegistry } from '$local';
|
|
12
|
+
import { Button } from 'carbon-components-svelte'
|
|
13
|
+
import type { PageData } from '../../routes/requests/[id]/approve/[programKey]/$types'
|
|
14
|
+
import { invalidate, invalidateAll } from '$app/navigation'
|
|
15
|
+
import Review from "carbon-icons-svelte/lib/Review.svelte";
|
|
16
|
+
import DelayedSkeleton from '$internal/components/DelayedSkeleton.svelte';
|
|
17
|
+
import WarningIconYellow from './WarningIconYellow.svelte';
|
|
18
|
+
import MissingDefinitionNotification from './MissingDefinitionNotification.svelte';
|
|
19
|
+
import { toasts } from '@txstate-mws/svelte-components';
|
|
20
|
+
import { randomid } from 'txstate-utils'
|
|
21
|
+
|
|
22
|
+
type ApplicationRequirement = (typeof appRequest)['applications'][number]['requirements'][number]
|
|
23
|
+
|
|
24
|
+
export let sections: { key: string, title: string, requirements: ApplicationRequirement[] }[]
|
|
25
|
+
/** the stage panel that gets the "Send to" button once the application is ready for workflow */
|
|
26
|
+
export let lastStageKey: string | undefined = undefined
|
|
27
|
+
export let promptIndicator: Record<string, any>
|
|
28
|
+
export let promptStatusReasons: Record<string, StatusReason[]> = {}
|
|
29
|
+
export let loading = false
|
|
30
|
+
export let appRequest: PageData['appRequest']
|
|
31
|
+
export let application: PageData['appRequest']['applications'][0]
|
|
32
|
+
export let basicRequestData: BasicRequestData
|
|
33
|
+
|
|
34
|
+
type PromptExtraData = Awaited<ReturnType<typeof api.getPromptData>>
|
|
35
|
+
type Prompt = PageData['appRequest']['applications'][0]['requirements'][0]['prompts'][0]
|
|
36
|
+
type PromptWithExtra = Prompt & PromptExtraData & { allowSaveWithoutChanges?: boolean }
|
|
37
|
+
let editingPromptWithData: PromptWithExtra | undefined = undefined
|
|
38
|
+
let promptBeingEdited: Prompt | undefined = undefined
|
|
39
|
+
let editingViaReviewDialog = false
|
|
40
|
+
let showPromptDialog = false
|
|
41
|
+
let fetchingEditPrompt = false
|
|
42
|
+
|
|
43
|
+
// every save on this screen writes the same appRequest row, so they all run in sequence through one
|
|
44
|
+
// queue that threads the row's dataVersion from each save into the next
|
|
45
|
+
let saveQueue = new PromptSaveQueue()
|
|
46
|
+
let saveQueueAppRequestId: string | undefined
|
|
47
|
+
$: {
|
|
48
|
+
if (saveQueueAppRequestId !== appRequest.id) {
|
|
49
|
+
saveQueueAppRequestId = appRequest.id
|
|
50
|
+
saveQueue = new PromptSaveQueue()
|
|
51
|
+
}
|
|
52
|
+
saveQueue.adopt(appRequest.dataVersion)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function editPrompt (prompt: Prompt, allowSaveWithoutChanges: boolean = false, viaReviewDialog: boolean = false) {
|
|
56
|
+
return async () => {
|
|
57
|
+
if (fetchingEditPrompt) return
|
|
58
|
+
promptBeingEdited = prompt
|
|
59
|
+
editingViaReviewDialog = viaReviewDialog
|
|
60
|
+
fetchingEditPrompt = true
|
|
61
|
+
showPromptDialog = true
|
|
62
|
+
try {
|
|
63
|
+
// empty the save queue before fetching, so the version we pin to the modal includes everything
|
|
64
|
+
// this session has typed inline.
|
|
65
|
+
await saveQueue.drain()
|
|
66
|
+
const extra = await api.getPromptData(appRequest.id, prompt.id)
|
|
67
|
+
editingPromptWithData = { ...prompt, ...extra, allowSaveWithoutChanges }
|
|
68
|
+
} finally {
|
|
69
|
+
fetchingEditPrompt = false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Diff issue, but needed because a bare document.querySelector('.tcbs-dialog') grabs the first
|
|
74
|
+
// dialog in document order, and the page keeps note form and the notes list permanently mounted.
|
|
75
|
+
// so need to be specific
|
|
76
|
+
const editDialogFormId = randomid()
|
|
77
|
+
function editDialogElement () {
|
|
78
|
+
return document.getElementById(editDialogFormId)?.closest('.tcbs-dialog') ?? undefined
|
|
79
|
+
}
|
|
80
|
+
function hideEditModalPrompt () { // keep the dialog in the DOM so on:saved fires, but remove from view
|
|
81
|
+
if (!showPromptDialog) return
|
|
82
|
+
editDialogElement()?.classList.add('invisible')
|
|
83
|
+
}
|
|
84
|
+
function showEditModalPrompt () {
|
|
85
|
+
editDialogElement()?.classList.remove('invisible')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function closePromptDialog () {
|
|
89
|
+
fetchingEditPrompt = false
|
|
90
|
+
showPromptDialog = false
|
|
91
|
+
editingPromptWithData = undefined
|
|
92
|
+
promptBeingEdited = undefined
|
|
93
|
+
editingViaReviewDialog = false
|
|
94
|
+
}
|
|
95
|
+
function onPromptSubmit (prompt: any, modal = false) {
|
|
96
|
+
return async (data: any) => {
|
|
97
|
+
if (modal) { loading = true; hideEditModalPrompt() }
|
|
98
|
+
try {
|
|
99
|
+
// the modal is editing the data it fetched when it opened, so it saves against that version;
|
|
100
|
+
// inline forms carry whatever version the queue is holding when their turn comes up
|
|
101
|
+
const pinnedVersion = modal ? prompt.dataVersion : undefined
|
|
102
|
+
const response = await saveQueue.save(async dataVersion => {
|
|
103
|
+
return (!prompt.allowSaveWithoutChanges)
|
|
104
|
+
? await api.updatePrompt(prompt.id, data, false, dataVersion)
|
|
105
|
+
: await api.updatePrompt(prompt.id, data, false, dataVersion, true) // triggers from review corrections edit selection, allow saving without changes to handle invalidate prompts that require no changes
|
|
106
|
+
}, pinnedVersion)
|
|
107
|
+
if (modal && !response.success) { loading = false; showEditModalPrompt() }
|
|
108
|
+
// prev resp.data was replacing the store's data with a wrongly-shaped object and
|
|
109
|
+
// re-baselines beforeUserChanges to it. Reduce to this prompt's slice like ApplicantPromptPage.onSubmit
|
|
110
|
+
return { ...response, data: response.data?.[prompt.key] }
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (modal) { loading = false; showEditModalPrompt() }
|
|
113
|
+
throw e
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function onPromptValidate (prompt: any) {
|
|
119
|
+
return async (data: any) => {
|
|
120
|
+
const response = await api.updatePrompt(prompt.id, data, true, prompt.dataVersion)
|
|
121
|
+
return response.messages
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function refreshReviewData () {
|
|
126
|
+
// Scoped instead of invalidateAll()
|
|
127
|
+
await Promise.all([invalidate('request:approve'), invalidate('request:id')])
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function appRequestPhaseChange (action: PhaseChangeMutations) {
|
|
131
|
+
const response = await api.appRequestPhaseChange(appRequest.id, action)
|
|
132
|
+
await invalidateAll()
|
|
133
|
+
loading = false
|
|
134
|
+
if (!response.success) {
|
|
135
|
+
toasts.add({
|
|
136
|
+
type: 'error',
|
|
137
|
+
title: 'Action Failed',
|
|
138
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
139
|
+
})
|
|
140
|
+
} else {
|
|
141
|
+
toasts.add({
|
|
142
|
+
type: 'success',
|
|
143
|
+
message: `Successfully ${translateMutations[action]}.`
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
await invalidateAll()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function advanceWorkflow () {
|
|
150
|
+
loading = true
|
|
151
|
+
const response = await api.advanceWorkflow(application.id)
|
|
152
|
+
await invalidateAll()
|
|
153
|
+
if (basicRequestData?.actions.completeReview) return await appRequestPhaseChange('completeReview')
|
|
154
|
+
if (basicRequestData?.actions.completeRequest) return await appRequestPhaseChange('completeRequest')
|
|
155
|
+
loading = false
|
|
156
|
+
if (!response.success) {
|
|
157
|
+
toasts.add({
|
|
158
|
+
type: 'error',
|
|
159
|
+
title: 'Could not advance application',
|
|
160
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
161
|
+
})
|
|
162
|
+
} else {
|
|
163
|
+
toasts.add({
|
|
164
|
+
type: 'success',
|
|
165
|
+
message: 'Application advanced.'
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function reverseWorkflow () {
|
|
171
|
+
loading = true
|
|
172
|
+
const response = await api.reverseWorkflow(application.id)
|
|
173
|
+
await invalidateAll()
|
|
174
|
+
loading = false
|
|
175
|
+
if (!response.success) {
|
|
176
|
+
toasts.add({
|
|
177
|
+
type: 'error',
|
|
178
|
+
title: 'Could not reverse application workflow',
|
|
179
|
+
message: response.messages.map(m => m.message).join('\n') || 'An unknown error occurred.'
|
|
180
|
+
})
|
|
181
|
+
} else {
|
|
182
|
+
toasts.add({
|
|
183
|
+
type: 'success',
|
|
184
|
+
message: 'Application workflow reversed.'
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// answered/total reviewer questions in a section
|
|
190
|
+
function reviewerQuestionTally (section: { requirements: ApplicationRequirement[] }) {
|
|
191
|
+
const questions = new Map<string, ApplicationRequirement['prompts'][number]>()
|
|
192
|
+
for (const requirement of section.requirements) {
|
|
193
|
+
if (!reviewerRequirementTypes.has(requirement.type)) continue
|
|
194
|
+
for (const prompt of requirement.prompts.filter(isDisplayablePrompt)) {
|
|
195
|
+
if (!uiRegistry.getPrompt(prompt.key)?.automation) questions.set(prompt.id, prompt)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const answered = [...questions.values()].filter(p => p.answered && !p.invalidated).length
|
|
199
|
+
return { answered, total: questions.size }
|
|
200
|
+
}
|
|
201
|
+
$: reviewerQuestionTallies = Object.fromEntries(sections.map(section => [section.key, reviewerQuestionTally(section)]))
|
|
202
|
+
|
|
203
|
+
$: readyForWorkflow = !application.nextWorkflowStage && application.phase === 'READY_FOR_WORKFLOW' ? lastStageKey : undefined
|
|
204
|
+
// otherwise the button sits on the last non-stage panel on the page, wherever the layout put it
|
|
205
|
+
$: latestWorkflow = readyForWorkflow ?? sections.filter(section => section.requirements.every(r => !r.workflowStage?.key)).pop()?.key
|
|
206
|
+
// the one panel "Edit answers" sits on: the stage being reversed onto, or the last review panel when reversing back into review.
|
|
207
|
+
// Requirement statuses don't matter here - a disqualified review must be reversible, and the API decides via actions.reverseWorkflow
|
|
208
|
+
$: reverseTarget = application.previousWorkflowStage
|
|
209
|
+
? sections.find(section => section.requirements[0]?.workflowStage?.key === application.previousWorkflowStage?.key)?.key
|
|
210
|
+
: sections.filter(section => section.requirements.every(r => !r.workflowStage?.key)).pop()?.key
|
|
211
|
+
|
|
212
|
+
/** inline autoSave forms: refresh statuses/indicators only, never touch the live form */
|
|
213
|
+
async function onPromptAutoSaved () {
|
|
214
|
+
await refreshReviewData()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** modal edit form: close first, so the dialog is not left hidden for the whole refresh */
|
|
218
|
+
async function onPromptSaved () {
|
|
219
|
+
loading = false
|
|
220
|
+
closePromptDialog()
|
|
221
|
+
await refreshReviewData()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
</script>
|
|
225
|
+
{#each sections as section (section.key)}
|
|
226
|
+
<Panel title={section.title} expandable expanded>
|
|
227
|
+
<svelte:fragment slot="headerRight">
|
|
228
|
+
{@const tally = reviewerQuestionTallies[section.key]}
|
|
229
|
+
{#if tally?.total}
|
|
230
|
+
<!-- visible pill is hidden from screen readers in favor of the spelled-out text, and the status role announces changes politely on autosave -->
|
|
231
|
+
<span role="status" class="reviewer-question-tally">
|
|
232
|
+
<span aria-hidden="true">
|
|
233
|
+
<TagSet small tags={[{ label: `${tally.answered}/${tally.total} answered`, type: tally.answered === tally.total ? 'green' : 'yellow' }]} />
|
|
234
|
+
</span>
|
|
235
|
+
<span class="bx--visually-hidden">{tally.answered} of {tally.total} questions answered</span>
|
|
236
|
+
</span>
|
|
237
|
+
{/if}
|
|
238
|
+
</svelte:fragment>
|
|
239
|
+
{#if section.requirements.some(hasDisplayablePrompts)}
|
|
240
|
+
<dl class="prompts">
|
|
241
|
+
{#each section.requirements as requirement (requirement.id)}
|
|
242
|
+
{#each requirement.prompts.filter(isDisplayablePrompt) as prompt (prompt.id)}
|
|
243
|
+
{@const def = uiRegistry.getPrompt(prompt.key)}
|
|
244
|
+
{@const isReviewerQuestion = reviewerRequirementTypes.has(requirement.type) && !def?.automation}
|
|
245
|
+
{@const isAutomation = !!def?.automation}
|
|
246
|
+
{@const editMode = isInlineReviewerEditPrompt(def, requirement.type, prompt)}
|
|
247
|
+
{@const opensReviewDialog = isReviewerQuestion && def?.formMode === 'full'}
|
|
248
|
+
{@const small = editMode && def?.formMode !== 'full' ? def?.formMode !== 'large' : def?.displayMode !== 'large'}
|
|
249
|
+
{@const large = editMode && def?.formMode !== 'full' ? def?.formMode === 'large' : def?.displayMode === 'large'}
|
|
250
|
+
{@const dtid = `dt-title-${prompt.id}`}
|
|
251
|
+
{@const currentWorkflow = application.workflowStage ? application.workflowStage?.key === section.requirements[0]?.workflowStage?.key : undefined}
|
|
252
|
+
{@const disabled = ((!editMode && def?.formMode !== 'full') && (currentWorkflow || requirement.status !== enumRequirementStatus.MET))}
|
|
253
|
+
{#snippet editButtons()}
|
|
254
|
+
{#if prompt.actions.update}
|
|
255
|
+
{#if prompt.invalidated && !applicantRequirementTypes.has(requirement.type)}
|
|
256
|
+
<Button kind="primary" size="field" class="prompt-edit mr-2" icon={Review} iconDescription="Review corrections" id="edit-btn-{prompt.id}" aria-labelledby="edit-btn-{prompt.id} {dtid}" on:click={editPrompt(prompt, true)} />
|
|
257
|
+
{:else if opensReviewDialog}
|
|
258
|
+
<Button kind="secondary" size="field" icon={SidePanelOpenFilled} class="prompt-dialog-open" id="edit-btn-{prompt.id}" aria-labelledby="edit-btn-{prompt.id} {dtid}" on:click={editPrompt(prompt, false, true)}>{uiRegistry.reviewerDialogButtonText(prompt.key)}</Button>
|
|
259
|
+
{:else}
|
|
260
|
+
<Button kind="ghost" size="field" icon={Edit} iconDescription="Edit" class="prompt-edit" id="edit-btn-{prompt.id}" aria-labelledby="edit-btn-{prompt.id} {dtid}" on:click={editPrompt(prompt)} />
|
|
261
|
+
{/if}
|
|
262
|
+
{/if}
|
|
263
|
+
{/snippet}
|
|
264
|
+
<dt class:small class:large class:isReviewerQuestion class:disabled={disabled} class:bg-tagyellow-200={isAutomation}>
|
|
265
|
+
{#if promptIndicator[prompt.key]?.indicator && (promptIndicator[prompt.key].indicator !== PromptIndicators.PENDING || editMode)}
|
|
266
|
+
<div class="indicator-tooltip">
|
|
267
|
+
<Tooltip align="start" direction="bottom">
|
|
268
|
+
<svelte:fragment slot="icon">
|
|
269
|
+
{#if promptIndicator[prompt.key]?.indicator === PromptIndicators.AUTOMATION}
|
|
270
|
+
<MachineLearning size={20} />
|
|
271
|
+
{:else if promptIndicator[prompt.key]?.indicator === PromptIndicators.WARNING}
|
|
272
|
+
<WarningIconYellow />
|
|
273
|
+
{:else if promptIndicator[prompt.key]?.indicator === PromptIndicators.DISQUALIFYING}
|
|
274
|
+
<WarningFilled size={20} class="disqualifying-icon" style="fill: var(--cds-support-01, #da1e28)" />
|
|
275
|
+
{:else if promptIndicator[prompt.key]?.indicator === PromptIndicators.PENDING}
|
|
276
|
+
<svg class="pending-dot" xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="none" aria-hidden="true">
|
|
277
|
+
<circle cx="4" cy="4" r="4" />
|
|
278
|
+
</svg>
|
|
279
|
+
{/if}
|
|
280
|
+
</svelte:fragment>
|
|
281
|
+
{promptIndicator[prompt.key]?.reason}
|
|
282
|
+
</Tooltip>
|
|
283
|
+
</div>
|
|
284
|
+
{/if}
|
|
285
|
+
<div id={dtid}>
|
|
286
|
+
{prompt.title}
|
|
287
|
+
</div>
|
|
288
|
+
{#if large && !editMode}
|
|
289
|
+
{@render editButtons()}
|
|
290
|
+
{/if}
|
|
291
|
+
</dt>
|
|
292
|
+
<dd class="flow" class:small class:large class:isReviewerQuestion class:disabled={disabled} class:bg-tagyellow-200={isAutomation} role={editMode ? 'group' : undefined} aria-labelledby={dtid}>
|
|
293
|
+
{#if editMode}
|
|
294
|
+
<Form bind:store={saveQueue.stores[prompt.id]} preload={prompt.preloadData} submit={onPromptSubmit(prompt)} validate={onPromptValidate(prompt)} autoSave on:autosaved={onPromptAutoSaved} let:data let:messages>
|
|
295
|
+
<svelte:component this={def?.formComponent} {data} appRequestData={appRequest.data} prestageData={{latest: prompt.prestageData, current: appRequest.data[prompt.key]?.__prestage}} fetched={prompt.fetchedData} configData={prompt.configurationData} gatheredConfigData={prompt.gatheredConfigData} invalidated={prompt.invalidated} invalidatedReason={prompt.invalidatedReason} statusReasons={promptStatusReasons[prompt.key] ?? []} />
|
|
296
|
+
{#each statusReasonsToFeedback(promptStatusReasons[prompt.key], { blamedOnly: true, statuses: uiRegistry.statusReasonNotificationStatuses(prompt.key, 'reviewer') }) as message}
|
|
297
|
+
<FormInlineNotification {message} />
|
|
298
|
+
{/each}
|
|
299
|
+
{#each messages as message (message.message, message.type)}
|
|
300
|
+
<FormInlineNotification {message} />
|
|
301
|
+
{/each}
|
|
302
|
+
</Form>
|
|
303
|
+
{:else}
|
|
304
|
+
<div class="pr-4">
|
|
305
|
+
<RenderDisplayComponent
|
|
306
|
+
{def}
|
|
307
|
+
appRequestId={appRequest.id}
|
|
308
|
+
appData={appRequest.data}
|
|
309
|
+
prompt={prompt}
|
|
310
|
+
prestageData={{latest: prompt.prestageData, current: appRequest.data[prompt.key]?.__prestage}}
|
|
311
|
+
configData={prompt.configurationData}
|
|
312
|
+
gatheredConfigData={prompt.gatheredConfigData} showMoot
|
|
313
|
+
statusReasons={promptStatusReasons[prompt.key] ?? []}
|
|
314
|
+
showInlineReviewNotification={prompt.actions.update && prompt.invalidated && !applicantRequirementTypes.has(requirement.type)} />
|
|
315
|
+
</div>
|
|
316
|
+
{#if !large}
|
|
317
|
+
{@render editButtons()}
|
|
318
|
+
{/if}
|
|
319
|
+
{/if}
|
|
320
|
+
</dd>
|
|
321
|
+
{/each}
|
|
322
|
+
{/each}
|
|
323
|
+
</dl>
|
|
324
|
+
{:else if section.requirements[0]?.workflowStage && section.requirements[0]?.workflowStage.key === application.workflowStage?.key}
|
|
325
|
+
No questions need to be answered in this section. You may advance to the next step.
|
|
326
|
+
{:else}
|
|
327
|
+
No questions in this section.
|
|
328
|
+
{/if}
|
|
329
|
+
{#if !section.requirements.every(r => r.type === enumRequirementType.PREQUAL) && (application.actions?.advanceWorkflow || application.actions?.reverseWorkflow)}
|
|
330
|
+
<div class="flex justify-end mt-8">
|
|
331
|
+
{#if application.actions?.advanceWorkflow && (application.workflowStage?.key ? application.workflowStage?.key === section.requirements[0]?.workflowStage?.key : section.key === latestWorkflow)}
|
|
332
|
+
<Button size="small" on:click={advanceWorkflow}>{'Send to ' + (application.nextWorkflowStage?.title ?? (!application.workflowStage?.blocking ? 'Complete' : 'Review Complete'))}</Button>
|
|
333
|
+
{:else if application.actions?.reverseWorkflow && section.key === reverseTarget}
|
|
334
|
+
<Button kind='secondary' size="small" on:click={reverseWorkflow}>Edit answers</Button>
|
|
335
|
+
{/if}
|
|
336
|
+
</div>
|
|
337
|
+
{/if}
|
|
338
|
+
</Panel>
|
|
339
|
+
{/each}
|
|
340
|
+
|
|
341
|
+
{#if showPromptDialog && promptBeingEdited}
|
|
342
|
+
{@const formMode = uiRegistry.getPrompt(promptBeingEdited.key)?.formMode === 'full' ? 'large' : undefined}
|
|
343
|
+
<PanelFormDialog
|
|
344
|
+
id={editDialogFormId}
|
|
345
|
+
title={editingPromptWithData?.invalidated ? `Review correction "${editingPromptWithData?.title}"` : editingViaReviewDialog ? uiRegistry.reviewerDialogButtonText(promptBeingEdited.key) : 'Edit Prompt'}
|
|
346
|
+
bind:open={showPromptDialog}
|
|
347
|
+
on:cancel={closePromptDialog}
|
|
348
|
+
submit={onPromptSubmit(editingPromptWithData, true)}
|
|
349
|
+
validate={onPromptValidate(editingPromptWithData)}
|
|
350
|
+
on:saved={onPromptSaved}
|
|
351
|
+
disableSaveUntilChanged={!editingPromptWithData?.allowSaveWithoutChanges} // allow saving without changes if prompt was previously invalidated ...accomodates reviewer saying no changes required on correction check
|
|
352
|
+
centered={!formMode}
|
|
353
|
+
size={formMode}
|
|
354
|
+
preload={editingPromptWithData?.preloadData}
|
|
355
|
+
let:data
|
|
356
|
+
>
|
|
357
|
+
{#if editingPromptWithData}
|
|
358
|
+
{@const def = uiRegistry.getPrompt(editingPromptWithData.key)}
|
|
359
|
+
<div class='font-medium text-center mt-2'>
|
|
360
|
+
<p class="text-xl font-medium ">{editingPromptWithData.title}</p>
|
|
361
|
+
</div>
|
|
362
|
+
{#if def?.formComponent == null}
|
|
363
|
+
<MissingDefinitionNotification kind="prompt" definitionKey={editingPromptWithData.key} />
|
|
364
|
+
{:else}
|
|
365
|
+
<svelte:component
|
|
366
|
+
this={def.formComponent}
|
|
367
|
+
appRequestId={appRequest.id}
|
|
368
|
+
{data}
|
|
369
|
+
appRequestData={editingPromptWithData.data}
|
|
370
|
+
prestageData={{
|
|
371
|
+
latest: editingPromptWithData.prestageData,
|
|
372
|
+
current: appRequest.data[editingPromptWithData.key]?.__prestage
|
|
373
|
+
}}
|
|
374
|
+
fetched={editingPromptWithData.fetchedData}
|
|
375
|
+
configData={editingPromptWithData.configurationData}
|
|
376
|
+
gatheredConfigData={editingPromptWithData.gatheredConfigData}
|
|
377
|
+
invalidated={editingPromptWithData.invalidated}
|
|
378
|
+
invalidatedReason={editingPromptWithData.invalidatedReason}
|
|
379
|
+
statusReasons={promptStatusReasons[editingPromptWithData.key] ?? []}
|
|
380
|
+
/>
|
|
381
|
+
{#each statusReasonsToFeedback(promptStatusReasons[editingPromptWithData.key], { blamedOnly: true, statuses: uiRegistry.statusReasonNotificationStatuses(editingPromptWithData.key, 'reviewer') }) as message}
|
|
382
|
+
<FormInlineNotification {message} />
|
|
383
|
+
{/each}
|
|
384
|
+
{/if}
|
|
385
|
+
{:else if fetchingEditPrompt}
|
|
386
|
+
{@const loader = uiRegistry.getPrompt(promptBeingEdited.key)?.loader}
|
|
387
|
+
<div class='font-medium text-center mt-2'>
|
|
388
|
+
<p class="text-xl font-medium ">{promptBeingEdited.title}</p>
|
|
389
|
+
</div>
|
|
390
|
+
{#if loader}
|
|
391
|
+
<DelayedSkeleton {loader} />
|
|
392
|
+
{/if}
|
|
393
|
+
{/if}
|
|
394
|
+
</PanelFormDialog>
|
|
395
|
+
{/if}
|
|
396
|
+
|
|
397
|
+
<style>
|
|
398
|
+
dl.prompts {
|
|
399
|
+
display: grid;
|
|
400
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
401
|
+
align-items: stretch;
|
|
402
|
+
gap: 0;
|
|
403
|
+
margin: -16px;
|
|
404
|
+
}
|
|
405
|
+
.prompts dt, .prompts dd {
|
|
406
|
+
position: relative;
|
|
407
|
+
border-bottom: 1px solid var(--cds-border-subtle);
|
|
408
|
+
padding: 1rem 15px;
|
|
409
|
+
}
|
|
410
|
+
.prompts dt.small {
|
|
411
|
+
padding-right: 15px;
|
|
412
|
+
}
|
|
413
|
+
.prompts dt.large, .prompts dd.large {
|
|
414
|
+
grid-column: span 2;
|
|
415
|
+
}
|
|
416
|
+
/* When a display component's root is a PromptDisplayGrid — directly, or through the
|
|
417
|
+
<form> of an inline edit (formMode 'large') — hand it our column tracks via subgrid
|
|
418
|
+
so its challenge/response rows align with the real prompt rows. */
|
|
419
|
+
.prompts dd.large:has(> :global(.prompt-display-grid)),
|
|
420
|
+
.prompts dd.large:has(> :global(form > .prompt-display-grid)) {
|
|
421
|
+
display: grid;
|
|
422
|
+
grid-template-columns: subgrid;
|
|
423
|
+
align-content: start;
|
|
424
|
+
padding: 0;
|
|
425
|
+
border-bottom: none;
|
|
426
|
+
--prompt-display-subgrid: subgrid;
|
|
427
|
+
--prompt-display-row-padding: 1rem 15px;
|
|
428
|
+
--prompt-display-challenge-padding-left: 32px;
|
|
429
|
+
}
|
|
430
|
+
.prompts dd.large:has(> :global(.prompt-display-grid)) > :global(*),
|
|
431
|
+
.prompts dd.large:has(> :global(form > .prompt-display-grid)) > :global(*) {
|
|
432
|
+
grid-column: 1 / -1;
|
|
433
|
+
}
|
|
434
|
+
.prompts dd.large > :global(form:has(> .prompt-display-grid)) {
|
|
435
|
+
display: grid;
|
|
436
|
+
grid-template-columns: subgrid;
|
|
437
|
+
}
|
|
438
|
+
.prompts dd.large > :global(form:has(> .prompt-display-grid) > *) {
|
|
439
|
+
grid-column: 1 / -1;
|
|
440
|
+
}
|
|
441
|
+
.prompts dt.isReviewerQuestion, .prompts dd.isReviewerQuestion {
|
|
442
|
+
background-color: var(--background-cool-light);
|
|
443
|
+
}
|
|
444
|
+
.prompts dt.isReviewerQuestion.disabled, .prompts dd.isReviewerQuestion.disabled {
|
|
445
|
+
background-color: #f2f2f2;
|
|
446
|
+
color: #8C8C8C;
|
|
447
|
+
}
|
|
448
|
+
.prompts dt {
|
|
449
|
+
display: flex;
|
|
450
|
+
align-items: center;
|
|
451
|
+
gap: 4px;
|
|
452
|
+
padding-left: 32px;
|
|
453
|
+
}
|
|
454
|
+
.prompts dt :global(.bx--btn.prompt-edit),
|
|
455
|
+
.prompts dd :global(.bx--btn.prompt-edit) {
|
|
456
|
+
position: absolute;
|
|
457
|
+
top: 0.35rem;
|
|
458
|
+
right: 0;
|
|
459
|
+
margin: 0;
|
|
460
|
+
}
|
|
461
|
+
.prompts dt .dt-title {
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
gap: 4px;
|
|
465
|
+
min-width: 0;
|
|
466
|
+
}
|
|
467
|
+
/* a large prompt's open-in-dialog button sits at the start of the answer column, so the dt borrows
|
|
468
|
+
the prompt list's column tracks */
|
|
469
|
+
.prompts dt.large:has(> :global(.prompt-dialog-open)) {
|
|
470
|
+
display: grid;
|
|
471
|
+
grid-template-columns: subgrid;
|
|
472
|
+
padding: 0;
|
|
473
|
+
}
|
|
474
|
+
.prompts dt.large:has(> :global(.prompt-dialog-open)) > .dt-title {
|
|
475
|
+
padding: 1rem 15px 1rem 32px;
|
|
476
|
+
}
|
|
477
|
+
.prompts dt.large > :global(.bx--btn.prompt-dialog-open) {
|
|
478
|
+
grid-column: 2;
|
|
479
|
+
justify-self: start;
|
|
480
|
+
align-self: center;
|
|
481
|
+
margin: 0.5rem 15px;
|
|
482
|
+
}
|
|
483
|
+
.prompts dt .indicator-tooltip {
|
|
484
|
+
display: inline-block;
|
|
485
|
+
margin-left: -24px;
|
|
486
|
+
}
|
|
487
|
+
.prompts dt .indicator-tooltip :global(> div) {
|
|
488
|
+
line-height: 0;
|
|
489
|
+
}
|
|
490
|
+
.prompts dt .indicator-tooltip :global(> div .bx--tooltip--shown) {
|
|
491
|
+
line-height: 1.2;
|
|
492
|
+
}
|
|
493
|
+
.prompts dt :global(.warning-icon) {
|
|
494
|
+
fill: var(--cds-inverse-support-03, #f1c21b);
|
|
495
|
+
}
|
|
496
|
+
:global(.disqualifying-icon) {
|
|
497
|
+
fill: var(--cds-support-01, #da1e28);
|
|
498
|
+
}
|
|
499
|
+
.pending-dot {
|
|
500
|
+
width: 8px;
|
|
501
|
+
height: 8px;
|
|
502
|
+
aspect-ratio: 1/1;
|
|
503
|
+
fill: var(--cds-support-01, #da1e28);
|
|
504
|
+
}
|
|
505
|
+
:global(.panel:has(.isReviewerQuestion.disabled) .panel-header) {
|
|
506
|
+
background-color: #8C8C8C !important;
|
|
507
|
+
}
|
|
508
|
+
</style>
|