@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,181 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ErrorPage, ProgressNav, type RootStepItem, type StepItem } from '@txstate-mws/carbon-svelte'
|
|
3
|
+
import { setContext } from 'svelte'
|
|
4
|
+
import { writable } from 'svelte/store'
|
|
5
|
+
import { resolve } from '$app/paths'
|
|
6
|
+
import { page } from '$app/stores'
|
|
7
|
+
import { applicantVisiblePromptVisibilities, submissionRequirementTypes } from '$internal'
|
|
8
|
+
import { enumRequirementType } from '@reqquest/ui'
|
|
9
|
+
import type { LayoutData } from './$types'
|
|
10
|
+
|
|
11
|
+
export let data: LayoutData
|
|
12
|
+
$: ({ appRequestForExport, prequalPrompts, postqualPrompts, applicationsForNav } = data)
|
|
13
|
+
|
|
14
|
+
function getNavItems (..._: any[]) {
|
|
15
|
+
const navItems: RootStepItem[] = []
|
|
16
|
+
let nextHref: string | undefined
|
|
17
|
+
let prevHref: string | undefined
|
|
18
|
+
let foundCurrent = false
|
|
19
|
+
let lastprompt: string | undefined
|
|
20
|
+
for (const prompt of prequalPrompts) {
|
|
21
|
+
if (foundCurrent) {
|
|
22
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`)
|
|
23
|
+
foundCurrent = false
|
|
24
|
+
} else if ($page.params.promptId === prompt.id) {
|
|
25
|
+
foundCurrent = true
|
|
26
|
+
if (lastprompt) prevHref = resolve(`/requests/${appRequestForExport.id}/apply/${lastprompt}`)
|
|
27
|
+
}
|
|
28
|
+
lastprompt = prompt.id
|
|
29
|
+
navItems.push({
|
|
30
|
+
id: `prompt${prompt.id}`,
|
|
31
|
+
label: prompt.navTitle,
|
|
32
|
+
href: resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`),
|
|
33
|
+
type: $page.params.promptId === prompt.id
|
|
34
|
+
? 'current'
|
|
35
|
+
: prompt.answered && !prompt.invalidated
|
|
36
|
+
? 'complete'
|
|
37
|
+
: prompt.answered && prompt.invalidated
|
|
38
|
+
? 'warning'
|
|
39
|
+
: 'available',
|
|
40
|
+
substeps: []
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
const outstandingPrequal = prequalPrompts.find(p =>
|
|
44
|
+
(!p.answered || p.invalidated) && applicantVisiblePromptVisibilities.has(p.visibility) && !p.moot)
|
|
45
|
+
if (outstandingPrequal) {
|
|
46
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${outstandingPrequal.id}`)
|
|
47
|
+
} else {
|
|
48
|
+
if (foundCurrent) nextHref = resolve(`/requests/${appRequestForExport.id}/apply/programs`)
|
|
49
|
+
foundCurrent = false
|
|
50
|
+
if ($page.route.id === '/requests/[id]/apply/programs') {
|
|
51
|
+
const promptsUnderPrograms = applicationsForNav
|
|
52
|
+
.flatMap(app => app.requirements.filter(r => r.type === enumRequirementType.QUALIFICATION))
|
|
53
|
+
.flatMap(r => r.prompts)
|
|
54
|
+
.filter(r => !r.optOut)
|
|
55
|
+
const qualFinished = promptsUnderPrograms.every(p => p.answered && !p.invalidated)
|
|
56
|
+
const postFinished = postqualPrompts.every(p => p.answered && !p.invalidated)
|
|
57
|
+
if (!qualFinished) {
|
|
58
|
+
const prompt = promptsUnderPrograms.find(p => !p.answered || p.invalidated)
|
|
59
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt!.id}`)
|
|
60
|
+
} else if (!postFinished) {
|
|
61
|
+
const prompt = postqualPrompts.find(p => !p.answered || p.invalidated)
|
|
62
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt!.id}`)
|
|
63
|
+
} else {
|
|
64
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/review`)
|
|
65
|
+
}
|
|
66
|
+
if (lastprompt) {
|
|
67
|
+
prevHref = resolve(`/requests/${appRequestForExport.id}/apply/${lastprompt}`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const programReview: RootStepItem = {
|
|
71
|
+
id: 'programs',
|
|
72
|
+
label: 'Program review',
|
|
73
|
+
href: resolve(`/requests/${appRequestForExport.id}/apply/programs`),
|
|
74
|
+
type: $page.route.id === '/requests/[id]/apply/programs' ? 'current' : 'available',
|
|
75
|
+
substeps: []
|
|
76
|
+
}
|
|
77
|
+
navItems.push(programReview)
|
|
78
|
+
let pastProgramReview = false
|
|
79
|
+
for (const application of applicationsForNav) {
|
|
80
|
+
const substeps: StepItem[] = []
|
|
81
|
+
lastprompt = undefined
|
|
82
|
+
for (const requirement of application.requirements) {
|
|
83
|
+
if (!submissionRequirementTypes.has(requirement.type)) continue
|
|
84
|
+
for (const prompt of requirement.prompts) {
|
|
85
|
+
if (prompt.optOut) continue
|
|
86
|
+
if (foundCurrent) {
|
|
87
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`)
|
|
88
|
+
foundCurrent = false
|
|
89
|
+
}
|
|
90
|
+
if ($page.params.promptId === prompt.id) {
|
|
91
|
+
pastProgramReview = true
|
|
92
|
+
foundCurrent = true
|
|
93
|
+
if (lastprompt) prevHref = resolve(`/requests/${appRequestForExport.id}/apply/${lastprompt}`)
|
|
94
|
+
else prevHref = resolve(`/requests/${appRequestForExport.id}/apply/programs`)
|
|
95
|
+
}
|
|
96
|
+
lastprompt = prompt.id
|
|
97
|
+
substeps.push({
|
|
98
|
+
id: `prompt${prompt.id}`,
|
|
99
|
+
label: prompt.navTitle,
|
|
100
|
+
href: resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`),
|
|
101
|
+
type: $page.params.promptId === prompt.id
|
|
102
|
+
? 'current'
|
|
103
|
+
: prompt.answered && !prompt.invalidated
|
|
104
|
+
? 'complete'
|
|
105
|
+
: prompt.answered && prompt.invalidated
|
|
106
|
+
? 'warning'
|
|
107
|
+
: 'available'
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- it can definitely be both true and false, typescript is doing something crazy
|
|
112
|
+
if (foundCurrent) {
|
|
113
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/programs`)
|
|
114
|
+
foundCurrent = false
|
|
115
|
+
}
|
|
116
|
+
if (substeps.length > 0) {
|
|
117
|
+
navItems.push({
|
|
118
|
+
id: `application${application.id}`,
|
|
119
|
+
label: application.navTitle,
|
|
120
|
+
href: substeps[0].href,
|
|
121
|
+
type: substeps.some(s => s.type === 'warning')
|
|
122
|
+
? 'warning'
|
|
123
|
+
: substeps.some(s => s.type === 'current')
|
|
124
|
+
? 'current'
|
|
125
|
+
: substeps.every(s => s.type === 'complete')
|
|
126
|
+
? 'complete'
|
|
127
|
+
: 'available',
|
|
128
|
+
substeps
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
lastprompt = undefined
|
|
133
|
+
for (const prompt of postqualPrompts) {
|
|
134
|
+
if (foundCurrent) {
|
|
135
|
+
nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`)
|
|
136
|
+
foundCurrent = false
|
|
137
|
+
}
|
|
138
|
+
if ($page.params.promptId === prompt.id) {
|
|
139
|
+
pastProgramReview = true
|
|
140
|
+
foundCurrent = true
|
|
141
|
+
if (lastprompt) prevHref = resolve(`/requests/${appRequestForExport.id}/apply/${lastprompt}`)
|
|
142
|
+
else prevHref = resolve(`/requests/${appRequestForExport.id}/apply/programs`)
|
|
143
|
+
}
|
|
144
|
+
lastprompt = prompt.id
|
|
145
|
+
navItems.push({
|
|
146
|
+
id: `prompt${prompt.id}`,
|
|
147
|
+
label: prompt.navTitle,
|
|
148
|
+
href: resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`),
|
|
149
|
+
type: $page.params.promptId === prompt.id
|
|
150
|
+
? 'current'
|
|
151
|
+
: prompt.answered && !prompt.invalidated ? 'complete' : 'available',
|
|
152
|
+
substeps: []
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
if ($page.route.id === '/requests/[id]/apply/review') pastProgramReview = true
|
|
156
|
+
navItems.push({
|
|
157
|
+
id: 'review',
|
|
158
|
+
label: 'Review submission',
|
|
159
|
+
href: resolve(`/requests/${appRequestForExport.id}/apply/review`),
|
|
160
|
+
type: $page.route.id === '/requests/[id]/apply/review' ? 'current' : 'available',
|
|
161
|
+
substeps: []
|
|
162
|
+
})
|
|
163
|
+
if (pastProgramReview) programReview.type = 'complete'
|
|
164
|
+
}
|
|
165
|
+
nextHref ??= resolve(`/requests/${appRequestForExport.id}/apply/review`)
|
|
166
|
+
return { navItems, nextHref, prevHref }
|
|
167
|
+
}
|
|
168
|
+
$: ({ navItems, nextHref, prevHref } = getNavItems(appRequestForExport, prequalPrompts, $page))
|
|
169
|
+
$: notInNavigation = !navItems.some(item => item.type === 'current' || item.substeps.some(s => s.type === 'current'))
|
|
170
|
+
const nextHrefStore = writable({ nextHref, prevHref })
|
|
171
|
+
$: nextHrefStore.set({ nextHref, prevHref })
|
|
172
|
+
setContext('nextHref', nextHrefStore)
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<ProgressNav steps={navItems}>
|
|
176
|
+
{#if notInNavigation}
|
|
177
|
+
<ErrorPage error={{ status: 404, message: 'Not Found' }} />
|
|
178
|
+
{:else}
|
|
179
|
+
<slot />
|
|
180
|
+
{/if}
|
|
181
|
+
</ProgressNav>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { api } from '$internal'
|
|
2
|
+
import { error } from '@sveltejs/kit'
|
|
3
|
+
import type { LayoutLoad } from './$types'
|
|
4
|
+
|
|
5
|
+
export const load: LayoutLoad = async ({ params, depends }) => {
|
|
6
|
+
const { appRequest, ...applicationInfo } = await api.getAppRequestForExport(params.id)
|
|
7
|
+
depends('request:apply')
|
|
8
|
+
// separate depend key so the nav/answered state can be refreshed without re-running the prompt page load, which was remounting the current prompt and could flash a skeleton.
|
|
9
|
+
depends('request:apply:nav')
|
|
10
|
+
if (!appRequest.actions.viewApplyUI) throw error(404, 'This application cannot be edited at this time.')
|
|
11
|
+
return { ...applicationInfo, appRequestForExport: { ...appRequest, applications: undefined } }
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit'
|
|
2
|
+
import { resolve } from '$app/paths'
|
|
3
|
+
import { applicantVisiblePromptVisibilities } from '$internal'
|
|
4
|
+
import type { PageLoad } from './$types'
|
|
5
|
+
|
|
6
|
+
export const load: PageLoad = async ({ params, parent }) => {
|
|
7
|
+
const { prequalPrompts } = await parent()
|
|
8
|
+
for (const prompt of prequalPrompts) {
|
|
9
|
+
if ((!prompt.answered || prompt.invalidated) && applicantVisiblePromptVisibilities.has(prompt.visibility) && !prompt.moot) {
|
|
10
|
+
throw redirect(303, resolve(`/requests/${params.id}/apply/${prompt.id}`))
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
throw redirect(303, resolve(`/requests/${params.id}/apply/programs`))
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let title: string | undefined = undefined
|
|
3
|
+
export let subtitle: string | undefined = undefined
|
|
4
|
+
</script>
|
|
5
|
+
<div class="progress-nav-outer">
|
|
6
|
+
<div class="progress-nav-inner">
|
|
7
|
+
{#if title}<h2>{title}</h2>{/if}
|
|
8
|
+
{#if subtitle}<div class="progress-nav-subtitle">{subtitle}</div>{/if}
|
|
9
|
+
<slot />
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
.progress-nav-outer {
|
|
15
|
+
container-type: inline-size;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.progress-nav-inner {
|
|
19
|
+
width: min(90%, 800px);
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
}
|
|
22
|
+
@media (max-width: 600px) {
|
|
23
|
+
.progress-nav-inner {
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h2, .progress-nav-subtitle {
|
|
29
|
+
text-align: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h2 {
|
|
33
|
+
font-weight: bold;
|
|
34
|
+
font-size: 1.5rem;
|
|
35
|
+
margin-bottom: 1.2rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.progress-nav-subtitle {
|
|
39
|
+
font-size: 0.9rem;
|
|
40
|
+
margin-bottom: 1.2rem;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ApplicantPromptPage } from '$internal'
|
|
3
|
+
import DelayedSkeleton from '$internal/components/DelayedSkeleton.svelte';
|
|
4
|
+
import { uiRegistry } from '$local';
|
|
5
|
+
import type { PageData } from './$types'
|
|
6
|
+
|
|
7
|
+
export let data: PageData
|
|
8
|
+
$: loader = uiRegistry.getPrompt(data.basicPromptData.key)?.loader
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#await data.applicantPromptPromise}
|
|
14
|
+
<div class="prompt-intro flow max-w-screen-md mx-auto pt-10 px-6">
|
|
15
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
16
|
+
<h2 id="prompt-title" tabindex="-1" autofocus class="font-bold text-2xl leading-normal text-center">{data.basicPromptData.title}</h2>
|
|
17
|
+
</div>
|
|
18
|
+
{#if loader}
|
|
19
|
+
<DelayedSkeleton {loader} />
|
|
20
|
+
{/if}
|
|
21
|
+
{:then { prompt, appRequest }}
|
|
22
|
+
<ApplicantPromptPage data={{ prompt, appRequest, dataVersion: appRequest.dataVersion }} statusReasons={data.basicPromptData?.statusReasons ?? []} />
|
|
23
|
+
{/await}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { api } from '$internal'
|
|
2
|
+
import { error } from '@sveltejs/kit'
|
|
3
|
+
import type { PageLoad } from './$types'
|
|
4
|
+
|
|
5
|
+
export const load: PageLoad = async ({ url, params, depends, parent }) => {
|
|
6
|
+
const { promptsById } = await parent()
|
|
7
|
+
depends('request:apply')
|
|
8
|
+
const basicPromptData = promptsById[params.promptId]
|
|
9
|
+
const applicantPromptPromise = api.getApplicantPrompt(params.id, params.promptId)
|
|
10
|
+
return { applicantPromptPromise, basicPromptData }
|
|
11
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from 'carbon-components-svelte'
|
|
3
|
+
import { Panel } from '@txstate-mws/carbon-svelte'
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
import type { Writable } from 'svelte/store'
|
|
6
|
+
import { afterNavigate } from '$app/navigation'
|
|
7
|
+
import type { PageData } from './$types'
|
|
8
|
+
import ProgressNavContainer from '../ProgressNavContainer.svelte'
|
|
9
|
+
import { ApplicantProgramList, DEFAULT_APPLICANT_NO_PROGRAMS_MESSAGE, isHiddenFromApplicant, isIneligibleByApplicantRequirement } from '$internal'
|
|
10
|
+
import { uiRegistry } from '$local'
|
|
11
|
+
import { enumApplicationStatus } from '@reqquest/ui'
|
|
12
|
+
|
|
13
|
+
export let data: PageData
|
|
14
|
+
$: ({ applicationsForNav, appRequestForExport } = data)
|
|
15
|
+
$: eligibleApplicationsForNav = applicationsForNav.filter(a => a.ineligiblePhase == null)
|
|
16
|
+
$: ineligibleApplicationsForNav = applicationsForNav.filter(a => isIneligibleByApplicantRequirement(a) && !isHiddenFromApplicant(a))
|
|
17
|
+
$: noProgramsToList = !eligibleApplicationsForNav.length && !ineligibleApplicationsForNav.length && applicationsForNav.some(isHiddenFromApplicant)
|
|
18
|
+
|
|
19
|
+
let prevHref: string | undefined
|
|
20
|
+
let nextHref: string | undefined
|
|
21
|
+
const getNextHref = getContext<Writable<{ nextHref: string | undefined, prevHref: string | undefined }>>('nextHref')
|
|
22
|
+
$: ({ prevHref, nextHref } = $getNextHref)
|
|
23
|
+
|
|
24
|
+
afterNavigate(() => {
|
|
25
|
+
const h2 = document.querySelector('h2')
|
|
26
|
+
if (h2) {
|
|
27
|
+
h2.tabIndex = -1
|
|
28
|
+
h2.focus()
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<ProgressNavContainer title="Your potential benefits" subtitle={noProgramsToList ? (uiRegistry.config.applicantNoProgramsMessage ?? DEFAULT_APPLICANT_NO_PROGRAMS_MESSAGE) : 'Based on your responses so far, you may be eligible for the benefits listed below. If you believe you qualify for additional benefits, you may need to review your answers, complete additional questions, or provide supporting documentation before eligibility can be confirmed.'}>
|
|
34
|
+
<div class="max-w-screen-md mx-auto">
|
|
35
|
+
{#if eligibleApplicationsForNav.length > 0}
|
|
36
|
+
<ApplicantProgramList applications={eligibleApplicationsForNav} appRequest={appRequestForExport} />
|
|
37
|
+
{/if}
|
|
38
|
+
{#if eligibleApplicationsForNav.some(a => a.status === enumApplicationStatus.INELIGIBLE)}
|
|
39
|
+
<div class="program-helptext">
|
|
40
|
+
If you believe you should be eligible, read the tooltips above and review your answers. If you believe there is an error, please contact us.
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
44
|
+
{#if ineligibleApplicationsForNav.length > 0}
|
|
45
|
+
<div class="max-w-screen-md mx-auto">
|
|
46
|
+
<Panel title="Ineligible benefits" expandable={true} expanded={true}>
|
|
47
|
+
<ApplicantProgramList applications={ineligibleApplicationsForNav} appRequest={appRequestForExport} />
|
|
48
|
+
</Panel>
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
51
|
+
<div class='form-submit flex gap-12 justify-center mt-16'>
|
|
52
|
+
{#if prevHref}
|
|
53
|
+
<Button kind="ghost" href={prevHref}>Back</Button>
|
|
54
|
+
{/if}
|
|
55
|
+
<Button type="submit" href={nextHref}>
|
|
56
|
+
{#if nextHref?.endsWith('/review')}
|
|
57
|
+
Review {uiRegistry.getWord('appRequest')}
|
|
58
|
+
{:else}
|
|
59
|
+
Continue
|
|
60
|
+
{/if}
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
</ProgressNavContainer>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.program-helptext {
|
|
68
|
+
text-align: center;
|
|
69
|
+
font-size: 0.9rem;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { toasts } from '@txstate-mws/svelte-components'
|
|
3
|
+
import { Button, Loading } from 'carbon-components-svelte'
|
|
4
|
+
import Touch_1 from 'carbon-icons-svelte/lib/Touch_1.svelte'
|
|
5
|
+
import { getContext } from 'svelte'
|
|
6
|
+
import type { Writable } from 'svelte/store'
|
|
7
|
+
import { afterNavigate, goto } from '$app/navigation'
|
|
8
|
+
import { resolve } from '$app/paths'
|
|
9
|
+
import type { ResolvedPathname } from '$app/types'
|
|
10
|
+
import { api, ApplicationDetailsView } from '$internal'
|
|
11
|
+
import { enumAppRequestStatus } from '@reqquest/ui'
|
|
12
|
+
import type { PageData } from './$types'
|
|
13
|
+
import { uiRegistry } from '$local'
|
|
14
|
+
import { enumIneligiblePhases } from '@reqquest/ui'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This page will allow applicants to review all the information they've
|
|
18
|
+
* entered so far, and to navigate to any prompts that need correction, then
|
|
19
|
+
* allows them to submit the app request.
|
|
20
|
+
*/
|
|
21
|
+
export let data: PageData
|
|
22
|
+
$: ({ appRequestForExport, prequalPrompts, postqualPrompts, applicationsForNav } = data)
|
|
23
|
+
|
|
24
|
+
const nextHref = getContext<Writable<{ nextHref?: ResolvedPathname, prevHref?: ResolvedPathname }>>('nextHref')
|
|
25
|
+
$: previousHref = nextHref ? $nextHref.prevHref : undefined
|
|
26
|
+
|
|
27
|
+
$: loading = false
|
|
28
|
+
|
|
29
|
+
async function handleBack () {
|
|
30
|
+
if (previousHref) {
|
|
31
|
+
// eslint-disable-next-line svelte/no-navigation-without-resolve -- already resolved
|
|
32
|
+
await goto(previousHref)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function onSubmit () {
|
|
37
|
+
loading = true
|
|
38
|
+
const resp = await api.appRequestPhaseChange(appRequestForExport.id, 'submitAppRequest')
|
|
39
|
+
loading = false
|
|
40
|
+
if (resp.success) {
|
|
41
|
+
toasts.add({ type: 'success', title: 'Submission Succeeded', message: 'Successfully submitted application for review' })
|
|
42
|
+
await goto(resolve('/dashboards/applicant'))
|
|
43
|
+
} else {
|
|
44
|
+
toasts.add({ type: 'error', title: 'Submission Failed', message: resp.messages.map(m => m.message).join('\n') || 'There was an error submitting your application. Please try again later.' })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
afterNavigate(({ from }) => {
|
|
49
|
+
if (from?.url?.pathname) previousHref = from.url.pathname + from.url.search
|
|
50
|
+
const h2 = document.querySelector('h2')
|
|
51
|
+
if (h2) {
|
|
52
|
+
h2.tabIndex = -1
|
|
53
|
+
h2.focus()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
</script>
|
|
57
|
+
{#if loading}
|
|
58
|
+
<Loading />
|
|
59
|
+
{/if}
|
|
60
|
+
<div class:max-w-screen-md={uiRegistry.config.applicantReviewMaxWidth !== false} class:mx-auto={uiRegistry.config.applicantReviewMaxWidth !== false}>
|
|
61
|
+
<ApplicationDetailsView
|
|
62
|
+
appRequest={appRequestForExport}
|
|
63
|
+
applications={applicationsForNav}
|
|
64
|
+
appData={appRequestForExport.data}
|
|
65
|
+
{prequalPrompts}
|
|
66
|
+
{postqualPrompts}
|
|
67
|
+
{uiRegistry}
|
|
68
|
+
title={uiRegistry.config.applicantReview?.title ?? "Review your application"}
|
|
69
|
+
subtitle={uiRegistry.config.applicantReview?.subTitle ?? "Confirm the benefits shown are the ones you are requesting and that your responses are correct, or make changes before submitting."}
|
|
70
|
+
expandable={false}
|
|
71
|
+
showWarningsInline={true}
|
|
72
|
+
showAppRequestStatus={false}
|
|
73
|
+
statusDisplay="icons"
|
|
74
|
+
>
|
|
75
|
+
<svelte:fragment slot="footer">
|
|
76
|
+
<footer class="[ mt-6 ] flow">
|
|
77
|
+
{#if uiRegistry.config.supportUrl}
|
|
78
|
+
<p class="text-center">If you think this was a mistake please review your answers or reach out through the support page.</p>
|
|
79
|
+
{/if}
|
|
80
|
+
<div class="footer-actions">
|
|
81
|
+
{#if previousHref}
|
|
82
|
+
<Button kind="ghost" on:click={handleBack}>Back</Button>
|
|
83
|
+
{/if}
|
|
84
|
+
{#if uiRegistry.config.supportUrl}
|
|
85
|
+
<Button kind="ghost" href={uiRegistry.config.supportUrl}>Support</Button>
|
|
86
|
+
{/if}
|
|
87
|
+
{#if appRequestForExport.status === enumAppRequestStatus.READY_TO_SUBMIT}
|
|
88
|
+
<Button icon={Touch_1} on:click={onSubmit}>Submit application</Button>
|
|
89
|
+
{/if}
|
|
90
|
+
</div>
|
|
91
|
+
</footer>
|
|
92
|
+
</svelte:fragment>
|
|
93
|
+
</ApplicationDetailsView>
|
|
94
|
+
</div>
|
|
95
|
+
<style>
|
|
96
|
+
.footer-actions {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 3rem;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
margin-top: 2rem;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resolve } from '$app/paths'
|
|
3
|
+
import { page } from '$app/stores'
|
|
4
|
+
import type { LayoutData } from './$types.js'
|
|
5
|
+
import { IntroPanel, TabLinks, applicantStatuses, getReviewerStatusTags, longNumericTime } from '$internal'
|
|
6
|
+
import { uiRegistry } from '$local'
|
|
7
|
+
import { enumAppRequestPhase } from '@reqquest/ui'
|
|
8
|
+
import { isIneligibleByApplicantRequirement } from '$internal'
|
|
9
|
+
|
|
10
|
+
export let data: LayoutData
|
|
11
|
+
$: ({ basicRequestData, requestId } = data)
|
|
12
|
+
$: eligibleApplications = basicRequestData.applications.filter(a => !isIneligibleByApplicantRequirement(a))
|
|
13
|
+
$: ineligibleApplications = basicRequestData.applications.filter(a => isIneligibleByApplicantRequirement(a))
|
|
14
|
+
$: activeIneligible = ineligibleApplications.find(a => resolve(`/requests/${requestId}/approve/${a.programKey}`) === $page.url.pathname)
|
|
15
|
+
$: navTabs = [
|
|
16
|
+
...(eligibleApplications.map(a => ({
|
|
17
|
+
label: a.navTitle,
|
|
18
|
+
href: resolve(`/requests/${requestId}/approve/${a.programKey}`)
|
|
19
|
+
}))),
|
|
20
|
+
{
|
|
21
|
+
label: 'Activity log',
|
|
22
|
+
href: resolve(`/requests/${requestId}/approve/activity`)
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
$: ineligibleTab = {
|
|
26
|
+
label: activeIneligible?.navTitle ?? 'Ineligible programs',
|
|
27
|
+
disabled: !ineligibleApplications.length,
|
|
28
|
+
selected: activeIneligible != null
|
|
29
|
+
}
|
|
30
|
+
$: loading = false
|
|
31
|
+
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<IntroPanel
|
|
35
|
+
title={basicRequestData.period.name + (basicRequestData.period.code ? ` (${basicRequestData.period.code})` : '')}
|
|
36
|
+
subtitle={`Review and complete the ${uiRegistry.getWord('appRequest').toLowerCase()} below or advance it in the workflow.`}
|
|
37
|
+
tags={getReviewerStatusTags(basicRequestData.status, basicRequestData.phase, basicRequestData.closedAt)}
|
|
38
|
+
>
|
|
39
|
+
<div class="block-end flex items-center" slot="block-end">
|
|
40
|
+
<section class="text-base text-center flex-col gap-2 mr-[12px]">
|
|
41
|
+
<dl class="flex gap-4 text-sm">
|
|
42
|
+
<div class="flex flex-col bg-[var(--cds-ui-03,#d9d9d9)] py-4 px-4 justify-center">
|
|
43
|
+
<dt class="font-bold">
|
|
44
|
+
{#if basicRequestData.closedAt}
|
|
45
|
+
Closed
|
|
46
|
+
{:else if applicantStatuses.has(basicRequestData.status)}
|
|
47
|
+
Submit By
|
|
48
|
+
{:else if basicRequestData.phase !== enumAppRequestPhase.COMPLETE}
|
|
49
|
+
Review By
|
|
50
|
+
{:else}
|
|
51
|
+
Auto-Closes
|
|
52
|
+
{/if}:
|
|
53
|
+
</dt>
|
|
54
|
+
<dd>
|
|
55
|
+
{#if basicRequestData.closedAt}
|
|
56
|
+
<time datetime={basicRequestData.closedAt}>{longNumericTime(basicRequestData.closedAt)}</time>
|
|
57
|
+
{:else if applicantStatuses.has(basicRequestData.status)}
|
|
58
|
+
{#if basicRequestData.period.closeDate}
|
|
59
|
+
<time datetime={basicRequestData.period.closeDate}>{longNumericTime(basicRequestData.period.closeDate)}</time>
|
|
60
|
+
{:else}
|
|
61
|
+
No deadline
|
|
62
|
+
{/if}
|
|
63
|
+
{:else if basicRequestData.phase !== enumAppRequestPhase.COMPLETE}
|
|
64
|
+
{#if basicRequestData.period.archiveDate}
|
|
65
|
+
<time datetime={basicRequestData.period.archiveDate}>{longNumericTime(basicRequestData.period.archiveDate)}</time>
|
|
66
|
+
{:else}
|
|
67
|
+
No deadline
|
|
68
|
+
{/if}
|
|
69
|
+
{:else}
|
|
70
|
+
{#if basicRequestData.period.archiveDate}
|
|
71
|
+
<time datetime={basicRequestData.period.archiveDate}>{longNumericTime(basicRequestData.period.archiveDate)}</time>
|
|
72
|
+
{:else}
|
|
73
|
+
Never
|
|
74
|
+
{/if}
|
|
75
|
+
{/if}
|
|
76
|
+
</dd>
|
|
77
|
+
</div>
|
|
78
|
+
</dl>
|
|
79
|
+
</section>
|
|
80
|
+
</div>
|
|
81
|
+
</IntroPanel>
|
|
82
|
+
{#snippet ineligibleProgramList()}
|
|
83
|
+
<ul class="ineligible-programs">
|
|
84
|
+
{#each ineligibleApplications as a}
|
|
85
|
+
<li>
|
|
86
|
+
<a href={resolve(`/requests/${requestId}/approve/${a.programKey}`)}>{a.navTitle}</a>
|
|
87
|
+
</li>
|
|
88
|
+
{/each}
|
|
89
|
+
</ul>
|
|
90
|
+
{/snippet}
|
|
91
|
+
<TabLinks tabs={[...navTabs, { ...ineligibleTab, panel: ineligibleProgramList }]} />
|
|
92
|
+
<slot />
|
|
93
|
+
<style>
|
|
94
|
+
.block-end :global(.bx--label) {
|
|
95
|
+
position: absolute;
|
|
96
|
+
top: 0;
|
|
97
|
+
left: 0;
|
|
98
|
+
transform: translateY(-100%);
|
|
99
|
+
}
|
|
100
|
+
.ineligible-programs {
|
|
101
|
+
padding: 0.25rem 0;
|
|
102
|
+
margin: 0;
|
|
103
|
+
list-style: none;
|
|
104
|
+
}
|
|
105
|
+
.ineligible-programs a {
|
|
106
|
+
display: block;
|
|
107
|
+
padding: 0.5rem 1rem;
|
|
108
|
+
color: var(--cds-text-01, #161616);
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
}
|
|
111
|
+
.ineligible-programs a:hover {
|
|
112
|
+
background-color: var(--cds-hover-ui, #e5e5e5);
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit'
|
|
2
|
+
import { base } from '$app/paths'
|
|
3
|
+
import type { PageLoad } from './$types'
|
|
4
|
+
import { excludeApplicantRequirementIneligibleApps } from '$internal'
|
|
5
|
+
|
|
6
|
+
export const load: PageLoad = async ({ params, parent }) => {
|
|
7
|
+
const { basicRequestData } = await parent()
|
|
8
|
+
// exclude previously ineligible applications from default landing
|
|
9
|
+
const eligibleApps = excludeApplicantRequirementIneligibleApps([basicRequestData])
|
|
10
|
+
const key = eligibleApps[0]?.applications[0]?.programKey ?? basicRequestData.applications[0]?.programKey // if no eligibile get first ineligible
|
|
11
|
+
// every program can be hidden from reviewers by showIneligiblePreSubmit: false, leaving only the activity log
|
|
12
|
+
if (!key) throw redirect(303, `${base}/requests/${params.id}/approve/activity`)
|
|
13
|
+
throw redirect(303, `${base}/requests/${params.id}/approve/${key}`)
|
|
14
|
+
}
|