|
1 | 1 | <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"; |
6 | 14 |
|
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 | +} |
11 | 101 |
|
12 | 102 | </script> |
13 | 103 | <template> |
14 | 104 | <section |
15 | | - v-if="isReportUser" |
| 105 | + v-if="isReportUser" |
16 | 106 | class="usa-prose" |
17 | 107 | > |
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" |
35 | 122 | > |
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> |
39 | 198 | </section> |
40 | 199 | <section v-else> |
41 | | - <USWDSAlert |
| 200 | + <USWDSAlert |
42 | 201 | status="error" |
43 | 202 | class="usa-alert" |
44 | 203 | heading="You are not authorized to receive reports." |
45 | 204 | > |
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 |
47 | 206 | <a |
48 | 207 | class="usa-link" |
49 | 208 | href="mailto:gsa_smartpay@gsa.gov" |
|
0 commit comments