Skip to content

Commit 841cab6

Browse files
authored
Frontend/cosmo search updates (#1308)
### Requirements List - _None_ ### Description List - Update Licensee Search UI for cosmetology-specific capability - Update Licensee List UI for cosmetology-specific capability - Update Licensee Detail UI for cosmetology-specific capability - Improve the License & Privilege card sorting on the Licensee Detail UI - Improve app-mode setting to be set automatically in store, rather than per component - Minor update to `eslintrc` for using `v-for` keys in `<template>` elements ### TODO - [x] Wait for backend changes to be merged - [x] Wait for tickets to be broken out on board and assign Closed # below ### Testing List - `yarn test:unit:all` should run without errors or warnings - `yarn serve` should run without errors or warnings - `yarn build` should run without errors or warnings - Code review - Testing - Public search - Public search for JCC (ASLP, OT, COUN) should continue to work as it has - Public search for Cosmetology should match the Figma designs - Adding License number search and no longer requiring Last Name if First Name is searched - List results should no longer have a Privileges column, and instead have a State License Number Column - Detail results should include Licenses - Licenses should be sorted by state, then license type - Privileges should be sorted by license type, then state - Staff search - Public search for JCC (ASLP, OT, COUN) should continue to work as it has - Public search for Cosmetology should match the Figma designs - Remove unused fields for cosmetology, add license number search + dob search - List results should no longer have a Privileges column, and instead have a State License Number Column - Detail results should have more limited privilege information in privilege cards - Licenses should be sorted by state, then license type - Privileges should be sorted by license type, then state Closes #1268 Closes #1269 Closes #1280 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Cosmetology mode: license number and DOB search fields, mock-populate and clear actions, and license-number column in results * Added cosmetologist and esthetician license types * **Improvements** * UI now shows/hides fields and actions per app mode (JCC vs Cosmetology) * Search and requests include DOB and license number; results/summaries display them when present * Consistent license/privilege sorting and improved tablet wrapping * Keyboard-accessible reset (Enter), improved pagination keyboard focus, and localized "Invalid date" validation message <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ddd218d commit 841cab6

35 files changed

Lines changed: 568 additions & 121 deletions

webroot/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ module.exports = {
9797
}],
9898
'vue/multi-word-component-names': OFF,
9999
'vue/no-multiple-template-root': OFF,
100+
'vue/no-v-for-template-key': OFF,
100101
'prefer-regex-literals': OFF,
101102
'no-promise-executor-return': OFF,
102103
},

webroot/src/components/CompactSelector/CompactSelector.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by InspiringApps on 10/2/2024.
66
//
77

8-
import { AppModes, compacts as compactsConfig } from '@/app.config';
8+
import { compacts as compactsConfig } from '@/app.config';
99
import {
1010
Component,
1111
mixins,
@@ -146,11 +146,6 @@ class CompactSelector extends mixins(MixinForm) {
146146
} else {
147147
// Refresh the compact type on the store
148148
await this.$store.dispatch('user/setCurrentCompact', CompactSerializer.fromServer({ type: selectedCompactType }));
149-
if (selectedCompactType === CompactType.COSMETOLOGY) {
150-
this.$store.dispatch('setAppMode', AppModes.COSMETOLOGY);
151-
} else {
152-
this.$store.dispatch('setAppMode', AppModes.JCC);
153-
}
154149
}
155150
}
156151

webroot/src/components/Forms/InputDate/InputDate.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ class InputDate extends mixins(MixinInput) {
109109
const { formInput } = this;
110110

111111
formInput.validate = () => {
112+
const { localValue, dateRaw } = this;
112113
const { validation } = formInput;
113114

115+
// Date format validation
114116
if (validation && (validation as any).validate) {
115-
const result = (validation as any).validate(this.localValue);
117+
const result = (validation as any).validate(localValue);
116118

117119
if (result.error) {
118120
formInput.isValid = false;
@@ -128,6 +130,15 @@ class InputDate extends mixins(MixinInput) {
128130
formInput.errorMessage = '';
129131
formInput.isValid = true;
130132
}
133+
134+
// Date existance validation
135+
if (formInput.isValid && (localValue && !dateRaw)) {
136+
formInput.isValid = false;
137+
138+
if (formInput.isTouched) {
139+
formInput.errorMessage = this.$t('inputErrors.invalidDate');
140+
}
141+
}
131142
};
132143
}
133144

webroot/src/components/Forms/_mixins/form.mixin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ class MixinForm extends Vue {
109109
'boolean.base': this.$t('inputErrors.required'),
110110
'any.invalid': this.$t('inputErrors.required'),
111111
},
112+
date: {
113+
'date.invalid': this.$t('inputErrors.invalidDate'),
114+
},
112115
dateWithFormat: (format: string) => ({
113116
'any.required': this.$t('inputErrors.required'),
114117
'string.empty': this.$t('inputErrors.required'),
118+
'date.invalid': this.$t('inputErrors.invalidDate'),
115119
'string.base': this.$t('inputErrors.invalidDateWithFormat', { format }),
116120
'string.pattern.base': this.$t('inputErrors.invalidDateWithFormat', { format }),
117121
}),

webroot/src/components/Licensee/LicenseeList/LicenseeList.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
align-content: center;
2727
align-items: center;
2828
justify-content: center;
29+
order: 1;
2930
width: 100%;
3031
margin-top: 1.2rem;
3132
padding: 0.4rem 1rem;
@@ -34,7 +35,7 @@
3435
background-color: @darkBlue;
3536

3637
@media @desktopWidth {
37-
order: 1;
38+
order: 0;
3839
width: auto;
3940
max-width: 75%;
4041
margin-top: 0;

webroot/src/components/Licensee/LicenseeList/LicenseeList.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ class LicenseeList extends Vue {
8585
return this.$store.state.license;
8686
}
8787

88+
get isAppModeJcc(): boolean {
89+
return this.$store.getters.isAppModeJcc;
90+
}
91+
92+
get isAppModeCosmetology(): boolean {
93+
return this.$store.getters.isAppModeCosmetology;
94+
}
95+
8896
get licenseStoreRecordCount(): number {
8997
return this.licenseStore.model?.length || 0;
9098
}
@@ -103,6 +111,12 @@ class LicenseeList extends Vue {
103111
return `${firstName} ${lastName}`.trim();
104112
}
105113

114+
get searchDisplayDob(): string {
115+
const { dob = '' } = this.searchParams;
116+
117+
return (dob) ? `${this.$t('common.dateOfBirthShort')}: ${moment(dob, serverDateFormat).format(displayDateFormat)}`.trim() : '';
118+
}
119+
106120
get searchDisplayHomeState(): string {
107121
const { homeState } = this.searchParams;
108122

@@ -189,17 +203,25 @@ class LicenseeList extends Vue {
189203
return (npi) ? `${this.$t('licensing.npi')}: ${npi}`.trim() : '';
190204
}
191205

206+
get searchDisplayLicenseNumber(): string {
207+
const { licenseNumber = '' } = this.searchParams;
208+
209+
return (licenseNumber) ? `${this.$t('licensing.licenseNumSymbol')}: ${licenseNumber}`.trim() : '';
210+
}
211+
192212
get searchDisplayAll(): string {
193213
const joined = [
194214
this.searchDisplayCompact,
195215
this.searchDisplayFullName,
216+
this.searchDisplayDob,
196217
this.searchDisplayHomeState,
197218
this.searchDisplayPrivilegeState,
198219
this.searchDisplayPrivilegePurchaseDates,
199220
this.searchDisplayMilitaryStatus,
200221
this.searchDisplayInvestigationStatus,
201222
this.searchDisplayEncumberDates,
202-
this.searchDisplayNpi
223+
this.searchDisplayNpi,
224+
this.searchDisplayLicenseNumber
203225
].join(', ').trim();
204226

205227
return joined.replace(/(^[,\s]+)|([,\s]+$)/g, '').replace(/(,\s)\1+/g, ', '); // Replace repeated commas with single comma
@@ -218,7 +240,14 @@ class LicenseeList extends Vue {
218240
firstName: this.$t('common.firstName'),
219241
lastName: this.$t('common.lastName'),
220242
homeJurisdictionDisplay: () => this.$t('licensing.homeState'),
221-
privilegeStatesDisplay: () => this.$t('licensing.privileges'),
243+
...(this.isAppModeCosmetology
244+
? {
245+
licenseNumber: this.$t('licensing.stateLicenseNumber'),
246+
}
247+
: {
248+
privilegeStatesDisplay: () => this.$t('licensing.privileges'),
249+
}
250+
),
222251
};
223252

224253
return record;
@@ -343,6 +372,9 @@ class LicenseeList extends Vue {
343372
if (searchParams?.lastName) {
344373
requestConfig.licenseeLastName = searchParams.lastName;
345374
}
375+
if (searchParams?.dob) {
376+
requestConfig.dob = searchParams.dob;
377+
}
346378
if (searchParams?.homeState) {
347379
requestConfig.homeState = searchParams.homeState.toLowerCase();
348380
}
@@ -370,6 +402,9 @@ class LicenseeList extends Vue {
370402
if (searchParams?.npi) {
371403
requestConfig.npi = searchParams.npi;
372404
}
405+
if (searchParams?.licenseNumber) {
406+
requestConfig.licenseNumber = searchParams.licenseNumber;
407+
}
373408

374409
// Paging params
375410
if (!isDirectExport) {

webroot/src/components/Licensee/LicenseeList/LicenseeList.vue

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,23 @@
2121
<div class="list-actions-container">
2222
<h1 class="list-title no-margin">{{ $t('licensing.licensingListTitle') }}</h1>
2323
<div class="search-toggle-container">
24-
<button
25-
class="search-toggle"
26-
@click="toggleSearch()"
27-
tabindex="0"
28-
>
29-
{{ $t('licensing.searchLabel') }}
30-
</button>
3124
<div v-if="searchDisplayAll" class="search-tag">
3225
<span class="title">{{ $t('common.viewing') }}:</span>
3326
<span class="search-terms">{{ searchDisplayAll }}</span>
3427
<CloseX
3528
class="search-terms-reset"
3629
@click="resetSearch()"
30+
@keyup.enter="resetSearch()"
3731
tabindex="0"
3832
/>
3933
</div>
34+
<button
35+
class="search-toggle"
36+
@click="toggleSearch()"
37+
tabindex="0"
38+
>
39+
{{ $t('licensing.searchLabel') }}
40+
</button>
4041
</div>
4142
</div>
4243
<div class="list-description">{{ $t('licensing.licensingListDescription')}}</div>

webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
align-content: center;
2727
align-items: center;
2828
justify-content: center;
29+
order: 1;
2930
width: 100%;
3031
margin-top: 1.2rem;
3132
padding: 0.4rem 1rem;
@@ -34,7 +35,7 @@
3435
background-color: @darkBlue;
3536

3637
@media @desktopWidth {
37-
order: 1;
38+
order: 0;
3839
width: auto;
3940
margin-top: 0;
4041
margin-right: 2.4rem;

webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.ts

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ class LicenseeList extends Vue {
8585
return this.$store.state.license;
8686
}
8787

88+
get isAppModeJcc(): boolean {
89+
return this.$store.getters.isAppModeJcc;
90+
}
91+
92+
get isAppModeCosmetology(): boolean {
93+
return this.$store.getters.isAppModeCosmetology;
94+
}
95+
8896
get licenseStoreRecordCount(): number {
8997
return this.licenseStore.model?.length || 0;
9098
}
@@ -97,58 +105,33 @@ class LicenseeList extends Vue {
97105
return (this.isPublicSearch) ? this.userStore.currentCompact?.abbrev() || '' : '';
98106
}
99107

100-
get searchDisplayFirstName(): string {
101-
const delimiter = (this.searchDisplayCompact) ? ', ' : '';
102-
let displayFirstName = '';
108+
get searchDisplayFullName(): string {
109+
const { firstName = '', lastName = '' } = this.searchParams;
103110

104-
if (this.searchParams.firstName) {
105-
displayFirstName = `${delimiter}${this.searchParams.firstName}` || '';
106-
}
107-
108-
return displayFirstName;
109-
}
110-
111-
get searchDisplayLastName(): string {
112-
const delimiter = (this.searchDisplayCompact && !this.searchDisplayFirstName) ? ', ' : '';
113-
const subDelimiter = (this.searchDisplayFirstName) ? ' ' : '';
114-
let displayLastName = '';
115-
116-
if (this.searchParams.lastName) {
117-
displayLastName = `${delimiter}${subDelimiter}${this.searchParams.lastName}` || '';
118-
}
119-
120-
return displayLastName;
111+
return `${firstName} ${lastName}`.trim();
121112
}
122113

123114
get searchDisplayState(): string {
124115
const { state } = this.searchParams;
125-
const { searchDisplayCompact, searchDisplayFirstName, searchDisplayLastName } = this;
126-
const delimiter = (searchDisplayCompact || searchDisplayFirstName || searchDisplayLastName) ? ', ' : '';
127-
let displayState = '';
128116

129-
if (state) {
130-
const stateModel = new State({ abbrev: state });
117+
return (state) ? `${new State({ abbrev: state }).name()}` : '';
118+
}
131119

132-
displayState = `${delimiter}${stateModel.name()}`;
133-
}
120+
get searchDisplayLicenseNumber(): string {
121+
const { licenseNumber = '' } = this.searchParams;
134122

135-
return displayState;
123+
return (licenseNumber) ? `${this.$t('licensing.licenseNumSymbol')}: ${licenseNumber}`.trim() : '';
136124
}
137125

138126
get searchDisplayAll(): string {
139-
const {
140-
searchDisplayCompact,
141-
searchDisplayFirstName,
142-
searchDisplayLastName,
143-
searchDisplayState
144-
} = this;
145-
146127
return [
147-
searchDisplayCompact,
148-
searchDisplayFirstName,
149-
searchDisplayLastName,
150-
searchDisplayState
151-
].join('').trim();
128+
this.searchDisplayCompact,
129+
this.searchDisplayFullName,
130+
this.searchDisplayState,
131+
this.searchDisplayLicenseNumber
132+
]
133+
.filter((displayPart) => !!displayPart?.trim())
134+
.join(', ').trim();
152135
}
153136

154137
get sortOptions(): Array<any> {
@@ -168,9 +151,15 @@ class LicenseeList extends Vue {
168151
const record = {
169152
firstName: this.$t('common.firstName'),
170153
lastName: this.$t('common.lastName'),
171-
ssnMaskedPartial: () => this.$t('licensing.ssn'),
172154
homeJurisdictionDisplay: () => this.$t('licensing.homeState'),
173-
privilegeStatesDisplay: () => this.$t('licensing.privileges'),
155+
...(this.isAppModeCosmetology
156+
? {
157+
licenseNumber: this.$t('licensing.stateLicenseNumber'),
158+
}
159+
: {
160+
privilegeStatesDisplay: () => this.$t('licensing.privileges'),
161+
}
162+
),
174163
};
175164

176165
return record;
@@ -307,6 +296,9 @@ class LicenseeList extends Vue {
307296
if (searchParams?.state) {
308297
requestConfig.jurisdiction = searchParams.state.toLowerCase();
309298
}
299+
if (this.isAppModeCosmetology && searchParams?.licenseNumber) {
300+
requestConfig.licenseNumber = searchParams.licenseNumber;
301+
}
310302

311303
// Make fetch request
312304
await this.$store.dispatch('license/getLicenseesRequest', {

webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.vue

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,23 @@
2020
<div class="list-actions-container">
2121
<h1 class="list-title no-margin">{{ $t('licensing.licensingListTitle') }}</h1>
2222
<div class="search-toggle-container">
23-
<button
24-
class="search-toggle"
25-
@click="toggleSearch()"
26-
tabindex="0"
27-
>
28-
{{ $t('licensing.searchLabel') }}
29-
</button>
3023
<div v-if="searchDisplayAll" class="search-tag">
3124
<span class="title">{{ $t('common.viewing') }}:</span>
3225
<span class="search-terms">{{ searchDisplayAll }}</span>
3326
<CloseX
3427
class="search-terms-reset"
3528
@click="resetSearch()"
29+
@keyup.enter="resetSearch()"
3630
tabindex="0"
3731
/>
3832
</div>
33+
<button
34+
class="search-toggle"
35+
@click="toggleSearch()"
36+
tabindex="0"
37+
>
38+
{{ $t('licensing.searchLabel') }}
39+
</button>
3940
</div>
4041
</div>
4142
<div class="list-description">{{ $t('licensing.licensingListDescription')}}</div>

0 commit comments

Comments
 (0)