diff --git a/webroot/.stylelintrc.json b/webroot/.stylelintrc.json index bea3b2e4e..e93bd1e6a 100644 --- a/webroot/.stylelintrc.json +++ b/webroot/.stylelintrc.json @@ -18,6 +18,12 @@ "ignorePseudoClasses": ["deep"] } ], + "property-no-unknown": [ + true, + { + "ignoreProperties": ["print-color-adjust"] + } + ], "order/properties-order": [], "plugin/rational-order": [true, { "border-in-box-model": true, diff --git a/webroot/src/components/Icons/User/User.less b/webroot/src/components/Icons/User/User.less new file mode 100644 index 000000000..7fccac1ad --- /dev/null +++ b/webroot/src/components/Icons/User/User.less @@ -0,0 +1,6 @@ +// +// User.less +// CompactConnect +// +// Created by InspiringApps on 5/6/2025. +// diff --git a/webroot/src/components/Icons/User/User.spec.ts b/webroot/src/components/Icons/User/User.spec.ts new file mode 100644 index 000000000..da217f23a --- /dev/null +++ b/webroot/src/components/Icons/User/User.spec.ts @@ -0,0 +1,19 @@ +// +// User.spec.ts +// CompactConnect +// +// Created by InspiringApps on 5/6/2025. +// + +import { expect } from 'chai'; +import { mountShallow } from '@tests/helpers/setup'; +import User from '@components/Icons/User/User.vue'; + +describe('User component', async () => { + it('should mount the component', async () => { + const wrapper = await mountShallow(User); + + expect(wrapper.exists()).to.equal(true); + expect(wrapper.findComponent(User).exists()).to.equal(true); + }); +}); diff --git a/webroot/src/components/Icons/User/User.ts b/webroot/src/components/Icons/User/User.ts new file mode 100644 index 000000000..d9ec5b465 --- /dev/null +++ b/webroot/src/components/Icons/User/User.ts @@ -0,0 +1,18 @@ +// +// User.ts +// CompactConnect +// +// Created by InspiringApps on 5/6/2025. +// + +import { Component, Vue, toNative } from 'vue-facing-decorator'; + +@Component({ + name: 'User', +}) +class User extends Vue { +} + +export default toNative(User); + +// export default User; diff --git a/webroot/src/components/Icons/User/User.vue b/webroot/src/components/Icons/User/User.vue new file mode 100644 index 000000000..77100d388 --- /dev/null +++ b/webroot/src/components/Icons/User/User.vue @@ -0,0 +1,32 @@ + + + + + + diff --git a/webroot/src/components/Page/PageContainer/PageContainer.less b/webroot/src/components/Page/PageContainer/PageContainer.less index 134e6560e..b8760ee0f 100644 --- a/webroot/src/components/Page/PageContainer/PageContainer.less +++ b/webroot/src/components/Page/PageContainer/PageContainer.less @@ -13,6 +13,17 @@ padding-top: @appHeaderHeight; background-color: @ambientGrey; + @media print { + @page { + size: 330mm 427mm; + margin: 14mm; + } + + width: @desktopWidth; + min-height: auto; + background-color: @white; + } + &.no-top-pad { @media @tabletWidth { padding-top: unset; @@ -36,7 +47,9 @@ flex-shrink: 0; @media @tabletWidth { - padding-left: @navPartialExpandWidth; + &.include-nav { + padding-left: @navPartialExpandWidth; + } } } } diff --git a/webroot/src/components/Page/PageContainer/PageContainer.ts b/webroot/src/components/Page/PageContainer/PageContainer.ts index d85b48d0b..046389fdf 100644 --- a/webroot/src/components/Page/PageContainer/PageContainer.ts +++ b/webroot/src/components/Page/PageContainer/PageContainer.ts @@ -56,11 +56,20 @@ class PageContainer extends Vue { const nonPadTopRouteNames: Array = [ 'LicensingDetail', 'LicenseeDetailPublic', + 'LicenseeVerification', ]; return !nonPadTopRouteNames.includes(this.currentRouteName); } + get includeMainNav(): boolean { + const nonMainNavRouteNames: Array = [ + 'LicenseeVerification', // This is a printer-friendly page + ]; + + return !nonMainNavRouteNames.includes(this.currentRouteName); + } + get includePageFooter(): boolean { return false; } diff --git a/webroot/src/components/Page/PageContainer/PageContainer.vue b/webroot/src/components/Page/PageContainer/PageContainer.vue index 52a9aa000..dec748577 100644 --- a/webroot/src/components/Page/PageContainer/PageContainer.vue +++ b/webroot/src/components/Page/PageContainer/PageContainer.vue @@ -12,8 +12,8 @@
- -
+ +
diff --git a/webroot/src/components/Page/PageHeader/PageHeader.less b/webroot/src/components/Page/PageHeader/PageHeader.less index a25953712..f83771c9c 100644 --- a/webroot/src/components/Page/PageHeader/PageHeader.less +++ b/webroot/src/components/Page/PageHeader/PageHeader.less @@ -18,6 +18,10 @@ color: @white; background-color: @primaryColor; + @media print { + display: none; + } + @media @tabletWidth { background-color: transparent; } diff --git a/webroot/src/locales/en.json b/webroot/src/locales/en.json index 6b0f99afe..4f1bc89c7 100644 --- a/webroot/src/locales/en.json +++ b/webroot/src/locales/en.json @@ -9,6 +9,7 @@ "cancel": "Cancel", "close": "Close", "skip": "Skip", + "print": "Print", "confirm": "Confirm", "caution": "Caution", "fees": "Fees", @@ -514,6 +515,8 @@ "licenseNumber": "License number", "licenseExpirationDate": "License expiration date", "residenceLocation": "Residence location", + "generateVerification": "Generate verification doc", + "generateVerificationSubtext": "Printer-friendly view", "obtainPrivileges": "Obtain Privileges", "privileges": "Privileges", "privilege": "Privilege", @@ -639,7 +642,12 @@ "eventNodeLabel": "Event: {eventNameDisplay} on {eventDate}", "statusBlockLabel": "{status} status block", "compactEligible": "Compact Eligible", - "notCompactEligible": "Not Compact Eligible" + "notCompactEligible": "Not Compact Eligible", + "privilegeVerification": "Privilege Verification", + "practitionerInformation": "Practitioner Information", + "homeStateLicenses": "Home State Licenses", + "activeDate": "Active date", + "privilegeProofFooter": "This document is officially generated by Compact Connect and serves as proof of the practitioner's current licenses and privileges." }, "military": { "militaryStatusTitle": "Military status", diff --git a/webroot/src/locales/es.json b/webroot/src/locales/es.json index 393a10125..9b9426fc8 100644 --- a/webroot/src/locales/es.json +++ b/webroot/src/locales/es.json @@ -11,6 +11,7 @@ "cancel": "Cancelar", "close": "Cerrar", "skip": "Saltear", + "print": "Imprimir", "fees": "Tarifas", "choose": "Elige", "chooseOne": "Elige uno", @@ -545,6 +546,8 @@ "licenseNumber": "Número de licencia", "licenseExpirationDate": "Fecha de vencimiento de la licencia", "licenseExpiredMessage": "Su licencia ha caducado.", + "generateVerification": "Generar documento de verificación", + "generateVerificationSubtext": "Vista para imprimir", "obtainPrivileges": "Obtener privilegios", "expired": "Caducada", "issued": "Emitida", @@ -638,7 +641,12 @@ "eventNodeLabel": "Evento: {eventNameDisplay} en {eventDate}", "statusBlockLabel": "{status} bloque de estado", "compactEligible": "Elegible Compacto", - "notCompactEligible": "No Compacto Elegible" + "notCompactEligible": "No Compacto Elegible", + "privilegeVerification": "Verificación de privilegios", + "practitionerInformation": "Información para profesionales", + "homeStateLicenses": "Licencias del estado de origen", + "activeDate": "Fecha activa", + "privilegeProofFooter": "Este documento es generado oficialmente por Compact Connect y sirve como prueba de las licencias y privilegios actuales del profesional." }, "military": { "militaryStatusTitle": "Estado militar", diff --git a/webroot/src/models/License/License.model.spec.ts b/webroot/src/models/License/License.model.spec.ts index 96a4b8376..4c6b42b46 100644 --- a/webroot/src/models/License/License.model.spec.ts +++ b/webroot/src/models/License/License.model.spec.ts @@ -145,6 +145,19 @@ describe('License model', () => { }, ]); }); + it('should create a License with specific values (custom displayName delimiter)', () => { + const data = { + issueState: new State({ abbrev: 'co' }), + licenseType: LicenseType.AUDIOLOGIST, + }; + const license = new License(data); + + // Test field values + expect(license).to.be.an.instanceof(License); + + // Test methods + expect(license.displayName(' ... ')).to.equal('Colorado ... AUD'); + }); it('should create a License with specific values through serializer', () => { const data = { compact: CompactType.ASLP, diff --git a/webroot/src/models/License/License.model.ts b/webroot/src/models/License/License.model.ts index 8ee3e23af..fab2ca768 100644 --- a/webroot/src/models/License/License.model.ts +++ b/webroot/src/models/License/License.model.ts @@ -141,8 +141,8 @@ export class License implements InterfaceLicense { return upperCaseAbbrev; } - public displayName(): string { - return `${this.issueState?.name() || ''}${this.issueState?.name() && this.licenseTypeAbbreviation() ? ' - ' : ''}${this.licenseTypeAbbreviation()}`; + public displayName(delimiter = ' - '): string { + return `${this.issueState?.name() || ''}${this.issueState?.name() && this.licenseTypeAbbreviation() ? delimiter : ''}${this.licenseTypeAbbreviation()}`; } public historyWithFabricatedEvents(): Array { diff --git a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.less b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.less index a38efa23e..5fcae9c3e 100644 --- a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.less +++ b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.less @@ -24,13 +24,16 @@ flex-direction: column; align-items: center; justify-content: center; + width: 100%; + padding: 0 4rem; - @media @tabletWidth { - display: flex; - flex-direction: row; - align-items: center; + @media @desktopWidth { + align-items: flex-start; justify-content: space-between; - width: 100%; + } + + @media @largeDesktopWidth { + flex-direction: row; } .welcome-user { @@ -41,39 +44,57 @@ .button-block { display: flex; - flex-direction: row; + flex-direction: column; + align-items: flex-start; + width: 100%; margin-top: 2rem; - .view-military-btn { - margin-right: 1rem; + @media @desktopWidth { + flex-direction: row; + } - &:deep(input) { - padding-right: 1rem; - padding-left: 1rem; - } + @media @largeDesktopWidth { + width: auto; + margin-top: 0; + margin-left: auto; + } - @media @tabletWidth { - margin-right: 2rem; + .btn-container { + display: flex; + flex-direction: column; + width: 100%; + margin-bottom: 1.2rem; - &:deep(input) { - padding-right: 2.4rem; - padding-left: 2.4rem; + @media @desktopWidth { + width: 100%; + + &:not(:first-child) { + margin-left: 1.8rem; } } - } - .obtain-priv-btn { - &:deep(input) { - padding-right: 1rem; - padding-left: 1rem; + @media @largeDesktopWidth { + width: auto; } - @media @tabletWidth { + .btn { + width: 100%; + margin-bottom: 0.4rem; + + @media @largeDesktopWidth { + width: auto; + } + &:deep(input) { - padding-right: 2.4rem; - padding-left: 2.4rem; + width: 100%; } } + + .btn-subtext { + color: darken(@darkGrey, 10%); + font-size: 1.2rem; + text-align: right; + } } } } diff --git a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.ts b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.ts index cb1ba901a..4ab193f4a 100644 --- a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.ts +++ b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.ts @@ -41,22 +41,6 @@ export default class LicenseeDashboard extends Vue { return this.$store.state.user; } - get user(): LicenseeUser | null { - return this.userStore.model; - } - - get licensee(): Licensee { - return this.user?.licensee || new Licensee(); - } - - get licenseePrivileges(): Array { - return this.licensee.privileges || []; - } - - get licenseeLicenses(): Array { - return this.licensee.licenses || []; - } - get currentCompact(): Compact | null { return this.userStore?.currentCompact || null; } @@ -65,6 +49,10 @@ export default class LicenseeDashboard extends Vue { return this.currentCompact?.type || null; } + get user(): LicenseeUser | null { + return this.userStore.model; + } + get userFullName(): string { let name = ''; @@ -75,32 +63,29 @@ export default class LicenseeDashboard extends Vue { return name; } - get homeJurisdiction(): State | null { - return this.licensee?.homeJurisdiction || null; - } - - get obtainPrivButtonLabel(): string { - return `+ ${this.$t('licensing.obtainPrivileges')}`; + get licensee(): Licensee { + return this.user?.licensee || new Licensee(); } - get privilegeTitle(): string { - return this.$t('licensing.privileges'); + get licenseeLicenses(): Array { + return this.licensee.licenses || []; } - get welcomeText(): string { - return this.$t('common.welcome'); + get licenseePrivileges(): Array { + return this.licensee.privileges || []; } - get isPrivilegePurchaseEnabled(): boolean { - return this.licensee?.canPurchasePrivileges() || false; + get homeJurisdiction(): State | null { + return this.licensee?.homeJurisdiction || null; } - get twoHomeStateErrorText(): string { - return this.$t('licensing.twoHomeStateErrorMessage'); + get isGenerateProofEnabled(): boolean { + return this.licenseePrivileges.filter((privilege: License) => + privilege.status === LicenseStatus.ACTIVE).length > 0; } - get licenseExpiredMessage(): string { - return this.$t('licensing.licenseExpiredMessage'); + get isPrivilegePurchaseEnabled(): boolean { + return this.licensee?.canPurchasePrivileges() || false; } // @@ -120,6 +105,13 @@ export default class LicenseeDashboard extends Vue { }); } + viewLicenseeProof() { + this.$router.push({ + name: 'LicenseeVerification', + params: { compact: this.currentCompactType } + }); + } + isLicenseActive(license: License): boolean { return license && license.status === LicenseStatus.ACTIVE; } diff --git a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.vue b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.vue index c2fae059b..1a6604bd5 100644 --- a/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.vue +++ b/webroot/src/pages/LicenseeDashboard/LicenseeDashboard.vue @@ -8,22 +8,37 @@