@reqquest/ui 1.16.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 (105) 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/dist/registry.js +1 -1
  100. package/dist/typed-client/schema.d.ts +25 -3
  101. package/dist/typed-client/schema.graphql +18 -2
  102. package/dist/typed-client/types.d.ts +9 -1
  103. package/dist/typed-client/types.js +29 -5
  104. package/package.json +29 -15
  105. package/dist/components/DelayedSkeleton.svelte.d.ts +0 -21
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import { ApplicationDetailsView } from '$internal'
3
+ import { uiRegistry } from '$local'
4
+ import type { PageData } from './$types'
5
+
6
+ export let data: PageData
7
+ $: ({ appRequest, appData, prequalPrompts, postqualPrompts, applicationsReview, basicRequestData } = data)
8
+ </script>
9
+
10
+ {#if appRequest}
11
+
12
+ <div class="export-content max-w-4xl">
13
+ <ApplicationDetailsView
14
+ {appRequest}
15
+ applications={applicationsReview}
16
+ {appData}
17
+ {prequalPrompts}
18
+ {postqualPrompts}
19
+ loading={false}
20
+ {uiRegistry}
21
+ expandable={false}
22
+ printMode
23
+ showWarningsInline={true}
24
+ title={`View ${basicRequestData.applicant.fullname} ${uiRegistry.getWord('appRequest').toLowerCase()}`}
25
+ subtitle={`Complete ${uiRegistry.getWord('appRequest').toLowerCase()} details for review.`}
26
+ />
27
+ </div>
28
+
29
+ {:else}
30
+ <div class="error">
31
+ <h1>{uiRegistry.getWord('appRequest')} Not Found</h1>
32
+ <p>The requested {uiRegistry.getWord('appRequest').toLowerCase()} could not be loaded.</p>
33
+ </div>
34
+ {/if}
35
+
36
+ <style>
37
+ /* Reviewer standard: prompt/requirement column 40%, answer column 60%. */
38
+ .export-content {
39
+ --prompt-list-columns: minmax(0, 2fr) minmax(0, 3fr);
40
+ }
41
+ </style>
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import { ApplicationDetailsView } from '$internal'
3
+ import { uiRegistry } from '$local'
4
+ import type { PageData } from './$types'
5
+
6
+ export let data: PageData
7
+ $: ({ appRequest, appData, prequalPrompts, postqualPrompts, applicationsReview } = data)
8
+ </script>
9
+
10
+ {#if appRequest}
11
+
12
+ <div class="max-w-4xl">
13
+ <ApplicationDetailsView
14
+ {appRequest}
15
+ applications={applicationsReview}
16
+ {appData}
17
+ {prequalPrompts}
18
+ {postqualPrompts}
19
+ loading={false}
20
+ {uiRegistry}
21
+ expandable={false}
22
+ printMode
23
+ showTooltipsAsText
24
+ />
25
+ </div>
26
+
27
+ {:else}
28
+ <div class="error">
29
+ <h1>Application Not Found</h1>
30
+ <p>The requested application could not be loaded.</p>
31
+ </div>
32
+ {/if}
@@ -0,0 +1,12 @@
1
+ import { api } from '$internal'
2
+ import type { PageLoad } from './$types'
3
+
4
+ export const load: PageLoad = async ({ params, depends, parent }) => {
5
+ const details = await api.getAppRequestForExport(params.id)
6
+
7
+ depends('request:export')
8
+ return {
9
+ ...details,
10
+ appData: details.appRequest.data
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { TabLinks } from '@txstate-mws/carbon-svelte'
3
+ import type { LayoutData } from './$types'
4
+ export let data: LayoutData
5
+ $: ({ showTabs } = data)
6
+ const tabs = [
7
+ { label: 'Role Management', href: '/roles' },
8
+ { label: 'Users', href: '/roles/users' }
9
+ ]
10
+ </script>
11
+ <div class="flow">
12
+ {#if showTabs}
13
+ <TabLinks {tabs} />
14
+ {/if}
15
+ <slot />
16
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { LayoutLoad } from './$types'
2
+ // import { api } from '$internal'
3
+ // import { unifiedAuth } from '@txstate-mws/sveltekit-utils'
4
+
5
+ export const load: LayoutLoad = async input => {
6
+ const lastPath = input.url.pathname.split('/').pop()
7
+ const showTabs = (lastPath === 'roles' || lastPath === 'users')
8
+ // api.fetch = input.fetch
9
+ // await unifiedAuth.handle(api, input)
10
+ // const access = await api.getAccess()
11
+ return { showTabs }
12
+ }
13
+
14
+ export const ssr = false
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ import { ActionSet, ColumnList, FieldMore, FieldTextArea, FieldTextInput, PanelFormDialog } from '@txstate-mws/carbon-svelte'
3
+ import { toasts } from '@txstate-mws/svelte-components'
4
+ import { Button, Modal } from 'carbon-components-svelte'
5
+ import Add from 'carbon-icons-svelte/lib/Add.svelte'
6
+ import SettingsEdit from 'carbon-icons-svelte/lib/SettingsEdit.svelte'
7
+ import { invalidate } from '$app/navigation'
8
+ import { api } from '$internal'
9
+ import type { AccessRoleInput } from '@reqquest/ui'
10
+ import Copy from 'carbon-icons-svelte/lib/Copy.svelte'
11
+ import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte'
12
+ import View from 'carbon-icons-svelte/lib/View.svelte'
13
+ import type { DateTime } from 'luxon'
14
+ import { omit, pick } from 'txstate-utils'
15
+ import type { PageData } from './$types'
16
+
17
+ export let data: PageData
18
+ const rolenames = new Set<string>()
19
+ $: ({ roles, access } = data)
20
+ $: {
21
+ rolenames.clear()
22
+ for (const role of roles ?? []) rolenames.add(role.name)
23
+ }
24
+
25
+ // Group updates to Roles accept only the group name and not a group object.
26
+ type AccessRoleUpdateForm = Omit<AccessRoleInput, 'groups'> & { groups: string[], id?: string }
27
+ function transformFromAPI (data: PageData['roles'][number]): AccessRoleUpdateForm {
28
+ return {
29
+ ...pick(data, 'id', 'name', 'description'),
30
+ groups: data.groups.map(group => group.groupName)
31
+ }
32
+ }
33
+
34
+ let createDialog = false
35
+ let editingRole: AccessRoleUpdateForm | undefined
36
+ let isDuplicating = false
37
+ let duplicateSourceId: string | undefined // Source role ID when duplicating (to copy grants)
38
+ let duplicateSourceName: string | undefined // Source role name for success message
39
+
40
+ async function validate (role: AccessRoleUpdateForm) {
41
+ const response = await api.upsertRole(editingRole?.id, role, true, isDuplicating ? duplicateSourceId : undefined)
42
+ return response.messages
43
+ }
44
+
45
+ async function onSubmit (role: AccessRoleUpdateForm) {
46
+ const response = await api.upsertRole(editingRole?.id, role, false, isDuplicating ? duplicateSourceId : undefined)
47
+ return {
48
+ ...response,
49
+ data: role
50
+ }
51
+ }
52
+
53
+ function onSaved (event: CustomEvent<AccessRoleUpdateForm>) {
54
+ const roleName = event.detail.name
55
+ let title: string
56
+ let message: string
57
+ if (editingRole?.id) {
58
+ title = 'Role edited'
59
+ message = `The role ${roleName} was successfully edited.`
60
+ } else if (isDuplicating) {
61
+ title = 'Role duplicated'
62
+ message = `The role ${duplicateSourceName} was successfully duplicated as ${roleName}.`
63
+ } else {
64
+ title = 'New role created'
65
+ message = `The role ${roleName} was successfully created.`
66
+ }
67
+ toasts.add({ type: 'success', title, message })
68
+ closeDialog()
69
+ invalidate('api:getRoleList').catch(console.error)
70
+ }
71
+
72
+ function closeDialog () {
73
+ createDialog = false
74
+ editingRole = undefined
75
+ isDuplicating = false
76
+ duplicateSourceId = undefined
77
+ }
78
+
79
+ let deleteDialog = false
80
+ let deleteDialogRole: PageData['roles'][number] | undefined
81
+ function openRoleDeleteDialog (role: PageData['roles'][number]) {
82
+ deleteDialog = true
83
+ deleteDialogRole = role
84
+ }
85
+ function closeRoleDeleteDialog () {
86
+ deleteDialog = false
87
+ deleteDialogRole = undefined
88
+ }
89
+ async function executeRoleDelete () {
90
+ try {
91
+ if (!deleteDialogRole) throw new Error('No role selected for deletion')
92
+ await api.deleteRole(deleteDialogRole.id)
93
+ toasts.add({
94
+ type: 'success',
95
+ title: 'Role deleted',
96
+ message: `The role ${deleteDialogRole.name} was successfully deleted.`
97
+ })
98
+ closeRoleDeleteDialog()
99
+ await invalidate('api:getRoleList')
100
+ } catch (e: any) {
101
+ closeRoleDeleteDialog()
102
+ console.error(e)
103
+ }
104
+ }
105
+ </script>
106
+
107
+ <!-- maxRowActionButton={1} -->
108
+ <!-- title={role.name + '<br>' + role.description} -->
109
+ <ActionSet includeLabels actions={[
110
+ {
111
+ label: 'Create Role',
112
+ icon: Add,
113
+ disabled: !access.createRole,
114
+ onClick: () => {
115
+ createDialog = true
116
+ editingRole = undefined
117
+ isDuplicating = false
118
+ }
119
+ }
120
+ ]}/>
121
+
122
+ {#each roles ?? [] as role, index (role.id)}
123
+ <div class="admin-settings">
124
+ <h2 class="text-lg">{role.name}</h2>
125
+ <p>{role.description}</p>
126
+ <div class="role-list">
127
+ <ColumnList
128
+ title={role.name}
129
+ listActionsMaxButtons={2}
130
+ listActionsIncludeLabels={false}
131
+ listActions={[
132
+ { label: 'Edit', icon: SettingsEdit, onClick: () => {
133
+ createDialog = true
134
+ editingRole = transformFromAPI(role)
135
+ isDuplicating = false
136
+ }
137
+ },
138
+ { label: 'View permissions', icon: View, href: `/roles/${role.id}` },
139
+ { label: 'Duplicate role', icon: Copy, onClick: () => {
140
+ createDialog = true
141
+ isDuplicating = true
142
+ duplicateSourceId = role.id
143
+ duplicateSourceName = role.name
144
+ const duplicateRole = transformFromAPI(role)
145
+ let name = duplicateRole.name
146
+ while (rolenames.has(name)) {
147
+ const s = name.search(/[0-9]+$/g)
148
+ if (s !== -1) {
149
+ const count = parseInt(name.slice(s))
150
+ name = name.slice(0, s) + (count + 1).toString()
151
+ } else {
152
+ name = name + '-1'
153
+ }
154
+ }
155
+ editingRole = { ...omit(duplicateRole, 'name', 'id'), name }
156
+ }
157
+ },
158
+ { label: 'Delete role', icon: TrashCan, onClick: () => openRoleDeleteDialog(role) }
159
+ ]}
160
+ noItemsTitle='There are no groups associated with this Role'
161
+ noItemsSubtitle=''
162
+ columns={[
163
+ { id: 'group', label: 'Group', get: 'groupName' },
164
+ { id: 'manager', label: 'Manager', render: row => (row.managers.length > 0) ? row.managers[0].fullname + '<br/>' + row.managers[0].email : 'no manager information' },
165
+ { id: 'added', label: 'Added Date', render: row => (row.dateAdded) ? (row.dateAdded as DateTime).toFormat('MM/dd/yyyy') : '--/--/----' }
166
+ ]}
167
+ rows={role.groups.map(g => ({ ...g, id: g.roleId + g.groupName }))}/>
168
+ </div>
169
+ </div>
170
+ {/each}
171
+
172
+ {#if createDialog}
173
+ <PanelFormDialog open title={editingRole?.id ? 'Edit role' : 'Create Role'} preload={editingRole ? pick(editingRole, 'name', 'description', 'groups') : undefined} submit={onSubmit} {validate} on:saved={onSaved} on:cancel={closeDialog} unsavedWarning >
174
+ <p class="panel-description [ text-sm ] ">You can grant or restrict access to various features and data after creating the role.</p>
175
+ <FieldTextInput path="name" labelText="Role Name" required notNull />
176
+ <FieldTextArea path="description" labelText="Description" helperText="This will help other admins understand what the role does." />
177
+ <FieldMore path="groups" legendText="Groups" required>
178
+ <FieldTextInput path="" labelText="Group Name" placeholder="Enter group name"/>
179
+ </FieldMore>
180
+ <svelte:fragment slot="afterform">
181
+ {#if editingRole?.id && !isDuplicating}
182
+ <div class="[ -mt-8 ]">
183
+ <Button kind="secondary" icon={Add} href="/roles/{editingRole.id}">
184
+ Edit permissions
185
+ </Button>
186
+ </div>
187
+ {/if}
188
+ </svelte:fragment>
189
+ </PanelFormDialog>
190
+ {/if}
191
+ <Modal
192
+ bind:open={deleteDialog}
193
+ modalHeading="Delete Role"
194
+ primaryButtonText="Delete"
195
+ secondaryButtonText="Cancel"
196
+ danger
197
+ size="sm"
198
+ on:click:button--secondary={closeRoleDeleteDialog}
199
+ on:submit={executeRoleDelete}>
200
+ <p>Are you sure you want to delete the role {deleteDialogRole?.name}?</p>
201
+ <p>This action cannot be undone.</p>
202
+ </Modal>
203
+
204
+ <style>
205
+ :global(div.admin-settings) {
206
+ margin: 0 0 20px 0;
207
+ background: var(--cds-layer);
208
+ }
209
+ :global(div.admin-settings h2) {
210
+ padding: 8px 8px 0 8px;
211
+ }
212
+ :global(div.admin-settings p) {
213
+ padding: 0 8px 8px 8px;
214
+ line-height: calc(2rem - 10px);
215
+ }
216
+
217
+ .panel-description {
218
+ margin-bottom: 1rem;
219
+ color: var(--cds-text-01);
220
+ }
221
+
222
+ .role-list :global(.column-list-head) {
223
+ background: var(--cds-ui-03);
224
+ }
225
+ </style>
@@ -0,0 +1,13 @@
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, depends, parent }) => {
6
+ const { access } = await parent()
7
+ // access is missing only when the layout failed; it renders layoutError in place of the page, so don't mask it
8
+ if (!access) return {} as never
9
+ if (!access.viewRoleManagement) throw error(403)
10
+ const roles = (await api.getRoleList()) ?? []
11
+ depends('api:getRoleList')
12
+ return { roles, access }
13
+ }
@@ -0,0 +1,10 @@
1
+ import { api } from '$internal'
2
+ import { keyby } from 'txstate-utils'
3
+ import type { LayoutLoad } from './$types'
4
+
5
+ export const load: LayoutLoad = async ({ depends }) => {
6
+ const controlGroups = await api.getAuthorizationInfo()
7
+ const controlGroupLookup = keyby(controlGroups, 'name')
8
+ depends('api:getAuthorizationInfo')
9
+ return { controlGroups, controlGroupLookup }
10
+ }