resolve merge conflicts: keep incoming .env version, incoming nominee HTML, HEAD guardian SCSS
This commit is contained in:
+25
-3
@@ -169,13 +169,28 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (linkedSelectError) {
|
||||
<div class="fm-linked-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ linkedSelectError }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (!searching && searchResults.length > 0) {
|
||||
<div class="fm-search-results">
|
||||
@for (r of searchResults; track r.id) {
|
||||
<div class="fm-search-item" (click)="selectClient(r)">
|
||||
<div
|
||||
class="fm-search-item"
|
||||
[class.is-disabled]="isClientAlreadyLinked(r.id)"
|
||||
(click)="selectClient(r)"
|
||||
>
|
||||
<mat-icon class="fm-search-item__icon">person</mat-icon>
|
||||
<div class="fm-search-item__info">
|
||||
<span class="fm-search-item__name">{{ r.displayName }}</span>
|
||||
<span class="fm-search-item__name">
|
||||
{{ r.displayName }}
|
||||
@if (isClientAlreadyLinked(r.id)) {
|
||||
<span class="fm-search-item__taken">· already linked</span>
|
||||
}
|
||||
</span>
|
||||
<span class="fm-search-item__meta"
|
||||
>ID: {{ r.id }} · {{ r.officeName }}
|
||||
@if (r.dateOfBirth) {
|
||||
@@ -306,7 +321,14 @@
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-hint>Father, Mother and Spouse can only be added once.</mat-hint>
|
||||
<mat-icon
|
||||
matSuffix
|
||||
class="fm-rel-hint-icon"
|
||||
matTooltip="Father, Mother and Spouse can only be added once."
|
||||
matTooltipPosition="left"
|
||||
>
|
||||
info_outline
|
||||
</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
@if (entryMode === 'manual') {
|
||||
|
||||
+54
@@ -36,6 +36,60 @@
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Small info icon in the Relationship field's suffix — hover reveals the
|
||||
"only once" hint without consuming layout space below the field. */
|
||||
.fm-rel-hint-icon {
|
||||
color: #94a3b8;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: help;
|
||||
margin-right: 4px;
|
||||
|
||||
&:hover {
|
||||
color: #1074b9;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline error shown above the linked-client search when a duplicate is picked. */
|
||||
.fm-linked-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 8px 0;
|
||||
padding: 8px 12px;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 6px;
|
||||
color: #b91c1c;
|
||||
font-size: 12.5px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Greyed-out search result for clients already linked as family members. */
|
||||
.fm-search-item.is-disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
background: #f8fafc;
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-search-item__taken {
|
||||
margin-left: 6px;
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fm-mode-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
+36
-1
@@ -24,6 +24,7 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
@@ -41,6 +42,7 @@ import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
MatDialogClose,
|
||||
MatIcon,
|
||||
MatProgressSpinner,
|
||||
MatTooltip,
|
||||
FaIconComponent,
|
||||
DateFormatPipe
|
||||
]
|
||||
@@ -245,6 +247,7 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
/** Search existing clients */
|
||||
onSearch(text: string): void {
|
||||
this.searchText = text;
|
||||
this.linkedSelectError = '';
|
||||
clearTimeout(this.searchTimer);
|
||||
if (!text || text.length < 2) {
|
||||
this.searchResults = [];
|
||||
@@ -252,7 +255,9 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
}
|
||||
this.searching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8, '', '', true).subscribe({
|
||||
// Family-member link picker: only Individual clients can be linked as a relative —
|
||||
// non-individuals (companies / trusts) have no person-to-person relationship to model here.
|
||||
this.clientsService.searchByText(text, 0, 8, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.searchResults = data?.content || [];
|
||||
this.searching = false;
|
||||
@@ -266,8 +271,38 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline error shown above the linked-client search box. Currently used
|
||||
* to surface "this client is already linked" when the user tries to
|
||||
* select a duplicate.
|
||||
*/
|
||||
linkedSelectError = '';
|
||||
|
||||
/**
|
||||
* True if the given client id is already linked as a family member of
|
||||
* this client. The member currently being edited is excluded so its own
|
||||
* linked client stays selectable.
|
||||
*/
|
||||
isClientAlreadyLinked(clientId: number | string): boolean {
|
||||
if (clientId == null) return false;
|
||||
const existing = this.data?.existingMembers || [];
|
||||
if (!existing.length) return false;
|
||||
const editingId = this.data?.context === 'Edit' ? this.data?.member?.id : undefined;
|
||||
const target = String(clientId);
|
||||
return existing.some((m: any) => {
|
||||
if (editingId && m.id === editingId) return false;
|
||||
const linked = m.linkedClientId ?? m._linkedClient?.id;
|
||||
return linked != null && String(linked) === target;
|
||||
});
|
||||
}
|
||||
|
||||
/** Select a client from search results and fetch full details */
|
||||
selectClient(client: any): void {
|
||||
if (this.isClientAlreadyLinked(client?.id)) {
|
||||
this.linkedSelectError = `${client?.displayName || 'This client'} is already linked as a family member.`;
|
||||
return;
|
||||
}
|
||||
this.linkedSelectError = '';
|
||||
this.selectedClient = client;
|
||||
this.searchResults = [];
|
||||
this.searchText = '';
|
||||
|
||||
+5
-2
@@ -255,7 +255,8 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
}
|
||||
this.nomineeSearching = true;
|
||||
this.nomineeSearchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 10, '', '', true).subscribe({
|
||||
// Nominee picker: a nominee must be a natural person, never a corporate entity.
|
||||
this.clientsService.searchByText(text, 0, 10, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.nomineeSearchResults = data?.content || [];
|
||||
this.nomineeSearching = false;
|
||||
@@ -351,7 +352,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
}
|
||||
this.guardianSearching = true;
|
||||
this.guardianSearchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 10, '', '', true).subscribe({
|
||||
// Guardian picker: a guardian is by definition a natural person responsible for a minor /
|
||||
// dependent — non-individuals can never play that role.
|
||||
this.clientsService.searchByText(text, 0, 10, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.guardianSearchResults = data?.content || [];
|
||||
this.guardianSearching = false;
|
||||
|
||||
@@ -987,7 +987,8 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
this.guardianSearching = true;
|
||||
this.guardianSearchTimer = setTimeout(() => {
|
||||
this.clientService.searchByText(text, 0, 10, '', '', true).subscribe({
|
||||
// Guardian picker: only natural persons can be a guardian.
|
||||
this.clientService.searchByText(text, 0, 10, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.guardianSearchResults = data?.content || [];
|
||||
this.guardianSearching = false;
|
||||
|
||||
Reference in New Issue
Block a user