@byu-oit/vue-decision-processing-components 8.36.1 → 8.37.0-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.
@@ -29,7 +29,7 @@
29
29
  </template>
30
30
  <script>
31
31
  import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
32
- import { faCheck, faHourglassHalf, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
32
+ import { faCheck, faExclamationTriangle, faSquare, faMinus, faHourglassHalf } from '@fortawesome/free-solid-svg-icons'
33
33
  import { mapState, mapGetters } from 'vuex'
34
34
 
35
35
  export default {
@@ -44,19 +44,70 @@
44
44
  endorsementPendingFlag () { return this.flagByValue('EP') },
45
45
  endorsementDeniedFlag () { return this.flagByValue('ED') },
46
46
  endorsementIssuesFlag () { return this.flagByValue('EI') },
47
+ endorsementNewFlag () { return this.flagByValue('endorsement')},
47
48
  icon () {
49
+ if (this.endorsementNewFlag != null){
50
+ switch(this.endorsementNewFlag.state.toLowerCase()){
51
+ case 'pass':
52
+ return faCheck
53
+ case 'fail':
54
+ return faMinus
55
+ case 'error':
56
+ return faExclamationTriangle
57
+ case 'indeterminate':
58
+ return faSquare
59
+ default:
60
+ return faExclamationTriangle
61
+ }
62
+ }
63
+ //BELOW THIS IS THE OLD SYSTEM'S FLAG LOGIC (before the 'state' column was added)
48
64
  if (this.endorsementDeniedFlag.isActive) return faExclamationTriangle
49
65
  else if (this.endorsementIssuesFlag.isActive) return faExclamationTriangle
50
66
  else if (this.endorsementPendingFlag.isActive) return faHourglassHalf
51
67
  return faCheck
52
68
  },
53
69
  statusClass () {
70
+ if (this.endorsementNewFlag != null){
71
+ switch(this.endorsementNewFlag.state.toLowerCase()){
72
+ case 'pass':
73
+ return { 'text-success': true }
74
+ case 'fail':
75
+ return { 'text-danger': true }
76
+ case 'error':
77
+ return { 'text-danger': true, 'font-weight-bold': true }
78
+ case 'indeterminate':
79
+ return { 'text-dark': true }
80
+ default:
81
+ return { 'text-danger': true, 'font-weight-bold': true }
82
+ }
83
+ }
84
+ //BELOW THIS IS THE OLD SYSTEM'S FLAG LOGIC (before the 'state' column was added)
54
85
  if (this.endorsementDeniedFlag.isActive) return { 'text-danger': true }
55
86
  else if (this.endorsementIssuesFlag.isActive) return { 'text-danger': true }
56
87
  else if (this.endorsementPendingFlag.isActive) return { 'text-dark': true, 'font-weight-bold': true }
57
88
  return { 'text-success': true }
58
89
  },
59
90
  endorsementStatus () {
91
+ if (this.endorsementNewFlag != null) {
92
+ if (this.endorsementNewFlag.message != null && this.endorsementNewFlag.message.length > 0) {
93
+ return this.endorsementNewFlag.message
94
+ }
95
+ else {
96
+ switch (this.endorsementNewFlag.state.toLowerCase()) {
97
+ case 'pass':
98
+ return 'Everything looks good!'
99
+ case 'fail':
100
+ return 'Requires additional attention.'
101
+ case 'error':
102
+ return 'An error occurred. Try again later.'
103
+ case 'indeterminate':
104
+ return 'No data available.'
105
+ default:
106
+ return 'An error occurred. Try again later.'
107
+ }
108
+ }
109
+ }
110
+ //BELOW THIS IS THE OLD SYSTEM'S FLAG LOGIC (before the 'state' column was added)
60
111
  if (this.endorsementDeniedFlag.isActive) return `Endorsement Denied`
61
112
  else if (this.endorsementIssuesFlag.isActive) return `Endorsement Issues`
62
113
  else if (this.endorsementPendingFlag.isActive) return `Endorsement Pending`
@@ -23,6 +23,7 @@
23
23
  <button :class="btnClass" @click="flagModal">
24
24
  {{btnText}}
25
25
  </button>
26
+ <!-- <slot name="refreshButton"></slot>-->
26
27
  </div>
27
28
  <div v-if="hasSummary" class="summary">
28
29
  <slot name="summary"></slot>
@@ -32,13 +33,11 @@
32
33
  <slot></slot>
33
34
  </div>
34
35
  </div>
35
- </span>
36
36
  </template>
37
37
  <script>
38
38
  import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
39
- import { faCheck, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
39
+ import { faCheck,faExclamationTriangle,faSquare,faMinus } from '@fortawesome/free-solid-svg-icons'
40
40
  import { mapGetters } from 'vuex'
41
-
42
41
  import ExpandIndicator from './ExpandIndicator'
43
42
 
44
43
  export default {
@@ -58,7 +57,11 @@
58
57
  },
59
58
  'flagActiveColor': { type: String },
60
59
  'flagInactiveColor': { type: String },
61
- 'hideIfUnset': { type: Boolean }
60
+ 'hideIfUnset': { type: Boolean },
61
+ 'flagPassColor': { type: String, default: 'success' },
62
+ 'flagErrorColor': { type: String, default: 'dark' },
63
+ 'flagFailColor': { type: String, default: 'danger' },
64
+ 'flagIndeterminateColor': { type: String, default: 'secondary' }
62
65
  },
63
66
  data () {
64
67
  return {
@@ -83,9 +86,46 @@
83
86
  return this.flag.isActive ? 'clear flag' : 'set flag'
84
87
  },
85
88
  flagIcon () {
89
+ //If there is no flag set, the state is 'indeterminate'
90
+ if (this.flag == null){
91
+ return faSquare
92
+ }
93
+ if (this.flag.state != null){
94
+ switch (this.flag.state.toLowerCase()){
95
+ case 'pass':
96
+ return faCheck
97
+ case 'fail':
98
+ return faMinus
99
+ case 'error':
100
+ return faExclamationTriangle
101
+ case 'indeterminate':
102
+ return faSquare
103
+ default:
104
+ return faExclamationTriangle
105
+ }
106
+ }
86
107
  return this.flag.isActive ? faExclamationTriangle : faCheck
87
108
  },
88
109
  flagBg () {
110
+ //If there is no flag set, the state is 'indeterminate'
111
+ if (this.flag == null){
112
+ return `bg-${this.flagIndeterminateColor}`
113
+ }
114
+ if (this.flag.state != null){
115
+ switch (this.flag.state.toLowerCase()){
116
+ case 'pass':
117
+ return `bg-${this.flagPassColor}`
118
+ case 'fail':
119
+ return `bg-${this.flagFailColor}`
120
+ case 'error':
121
+ return `bg-${this.flagErrorColor}`
122
+ case 'indeterminate':
123
+ return `bg-${this.flagIndeterminateColor}`
124
+ default:
125
+ return `bg-${this.flagErrorColor}`
126
+ }
127
+ }
128
+
89
129
  const stateAware = this.flagActiveColor && this.flagInactiveColor
90
130
  if (!stateAware) return `bg-${this.flagType}`
91
131
 
package/SearchForm.vue ADDED
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <div class="search-form p-2">
3
+ <form @submit.prevent="doSearch(null)">
4
+ <div class="p-1 d-inline-block">
5
+ <div class="custom-control custom-switch">
6
+ <input type="checkbox" class="custom-control-input float-end" id="searchNewAppSwitch" v-model="searchNewApp">
7
+ <label class="custom-control-label" for="searchNewAppSwitch">{{ searchNewApp ? 'NEW' : 'OLD' }} App</label>
8
+ </div>
9
+
10
+ <label for="lastNameInput">Last Name/ID</label>
11
+ <input id="lastName00Input" v-model="lastName" @focus="$event.target.select()">
12
+
13
+ <label for="firstNameInput">First Name</label>
14
+ <input id="firstNameInput" v-model="firstName" @focus="$event.target.select()">
15
+
16
+ <label for="monthInput">Birthdate</label>
17
+ <input id="monthInput" aria-label="month" placeholder="mm" v-model="month" @focus="$event.target.select()" @blur="month = month.length===1 ? '0' + month : month" size="2" maxlength="2" :required="day !== '' || year !== ''">
18
+ <input id="dayInput" aria-label="day" placeholder="dd" v-model="day" @focus="$event.target.select()" @blur="day = day.length===1 ? '0' + day : day" size="2" maxlength="2" :required="month !== '' || year !== ''">
19
+ <input id="yearInput" aria-label="year" placeholder="yyyy" v-model="year" @focus="$event.target.select()" size="4" maxlength="4" :required="month !== '' || day !== ''">
20
+ </div>
21
+
22
+ <button id="submitBtn" type="submit" class="btn btn-sm btn-outline-primary">
23
+ {{searching ? 'Searching' : 'Search'}}
24
+ <FontAwesomeIcon v-if="searching" :icon="icon" :spin="searching" />
25
+ </button>
26
+ </form>
27
+ <SearchResults :results="results" @searchPaginate="doSearch" @select="$emit('select', { appId: $event, searchNewApp })" />
28
+ </div>
29
+ </template>
30
+ <script>
31
+ import SearchResults from './SearchResults.vue'
32
+ import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
33
+ import {faSpinner} from '@fortawesome/free-solid-svg-icons'
34
+ import {mapActions} from "vuex";
35
+
36
+ export default {
37
+ name: 'SearchForm',
38
+ components: {
39
+ SearchResults,
40
+ FontAwesomeIcon
41
+ },
42
+ data() {
43
+ return {
44
+ lastName: '',
45
+ firstName: '',
46
+ month: '',
47
+ day: '',
48
+ year: '',
49
+ results: {},
50
+ searchNewApp: true,
51
+ searching: false
52
+ }
53
+ },
54
+ computed: {
55
+ icon() {
56
+ return faSpinner
57
+ },
58
+ birthDate() {
59
+ if (!(this.year.length === 4 && this.month.length === 2 && this.day.length === 2)) {
60
+ return ''
61
+ }
62
+ return this.year + '-' + this.month + '-' + this.day
63
+ }
64
+ },
65
+ methods: {
66
+ // mapActions is a Vuex helper function that maps stored actions to a local method
67
+ ...mapActions({
68
+ oldSearchFunction: 'oldAppSearch',
69
+ newSearchFunction: 'newAppSearch'
70
+ }),
71
+
72
+ async doSearch(pageLink) {
73
+ this.lastName = this.lastName.trim()
74
+ this.firstName = this.firstName.trim()
75
+
76
+ if (this.searchNewApp) {
77
+ this.results = await this.newAppSearch()
78
+ } else {
79
+ this.results = await this.oldAppSearch(pageLink)
80
+ }
81
+
82
+ if (!this.results) return
83
+
84
+ if (this.results.people.length === 1) {
85
+ this.$emit('select', {appId: this.results.people[0].appId, searchNewApp: this.searchNewApp})
86
+ } else {
87
+ this.$modal.show('search-results')
88
+ }
89
+
90
+ // clear search fields
91
+ this.lastName = ''
92
+ this.firstName = ''
93
+ this.year = ''
94
+ this.month = ''
95
+ this.day = ''
96
+ },
97
+ async newAppSearch() {
98
+ const params = {}
99
+ if (this.lastName.length > 0) {
100
+ if (this.firstName.length > 0) {
101
+ params.surname = this.lastName
102
+ params.first_name = this.firstName
103
+ } else {
104
+ params.search = this.lastName
105
+ }
106
+ } else if (this.firstName.length > 0) {
107
+ params.first_name = this.firstName
108
+ }
109
+ if (this.birthDate) {
110
+ params.birth_date = this.birthDate
111
+ }
112
+
113
+ if (Object.keys(params).length === 0) {
114
+ alert('No search terms specified!')
115
+ return
116
+ }
117
+
118
+ this.searching = true
119
+ return this.newSearchFunction(params)
120
+ .finally(() => this.searching = false)
121
+ },
122
+ async oldAppSearch(pageLink) {
123
+ let params = {}
124
+ if (this.lastName.length > 0) {
125
+ if (this.firstName.length > 0) {
126
+ params.last_name = this.lastName
127
+ params.first_name = this.firstName
128
+ if (this.birthDate) {
129
+ params.birth = this.birthDate
130
+ }
131
+ } else if (this.birthDate) {
132
+ params.last_name = this.lastName
133
+ params.birth = this.birthDate
134
+ } else {
135
+ params.search = this.lastName
136
+ }
137
+ } else if (this.firstName.length > 0) {
138
+ params.first_name = this.firstName
139
+ if (this.birthDate) {
140
+ params.birth = this.birthDate
141
+ }
142
+ }
143
+ if (Object.keys(params).length === 0) {
144
+ alert('No search terms specified!')
145
+ return
146
+ }
147
+
148
+ this.searching = true
149
+ return this.oldSearchFunction(params, pageLink)
150
+ .finally(() => this.searching = false)
151
+ }
152
+ }
153
+ }
154
+ </script>
155
+ <style scoped>
156
+ .search-form {
157
+ box-shadow: inset 0rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25);
158
+ }
159
+
160
+ .custom-switch .custom-control-label::before,
161
+ .custom-control-label::before {
162
+ right: -2.25rem;
163
+ left: unset;
164
+ }
165
+
166
+ .custom-switch .custom-control-label::after,
167
+ .custom-control-label::after {
168
+ right: calc(-1.5rem + 2px);
169
+ left: unset;
170
+ }
171
+
172
+ .custom-switch {
173
+ display: inherit;
174
+ padding-right: 2.25rem;
175
+ padding-left: unset;
176
+ }
177
+ </style>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <modal name="search-results" :width="dialogWidth" height="auto" :scrollable="true">
3
+ <div class="bg-light p-1 text-uppercase">Search Results</div>
4
+ <div class="search-results p-2">
5
+ <div class="results">
6
+ <table class="table table-sm table-striped table-hover">
7
+ <thead>
8
+ <th>Name</th>
9
+ <th>Birthday</th>
10
+ <th>BYU ID</th>
11
+ <th>Net ID</th>
12
+ <th>Applicant Type</th>
13
+ <th>Admit Period</th>
14
+ <th>Application Status</th>
15
+ <th>Submitted On</th>
16
+ </thead>
17
+ <tbody>
18
+ <tr v-for="row of filteredApps" :key="row.appId">
19
+ <td>
20
+ <a href="#" @click.prevent="$emit('select', row.appId)">
21
+ {{row.name}}
22
+ </a>
23
+ </td>
24
+ <td>{{row.dateOfBirth | dateFormat}}</td>
25
+ <td>{{row.byuId}}</td>
26
+ <td>{{row.netId}}</td>
27
+ <td>{{row.applicantType}}</td>
28
+ <td>{{row.admitPeriod | yearTerm }}</td>
29
+ <td>{{row.applicationStatus + (row.decisionQualifier ? ' | ' + row.decisionQualifier : '')}}</td>
30
+ <td>{{row.submittedDate | dateFormat}}</td>
31
+ </tr>
32
+ </tbody>
33
+ </table>
34
+ </div>
35
+ <span class="modal-footer">
36
+ <input type="checkbox" v-model="showNonPending" id="showNonPending">
37
+ <label for="showNonPending">Show Closed/Past Applications</label>
38
+ <button v-if="results.prev" @click="prev" class="btn btn-outline-info">Prev Page</button>
39
+ <button v-if="results.next" @click="next" class="btn btn-outline-info">Next Page</button>
40
+ <button @click="close" class="btn btn-outline-secondary">Close</button>
41
+ </span>
42
+ </div>
43
+ </modal>
44
+ </template>
45
+ <script>
46
+ export default {
47
+ name: 'SearchResults',
48
+ props: {
49
+ results: {
50
+ type: Object,
51
+ default() {
52
+ return {next: null, prev: null, people: []}
53
+ }
54
+ }
55
+ },
56
+ data() {
57
+ return {
58
+ showNonPending: false
59
+ }
60
+ },
61
+ computed: {
62
+ dialogWidth() {
63
+ const vw = v => {
64
+ const w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
65
+ return (v * w) / 100
66
+ }
67
+ const desiredWidth = vw(80)
68
+ return Math.max(800, desiredWidth)
69
+ },
70
+ filteredApps() {
71
+ if (this.showNonPending) {
72
+ return this.results.people
73
+ }
74
+ if (!Array.isArray(this.results.people)) {
75
+ return []
76
+ }
77
+ console.log(this.results.people[0])
78
+ return this.results.people.filter(({applicationStatusValue}) => ['OPEN', 'SUBMITTED'].includes(applicationStatusValue))
79
+ }
80
+ },
81
+ methods: {
82
+ close() {
83
+ this.$modal.hide('search-results')
84
+ },
85
+ prev() {
86
+ const link = this.results.prev
87
+ this.$emit('searchPaginate', link)
88
+ },
89
+ next() {
90
+ const link = this.results.next
91
+ this.$emit('searchPaginate', link)
92
+ }
93
+ },
94
+ watch: {
95
+ $route: {
96
+ handler() {
97
+ this.close()
98
+ },
99
+ deep: true
100
+ },
101
+ results: {
102
+ handler() {
103
+ if (!this.showNonPending && this.results.people.length > 0 && this.filteredApps.length === 0) {
104
+ this.showNonPending = true
105
+ }
106
+ },
107
+ deep: true
108
+ }
109
+ }
110
+ }
111
+ </script>
112
+ <style scoped>
113
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byu-oit/vue-decision-processing-components",
3
- "version": "8.36.1",
3
+ "version": "8.37.0-0",
4
4
  "description": "Vue components shared between decision processing systems for the CES schools.",
5
5
  "dependencies": {
6
6
  "@fortawesome/fontawesome-free": "^5.15.4",
package/parsers/packet.js CHANGED
@@ -12,6 +12,8 @@ export const parseFlag = f => {
12
12
  const clearedBy = get(f, 'id_cleared_by.description')
13
13
  const clearedById = get(f, 'id_cleared_by.value')
14
14
  const type = get(f, 'type.value')
15
+ const state = get(f, 'state.value')
16
+ const message = get(f, 'message.value')
15
17
  return {
16
18
  dateTimeCleared,
17
19
  setBy,
@@ -21,7 +23,9 @@ export const parseFlag = f => {
21
23
  dateTimeSet,
22
24
  clearedBy,
23
25
  clearedById,
24
- type
26
+ type,
27
+ state,
28
+ message
25
29
  }
26
30
  }
27
31
 
@@ -17,14 +17,21 @@ const blockingVirtualFlag = flags => {
17
17
  }
18
18
 
19
19
  const endorseVirtualFlag = flags => {
20
+ // Get new endorsement flag data
21
+ const endorsementNewFlag = flags.filter(f => f.value === 'endorsement')
22
+ if(endorsementNewFlag.length > 0) return endorsementNewFlag[0]
23
+
24
+ // Maintain old functionality
20
25
  const edFlag = flags.filter(f => f.value === 'ED').filter(f => f.isActive)
21
26
  const eiFlag = flags.filter(f => f.value === 'EI').filter(f => f.isActive)
22
27
  const allFlags = edFlag.concat(eiFlag)
23
28
  if (allFlags.length > 0) return allFlags[0]
24
- const value = 'endorse'
29
+
30
+ // Default if no flag data is found
31
+ const value = 'endorsement'
25
32
  const name = 'Endorsement Problem'
26
33
  const type = 'Process'
27
- return { value, name, type, isActive: false, isSet: false, isCleared: false, isBlocking: true }
34
+ return { value, name, type, isActive: false, isSet: false, isCleared: false, isBlocking: true, state: 'indeterminate' }
28
35
  }
29
36
 
30
37
  // getters
@@ -38,7 +45,7 @@ export const getters = {
38
45
  const flags = getters.flags
39
46
  if (flagValue === 'blocking') {
40
47
  return blockingVirtualFlag(flags)
41
- } else if (flagValue === 'endorse') {
48
+ } else if (flagValue === 'endorsement') {
42
49
  return endorseVirtualFlag(flags)
43
50
  }
44
51
  const flag = flags.find(({ value }) => value === flagValue)
@@ -86,7 +93,9 @@ export const convertFlag = flag => {
86
93
  dateTimeSet,
87
94
  setBy,
88
95
  dateTimeCleared,
89
- clearedBy
96
+ clearedBy,
97
+ state,
98
+ message
90
99
  } = flag
91
100
  const isBlocking = /process|wait/i.test(type)
92
101
  return {
@@ -100,7 +109,9 @@ export const convertFlag = flag => {
100
109
  setBy,
101
110
  dateTimeCleared,
102
111
  clearedBy,
103
- isBlocking
112
+ isBlocking,
113
+ state,
114
+ message
104
115
  }
105
116
  }
106
117