diff --git a/src/app/clients/clients-view/address-tab/address-tab.component.html b/src/app/clients/clients-view/address-tab/address-tab.component.html index f0ea2cd8a..c37a9c29e 100644 --- a/src/app/clients/clients-view/address-tab/address-tab.component.html +++ b/src/app/clients/clients-view/address-tab/address-tab.component.html @@ -6,74 +6,111 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-
-
-

{{ 'labels.heading.Address' | translate }}

-
-
- +
+ +
+
+ +

{{ 'labels.heading.Address' | translate }}

+ {{ clientAddressData?.length || 0 }}
+
- + + @if (!clientAddressData || clientAddressData.length === 0) { +
+ +

No addresses added yet

+
+ } + + @for (address of clientAddressData; track address.addressId; let i = $index) { - - - - {{ address.addressType }} + + + + + {{ address.addressType }} + + {{ address.isActive ? 'Active' : 'Inactive' }} + - - {{ address.relationship }} + + {{ address.city || address.townVillage || '' }}{{ address.city || address.townVillage ? ', ' : '' }}{{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name || '' }} - -
- - +
+ {{ 'labels.inputs.Active Status' | translate }} + +
-

- @if (isFieldEnabled('street')) { - {{ 'labels.inputs.Street' | translate }} : {{ address.street }}
+ + +

+ @if (isFieldEnabled('street') && address.street) { +
+
{{ 'labels.inputs.Street' | translate }}
+
{{ address.street }}
+
} - @if (isFieldEnabled('addressLine1')) { - {{ 'labels.inputs.Address Line' | translate }} 1 : {{ address.addressLine1 }}
+ @if (isFieldEnabled('addressLine1') && address.addressLine1) { +
+
{{ 'labels.inputs.Address Line' | translate }} 1
+
{{ address.addressLine1 }}
+
} - @if (isFieldEnabled('addressLine2')) { - {{ 'labels.inputs.Address Line' | translate }} 2 : {{ address.addressLine2 }}
+ @if (isFieldEnabled('addressLine2') && address.addressLine2) { +
+
{{ 'labels.inputs.Address Line' | translate }} 2
+
{{ address.addressLine2 }}
+
} - @if (isFieldEnabled('addressLine3')) { - {{ 'labels.inputs.Address Line' | translate }} 3 : {{ address.addressLine3 }}
+ @if (isFieldEnabled('addressLine3') && address.addressLine3) { +
+
{{ 'labels.inputs.Address Line' | translate }} 3
+
{{ address.addressLine3 }}
+
} - @if (isFieldEnabled('townVillage')) { - {{ 'labels.inputs.Town / Village' | translate }} : {{ address.townVillage }}
+ @if (isFieldEnabled('townVillage') && address.townVillage) { +
+
{{ 'labels.inputs.Town / Village' | translate }}
+
{{ address.townVillage }}
+
} - @if (isFieldEnabled('city')) { - {{ 'labels.inputs.City' | translate }} : {{ address.city }}
+ @if (isFieldEnabled('city') && address.city) { +
+
{{ 'labels.inputs.City' | translate }}
+
{{ address.city }}
+
} @if (isFieldEnabled('stateProvinceId')) { - {{ 'labels.inputs.State / Province' | translate }} : - {{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name }}
+
+
{{ 'labels.inputs.State / Province' | translate }}
+
{{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name || '-' }}
+
} @if (isFieldEnabled('countryId')) { - {{ 'labels.inputs.Country' | translate }} : - {{ getSelectedValue('countryIdOptions', address.countryId)?.name }}
+
+
{{ 'labels.inputs.Country' | translate }}
+
{{ getSelectedValue('countryIdOptions', address.countryId)?.name || '-' }}
+
} - @if (isFieldEnabled('postalCode')) { - {{ 'labels.inputs.Postal Code' | translate }} : {{ address.postalCode }}
+ @if (isFieldEnabled('postalCode') && address.postalCode) { +
+
{{ 'labels.inputs.Postal Code' | translate }}
+
{{ address.postalCode }}
+
} - @if (isFieldEnabled('isActive')) { - {{ 'labels.inputs.Active Status' | translate }} : {{ address.isActive }}
- } -

+
}
diff --git a/src/app/clients/clients-view/address-tab/address-tab.component.scss b/src/app/clients/clients-view/address-tab/address-tab.component.scss index a2c909a18..8166ba235 100644 --- a/src/app/clients/clients-view/address-tab/address-tab.component.scss +++ b/src/app/clients/clients-view/address-tab/address-tab.component.scss @@ -6,30 +6,236 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -.tab-container { - padding: 1%; - margin: 1%; +$primary: #1a73e8; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; +$green: #34a853; +$green-light: #e6f4ea; +$red: #ea4335; +$red-light: #fce8e6; - .action-button { - margin-left: auto; +.addr-container { + padding: 20px 24px; +} + +// Header +.addr-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + + &__left { + display: flex; + align-items: center; + gap: 10px; } - .address { - .address-actions { - margin-top: 1%; + &__icon { + font-size: 18px; + color: $primary; + } - button { - margin-right: 1%; - } + &__title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: $gray-900; + } + + &__badge { + background: $primary-light; + color: $primary; + font-size: 11px; + font-weight: 700; + padding: 2px 8px; + border-radius: 10px; + } + + &__btn { + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + height: 36px !important; + } +} + +// Empty state +.addr-empty { + text-align: center; + padding: 48px 20px; + background: $gray-50; + border-radius: 10px; + border: 1px dashed $gray-200; + + &__icon { + font-size: 36px; + color: $gray-500; + margin-bottom: 10px; + } + + &__text { + color: $gray-500; + font-size: 14px; + margin: 0; + } +} + +// Expansion panels +.addr-panel { + margin-bottom: 12px !important; + border-radius: 10px !important; + border: 1px solid $gray-200 !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important; + + &::ng-deep .mat-expansion-panel-body { + padding: 0 20px 20px !important; + } + + &__header { + height: auto !important; + min-height: 56px !important; + padding: 8px 20px !important; + } + + &__title { + display: flex !important; + align-items: center; + gap: 10px; + flex: 1; + } + + &__type-icon { + font-size: 16px; + color: $primary; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: $primary-light; + border-radius: 6px; + } + + &__type { + font-size: 14px; + font-weight: 600; + color: $gray-900; + text-transform: uppercase; + letter-spacing: 0.3px; + } + + &__status { + font-size: 11px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; + + &--active { + background: $green-light; + color: $green; } - h3 { - margin: 1% auto; + &--inactive { + background: $red-light; + color: $red; } + } - p { - line-height: 30px; - margin-left: 2%; + &__desc { + font-size: 12px; + color: $gray-500 !important; + flex: 0 !important; + white-space: nowrap; + } +} + +// Actions bar inside panel +.addr-actions { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; + margin-bottom: 12px; + border-bottom: 1px solid $gray-100; + + &__edit { + border-radius: 6px !important; + font-size: 12px !important; + font-weight: 600 !important; + height: 32px !important; + } + + &__toggle { + display: flex; + align-items: center; + gap: 8px; + } + + &__toggle-label { + font-size: 12px; + color: $gray-500; + font-weight: 500; + } +} + +// Address details grid +.addr-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0; + border: 1px solid $gray-100; + border-radius: 8px; + overflow: hidden; + + @media (max-width: 768px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (max-width: 480px) { + grid-template-columns: 1fr; + } +} + +.addr-field { + padding: 10px 14px; + border-bottom: 1px solid $gray-100; + border-right: 1px solid $gray-100; + transition: background 0.15s ease; + + &:hover { + background: $gray-50; + } + + &:nth-child(3n) { + border-right: none; + } + + &__label { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: $gray-500; + margin-bottom: 3px; + } + + &__value { + font-size: 13.5px; + font-weight: 500; + color: $gray-900; + word-break: break-word; + + &--mono { + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-size: 13px; + letter-spacing: 0.5px; } } } diff --git a/src/app/clients/clients-view/address-tab/address-tab.component.ts b/src/app/clients/clients-view/address-tab/address-tab.component.ts index 1dcc4e36b..24e32087c 100644 --- a/src/app/clients/clients-view/address-tab/address-tab.component.ts +++ b/src/app/clients/clients-view/address-tab/address-tab.component.ts @@ -94,7 +94,8 @@ export class AddressTabComponent { this.translateService.instant('labels.catalogs.Client') + ' ' + this.translateService.instant('labels.heading.Address'), - formfields: this.getAddressFormFields('add') + formfields: this.getAddressFormFields('add'), + layout: { columns: 2, columnWidth: 350 } }; const addAddressDialogRef = this.dialog.open(FormDialogComponent, { data }); addAddressDialogRef.afterClosed().subscribe((response: any) => { @@ -126,7 +127,7 @@ export class AddressTabComponent { ' ' + this.translateService.instant('labels.heading.Address'), formfields: this.getAddressFormFields('edit', address), - layout: { addButtonText: 'Edit' } + layout: { columns: 2, columnWidth: 350, addButtonText: 'Edit' } }; const editAddressDialogRef = this.dialog.open(FormDialogComponent, { data }); editAddressDialogRef.afterClosed().subscribe((response: any) => { diff --git a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html index 19d7ae283..b4594a106 100644 --- a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html +++ b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html @@ -6,99 +6,146 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -

- @if (documentIdentifier) { - {{ 'labels.heading.Add Client Identifier' | translate }} - } @else { - {{ 'labels.heading.Upload' | translate }} {{ entityType }} {{ 'labels.heading.Documents' | translate }} - } -

-
-
- - @if (documentIdentifier) { - - {{ 'labels.inputs.Document Type' | translate }} - - @for (docType of allowedDocumentTypes; track docType) { - - {{ docType.name }} - - } - - @if (uploadDocumentForm.controls.documentTypeId?.hasError('required')) { - - {{ 'labels.inputs.Document Type' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } - - - {{ 'labels.inputs.Status' | translate }} - - @for (status of statusOptions; track status) { - - {{ status.label }} - - } - - @if (uploadDocumentForm.controls.status?.hasError('required')) { - - {{ 'labels.inputs.Status' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } - - - {{ 'labels.inputs.Document Key' | translate }} - - @if (uploadDocumentForm.controls.documentKey?.hasError('required')) { - - {{ 'labels.inputs.Document Key' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - - } - - - {{ 'labels.inputs.Description' | translate }} - - - } - - - {{ 'labels.inputs.File Name' | translate }} - - @if (uploadDocumentForm.controls.fileName.hasError('required')) { - - {{ 'labels.inputs.File Name' | translate }} {{ 'labels.commons.is' | translate }} - {{ 'labels.commons.required' | translate }} - +
+
+

+ @if (documentIdentifier) { + + {{ 'labels.heading.Add Client Identifier' | translate }} + } @else { + + {{ 'labels.heading.Upload' | translate }} {{ entityType }} {{ 'labels.heading.Documents' | translate }} } - +

+ +
- @if (!documentIdentifier) { - - {{ 'labels.inputs.Description' | translate }} - - - } +
+ + @if (documentIdentifier) { + +
+
+ + {{ 'labels.inputs.Document Type' | translate }} + + @for (docType of allowedDocumentTypes; track docType) { + + {{ docType.name }} + + } + + @if (uploadDocumentForm.controls.documentTypeId?.hasError('required')) { + + {{ 'labels.inputs.Document Type' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+
+ + {{ 'labels.inputs.Status' | translate }} + + @for (status of statusOptions; track status) { + + {{ status.label }} + + } + + +
+
+ + {{ 'labels.inputs.Document Key' | translate }} + + @if (uploadDocumentForm.controls.documentKey?.hasError('required')) { + + {{ 'labels.inputs.Document Key' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+
+ + {{ 'labels.inputs.Description' | translate }} + + +
+
- + +
+ + {{ 'labels.heading.Documents' | translate }} + +
+
+
+ + {{ 'labels.inputs.File Name' | translate }} + + @if (uploadDocumentForm.controls.fileName.hasError('required')) { + + {{ 'labels.inputs.File Name' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+
+ +
+
+ } @else { + +
+
+ + {{ 'labels.inputs.File Name' | translate }} + + @if (uploadDocumentForm.controls.fileName.hasError('required')) { + + {{ 'labels.inputs.File Name' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+
+ + {{ 'labels.inputs.Description' | translate }} + + +
+
+
+ +
+ } + +
- - - - - + + + +
diff --git a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.scss b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.scss index af9f4c075..becbb462a 100644 --- a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.scss +++ b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.scss @@ -5,3 +5,179 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +$primary: #1a73e8; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; + +.fd-dialog { + display: flex; + flex-direction: column; + min-width: 0; +} + +.fd-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 24px 12px; + border-bottom: 1px solid $gray-200; +} + +.fd-title { + margin: 0 !important; + padding: 0 !important; + font-size: 17px !important; + font-weight: 600 !important; + color: $gray-900; + display: flex; + align-items: center; + gap: 10px; + + &__icon { + font-size: 18px; + color: $primary; + } +} + +.fd-close { + width: 36px !important; + height: 36px !important; + min-width: 36px !important; + padding: 0 !important; + display: flex !important; + align-items: center; + justify-content: center; + color: $gray-500; + border: 1px solid $gray-200; + border-radius: 8px !important; + background: $white; + cursor: pointer; + transition: all 0.15s ease; + flex-shrink: 0; + + &:hover { + color: $white; + background: #ea4335; + border-color: #ea4335; + } + + i { + font-size: 13px; + line-height: 1; + } +} + +.fd-content { + padding: 20px 24px !important; + overflow: visible !important; + max-height: none !important; +} + +.fd-form { + display: flex; + flex-direction: column; + gap: 4px; +} + +.fd-grid { + display: flex; + flex-direction: column; + gap: 4px; + + &--cols-2 { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + } +} + +.fd-field { + min-width: 0; + + ::ng-deep { + .mat-mdc-form-field { + width: 100%; + } + + .mat-mdc-text-field-wrapper { + background: $gray-50; + border-radius: 8px; + } + } + + &--file { + display: flex; + align-items: center; + padding-top: 4px; + + ::ng-deep mifosx-file-upload { + width: 100%; + } + } +} + +.fd-section-divider { + margin: 8px 0 12px; + border-bottom: 1px solid $gray-200; + padding-bottom: 8px; + + &__text { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: $gray-500; + + i { + margin-right: 6px; + color: $primary; + } + } +} + +.fd-actions { + padding: 12px 24px 16px !important; + border-top: 1px solid $gray-200; + gap: 10px; + margin: 0 !important; +} + +.fd-btn { + min-width: 100px; + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + letter-spacing: 0.2px; + padding: 0 20px !important; + height: 38px !important; + + &--cancel { + color: $gray-700 !important; + border-color: $gray-200 !important; + + &:hover { + background: $gray-50 !important; + } + } + + &--submit { + box-shadow: 0 1px 3px rgba($primary, 0.3) !important; + } + + &__icon { + margin-right: 6px; + font-size: 12px; + } +} + +@media (max-width: 600px) { + .fd-grid--cols-2 { + grid-template-columns: 1fr; + } +} diff --git a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.ts b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.ts index 70ec3d1b1..9fc122dca 100644 --- a/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.ts +++ b/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.ts @@ -16,6 +16,7 @@ import { } from '@angular/material/dialog'; import { UntypedFormGroup, UntypedFormBuilder, Validators, FormControl, ReactiveFormsModule } from '@angular/forms'; import { FileUploadComponent } from '../../../../shared/file-upload/file-upload.component'; +import { MatIconButton } from '@angular/material/button'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ @@ -27,7 +28,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; MatDialogTitle, FileUploadComponent, MatDialogActions, - MatDialogClose + MatDialogClose, + MatIconButton ] }) export class UploadDocumentDialogComponent implements OnInit { diff --git a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.html b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.html index e588f5de7..b1cd24826 100644 --- a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.html +++ b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.html @@ -6,83 +6,124 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- -
-
-

{{ 'labels.heading.Family Members' | translate }}

-
-
- -
+
+ +
+
+ +

{{ 'labels.heading.Family Members' | translate }}

+ {{ clientFamilyMembers?.length || 0 }}
+ +
- - @for (member of clientFamilyMembers; track member.id; let i = $index) { - - - - {{ displayName(member) }} - - - {{ member.relationship }} - - - -
-
-

- @if (member.nominationType) { - {{ 'labels.inputs.Nomination Type' | translate }} : {{ member.nominationType }}
- } - @if (member.firstName) { - {{ 'labels.inputs.Nominee Name' | translate }} : {{ member.firstName }}
- } - @if (member.relationship) { - {{ 'labels.inputs.Relationship' | translate }} : {{ member.relationship }}
- } - @if (member.dateOfBirth) { - {{ 'labels.inputs.Date Of Birth' | translate }} : {{ member.dateOfBirth | dateFormat }}
- } - @if (member.percentageShare != null) { - {{ 'labels.inputs.Percentage Share' | translate }} : {{ member.percentageShare }}%
- } - @if (member.isDependent) { - {{ 'labels.inputs.Nominee is Minor' | translate }} : {{ member.isDependent | yesNo }}
- } - @if (member.guardianName) { - {{ 'labels.inputs.Guardian Name' | translate }} : {{ member.guardianName }}
- } - @if (member.guardianRelationship) { - {{ 'labels.inputs.Relationship with Minor' | translate }} : {{ member.guardianRelationship }}
- } - @if (member.remarks) { - {{ 'labels.inputs.Remarks' | translate }} : {{ member.remarks }}
- } -

-
- } -
- +
+ + +
+ @if (member.nominationType) { +
+
{{ 'labels.inputs.Nomination Type' | translate }}
+
{{ member.nominationType }}
+
+ } + @if (member.firstName) { +
+
{{ 'labels.inputs.Nominee Name' | translate }}
+
{{ member.firstName }}
+
+ } + @if (member.relationship) { +
+
{{ 'labels.inputs.Relationship' | translate }}
+
{{ member.relationship }}
+
+ } + @if (member.dateOfBirth) { +
+
{{ 'labels.inputs.Date Of Birth' | translate }}
+
{{ member.dateOfBirth | dateFormat }}
+
+ } + @if (member.percentageShare != null) { +
+
{{ 'labels.inputs.Percentage Share' | translate }}
+
{{ member.percentageShare }}%
+
+ } + @if (member.isDependent != null) { +
+
{{ 'labels.inputs.Nominee is Minor' | translate }}
+
{{ member.isDependent | yesNo }}
+
+ } + @if (member.guardianName) { +
+
{{ 'labels.inputs.Guardian Name' | translate }}
+
{{ member.guardianName }}
+
+ } + @if (member.guardianRelationship) { +
+
{{ 'labels.inputs.Relationship with Minor' | translate }}
+
{{ member.guardianRelationship }}
+
+ } + @if (member.remarks) { +
+
{{ 'labels.inputs.Remarks' | translate }}
+
{{ member.remarks }}
+
+ } +
+ + } +
diff --git a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.scss b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.scss index 3a012cf47..7a8cbac9b 100644 --- a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.scss +++ b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.scss @@ -6,30 +6,232 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -.tab-container { - padding: 1%; - margin: 1%; +$primary: #1a73e8; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; +$green: #34a853; +$green-light: #e6f4ea; +$red: #ea4335; +$red-light: #fce8e6; +$blue-light: #e3f2fd; +$blue: #1565c0; +$orange: #e65100; +$orange-light: #fff3e0; - .action-button { - margin-left: auto; +.nom-container { + padding: 20px 24px; +} + +// Header +.nom-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + + &__left { + display: flex; + align-items: center; + gap: 10px; } - .family-member { - .family-member-actions { - margin-top: 1%; + &__icon { + font-size: 18px; + color: $primary; + } - button { - margin-right: 1%; - } + &__title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: $gray-900; + } + + &__badge { + background: $primary-light; + color: $primary; + font-size: 11px; + font-weight: 700; + padding: 2px 8px; + border-radius: 10px; + } + + &__btn { + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + height: 36px !important; + } +} + +// Empty state +.nom-empty { + text-align: center; + padding: 48px 20px; + background: $gray-50; + border-radius: 10px; + border: 1px dashed $gray-200; + + &__icon { + font-size: 36px; + color: $gray-500; + margin-bottom: 10px; + } + + &__text { + color: $gray-500; + font-size: 14px; + margin: 0; + } +} + +// Expansion panels +.nom-panel { + margin-bottom: 12px !important; + border-radius: 10px !important; + border: 1px solid $gray-200 !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important; + + &::ng-deep .mat-expansion-panel-body { + padding: 0 20px 20px !important; + } + + &__header { + height: auto !important; + min-height: 56px !important; + padding: 8px 20px !important; + } + + &__title { + display: flex !important; + align-items: center; + gap: 10px; + flex: 1; + } + + &__type-icon { + font-size: 16px; + color: $primary; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: $primary-light; + border-radius: 6px; + } + + &__name { + font-size: 14px; + font-weight: 600; + color: $gray-900; + } + + &__badge { + font-size: 11px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; + + &--relation { + background: $blue-light; + color: $blue; } - h3 { - margin: 1% auto; + &--type { + background: $orange-light; + color: $orange; } + } - p { - line-height: 30px; - margin-left: 2%; + &__desc { + font-size: 12px; + color: $gray-500 !important; + flex: 0 !important; + white-space: nowrap; + } +} + +// Actions bar inside panel +.nom-actions { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; + margin-bottom: 12px; + border-bottom: 1px solid $gray-100; + + &__buttons { + display: flex; + gap: 8px; + } + + &__edit, + &__delete { + border-radius: 6px !important; + font-size: 12px !important; + font-weight: 600 !important; + height: 32px !important; + } +} + +// Details grid +.nom-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0; + border: 1px solid $gray-100; + border-radius: 8px; + overflow: hidden; + + @media (max-width: 768px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (max-width: 480px) { + grid-template-columns: 1fr; + } +} + +.nom-field { + padding: 10px 14px; + border-bottom: 1px solid $gray-100; + border-right: 1px solid $gray-100; + transition: background 0.15s ease; + + &:hover { + background: $gray-50; + } + + &:nth-child(3n) { + border-right: none; + } + + &__label { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: $gray-500; + margin-bottom: 3px; + } + + &__value { + font-size: 13.5px; + font-weight: 500; + color: $gray-900; + word-break: break-word; + + &--mono { + font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; + font-size: 13px; + letter-spacing: 0.5px; } } } diff --git a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.ts b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.ts index ba18e24b5..fabda094d 100644 --- a/src/app/clients/clients-view/family-members-tab/family-members-tab.component.ts +++ b/src/app/clients/clients-view/family-members-tab/family-members-tab.component.ts @@ -8,14 +8,19 @@ /** Angular Imports */ import { Component, inject } from '@angular/core'; -import { ActivatedRoute, RouterOutlet, RouterLink } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; /** Custom Components */ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component'; +import { NomineeDialogComponent } from './nominee-dialog/nominee-dialog.component'; /** Custom Services */ import { ClientsService } from '../../clients.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; + import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatAccordion, @@ -38,7 +43,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; styleUrls: ['./family-members-tab.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, - RouterOutlet, FaIconComponent, MatAccordion, MatExpansionPanel, @@ -53,24 +57,79 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; export class FamilyMembersTabComponent { private route = inject(ActivatedRoute); private clientsService = inject(ClientsService); - dialog = inject(MatDialog); + private dialog = inject(MatDialog); + private translateService = inject(TranslateService); + private settingsService = inject(SettingsService); + private dateUtils = inject(Dates); /** Client Family Members */ clientFamilyMembers: any; + /** Client ID */ + clientId: string; + /** Family member template options (loaded on first dialog open) */ + familyMemberOptions: any; - /** - * @param {ActivatedRoute} route Activated Route - * @param {ClientsService} clientsService Clients Service - * @param {MatDialog }dialog Mat Dialog - */ constructor() { this.route.data.subscribe((data: { clientFamilyMembers: any }) => { this.clientFamilyMembers = data.clientFamilyMembers; }); + this.clientId = this.route.parent.snapshot.params['clientId']; } /** - * Deletes the family member and redirects to family members tab. + * Opens the nominee dialog in Add mode. + */ + addNominee() { + this.ensureTemplateLoaded().then(() => { + const dialogRef = this.dialog.open(NomineeDialogComponent, { + width: '700px', + data: { + title: this.translateService.instant('labels.buttons.Add') + ' ' + this.translateService.instant('labels.heading.Nominee'), + familyMemberOptions: this.familyMemberOptions + } + }); + + dialogRef.afterClosed().subscribe((result: any) => { + if (result) { + this.clientsService.addFamilyMember(this.clientId, result).subscribe((res: any) => { + // Refresh the list by fetching the member details + this.clientsService.getClientFamilyMember(this.clientId, res.resourceId).subscribe((member: any) => { + this.clientFamilyMembers.push(member); + }); + }); + } + }); + }); + } + + /** + * Opens the nominee dialog in Edit mode, pre-filled with member data. + */ + editNominee(member: any, index: number) { + this.ensureTemplateLoaded().then(() => { + const dialogRef = this.dialog.open(NomineeDialogComponent, { + width: '700px', + data: { + title: this.translateService.instant('labels.buttons.Edit') + ' ' + this.translateService.instant('labels.heading.Nominee'), + familyMemberOptions: this.familyMemberOptions, + member + } + }); + + dialogRef.afterClosed().subscribe((result: any) => { + if (result) { + this.clientsService.editFamilyMember(member.clientId, member.id, result).subscribe(() => { + this.clientsService.getClientFamilyMember(this.clientId, member.id).subscribe((updated: any) => { + this.clientFamilyMembers[index] = updated; + }); + }); + } + }); + }); + } + + /** + * Deletes the family member. */ deleteFamilyMember(clientId: string, id: string, name: string, index: number) { const deleteFamilyMemberDialogRef = this.dialog.open(DeleteDialogComponent, { @@ -88,4 +147,19 @@ export class FamilyMembersTabComponent { displayName(member: any): string { return member.firstName || ''; } + + /** + * Lazily loads family member template options from the API. + */ + private ensureTemplateLoaded(): Promise { + if (this.familyMemberOptions) { + return Promise.resolve(); + } + return new Promise((resolve) => { + this.clientsService.getClientTemplate().subscribe((template: any) => { + this.familyMemberOptions = template.familyMemberOptions; + resolve(); + }); + }); + } } diff --git a/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.html b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.html new file mode 100644 index 000000000..6aa16b0c1 --- /dev/null +++ b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.html @@ -0,0 +1,147 @@ + + +
+ +
+

{{ data.title }}

+ +
+ + +
+
+
+ +
+ + {{ 'labels.inputs.Nomination Type' | translate }} + + {{ 'labels.inputs.DA1 - Add Nominee' | translate }} + {{ 'labels.inputs.DA5 - Modify / Cancel Nominee' | translate }} + + +
+ + +
+ + {{ 'labels.inputs.Nominee Name' | translate }} + + @if (nomineeForm.controls['firstName'].hasError('required')) { + + {{ 'labels.inputs.Nominee Name' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+ + +
+ + {{ 'labels.inputs.Relationship' | translate }} + + @for (relation of data.familyMemberOptions?.relationshipIdOptions; track relation) { + + {{ relation.name | translateKey: 'catalogs' }} + + } + + @if (nomineeForm.controls['relationshipId'].hasError('required')) { + + {{ 'labels.inputs.Relationship' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + +
+ + +
+ + {{ 'labels.inputs.Date Of Birth' | translate }} + + + + +
+ + +
+ + {{ 'labels.inputs.Percentage Share' | translate }} (%) + + +
+ + +
+ + {{ 'labels.inputs.Nominee is Minor' | translate }}? + +
+
+ + + @if (nomineeForm.get('isDependent')?.value) { +

{{ 'labels.heading.Guardian Details (If Minor)' | translate }}

+
+
+ + {{ 'labels.inputs.Guardian Name' | translate }} + + +
+ +
+ + {{ 'labels.inputs.Relationship with Minor' | translate }} + + +
+
+ } + + +
+
+ + {{ 'labels.inputs.Remarks' | translate }} + + +
+
+
+
+ + +
+ + +
+
diff --git a/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.scss b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.scss new file mode 100644 index 000000000..0b9ac9b8c --- /dev/null +++ b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.scss @@ -0,0 +1,191 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$primary: #1a73e8; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; +$white: #fff; + +.fd-dialog { + display: flex; + flex-direction: column; + min-width: 0; +} + +// Header +.fd-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 24px 12px; + border-bottom: 1px solid $gray-200; +} + +.fd-title { + margin: 0 !important; + padding: 0 !important; + font-size: 17px !important; + font-weight: 600 !important; + color: $gray-900; + letter-spacing: -0.2px; +} + +.fd-close { + width: 32px !important; + height: 32px !important; + line-height: 32px !important; + color: $gray-500; + transition: color 0.15s ease, background 0.15s ease; + + &:hover { + color: $gray-900; + background: $gray-100; + border-radius: 50%; + } + + i { + font-size: 14px; + } +} + +// Content area +.fd-content { + padding: 20px 24px !important; + max-height: 75vh; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 4px; + } + + &::-webkit-scrollbar-thumb { + background: $gray-200; + border-radius: 4px; + } +} + +.fd-grid { + display: flex; + flex-direction: column; + gap: 4px; + + &--cols-2 { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + } +} + +.fd-field { + min-width: 0; + + &--full { + grid-column: 1 / -1; + } + + &--checkbox { + display: flex; + align-items: center; + padding-top: 10px; + } + + ::ng-deep { + .mat-mdc-form-field { + width: 100%; + } + + .mat-mdc-text-field-wrapper { + background: $gray-50; + border-radius: 8px; + } + + .mdc-text-field--outlined .mdc-notched-outline__leading, + .mdc-text-field--outlined .mdc-notched-outline__trailing, + .mdc-text-field--outlined .mdc-notched-outline__notch { + border-color: $gray-200; + } + + .mdc-text-field--outlined:not(.mdc-text-field--disabled):hover .mdc-notched-outline__leading, + .mdc-text-field--outlined:not(.mdc-text-field--disabled):hover .mdc-notched-outline__trailing, + .mdc-text-field--outlined:not(.mdc-text-field--disabled):hover .mdc-notched-outline__notch { + border-color: $primary; + } + + .mat-mdc-floating-label { + font-size: 13px; + color: $gray-700; + } + + input.mat-mdc-input-element { + font-size: 14px; + color: $gray-900; + } + } +} + +.fd-section-title { + margin: 12px 0 4px; + font-size: 14px; + font-weight: 600; + color: $gray-700; +} + +// Actions +.fd-actions { + padding: 12px 24px 16px !important; + border-top: 1px solid $gray-200; + gap: 10px; + margin: 0 !important; +} + +.fd-btn { + min-width: 100px; + border-radius: 8px !important; + font-size: 13px !important; + font-weight: 600 !important; + letter-spacing: 0.2px; + padding: 0 20px !important; + height: 38px !important; + + &--cancel { + color: $gray-700 !important; + border-color: $gray-200 !important; + + &:hover { + background: $gray-50 !important; + } + } + + &--submit { + box-shadow: 0 1px 3px rgba($primary, 0.3) !important; + } + + &__icon { + margin-right: 6px; + font-size: 12px; + } +} + +// Responsive +@media (max-width: 600px) { + .fd-grid--cols-2 { + grid-template-columns: 1fr; + } + + .fd-content { + padding: 16px !important; + } + + .fd-actions { + padding: 12px 16px !important; + } +} diff --git a/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.ts b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.ts new file mode 100644 index 000000000..454f44f50 --- /dev/null +++ b/src/app/clients/clients-view/family-members-tab/nominee-dialog/nominee-dialog.component.ts @@ -0,0 +1,87 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, OnInit, inject } from '@angular/core'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; + +export interface NomineeDialogData { + title: string; + familyMemberOptions: any; + member?: any; +} + +@Component({ + selector: 'mifosx-nominee-dialog', + templateUrl: './nominee-dialog.component.html', + styleUrls: ['./nominee-dialog.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatCheckbox + ] +}) +export class NomineeDialogComponent implements OnInit { + private formBuilder = inject(UntypedFormBuilder); + private dialogRef = inject(MatDialogRef); + private settingsService = inject(SettingsService); + private dateUtils = inject(Dates); + data: NomineeDialogData = inject(MAT_DIALOG_DATA); + + nomineeForm: UntypedFormGroup; + maxDate = new Date(); + + ngOnInit() { + this.maxDate = this.settingsService.businessDate; + this.createForm(); + } + + createForm() { + const member = this.data.member; + this.nomineeForm = this.formBuilder.group({ + nominationType: [member?.nominationType || 'DA1'], + firstName: [member?.firstName || '', Validators.required], + relationshipId: [member?.relationshipId || '', Validators.required], + dateOfBirth: [member?.dateOfBirth ? this.dateUtils.formatDate(member.dateOfBirth, 'yyyy-MM-dd') : null], + percentageShare: [member?.percentageShare ?? 100], + isDependent: [member?.isDependent || false], + guardianName: [member?.guardianName || ''], + guardianRelationship: [member?.guardianRelationship || ''], + remarks: [member?.remarks || ''] + }); + } + + onSubmit() { + if (this.nomineeForm.valid) { + const formValue = { ...this.nomineeForm.value }; + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + + if (formValue.dateOfBirth) { + if (formValue.dateOfBirth instanceof Date) { + formValue.dateOfBirth = this.dateUtils.formatDate(formValue.dateOfBirth, dateFormat); + } + } else { + delete formValue.dateOfBirth; + } + + this.dialogRef.close({ + ...formValue, + dateFormat, + locale + }); + } + } + + onCancel() { + this.dialogRef.close(null); + } +} diff --git a/src/app/clients/clients-view/identities-tab/identities-tab.component.ts b/src/app/clients/clients-view/identities-tab/identities-tab.component.ts index a89d31fa6..f733f1a6e 100644 --- a/src/app/clients/clients-view/identities-tab/identities-tab.component.ts +++ b/src/app/clients/clients-view/identities-tab/identities-tab.component.ts @@ -159,6 +159,7 @@ export class IdentitiesTabComponent implements OnDestroy { ]; const dialogRef = this.dialog.open(UploadDocumentDialogComponent, { + width: '700px', data: { documentIdentifier: true, allowedDocumentTypes: translatedDocTypes, diff --git a/src/app/shared/file-upload/file-upload.component.html b/src/app/shared/file-upload/file-upload.component.html index a78cab37b..547437987 100644 --- a/src/app/shared/file-upload/file-upload.component.html +++ b/src/app/shared/file-upload/file-upload.component.html @@ -6,23 +6,20 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- - - - - - - +
+ @if (fileName) { +
+ + {{ fileName }} + Change +
+ } @else { +
+ + {{ 'labels.buttons.Browse' | translate }} + {{ 'labels.text.NoFileSelected' | translate }} +
+ }
diff --git a/src/app/shared/file-upload/file-upload.component.scss b/src/app/shared/file-upload/file-upload.component.scss index af9f4c075..aae93455c 100644 --- a/src/app/shared/file-upload/file-upload.component.scss +++ b/src/app/shared/file-upload/file-upload.component.scss @@ -5,3 +5,83 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +$primary: #1a73e8; +$primary-light: #e8f0fe; +$gray-50: #f8f9fa; +$gray-100: #f1f3f4; +$gray-200: #e8eaed; +$gray-500: #9aa0a6; +$gray-700: #5f6368; +$gray-900: #202124; + +.file-upload-zone { + border: 1.5px dashed $gray-200; + border-radius: 8px; + padding: 10px 14px; + cursor: pointer; + transition: all 0.15s ease; + background: $gray-50; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + border-color: $primary; + background: $primary-light; + } + + &__empty { + display: flex; + align-items: center; + gap: 8px; + } + + &__upload-icon { + font-size: 16px; + color: $primary; + } + + &__text { + font-size: 12px; + font-weight: 600; + color: $primary; + } + + &__hint { + font-size: 11px; + color: $gray-500; + margin-left: 4px; + } + + &__selected { + display: flex; + align-items: center; + gap: 8px; + } + + &__file-icon { + font-size: 14px; + color: $primary; + } + + &__filename { + font-size: 12px; + font-weight: 500; + color: $gray-900; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__change { + font-size: 10px; + font-weight: 600; + color: $primary; + padding: 1px 6px; + background: $primary-light; + border-radius: 3px; + } +} diff --git a/src/app/shared/form-dialog/form-dialog.component.scss b/src/app/shared/form-dialog/form-dialog.component.scss index fc210402f..78fa21a97 100644 --- a/src/app/shared/form-dialog/form-dialog.component.scss +++ b/src/app/shared/form-dialog/form-dialog.component.scss @@ -40,28 +40,47 @@ $white: #fff; } .fd-close { - width: 32px !important; - height: 32px !important; - line-height: 32px !important; + width: 36px !important; + height: 36px !important; + min-width: 36px !important; + padding: 0 !important; + display: flex !important; + align-items: center; + justify-content: center; color: $gray-500; - transition: color 0.15s ease, background 0.15s ease; + border: 1px solid $gray-200; + border-radius: 8px !important; + background: $white; + cursor: pointer; + transition: all 0.15s ease; + flex-shrink: 0; &:hover { - color: $gray-900; - background: $gray-100; - border-radius: 50%; + color: $white; + background: #ea4335; + border-color: #ea4335; } i { - font-size: 14px; + font-size: 13px; + line-height: 1; } } // Content area .fd-content { padding: 20px 24px !important; - max-height: 65vh; - overflow-y: auto; + overflow: visible !important; + max-height: none !important; + + &::-webkit-scrollbar { + width: 4px; + } + + &::-webkit-scrollbar-thumb { + background: $gray-200; + border-radius: 4px; + } } .fd-grid { diff --git a/src/app/shared/form-dialog/formfield/formfield.component.html b/src/app/shared/form-dialog/formfield/formfield.component.html index ef7acb113..81e9be740 100644 --- a/src/app/shared/form-dialog/formfield/formfield.component.html +++ b/src/app/shared/form-dialog/formfield/formfield.component.html @@ -9,7 +9,7 @@
@if (formfield.controlType === 'input' || formfield.controlType === 'select') {
- + {{ formfield.label }} @if (formfield.controlType === 'input') {