@reqquest/ui 1.17.0 → 1.18.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.
Files changed (100) hide show
  1. package/app/env.template +5 -0
  2. package/app/runtime/apply-env.sh +10 -0
  3. package/app/runtime/nginx.conf +37 -0
  4. package/app/src/app.css +21 -0
  5. package/app/src/app.d.ts +13 -0
  6. package/app/src/app.html +12 -0
  7. package/app/src/internal/api.ts +1707 -0
  8. package/app/src/internal/appreq-utils.ts +33 -0
  9. package/app/src/internal/components/AnnouncementBanner.svelte +47 -0
  10. package/app/src/internal/components/AppRequestCard.svelte +173 -0
  11. package/app/src/internal/components/ApplicantOptOutModal.svelte +78 -0
  12. package/app/src/internal/components/ApplicantProgramList.svelte +276 -0
  13. package/app/src/internal/components/ApplicantProgramListTooltip.svelte +44 -0
  14. package/app/src/internal/components/ApplicantPromptPage.svelte +136 -0
  15. package/app/src/internal/components/ApplicantPromptSkeleton.svelte +12 -0
  16. package/app/src/internal/components/ApplicationDetailsView.svelte +369 -0
  17. package/app/src/internal/components/ButtonLoadingIcon.svelte +28 -0
  18. package/{dist → app/src/internal}/components/DelayedSkeleton.svelte +2 -2
  19. package/app/src/internal/components/FieldNestedMultiselect.svelte +270 -0
  20. package/app/src/internal/components/IntroPanel.svelte +41 -0
  21. package/app/src/internal/components/MissingDefinitionNotification.svelte +20 -0
  22. package/app/src/internal/components/PeriodPanel.svelte +125 -0
  23. package/app/src/internal/components/ProgramStatusCell.svelte +134 -0
  24. package/app/src/internal/components/RenderDisplayComponent.svelte +42 -0
  25. package/app/src/internal/components/ReviewerActions.svelte +171 -0
  26. package/app/src/internal/components/ReviewerQuestions.svelte +444 -0
  27. package/app/src/internal/components/StatusMessageList.svelte +54 -0
  28. package/app/src/internal/components/SystemDeterminationNotice.svelte +37 -0
  29. package/app/src/internal/components/TabLinks.svelte +265 -0
  30. package/app/src/internal/components/WarningIconYellow.svelte +22 -0
  31. package/app/src/internal/components/index.ts +19 -0
  32. package/app/src/internal/csv.ts +30 -0
  33. package/app/src/internal/index.ts +8 -0
  34. package/app/src/internal/prompt-save-queue.ts +91 -0
  35. package/app/src/internal/prompt-utils.ts +40 -0
  36. package/app/src/internal/status-utils.ts +503 -0
  37. package/app/src/internal/stores/IStateStore.ts +5 -0
  38. package/app/src/internal/util.ts +27 -0
  39. package/app/src/routes/+error.svelte +4 -0
  40. package/app/src/routes/+layout.svelte +134 -0
  41. package/app/src/routes/+layout.ts +18 -0
  42. package/app/src/routes/+page.ts +14 -0
  43. package/app/src/routes/announcement/+page.svelte +164 -0
  44. package/app/src/routes/announcement/+page.ts +16 -0
  45. package/app/src/routes/dashboards/applicant/+page.svelte +494 -0
  46. package/app/src/routes/dashboards/applicant/+page.ts +95 -0
  47. package/app/src/routes/dashboards/reviewer/+page.svelte +143 -0
  48. package/app/src/routes/dashboards/reviewer/+page.ts +43 -0
  49. package/app/src/routes/periods/+page.svelte +158 -0
  50. package/app/src/routes/periods/+page.ts +13 -0
  51. package/app/src/routes/periods/[id]/configure/+page.svelte +150 -0
  52. package/app/src/routes/periods/[id]/configure/+page.ts +10 -0
  53. package/app/src/routes/requests/+page.svelte +246 -0
  54. package/app/src/routes/requests/+page.ts +24 -0
  55. package/app/src/routes/requests/[id]/+layout.ts +11 -0
  56. package/app/src/routes/requests/[id]/accept/+layout.svelte +80 -0
  57. package/app/src/routes/requests/[id]/accept/+layout.ts +12 -0
  58. package/app/src/routes/requests/[id]/accept/+page.ts +13 -0
  59. package/app/src/routes/requests/[id]/accept/[promptId]/+page.svelte +7 -0
  60. package/app/src/routes/requests/[id]/accept/[promptId]/+page.ts +10 -0
  61. package/app/src/routes/requests/[id]/accept/review/+page.svelte +87 -0
  62. package/app/src/routes/requests/[id]/apply/+layout.svelte +181 -0
  63. package/app/src/routes/requests/[id]/apply/+layout.ts +12 -0
  64. package/app/src/routes/requests/[id]/apply/+page.ts +14 -0
  65. package/app/src/routes/requests/[id]/apply/ProgressNavContainer.svelte +42 -0
  66. package/app/src/routes/requests/[id]/apply/[promptId]/+page.svelte +23 -0
  67. package/app/src/routes/requests/[id]/apply/[promptId]/+page.ts +11 -0
  68. package/app/src/routes/requests/[id]/apply/programs/+page.svelte +71 -0
  69. package/app/src/routes/requests/[id]/apply/review/+page.svelte +102 -0
  70. package/app/src/routes/requests/[id]/approve/+layout.svelte +114 -0
  71. package/app/src/routes/requests/[id]/approve/+page.ts +14 -0
  72. package/app/src/routes/requests/[id]/approve/ApproveLayout.svelte +164 -0
  73. package/app/src/routes/requests/[id]/approve/[programKey]/+page.svelte +396 -0
  74. package/app/src/routes/requests/[id]/approve/[programKey]/+page.ts +15 -0
  75. package/app/src/routes/requests/[id]/approve/activity/+page.svelte +66 -0
  76. package/app/src/routes/requests/[id]/approve/activity/+page.ts +12 -0
  77. package/app/src/routes/requests/[id]/approve/export/+page.ts +12 -0
  78. package/app/src/routes/requests/[id]/approve/export/+page@[id].svelte +41 -0
  79. package/app/src/routes/requests/[id]/export/+page.svelte +32 -0
  80. package/app/src/routes/requests/[id]/export/+page.ts +12 -0
  81. package/app/src/routes/roles/+layout.svelte +16 -0
  82. package/app/src/routes/roles/+layout.ts +14 -0
  83. package/app/src/routes/roles/+page.svelte +225 -0
  84. package/app/src/routes/roles/+page.ts +13 -0
  85. package/app/src/routes/roles/[id]/+layout.ts +10 -0
  86. package/app/src/routes/roles/[id]/+page.svelte +335 -0
  87. package/app/src/routes/roles/[id]/+page.ts +10 -0
  88. package/app/src/routes/roles/[id]/ControlWithTooltip.svelte +15 -0
  89. package/app/src/routes/roles/[id]/TagsWithTooltip.svelte +42 -0
  90. package/app/src/routes/roles/users/+layout.ts +13 -0
  91. package/app/src/routes/roles/users/+page.svelte +46 -0
  92. package/app/src/routes/roles/users/+page.ts +90 -0
  93. package/app/static/favicon.png +0 -0
  94. package/bin/reqquest-ui.js +117 -0
  95. package/cli/config.js +105 -0
  96. package/cli/sync.js +296 -0
  97. package/cli/util.js +29 -0
  98. package/dist/components.md +0 -4
  99. package/package.json +29 -15
  100. package/dist/components/DelayedSkeleton.svelte.d.ts +0 -21
@@ -0,0 +1,136 @@
1
+ <script lang="ts">
2
+ /**
3
+ * This page collects prompt data from the applicant. Only one route exists per
4
+ * prompt, even if the prompt is shared between multiple applications. We should
5
+ * be able to figure out which application we are in based on the full state of
6
+ * the app request since the prompt should only be visible in a single spot.
7
+ */
8
+
9
+ import { Form, FormInlineNotification, confirmationStore } from '@txstate-mws/carbon-svelte'
10
+ import type { FormStore } from '@txstate-mws/svelte-forms'
11
+ import { Button, InlineNotification } from 'carbon-components-svelte'
12
+ import { getContext } from 'svelte'
13
+ import { get, type Writable } from 'svelte/store'
14
+ import { goto, invalidate } from '$app/navigation'
15
+ import type { ResolvedPathname } from '$app/types'
16
+ import { statusReasonsToFeedback, type StatusReason } from '@reqquest/ui'
17
+ import { uiRegistry } from '$local'
18
+ import { api } from '../api.js'
19
+ import type { PageData } from '../../routes/requests/[id]/apply/[promptId]/$types.js'
20
+ import ButtonLoadingIcon from './ButtonLoadingIcon.svelte'
21
+ import MissingDefinitionNotification from './MissingDefinitionNotification.svelte'
22
+
23
+
24
+ export let data: Awaited<PageData['applicantPromptPromise']> & { dataVersion: number }
25
+ export let statusReasons: StatusReason[] = []
26
+ $: ({ prompt, appRequest: appRequestForExport, dataVersion } = data)
27
+ $: def = uiRegistry.getPrompt(prompt.key)
28
+ $: statusFeedback = statusReasonsToFeedback(statusReasons, { blamedOnly: true, statuses: uiRegistry.statusReasonNotificationStatuses(prompt.key, 'applicant') })
29
+ const nextHref = getContext<Writable<{ nextHref: ResolvedPathname, prevHref: ResolvedPathname | undefined }>>('nextHref')
30
+
31
+ let store: FormStore | undefined
32
+ let continueAfterSave = false
33
+ $: hasPreviousPrompt = $nextHref.prevHref != null
34
+
35
+ async function handleBack () {
36
+ const previousHref = $nextHref.prevHref
37
+ if (previousHref) {
38
+ // eslint-disable-next-line svelte/no-navigation-without-resolve -- already resolved
39
+ await goto(previousHref)
40
+ }
41
+ }
42
+
43
+ async function onSubmit (data: any) {
44
+ const { success, messages, data: newData } = await api.updatePrompt(prompt.id, data, false, dataVersion, await confirmInvalidatedOverride())
45
+ return {
46
+ success,
47
+ messages,
48
+ data: newData[prompt.key]
49
+ }
50
+ }
51
+
52
+ async function onValidate (data: any) {
53
+ const { messages } = await api.updatePrompt(prompt.id, data, true, dataVersion)
54
+ return messages
55
+ }
56
+
57
+ async function onSaved () {
58
+ if (continueAfterSave) {
59
+ // Bug issue that became apparent with loaders.
60
+ // only refresh nav/answered state, not this prompt page. read the store directly with
61
+ // get() rather than $nextHref/prompt.answered because its stale until reload reactive updates.
62
+ await invalidate('request:apply:nav')
63
+ // eslint-disable-next-line svelte/no-navigation-without-resolve -- already resolved
64
+ await goto(get(nextHref).nextHref)
65
+ } else {
66
+ await invalidate('request:apply')
67
+ }
68
+ }
69
+
70
+ async function confirmInvalidatedOverride() {
71
+ return (prompt.invalidated && !$store?.hasUnsavedChanges)
72
+ ? await confirmationStore.confirm(
73
+ 'Corrections are required, but no changes have been made. Can you confirm that all data is correct, and does not require changes?',
74
+ {
75
+ title: 'Confirm data is correct',
76
+ yesText: 'Yes',
77
+ noText: 'No'
78
+ }
79
+ )
80
+ : false
81
+ }
82
+
83
+ let lastPromptId: string | undefined
84
+ $: if (prompt.id !== lastPromptId) {
85
+ lastPromptId = prompt.id
86
+ store = undefined
87
+ }
88
+ </script>
89
+
90
+ {#key prompt.id}
91
+ <div class="prompt-intro flow max-w-screen-md mx-auto pt-10 px-6">
92
+ <!-- svelte-ignore a11y_autofocus -->
93
+ <h2 id="prompt-title" tabindex="-1" autofocus class="font-bold text-2xl leading-normal text-center">{prompt.title}</h2>
94
+ <p class="text-center"> {prompt.description}</p>
95
+ </div>
96
+ {#if def == null}
97
+ <div class="max-w-screen-md mx-auto px-6 pt-6">
98
+ <MissingDefinitionNotification kind="prompt" definitionKey={prompt.key} />
99
+ {#if hasPreviousPrompt}
100
+ <Button kind="ghost" on:click={handleBack}>Back</Button>
101
+ {/if}
102
+ </div>
103
+ {:else}
104
+ <Form class={def.applicantPromptPage?.formClass ?? uiRegistry.config.applicantPromptPage?.formClass} bind:store hideFallbackMessage unsavedWarning submit={onSubmit} validate={onValidate} preloadAsDraft={!prompt.hasSavedData} preload={prompt.preloadData} on:saved={onSaved} let:data>
105
+ <svelte:component this={def.formComponent} {data} appRequestId={appRequestForExport.id} appRequestData={appRequestForExport.data} prestageData={{latest: prompt.prestageData, current: appRequestForExport.data[prompt.key]?.__prestage}} fetched={prompt.fetchedData} configData={prompt.configurationData} gatheredConfigData={prompt.gatheredConfigData} invalidated={prompt.invalidated} invalidatedReason={prompt.invalidatedReason} {statusReasons} />
106
+ {#if statusFeedback.length}
107
+ <div class={def.applicantPromptPage?.statusReasonInlineNotificationClass ?? uiRegistry.config.applicantPromptPage?.statusReasonInlineNotificationClass}>
108
+ {#each statusFeedback as message}
109
+ <FormInlineNotification {message} />
110
+ {/each}
111
+ </div>
112
+ {/if}
113
+ <svelte:fragment slot="submit" let:submitting>
114
+ <div class='form-submit flex gap-12 justify-center mt-16'>
115
+ {#if hasPreviousPrompt}
116
+ <Button kind="ghost" on:click={handleBack}>Back</Button>
117
+ {/if}
118
+ <Button icon={submitting && !continueAfterSave ? ButtonLoadingIcon : null} type="submit" kind="secondary" disabled={submitting} on:click={() => { continueAfterSave = false }}>Save</Button>
119
+ <Button icon={submitting && !continueAfterSave ? ButtonLoadingIcon : null} type="submit" disabled={submitting} on:click={() => { continueAfterSave = true }}>Continue</Button>
120
+ </div>
121
+ </svelte:fragment>
122
+ <!-- Correction inline prompt notification -->
123
+ {#if prompt.invalidated && !$store?.hasUnsavedChanges}
124
+ <InlineNotification
125
+ class={def.applicantPromptPage?.invalidatedInlineNotificationClass ?? uiRegistry.config.applicantPromptPage?.invalidatedInlineNotificationClass}
126
+ kind="warning-alt"
127
+ title='Corrections needed'
128
+ hideCloseButton={true}
129
+ lowContrast
130
+ subtitle={prompt.invalidatedReason ?? 'Must update form data before continuing'}
131
+ />
132
+ {/if}
133
+ </Form>
134
+ {/if}
135
+ {/key}
136
+
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import { SkeletonText } from 'carbon-components-svelte'
3
+ </script>
4
+ <div class="gap-4 flex flex-col mt-10">
5
+ <SkeletonText />
6
+ <SkeletonText />
7
+ <SkeletonText />
8
+ <div style="width:100%" class="[ flex flex-row gap-20 justify-center ]">
9
+ <div class="still-skeleton bx--skeleton__text" style="height: 48px; width: 90px;" />
10
+ <div class="still-skeleton bx--skeleton__text" style="height: 48px; width: 90px;" />
11
+ </div>
12
+ </div>
@@ -0,0 +1,369 @@
1
+ <script lang="ts">
2
+ import { getAppRequestStatusInfo, getApplicationStatusInfo, applicantRequirementTypes, reviewRequirementTypes } from '../status-utils.js'
3
+ import { DEFAULT_APPLICANT_NO_PROGRAMS_MESSAGE, isHiddenFromApplicant, isIneligibleByApplicantRequirement } from '../appreq-utils.js'
4
+ import { Panel, TagSet } from '@txstate-mws/carbon-svelte'
5
+ import { Button, InlineNotification, Tooltip } from 'carbon-components-svelte'
6
+ import Edit from 'carbon-icons-svelte/lib/Edit.svelte'
7
+ import type { AnsweredPrompt, PromptSection, AppRequestForDetails, ApplicationForDetails, Scalars } from '@reqquest/ui'
8
+ import { enumRequirementType } from '@reqquest/ui'
9
+ import type { UIRegistry } from '@reqquest/ui'
10
+ import RenderDisplayComponent from './RenderDisplayComponent.svelte'
11
+ import ApplicantProgramList from './ApplicantProgramList.svelte'
12
+ import WarningIconYellow from './WarningIconYellow.svelte'
13
+
14
+ // TODO: design alignement could reduce props here
15
+ export let appRequest: AppRequestForDetails
16
+ export let applications: ApplicationForDetails[]
17
+ export let appData: Scalars['JsonData'] = {}
18
+ export let prequalPrompts: AnsweredPrompt[] | undefined = undefined
19
+ export let postqualPrompts: AnsweredPrompt[] | undefined = undefined
20
+ export let loading = false
21
+ export let uiRegistry: UIRegistry
22
+ export let title = `View your ${uiRegistry.getWord('appRequest').toLowerCase()}`
23
+ export let subtitle = 'Select document names to preview them.'
24
+ export let expandable = true
25
+ export let showWarningsInline = false
26
+ export let showCorrectionsInline = false
27
+ export let showAppRequestStatus = true
28
+ export let statusDisplay: 'tags' | 'icons' = 'tags'
29
+ export let showTooltipsAsText = false
30
+ /** Apply print-export styles: hide app chrome and paginate by prompt row rather than by panel. */
31
+ export let printMode = false
32
+
33
+ const CORRECTABLE_STATUSES = ['STARTED', 'READY_TO_SUBMIT', 'DISQUALIFIED']
34
+
35
+ $: canMakeCorrections = CORRECTABLE_STATUSES.includes(appRequest.status)
36
+ $: eligibleApplications = applications.filter(a => a.ineligiblePhase == null)
37
+ $: ineligibleApplications = applications.filter(a => isIneligibleByApplicantRequirement(a) && !isHiddenFromApplicant(a))
38
+ $: noProgramsToList = !eligibleApplications.length && !ineligibleApplications.length && applications.some(isHiddenFromApplicant)
39
+
40
+ // Group prompts by sections, with reviewer prompts nested within application sections
41
+ $: sections = (() => {
42
+ const sections: PromptSection[] = []
43
+
44
+ // General Questions === PREQUAL prompts
45
+ if (prequalPrompts?.length) {
46
+ sections.push({ title: 'Eligibility screening questions', prompts: prequalPrompts })
47
+ }
48
+
49
+ // Application-Specific Questions with nested Reviewer Questions
50
+ for (const application of applications) {
51
+ const applicantPrompts = application.requirements
52
+ .filter(r => applicantRequirementTypes.has(r.type))
53
+ .flatMap(r => r.prompts).filter(p => !p.optOut)
54
+ const reviewerPrompts = application.requirements
55
+ .filter(r => reviewRequirementTypes.has(r.type))
56
+ .flatMap(r => r.prompts)
57
+ const workflowPrompts = application.requirements
58
+ .filter(r => r.type === enumRequirementType.WORKFLOW)
59
+ .flatMap(r => r.prompts)
60
+
61
+ if (applicantPrompts.length || reviewerPrompts.length || workflowPrompts.length) {
62
+ const subsections: PromptSection[] = []
63
+ if (reviewerPrompts.length) subsections.push({ title: 'Reviewer Questions', prompts: reviewerPrompts })
64
+ if (workflowPrompts.length) subsections.push({ title: 'Workflow Questions', prompts: workflowPrompts })
65
+ sections.push({
66
+ title: application.title,
67
+ prompts: applicantPrompts,
68
+ subsections,
69
+ applicationStatus: application.status,
70
+ applicationRescindedStatus: application.rescindedStatus
71
+ })
72
+ }
73
+ }
74
+
75
+ // Additional Questions === POSTQUAL prompts
76
+ if (postqualPrompts?.length) {
77
+ sections.push({ title: 'Additional Questions', prompts: postqualPrompts })
78
+ }
79
+
80
+ return sections
81
+ })()
82
+
83
+ function hasWarning (prompt: AnsweredPrompt) {
84
+ return prompt.statusReasons.some(r => r.status === 'WARNING' || r.status === 'DISQUALIFYING')
85
+ }
86
+
87
+ function getWarnings (prompt: AnsweredPrompt) {
88
+ return prompt.statusReasons.filter(r => r.status === 'WARNING' || r.status === 'DISQUALIFYING')
89
+ }
90
+
91
+ function needsCorrection (prompt: AnsweredPrompt) {
92
+ return prompt.invalidated && prompt.invalidatedReason
93
+ }
94
+ </script>
95
+
96
+ {#if appRequest}
97
+ <div class="[ my-6 ] application-details flow" class:print-mode={printMode}>
98
+ <!-- Application Status Panel -->
99
+ <header class="[ mb-12 ] app-view-intro text-center">
100
+ <h2 class="[ text-2xl mb-3 font-bold ]">{title}</h2>
101
+ <p class="app-view-subtitle mb-3">{subtitle}</p>
102
+ </header>
103
+ <section class="prompt-section">
104
+ <div class="status-content">
105
+ {#if showAppRequestStatus}
106
+ <dl class="status-list-item [ flex items-center justify-between px-2 py-3 border-b ]">
107
+ <dt class="status-list-label font-medium">{uiRegistry.getWord('appRequest')} Status</dt>
108
+ <dd class="px-2">
109
+ <TagSet tags={getAppRequestStatusInfo(appRequest.status, appRequest.phase, appRequest.closedAt).tags} />
110
+ </dd>
111
+ </dl>
112
+ {/if}
113
+
114
+ <!-- Application Status List -->
115
+ {#if eligibleApplications.length > 0}
116
+ <ApplicantProgramList applications={eligibleApplications} {appRequest} viewMode={statusDisplay === 'tags'} {showTooltipsAsText} />
117
+ {/if}
118
+ <!-- Ineligible Status List -->
119
+ {#if ineligibleApplications.length > 0}
120
+ <Panel title="Ineligible benefits" expandable={true} expanded={(eligibleApplications.length === 0)}>
121
+ <ApplicantProgramList applications={ineligibleApplications} {appRequest} viewMode={statusDisplay === 'tags'} {showTooltipsAsText} />
122
+ </Panel>
123
+ {/if}
124
+ {#if noProgramsToList}
125
+ <p class="text-center">{uiRegistry.config.applicantNoProgramsMessage ?? DEFAULT_APPLICANT_NO_PROGRAMS_MESSAGE}</p>
126
+ {/if}
127
+ </div>
128
+ </section>
129
+
130
+ <!-- Prompt Sections -->
131
+ {#if loading}
132
+ <Panel title="Loading...">
133
+ <p>Loading prompt data...</p>
134
+ </Panel>
135
+ {:else if sections.length > 0}
136
+ {#each sections as section (section.title)}
137
+ {@const applicationStatusInfo = section.applicationStatus ? getApplicationStatusInfo(section.applicationStatus, appRequest.phase, appRequest.closedAt, section.applicationRescindedStatus) : undefined}
138
+ <Panel title={section.title} {expandable} expanded>
139
+ {#if section.prompts.length}
140
+ <dl class="prompt-list">
141
+ {#each section.prompts as prompt (prompt.id)}
142
+ {@const def = uiRegistry.getPrompt(prompt.key)}
143
+ <dt class="prompt-term [ font-medium ]">
144
+ {#if showWarningsInline && hasWarning(prompt)}
145
+ <Tooltip align="start">
146
+ <div class="icon" slot="icon">
147
+ <WarningIconYellow size={16} />
148
+ </div>
149
+ {#each getWarnings(prompt) as r, i (i)}
150
+ <p>
151
+ {r.status === 'WARNING' ? 'Warning' : 'Disqualifying'}{#if section.title !== r.programName}&nbsp;for {r.programName}{/if}<br>
152
+ {r.statusReason}
153
+ </p>
154
+ {/each}
155
+ </Tooltip>
156
+ {/if}
157
+ {#if showCorrectionsInline && canMakeCorrections && needsCorrection(prompt)}
158
+ <Tooltip align="start">
159
+ <div class="icon" slot="icon">
160
+ <WarningIconYellow size={16} />
161
+ </div>
162
+ <p>Corrections needed<br>{prompt.invalidatedReason}</p>
163
+ </Tooltip>
164
+ {/if}
165
+ {prompt.title}
166
+ </dt>
167
+ <dd class="prompt-answer flow" class:large={def?.displayMode === 'large'}>
168
+ <RenderDisplayComponent {def} appRequestId={appRequest.id} appData={appData} prompt={prompt} prestageData={{latest: prompt.prestageData, current: appRequest.data[prompt.key]?.__prestage}} configData={prompt.configurationData} gatheredConfigData={prompt.gatheredConfigData} statusReasons={prompt.statusReasons} />
169
+ {#if showCorrectionsInline && canMakeCorrections && needsCorrection(prompt)}
170
+ <div class="correction-notice">
171
+ <Button kind="ghost" size="small" icon={Edit} iconDescription="Edit this answer" href={`/requests/${appRequest.id}/apply/${prompt.id}`} class="edit-button" />
172
+ <InlineNotification kind="warning-alt" title="Corrections needed" subtitle={prompt.invalidatedReason ?? ''} hideCloseButton lowContrast />
173
+ </div>
174
+ {/if}
175
+ </dd>
176
+ {/each}
177
+ </dl>
178
+ {/if}
179
+
180
+ <!-- Nested subsections (e.g., Reviewer Questions) -->
181
+ {#if section.subsections}
182
+ {#each section.subsections as subsection (subsection.title)}
183
+ <Panel title={subsection.title} {expandable} expanded>
184
+ <dl class="prompt-list">
185
+ {#each subsection.prompts as prompt (prompt.id)}
186
+ {@const def = uiRegistry.getPrompt(prompt.key)}
187
+ <dt class="prompt-term [ font-medium ]">
188
+ {#if showWarningsInline && hasWarning(prompt)}
189
+ <Tooltip align="start">
190
+ <div class="icon" slot="icon">
191
+ <WarningIconYellow size={16} />
192
+ </div>
193
+ {#each getWarnings(prompt) as r, i (i)}
194
+ <p>
195
+ {r.status === 'WARNING' ? 'Warning' : 'Disqualifying'}{#if section.title !== r.programName}&nbsp;for {r.programName}{/if}<br>
196
+ {r.statusReason}
197
+ </p>
198
+ {/each}
199
+ </Tooltip>
200
+ {/if}
201
+ {#if showCorrectionsInline && canMakeCorrections && needsCorrection(prompt)}
202
+ <Tooltip align="start">
203
+ <div class="icon" slot="icon">
204
+ <WarningIconYellow size={16} />
205
+ </div>
206
+ <p>Corrections needed<br>{prompt.invalidatedReason}</p>
207
+ </Tooltip>
208
+ {/if}
209
+ {prompt.title}
210
+ </dt>
211
+ <dd class="prompt-answer flow" class:large={def?.displayMode === 'large'}>
212
+ <RenderDisplayComponent {def} appRequestId={appRequest.id} appData={appData} prompt={prompt} prestageData={{latest: prompt.prestageData, current: appRequest.data[prompt.key]?.__prestage}} configData={prompt.configurationData} gatheredConfigData={prompt.gatheredConfigData} statusReasons={prompt.statusReasons} />
213
+ {#if showCorrectionsInline && canMakeCorrections && needsCorrection(prompt)}
214
+ <Button kind="ghost" size="small" icon={Edit} iconDescription="Edit this answer" href={`/requests/${appRequest.id}/apply/${prompt.id}`} class="edit-button" />
215
+ {/if}
216
+ </dd>
217
+ {#if showCorrectionsInline && canMakeCorrections && needsCorrection(prompt)}
218
+ <div class="correction-notice">
219
+ <InlineNotification kind="warning-alt" title="Corrections needed" subtitle={prompt.invalidatedReason ?? ''} hideCloseButton lowContrast />
220
+ </div>
221
+ {/if}
222
+ {/each}
223
+ </dl>
224
+ </Panel>
225
+ {/each}
226
+ {/if}
227
+ </Panel>
228
+ {/each}
229
+ {:else}
230
+ <Panel title="Prompts and Answers">
231
+ <p>No prompts available for this {uiRegistry.getWord('appRequest').toLowerCase()}.</p>
232
+ </Panel>
233
+ {/if}
234
+
235
+ <slot name="footer" />
236
+ </div>
237
+ {:else}
238
+ <p>No {uiRegistry.getWord('appRequest').toLowerCase()} selected</p>
239
+ {/if}
240
+
241
+ <style>
242
+ .app-view-subtitle {
243
+ color: var(--cds-text-02);
244
+ font-size: 0.9rem;
245
+ /* margin-top: -0.5rem; */
246
+ margin-bottom: 1rem;
247
+ }
248
+
249
+ .status-list-item {
250
+ border-bottom: 1px solid var(--cds-border-subtle);
251
+ }
252
+
253
+ .prompt-answer :global(dl:not(.prompt-display-grid)) {
254
+ padding-block-start:1em;
255
+ display:grid;
256
+ grid-template-columns: 1fr 1fr;
257
+ row-gap:0.5rem;
258
+ }
259
+
260
+ .status-content dl {
261
+ padding-block-start:1em;
262
+ display:grid;
263
+ grid-template-columns: 1fr 1fr;
264
+ row-gap:0.5rem;
265
+ }
266
+
267
+ .status-content :global(.programs-container) {
268
+ grid-template-columns: 1fr 1fr;
269
+ }
270
+
271
+ .status-list-item {
272
+ border-color: var(--cds-border-subtle);
273
+ }
274
+
275
+ .status-list-label {
276
+ color: var(--cds-text-01);
277
+ }
278
+
279
+ .prompt-list {
280
+ display: grid;
281
+ grid-template-columns: var(--prompt-list-columns, 1fr 1fr);
282
+ align-items: stretch;
283
+ row-gap: 0.5rem;
284
+ background-color: #F2F2F2;
285
+ padding-top: 0.5rem;
286
+ }
287
+ .prompt-list dt, .prompt-list dd {
288
+ padding-top: 0.25rem;
289
+ }
290
+
291
+ .prompt-term {
292
+ display: flex;
293
+ gap:1em;
294
+ color: var(--cds-text-01);
295
+ border-bottom: 1px solid var(--cds-border-subtle);
296
+ padding: 0.5rem 10px;
297
+ }
298
+ .prompt-term:has(+ .prompt-answer.large) {
299
+ border-bottom: none;
300
+ }
301
+
302
+ .prompt-answer {
303
+ color: var(--cds-text-02);
304
+ border-bottom: 1px solid var(--cds-border-subtle);
305
+ padding: 0.5rem 10px;
306
+ }
307
+ .prompt-answer.large {
308
+ grid-column: span 2;
309
+ }
310
+ /* When a display component's root is a PromptDisplayGrid, hand it our column tracks
311
+ via subgrid so its challenge/response rows align with the real prompt rows. */
312
+ .prompt-answer.large:has(> :global(.prompt-display-grid)) {
313
+ display: grid;
314
+ grid-template-columns: subgrid;
315
+ align-content: start;
316
+ padding: 0;
317
+ border-bottom: none;
318
+ --prompt-display-subgrid: subgrid;
319
+ --prompt-display-columns: var(--prompt-list-columns, 1fr 1fr);
320
+ --prompt-display-row-padding: 0.5rem 10px;
321
+ --prompt-display-challenge-padding-left: 10px;
322
+ --prompt-display-row-gap: 0.5rem;
323
+ }
324
+ .prompt-answer.large:has(> :global(.prompt-display-grid)) > :global(*) {
325
+ grid-column: 1 / -1;
326
+ }
327
+ .prompt-answer.large :global(.prompt-display-challenge) {
328
+ font-weight: 500;
329
+ color: var(--cds-text-01);
330
+ }
331
+
332
+ .correction-notice {
333
+ grid-column: span 2;
334
+ margin-bottom: 0.5rem;
335
+ }
336
+
337
+ .correction-notice :global(.bx--inline-notification) {
338
+ max-width: 100%;
339
+ }
340
+
341
+ .prompt-answer :global(.edit-button) {
342
+ float: right;
343
+ margin-top: -0.5rem;
344
+ --cds-icon-01: var(--cds-link-01);
345
+ }
346
+
347
+ @media print {
348
+ :global(body:has(.application-details.print-mode) .bx--header),
349
+ :global(body:has(.application-details.print-mode) .breadcrumbs) {
350
+ display: none !important;
351
+ }
352
+
353
+ /* Never mark the whole view or a whole panel break-inside: avoid — when either is
354
+ taller than the space left on a page, the browser pushes it to the next page and
355
+ leaves the first page nearly blank. Keep only small units together instead. */
356
+ .print-mode :global(.panel) {
357
+ margin-bottom: 1rem !important;
358
+ }
359
+
360
+ .print-mode :global(.panel-header) {
361
+ break-after: avoid;
362
+ }
363
+
364
+ .print-mode .prompt-term,
365
+ .print-mode .prompt-answer:not(.large) {
366
+ break-inside: avoid;
367
+ }
368
+ }
369
+ </style>
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { ScreenReaderOnly } from '@txstate-mws/svelte-components'
3
+ </script>
4
+
5
+ <svg style:display="none"></svg>
6
+ <span {...$$restProps} class="{$$restProps.class} loader" aria-hidden="false"><ScreenReaderOnly>loading</ScreenReaderOnly></span>
7
+
8
+ <style>
9
+ .loader {
10
+ width: 16px;
11
+ height: 16px;
12
+ border: 3px solid #FFF;
13
+ border-bottom-color: transparent;
14
+ border-radius: 50%;
15
+ display: inline-block;
16
+ box-sizing: border-box;
17
+ animation: rotation 1s linear infinite;
18
+ }
19
+
20
+ @keyframes rotation {
21
+ 0% {
22
+ transform: rotate(0deg);
23
+ }
24
+ 100% {
25
+ transform: rotate(360deg);
26
+ }
27
+ }
28
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import type { Loader } from "../registry"
3
- import ApplicantPromptSkeleton from "../../internal/components/ApplicantPromptSkeleton.svelte"
2
+ import type { Loader } from "@reqquest/ui"
3
+ import ApplicantPromptSkeleton from "$internal/components/ApplicantPromptSkeleton.svelte"
4
4
 
5
5
  export let loader: Loader
6
6