Skip to content

Commit 9ca102e

Browse files
authored
Gsa/production release (#731)
* Production Release Sprint 46 issues included: BUG: Admin/User Profile Saved Changes are Not Displayed on Results screen #709 BUG: Insert a space between the date field of Completion date range end and Quiz type(s) on the SmartPay Training Report #705 Customizable Training Report for A/OPC #633 * Fixed merge issue * Added finally block to reset loading and show spinner boolean value * Update Fedrooms broken link
1 parent cb87b5b commit 9ca102e

File tree

13 files changed

+547
-61
lines changed

13 files changed

+547
-61
lines changed

training-front-end/src/components/AdminSearchUser.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
function cancelEdit(){
7171
setSelectedUser(undefined)
7272
setSelectedAgencyId(undefined)
73+
// refresh search on cancel from edit reporting page as you can also update user details on the page without
74+
// updating the reporting access. Refresh page to display updated details.
75+
search()
7376
}
7477
7578
async function updateUserSuccess(message) {

training-front-end/src/components/AdminTrainingReport.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
5959
//format dates from uswds standard to the format needed for backend
6060
const formatDateToYYYYMMDD = (dates) => {
61-
return dates ? dates.map(date => (date ? new Date(date).toISOString().split('T')[0] : null)) : [];
61+
return dates ? dates.map(date => (date ? new Date(date).toISOString() : null)) : [];
6262
};
6363
6464
async function downloadReport() {
@@ -133,6 +133,7 @@
133133
:validator="v_all_info$.quiz_names"
134134
name="Quiz type(s)"
135135
legend="Quiz type(s)"
136+
class="margin-top-4"
136137
/>
137138
<USWDSComboBox
138139
v-model="user_input.agency_id"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script>
2+
3+
import {useStore} from "@nanostores/vue";
4+
import {profile} from "../stores/user.js";
5+
6+
const user = useStore(profile)
7+
const base_url = import.meta.env.PUBLIC_API_BASE_URL
8+
const downloadTrainingCompletionReport = async function(filterData){
9+
const response = await fetch(`${base_url}/api/v1/users/download-smartpay-training-report`, {
10+
method: 'POST',
11+
headers: {
12+
'Authorization': `Bearer ${user.value.jwt}`,
13+
'Content-Type': 'application/json'
14+
},
15+
body: JSON.stringify(filterData)
16+
});
17+
if (!response.ok) {
18+
const message = await response.text()
19+
throw new Error(message)
20+
}
21+
22+
return await response //needs to be returned as raw not json
23+
}
24+
25+
export default {
26+
downloadTrainingCompletionReport
27+
}
28+
29+
</script>

training-front-end/src/components/TrainingReportDownload.vue

Lines changed: 190 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,208 @@
11
<script setup>
2-
import { computed } from "vue"
3-
import { useStore } from '@nanostores/vue'
4-
import { profile} from '../stores/user'
5-
import USWDSAlert from './USWDSAlert.vue'
2+
import { computed, reactive, ref, watch} from "vue"
3+
import { useStore } from '@nanostores/vue'
4+
import { useVuelidate } from "@vuelidate/core";
5+
import { profile } from '../stores/user'
6+
import USWDSAlert from './USWDSAlert.vue'
7+
import ValidatedDateRangePicker from "./form-components/ValidatedDateRangePicker.vue";
8+
import ValidatedCheckboxGroup from "./form-components/ValidatedCheckboxGroup.vue";
9+
import USWDSComboBox from "./form-components/USWDSComboBox.vue";
10+
import {agencyList, bureauList, setSelectedAgencyId} from "../stores/agencies.js";
11+
import ReportRepository from "./ReportRepository.vue";
12+
import ReportUtilities from "./ReportUtilities.vue";
13+
import SpinnerGraphic from "./SpinnerGraphic.vue";
614
7-
const user = useStore(profile)
8-
const isReportUser = computed(() => user.value.roles.includes('Report'))
9-
const base_url = import.meta.env.PUBLIC_API_BASE_URL
10-
const report_url = `${base_url}/api/v1/users/download-user-quiz-completion-report`
15+
const error = ref()
16+
const showSuccessMessage = ref(false)
17+
const isLoading = ref(false)
18+
const showSpinner = ref(false)
19+
const user = useStore(profile)
20+
const isReportUser = computed(() => user.value.roles.includes('Report'))
21+
const agency_options = useStore(agencyList)
22+
const bureaus = useStore(bureauList)
23+
const report_agencies = user.value.report_agencies
24+
let filteredAgencyOptions
25+
let filteredBureauOptions
26+
27+
filteredAgencyOptions = computed(() => {
28+
return agency_options.value.filter((agency) => report_agencies.map(x => x.name).includes(agency.name))
29+
})
30+
31+
filteredBureauOptions = computed(() => {
32+
return bureaus.value.filter((bureau) => report_agencies.map(x => x.id).includes(bureau.id))
33+
})
34+
35+
//Properties
36+
const user_input = reactive({
37+
agency_id: undefined,
38+
bureau_id: undefined,
39+
quiz_names: undefined,
40+
completion_date_range: undefined,
41+
})
42+
43+
watch(() => user_input.agency_id, async() => {
44+
setSelectedAgencyId(user_input.agency_id)
45+
user_input.bureau_id = undefined
46+
})
47+
48+
function setError(event){
49+
error.value = event
50+
}
51+
52+
const validation_info = {
53+
agency_id: {},
54+
bureau_id: {},
55+
completion_date_range: {},
56+
quiz_names: {}
57+
}
58+
59+
const quiz_names_options = [
60+
{value: 'Fleet Training For Program Coordinators', label: 'Fleet Training For Program Coordinators'},
61+
{value: 'Purchase Training for Card/Account Holders and Approving Officials', label: 'Purchase Training for Card/Account Holders and Approving Officials'},
62+
{value: 'Purchase Training For Program Coordinators', label: 'Purchase Training For Program Coordinators'},
63+
{value: 'Travel Training for Agency/Organization Program Coordinators', label: 'Travel Training for Agency/Organization Program Coordinators'},
64+
{value: 'Travel Training for Card/Account Holders and Approving Officials', label: 'Travel Training for Card/Account Holders and Approving Officials'},
65+
];
66+
67+
const v_all_info$ = useVuelidate(validation_info, user_input)
68+
69+
//format dates from uswds standard to the format needed for backend
70+
const formatDateToYYYYMMDD = (dates) => {
71+
return dates ? dates.map(date => (date ? new Date(date).toISOString() : null)) : [];
72+
};
73+
74+
async function downloadReport() {
75+
isLoading.value = true
76+
showSpinner.value = true
77+
error.value = undefined
78+
showSuccessMessage.value = false
79+
80+
try {
81+
const dates = formatDateToYYYYMMDD(user_input.completion_date_range)
82+
const model = {
83+
'agency_id': user_input.agency_id || null,
84+
'bureau_id': user_input.bureau_id || null,
85+
'completion_date_start': dates[0] || null,
86+
'completion_date_end': dates[1] || null,
87+
'quiz_names': user_input.quiz_names || null
88+
}
89+
let response = await ReportRepository.downloadTrainingCompletionReport(model)
90+
const blob = await response.blob();
91+
await ReportUtilities.downloadBlobAsFile(blob, 'SmartPayTrainingReport.csv')
92+
showSuccessMessage.value = true
93+
94+
} catch (error) {
95+
setError(error)
96+
} finally {
97+
isLoading.value = false
98+
showSpinner.value = false
99+
}
100+
}
11101
12102
</script>
13103
<template>
14104
<section
15-
v-if="isReportUser"
105+
v-if="isReportUser"
16106
class="usa-prose"
17107
>
18-
<h2>Download Your Report</h2>
19-
<p>
20-
We’ve created a report for you in CSV format. You can open it in the spreadsheet
21-
application of your choice (e.g. Microsoft Excel, Google Sheets, Apple Numbers).
22-
</p>
23-
<form
24-
:action="report_url"
25-
method="post"
26-
>
27-
<input
28-
type="hidden"
29-
name="jwtToken"
30-
:value="user.jwt"
31-
>
32-
<button
33-
class="usa-button"
34-
type="submit"
108+
<div class="padding-top-4 padding-bottom-4 grid-container">
109+
<h2>Download Your Report</h2>
110+
<h2>Enter Report Parameters</h2>
111+
<p>
112+
The GSA SmartPay Training Report has no required parameters.
113+
</p>
114+
<p>
115+
<b>Note:</b> If a report is generated with an individual completing multiple trainings, each training will be listed separately on the report.
116+
</p>
117+
<form
118+
ref="form"
119+
class="usa-form usa-form--large margin-bottom-3"
120+
data-test="report-form"
121+
@submit.prevent="downloadReport"
35122
>
36-
Download Report
37-
</button>
38-
</form>
123+
<ValidatedDateRangePicker
124+
v-model="user_input.completion_date_range"
125+
client:load
126+
:validator="v_all_info$.completion_date_range"
127+
label="Completion date range"
128+
name="Completion date range"
129+
/>
130+
<ValidatedCheckboxGroup
131+
v-model="user_input.quiz_names"
132+
:options="quiz_names_options"
133+
:validator="v_all_info$.quiz_names"
134+
name="Quiz type(s)"
135+
legend="Quiz type(s)"
136+
class="margin-top-4"
137+
/>
138+
<USWDSComboBox
139+
v-model="user_input.agency_id"
140+
client:load
141+
:validator="v_all_info$.agency_id"
142+
:items="filteredAgencyOptions"
143+
label="Agency / organization"
144+
name="Agency"
145+
/>
146+
<USWDSComboBox
147+
v-if="filteredBureauOptions.length"
148+
v-model="user_input.bureau_id"
149+
client:load
150+
:validator="v_all_info$.bureau_id"
151+
:items="filteredBureauOptions"
152+
label="Sub-agency, organization, or bureau"
153+
name="Bureau"
154+
/>
155+
<input
156+
class="usa-button"
157+
type="submit"
158+
value="Submit"
159+
:disabled="isLoading"
160+
data-test="submit"
161+
>
162+
<div>
163+
<USWDSAlert
164+
v-if="error"
165+
status="error"
166+
class="usa-alert--slim"
167+
:has-heading="false"
168+
>
169+
{{ error }}
170+
</USWDSAlert>
171+
<USWDSAlert
172+
v-if="showSuccessMessage"
173+
status="success"
174+
class="usa-alert--slim"
175+
:has-heading="false"
176+
>
177+
Report has been generated.
178+
</USWDSAlert>
179+
</div>
180+
<div class="grid-row grid-gap margin-top-3">
181+
<!--display spinner along with submit button in one row for desktop-->
182+
<div
183+
v-if="showSpinner"
184+
class="display-none tablet:display-block tablet:grid-col-1 tablet:padding-top-3 tablet:margin-left-neg-1"
185+
>
186+
<SpinnerGraphic />
187+
</div>
188+
</div>
189+
<!--display spinner under submit button for mobile view-->
190+
<div
191+
v-if="showSpinner"
192+
class="tablet:display-none margin-top-1 text-center"
193+
>
194+
<SpinnerGraphic />
195+
</div>
196+
</form>
197+
</div>
39198
</section>
40199
<section v-else>
41-
<USWDSAlert
200+
<USWDSAlert
42201
status="error"
43202
class="usa-alert"
44203
heading="You are not authorized to receive reports."
45204
>
46-
Your email account is not authorized to access training reports. If you should be authorized, you can
205+
Your email account is not authorized to access training reports. If you should be authorized, you can
47206
<a
48207
class="usa-link"
49208
href="mailto:gsa_smartpay@gsa.gov"

0 commit comments

Comments
 (0)