diff --git a/webroot/.eslintrc.js b/webroot/.eslintrc.js
index 377253b03..55b42d65c 100644
--- a/webroot/.eslintrc.js
+++ b/webroot/.eslintrc.js
@@ -97,6 +97,7 @@ module.exports = {
}],
'vue/multi-word-component-names': OFF,
'vue/no-multiple-template-root': OFF,
+ 'vue/no-v-for-template-key': OFF,
'prefer-regex-literals': OFF,
'no-promise-executor-return': OFF,
},
diff --git a/webroot/src/components/CompactSelector/CompactSelector.ts b/webroot/src/components/CompactSelector/CompactSelector.ts
index 95f95b5f0..8db49ff50 100644
--- a/webroot/src/components/CompactSelector/CompactSelector.ts
+++ b/webroot/src/components/CompactSelector/CompactSelector.ts
@@ -5,7 +5,7 @@
// Created by InspiringApps on 10/2/2024.
//
-import { AppModes, compacts as compactsConfig } from '@/app.config';
+import { compacts as compactsConfig } from '@/app.config';
import {
Component,
mixins,
@@ -146,11 +146,6 @@ class CompactSelector extends mixins(MixinForm) {
} else {
// Refresh the compact type on the store
await this.$store.dispatch('user/setCurrentCompact', CompactSerializer.fromServer({ type: selectedCompactType }));
- if (selectedCompactType === CompactType.COSMETOLOGY) {
- this.$store.dispatch('setAppMode', AppModes.COSMETOLOGY);
- } else {
- this.$store.dispatch('setAppMode', AppModes.JCC);
- }
}
}
diff --git a/webroot/src/components/Forms/InputDate/InputDate.ts b/webroot/src/components/Forms/InputDate/InputDate.ts
index 27e361dc3..098bde575 100644
--- a/webroot/src/components/Forms/InputDate/InputDate.ts
+++ b/webroot/src/components/Forms/InputDate/InputDate.ts
@@ -109,10 +109,12 @@ class InputDate extends mixins(MixinInput) {
const { formInput } = this;
formInput.validate = () => {
+ const { localValue, dateRaw } = this;
const { validation } = formInput;
+ // Date format validation
if (validation && (validation as any).validate) {
- const result = (validation as any).validate(this.localValue);
+ const result = (validation as any).validate(localValue);
if (result.error) {
formInput.isValid = false;
@@ -128,6 +130,15 @@ class InputDate extends mixins(MixinInput) {
formInput.errorMessage = '';
formInput.isValid = true;
}
+
+ // Date existance validation
+ if (formInput.isValid && (localValue && !dateRaw)) {
+ formInput.isValid = false;
+
+ if (formInput.isTouched) {
+ formInput.errorMessage = this.$t('inputErrors.invalidDate');
+ }
+ }
};
}
diff --git a/webroot/src/components/Forms/_mixins/form.mixin.ts b/webroot/src/components/Forms/_mixins/form.mixin.ts
index fcf45eec2..e1d03ff80 100644
--- a/webroot/src/components/Forms/_mixins/form.mixin.ts
+++ b/webroot/src/components/Forms/_mixins/form.mixin.ts
@@ -109,9 +109,13 @@ class MixinForm extends Vue {
'boolean.base': this.$t('inputErrors.required'),
'any.invalid': this.$t('inputErrors.required'),
},
+ date: {
+ 'date.invalid': this.$t('inputErrors.invalidDate'),
+ },
dateWithFormat: (format: string) => ({
'any.required': this.$t('inputErrors.required'),
'string.empty': this.$t('inputErrors.required'),
+ 'date.invalid': this.$t('inputErrors.invalidDate'),
'string.base': this.$t('inputErrors.invalidDateWithFormat', { format }),
'string.pattern.base': this.$t('inputErrors.invalidDateWithFormat', { format }),
}),
diff --git a/webroot/src/components/Licensee/LicenseeList/LicenseeList.less b/webroot/src/components/Licensee/LicenseeList/LicenseeList.less
index e6fddc300..fea52e3e8 100644
--- a/webroot/src/components/Licensee/LicenseeList/LicenseeList.less
+++ b/webroot/src/components/Licensee/LicenseeList/LicenseeList.less
@@ -26,6 +26,7 @@
align-content: center;
align-items: center;
justify-content: center;
+ order: 1;
width: 100%;
margin-top: 1.2rem;
padding: 0.4rem 1rem;
@@ -34,7 +35,7 @@
background-color: @darkBlue;
@media @desktopWidth {
- order: 1;
+ order: 0;
width: auto;
max-width: 75%;
margin-top: 0;
diff --git a/webroot/src/components/Licensee/LicenseeList/LicenseeList.ts b/webroot/src/components/Licensee/LicenseeList/LicenseeList.ts
index 5403978c4..7c6154ce0 100644
--- a/webroot/src/components/Licensee/LicenseeList/LicenseeList.ts
+++ b/webroot/src/components/Licensee/LicenseeList/LicenseeList.ts
@@ -85,6 +85,14 @@ class LicenseeList extends Vue {
return this.$store.state.license;
}
+ get isAppModeJcc(): boolean {
+ return this.$store.getters.isAppModeJcc;
+ }
+
+ get isAppModeCosmetology(): boolean {
+ return this.$store.getters.isAppModeCosmetology;
+ }
+
get licenseStoreRecordCount(): number {
return this.licenseStore.model?.length || 0;
}
@@ -103,6 +111,12 @@ class LicenseeList extends Vue {
return `${firstName} ${lastName}`.trim();
}
+ get searchDisplayDob(): string {
+ const { dob = '' } = this.searchParams;
+
+ return (dob) ? `${this.$t('common.dateOfBirthShort')}: ${moment(dob, serverDateFormat).format(displayDateFormat)}`.trim() : '';
+ }
+
get searchDisplayHomeState(): string {
const { homeState } = this.searchParams;
@@ -189,17 +203,25 @@ class LicenseeList extends Vue {
return (npi) ? `${this.$t('licensing.npi')}: ${npi}`.trim() : '';
}
+ get searchDisplayLicenseNumber(): string {
+ const { licenseNumber = '' } = this.searchParams;
+
+ return (licenseNumber) ? `${this.$t('licensing.licenseNumSymbol')}: ${licenseNumber}`.trim() : '';
+ }
+
get searchDisplayAll(): string {
const joined = [
this.searchDisplayCompact,
this.searchDisplayFullName,
+ this.searchDisplayDob,
this.searchDisplayHomeState,
this.searchDisplayPrivilegeState,
this.searchDisplayPrivilegePurchaseDates,
this.searchDisplayMilitaryStatus,
this.searchDisplayInvestigationStatus,
this.searchDisplayEncumberDates,
- this.searchDisplayNpi
+ this.searchDisplayNpi,
+ this.searchDisplayLicenseNumber
].join(', ').trim();
return joined.replace(/(^[,\s]+)|([,\s]+$)/g, '').replace(/(,\s)\1+/g, ', '); // Replace repeated commas with single comma
@@ -218,7 +240,14 @@ class LicenseeList extends Vue {
firstName: this.$t('common.firstName'),
lastName: this.$t('common.lastName'),
homeJurisdictionDisplay: () => this.$t('licensing.homeState'),
- privilegeStatesDisplay: () => this.$t('licensing.privileges'),
+ ...(this.isAppModeCosmetology
+ ? {
+ licenseNumber: this.$t('licensing.stateLicenseNumber'),
+ }
+ : {
+ privilegeStatesDisplay: () => this.$t('licensing.privileges'),
+ }
+ ),
};
return record;
@@ -343,6 +372,9 @@ class LicenseeList extends Vue {
if (searchParams?.lastName) {
requestConfig.licenseeLastName = searchParams.lastName;
}
+ if (searchParams?.dob) {
+ requestConfig.dob = searchParams.dob;
+ }
if (searchParams?.homeState) {
requestConfig.homeState = searchParams.homeState.toLowerCase();
}
@@ -370,6 +402,9 @@ class LicenseeList extends Vue {
if (searchParams?.npi) {
requestConfig.npi = searchParams.npi;
}
+ if (searchParams?.licenseNumber) {
+ requestConfig.licenseNumber = searchParams.licenseNumber;
+ }
// Paging params
if (!isDirectExport) {
diff --git a/webroot/src/components/Licensee/LicenseeList/LicenseeList.vue b/webroot/src/components/Licensee/LicenseeList/LicenseeList.vue
index a266e9188..b92853796 100644
--- a/webroot/src/components/Licensee/LicenseeList/LicenseeList.vue
+++ b/webroot/src/components/Licensee/LicenseeList/LicenseeList.vue
@@ -21,22 +21,23 @@
{{ $t('licensing.licensingListTitle') }}
-
{{ $t('common.viewing') }}:
{{ searchDisplayAll }}
+
{{ $t('licensing.licensingListDescription')}}
diff --git a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.less b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.less
index 9f25908f2..87acb1ef3 100644
--- a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.less
+++ b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.less
@@ -26,6 +26,7 @@
align-content: center;
align-items: center;
justify-content: center;
+ order: 1;
width: 100%;
margin-top: 1.2rem;
padding: 0.4rem 1rem;
@@ -34,7 +35,7 @@
background-color: @darkBlue;
@media @desktopWidth {
- order: 1;
+ order: 0;
width: auto;
margin-top: 0;
margin-right: 2.4rem;
diff --git a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.ts b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.ts
index 7ab3fb689..363a6cec4 100644
--- a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.ts
+++ b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.ts
@@ -85,6 +85,14 @@ class LicenseeList extends Vue {
return this.$store.state.license;
}
+ get isAppModeJcc(): boolean {
+ return this.$store.getters.isAppModeJcc;
+ }
+
+ get isAppModeCosmetology(): boolean {
+ return this.$store.getters.isAppModeCosmetology;
+ }
+
get licenseStoreRecordCount(): number {
return this.licenseStore.model?.length || 0;
}
@@ -97,58 +105,33 @@ class LicenseeList extends Vue {
return (this.isPublicSearch) ? this.userStore.currentCompact?.abbrev() || '' : '';
}
- get searchDisplayFirstName(): string {
- const delimiter = (this.searchDisplayCompact) ? ', ' : '';
- let displayFirstName = '';
+ get searchDisplayFullName(): string {
+ const { firstName = '', lastName = '' } = this.searchParams;
- if (this.searchParams.firstName) {
- displayFirstName = `${delimiter}${this.searchParams.firstName}` || '';
- }
-
- return displayFirstName;
- }
-
- get searchDisplayLastName(): string {
- const delimiter = (this.searchDisplayCompact && !this.searchDisplayFirstName) ? ', ' : '';
- const subDelimiter = (this.searchDisplayFirstName) ? ' ' : '';
- let displayLastName = '';
-
- if (this.searchParams.lastName) {
- displayLastName = `${delimiter}${subDelimiter}${this.searchParams.lastName}` || '';
- }
-
- return displayLastName;
+ return `${firstName} ${lastName}`.trim();
}
get searchDisplayState(): string {
const { state } = this.searchParams;
- const { searchDisplayCompact, searchDisplayFirstName, searchDisplayLastName } = this;
- const delimiter = (searchDisplayCompact || searchDisplayFirstName || searchDisplayLastName) ? ', ' : '';
- let displayState = '';
- if (state) {
- const stateModel = new State({ abbrev: state });
+ return (state) ? `${new State({ abbrev: state }).name()}` : '';
+ }
- displayState = `${delimiter}${stateModel.name()}`;
- }
+ get searchDisplayLicenseNumber(): string {
+ const { licenseNumber = '' } = this.searchParams;
- return displayState;
+ return (licenseNumber) ? `${this.$t('licensing.licenseNumSymbol')}: ${licenseNumber}`.trim() : '';
}
get searchDisplayAll(): string {
- const {
- searchDisplayCompact,
- searchDisplayFirstName,
- searchDisplayLastName,
- searchDisplayState
- } = this;
-
return [
- searchDisplayCompact,
- searchDisplayFirstName,
- searchDisplayLastName,
- searchDisplayState
- ].join('').trim();
+ this.searchDisplayCompact,
+ this.searchDisplayFullName,
+ this.searchDisplayState,
+ this.searchDisplayLicenseNumber
+ ]
+ .filter((displayPart) => !!displayPart?.trim())
+ .join(', ').trim();
}
get sortOptions(): Array {
@@ -168,9 +151,15 @@ class LicenseeList extends Vue {
const record = {
firstName: this.$t('common.firstName'),
lastName: this.$t('common.lastName'),
- ssnMaskedPartial: () => this.$t('licensing.ssn'),
homeJurisdictionDisplay: () => this.$t('licensing.homeState'),
- privilegeStatesDisplay: () => this.$t('licensing.privileges'),
+ ...(this.isAppModeCosmetology
+ ? {
+ licenseNumber: this.$t('licensing.stateLicenseNumber'),
+ }
+ : {
+ privilegeStatesDisplay: () => this.$t('licensing.privileges'),
+ }
+ ),
};
return record;
@@ -307,6 +296,9 @@ class LicenseeList extends Vue {
if (searchParams?.state) {
requestConfig.jurisdiction = searchParams.state.toLowerCase();
}
+ if (this.isAppModeCosmetology && searchParams?.licenseNumber) {
+ requestConfig.licenseNumber = searchParams.licenseNumber;
+ }
// Make fetch request
await this.$store.dispatch('license/getLicenseesRequest', {
diff --git a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.vue b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.vue
index d5a083554..402735aee 100644
--- a/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.vue
+++ b/webroot/src/components/Licensee/LicenseeListLegacy/LicenseeListLegacy.vue
@@ -20,22 +20,23 @@
{{ $t('licensing.licensingListTitle') }}
-
{{ $t('common.viewing') }}:
{{ searchDisplayAll }}
+
{{ $t('licensing.licensingListDescription')}}
diff --git a/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.ts b/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.ts
index 50792dd49..692f5ee46 100644
--- a/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.ts
+++ b/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.ts
@@ -42,6 +42,14 @@ class LicenseeRow extends Vue {
return this.$store.state.sorting;
}
+ get isAppModeJcc(): boolean {
+ return this.$store.getters.isAppModeJcc;
+ }
+
+ get isAppModeCosmetology(): boolean {
+ return this.$store.getters.isAppModeCosmetology;
+ }
+
get sortingStoreOption(): any {
return this.sortingStore.sortingMap[this.listId]?.option;
}
diff --git a/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.vue b/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.vue
index 83f82badf..7110c6fa4 100644
--- a/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.vue
+++ b/webroot/src/components/Licensee/LicenseeRow/LicenseeRow.vue
@@ -63,6 +63,7 @@
}">
+
+ {{ $t('licensing.stateLicenseNumber') }}:
+ {{ item.licenseNumber }}
+ {{ item.bestLicense().licenseNumber }}
+
+
diff --git a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts
index 13c33c34b..d58d70cfd 100644
--- a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts
+++ b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts
@@ -18,6 +18,7 @@ import {
ComputedRef,
nextTick
} from 'vue';
+import { AppModes, dateFormatPatterns } from '@/app.config';
import MixinForm from '@components/Forms/_mixins/form.mixin';
import InputRadioGroup from '@components/Forms/InputRadioGroup/InputRadioGroup.vue';
import InputText from '@components/Forms/InputText/InputText.vue';
@@ -44,6 +45,7 @@ export interface LicenseSearch {
firstName?: string;
lastName?: string;
homeState?: string;
+ dob?: string;
privilegeState?: string;
privilegePurchaseStartDate?: string;
privilegePurchaseEndDate?: string;
@@ -52,6 +54,7 @@ export interface LicenseSearch {
encumberStartDate?: string;
encumberEndDate?: string;
npi?: string;
+ licenseNumber?: string;
}
@Component({
@@ -87,6 +90,10 @@ class LicenseeSearch extends mixins(MixinForm) {
//
// Computed
//
+ get globalStore() {
+ return this.$store.state;
+ }
+
get licenseStore(): any {
return this.$store.state.license;
}
@@ -95,6 +102,18 @@ class LicenseeSearch extends mixins(MixinForm) {
return this.$store.state.user;
}
+ get appMode(): AppModes {
+ return this.globalStore.appMode;
+ }
+
+ get isAppModeJcc(): boolean {
+ return this.$store.getters.isAppModeJcc;
+ }
+
+ get isAppModeCosmetology(): boolean {
+ return this.$store.getters.isAppModeCosmetology;
+ }
+
get compactType(): CompactType | null {
return this.userStore.currentCompact?.type;
}
@@ -236,6 +255,26 @@ class LicenseeSearch extends mixins(MixinForm) {
value: this.searchParams.homeState || '',
isDisabled: computed(() => this.enableCompactSelect && !this.compactType),
}),
+ licenseNumber: new FormInput({
+ id: 'license-number',
+ name: 'license-number',
+ label: computed(() => this.$t('licensing.licenseNumber')),
+ placeholder: '',
+ validation: Joi.string().min(0).max(100).messages(this.joiMessages.string),
+ value: this.searchParams.licenseNumber || '',
+ enforceMax: true,
+ }),
+ dob: new FormInput({
+ id: 'dob',
+ name: 'dob',
+ label: computed(() => this.$t('common.dateOfBirth')),
+ placeholder: computed(() => 'MM/DD/YYYY'),
+ validation: Joi.string()
+ .allow(null, '')
+ .pattern(dateFormatPatterns.MM_DD_YYYY)
+ .messages(this.joiMessages.dateWithFormat('MM/DD/YYYY')),
+ value: this.searchParams.dob || '',
+ }),
privilegeState: new FormInput({
id: 'privilege-state',
name: 'privilege-state',
@@ -331,24 +370,36 @@ class LicenseeSearch extends mixins(MixinForm) {
if (this.isFormValid) {
this.startFormLoading();
- const allowedSearchProps = [
+ const searchProps: LicenseSearch = {
+ searchType: this.selectedSearchType || SearchTypes.PROVIDER,
+ isDirectExport: this.isSearchByPrivileges,
+ };
+ const allowedSearchProps = [ // Common search props
'compact',
'firstName',
'lastName',
'homeState',
- 'privilegeState',
- 'privilegePurchaseStartDate',
- 'privilegePurchaseEndDate',
- 'militaryStatus',
'investigationStatus',
'encumberStartDate',
'encumberEndDate',
- 'npi',
];
- const searchProps: LicenseSearch = {
- searchType: this.selectedSearchType || SearchTypes.PROVIDER,
- isDirectExport: this.isSearchByPrivileges,
- };
+
+ // Per compact search props
+ switch (this.appMode) {
+ case AppModes.JCC:
+ allowedSearchProps.push('privilegeState');
+ allowedSearchProps.push('privilegePurchaseStartDate');
+ allowedSearchProps.push('privilegePurchaseEndDate');
+ allowedSearchProps.push('militaryStatus');
+ allowedSearchProps.push('npi');
+ break;
+ case AppModes.COSMETOLOGY:
+ allowedSearchProps.push('licenseNumber');
+ allowedSearchProps.push('dob');
+ break;
+ default:
+ break;
+ }
allowedSearchProps.forEach((searchProp) => { searchProps[searchProp] = this.formValues[searchProp]; });
this.$emit('searchParams', searchProps);
@@ -369,6 +420,8 @@ class LicenseeSearch extends mixins(MixinForm) {
this.formData.encumberStartDate.value = '';
this.formData.encumberEndDate.value = '';
this.formData.npi.value = '';
+ this.formData.licenseNumber.value = '';
+ this.formData.dob.value = '';
this.isFormLoading = false;
this.isFormSuccessful = false;
this.isFormError = false;
@@ -380,14 +433,20 @@ class LicenseeSearch extends mixins(MixinForm) {
this.formData.firstName.value = 'Test';
this.formData.lastName.value = 'User';
this.formData.homeState.value = 'co';
- this.formData.privilegeState.value = 'co';
- this.formData.privilegePurchaseStartDate.value = moment().startOf('month').format('YYYY-MM-DD');
- this.formData.privilegePurchaseEndDate.value = moment().endOf('month').format('YYYY-MM-DD');
- this.formData.militaryStatus.value = 'approved';
this.formData.investigationStatus.value = 'underInvestigation';
this.formData.encumberStartDate.value = moment().startOf('month').format('YYYY-MM-DD');
this.formData.encumberEndDate.value = moment().endOf('month').format('YYYY-MM-DD');
- this.formData.npi.value = 'ABC123';
+
+ if (this.isAppModeJcc) {
+ this.formData.privilegeState.value = 'co';
+ this.formData.privilegePurchaseStartDate.value = moment().startOf('month').format('YYYY-MM-DD');
+ this.formData.privilegePurchaseEndDate.value = moment().endOf('month').format('YYYY-MM-DD');
+ this.formData.militaryStatus.value = 'approved';
+ this.formData.npi.value = 'ABC123';
+ } else if (this.isAppModeCosmetology) {
+ this.formData.licenseNumber.value = 'ABC123';
+ this.formData.dob.value = moment('1970-01-01').format('YYYY-MM-DD');
+ }
this.validateAll({ asTouched: true });
await nextTick();
diff --git a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue
index eb8d80ceb..b9e92efd7 100644
--- a/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue
+++ b/webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.vue
@@ -34,7 +34,7 @@
@input="updateCurrentCompact"
/>
-