Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#13054] Make substrings of the name in dropdown selections of recipient to be searchable #13179

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
32dd547
Add <input /> aside <select />
LeeY0846 Oct 10, 2024
23491f8
Add new style for select-input
LeeY0846 Oct 10, 2024
48f4cb1
Add field and method handling select-input value
LeeY0846 Oct 10, 2024
4a8dfb4
Correct value binding
LeeY0846 Oct 10, 2024
7d7f70f
Implement filter function
LeeY0846 Oct 10, 2024
859d6cb
Add unit tests for filterRecipientsBySearchText
LeeY0846 Oct 10, 2024
a231c6b
Tidy up to pass lint
LeeY0846 Oct 11, 2024
7e44913
Merge branch 'add-text-input-as-search-bar' into feature/13054-search…
GraceXiehahaha Oct 12, 2024
5e6e1ab
update filter function to allow search for all possible substrings
GraceXiehahaha Oct 12, 2024
11ebfab
update test cases
GraceXiehahaha Oct 12, 2024
15c9445
Merge branch 'feature/13054-searchable-dropdown-Grace' into feature/1…
GraceXiehahaha Oct 12, 2024
a355828
Fix lint errors
LeeY0846 Oct 12, 2024
d512c97
Add input-selection styles
LeeY0846 Oct 14, 2024
e0ccbc0
implement input-mocked selection
LeeY0846 Oct 15, 2024
40c2c14
Move to simple col-stacked design
LeeY0846 Oct 15, 2024
62f6182
Fix lint error
LeeY0846 Oct 15, 2024
8f9b2ee
Revert "Move to simple col-stacked design"
LeeY0846 Oct 19, 2024
cae895c
Implement input-mocked select
LeeY0846 Oct 19, 2024
5aa33d5
Tidy up
LeeY0846 Oct 19, 2024
e8a37ef
Merge pull request #2 from GraceXiehahaha/retry-input-design
LeeY0846 Oct 19, 2024
00e5146
Add hidden select for e2e tests
LeeY0846 Oct 19, 2024
3db5b16
Merge pull request #3 from GraceXiehahaha/retry-input-design
LeeY0846 Oct 19, 2024
f75c908
Change div id
LeeY0846 Oct 19, 2024
96df4f5
Try to fix e2e tests
LeeY0846 Oct 19, 2024
62fc52f
Revert "Add hidden select for e2e tests"
LeeY0846 Oct 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,24 @@ <h2 class="question-details"><b>Question {{ model.questionNumber }}: </b>{{ mode
<b>{{ getRecipientName(recipientSubmissionFormModel.recipientIdentifier) }} </b> <span>({{ model.recipientType | recipientTypeName:model.giverType }})</span>
</div>
<div class="row evaluee-select align-items-center" *ngIf="formMode === QuestionSubmissionFormMode.FLEXIBLE_RECIPIENT">
<select id="recipient-dropdown-qn-{{ model.questionNumber }}-idx-{{ i }}" class="form-control form-select fw-bold col" [ngModel]="recipientSubmissionFormModel.recipientIdentifier"
(ngModelChange)="triggerRecipientSubmissionFormChange(i, 'recipientIdentifier', $event)"
[disabled]="isFormsDisabled"
[attr.aria-label]="'Select recipient dropdown question ' + model.questionNumber + ' index ' + i">
<option value=""></option>
<ng-container *ngFor="let recipient of model.recipientList">
<option *ngIf="!isRecipientSelected(recipient) || recipientSubmissionFormModel.recipientIdentifier === recipient.recipientIdentifier" [ngValue]="recipient.recipientIdentifier">{{ getSelectionOptionLabel(recipient) }}</option>
</ng-container>
</select>
<div class="input-selection-wrapper col">
<input type="text" class="form-control form-select fw-bold" id="recipient-dropdown-qn-{{ model.questionNumber }}-idx-{{ i }}"
[(ngModel)]="searchNameTexts[i]"
(ngModelChange)="triggerSelectInputChange(i)"
[disabled]="isFormsDisabled"
[attr.aria-label]="'Recipient names filter ' + model.questionNumber + ' index ' + i"
[ngClass]="(feedbackRecipients[i] === null && filterRecipientsBySearchText(searchNameTexts[i],model.recipientList).length === 0) ? 'no-match' : ''"
(focus)="triggerSelectInputFocus(i)"
autocomplete="off" />
<div class="form-control input-selection-option-wrapper" [ngClass]="feedbackRecipients[i] !== null ? 'selected-input' : ''">
<ng-container *ngFor="let recipient of filterRecipientsBySearchText(searchNameTexts[i],model.recipientList)">
<option class="input-selection-option" *ngIf="!isRecipientSelected(recipient) || recipientSubmissionFormModel.recipientIdentifier === recipient.recipientIdentifier"
[ngValue]="recipient.recipientIdentifier"
(click)="triggerRecipientOptionSelect(i, recipient, $event)"
tabindex="0">{{ getSelectionOptionLabel(recipient) }}</option>
</ng-container>
</div>
</div>
<div class="col-auto text-start">
({{ model.recipientType | recipientTypeName: model.giverType }})
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,43 @@
.collapse-caret {
margin-right: 10px;
}

.input-selection-wrapper {
position: relative;
display: flex;
flex-direction: column;
box-sizing: content-box;
}

.input-selection-wrapper:focus-within .input-selection-option-wrapper:not(.selected-input){
display: flex;
}

.input-selection-option-wrapper {
position: absolute;
z-index: 10;
top: 100%;
width: auto;
left:calc(var(--bs-gutter-x) * 0.5);
right:calc(var(--bs-gutter-x) * 0.5);
max-height: 9rem;
box-sizing: content-box;
display: none;
flex-direction: column;
overflow: hidden auto;
}

.input-selection-option {
cursor: pointer;
user-select: none;
}

.input-selection-option:hover {
color: white;
background-color: var(--bs-blue);
border-radius: 0.15rem;
}

.no-match {
color: var(--bs-danger)
}
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,32 @@ describe('QuestionSubmissionFormComponent', () => {
expect(component.isRecipientSelected(feedbackResponseRecipient)).toBeFalsy();
});

it('filterRecipientsBySearchText: should return correct filtered names', () => {
const doubleLucy = { recipientIdentifier: 'lucyLucy', recipientName: 'Lucy Lucy' };
const charlie = { recipientIdentifier: 'charlieBrown', recipientName: 'Charlie Brown' };
const lucy = { recipientIdentifier: 'lucyVanPelt', recipientName: 'Lucy van Pelt' };
const sally = { recipientIdentifier: 'sallyBrown', recipientName: 'Sally Brown' };
const snoopy = { recipientIdentifier: 'snoopy', recipientName: 'Snoopy' };
const linus = { recipientIdentifier: 'linusVanPelt', recipientName: 'Linus van Pelt' };
const benny = { recipientIdentifier: 'bennyCharles', recipientName: 'Benny Charles' };
const charlieDavis = { recipientIdentifier: 'charlieDavis', recipientName: 'Charlie Davis' };
const francis = { recipientIdentifier: 'francisGabriel', recipientName: 'Francis Gabriel' };

const recipients = [doubleLucy, charlie, lucy, sally, snoopy, linus, benny, charlieDavis, francis];
expect(component.filterRecipientsBySearchText('', recipients)).toStrictEqual(recipients);
expect(component.filterRecipientsBySearchText(' ', recipients)).toStrictEqual(recipients);
expect(component.filterRecipientsBySearchText('Lucy', recipients)).toStrictEqual([doubleLucy, lucy]);
expect(component.filterRecipientsBySearchText('s', recipients))
.toStrictEqual([sally, snoopy, linus, benny, charlieDavis, francis]);
expect(component.filterRecipientsBySearchText('Brow', recipients)).toStrictEqual([charlie, sally]);
expect(component.filterRecipientsBySearchText('van pel', recipients)).toStrictEqual([lucy, linus]);
expect(component.filterRecipientsBySearchText('van Pelt', recipients)).toStrictEqual([lucy, linus]);
expect(component.filterRecipientsBySearchText('cy', recipients)).toStrictEqual([doubleLucy, lucy]);
expect(component.filterRecipientsBySearchText('char', recipients))
.toStrictEqual([charlie, benny, charlieDavis]);
expect(component.filterRecipientsBySearchText('is', recipients)).toStrictEqual([charlieDavis, francis]);
});

it('triggerDeleteCommentEvent: should emit the correct index to deleteCommentEvent', () => {
let emittedIndex: number | undefined;
testEventEmission(component.deleteCommentEvent, (index) => { emittedIndex = index; });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export class QuestionSubmissionFormComponent implements DoCheck {
isSaved: boolean = false;
hasResponseChanged: boolean = false;

searchNameTexts: string[] = [];
feedbackRecipients: Array<FeedbackResponseRecipient | null> = [];

@Input()
formMode: QuestionSubmissionFormMode = QuestionSubmissionFormMode.FIXED_RECIPIENT;

Expand Down Expand Up @@ -94,6 +97,8 @@ export class QuestionSubmissionFormComponent implements DoCheck {
this.model.isTabExpandedForRecipients.set(recipient.recipientIdentifier, true);
});
this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some((value) => value);
this.searchNameTexts = new Array(this.model.recipientSubmissionForms.length).fill('');
this.feedbackRecipients = new Array(this.model.recipientSubmissionForms.length).fill(null);
}

@Input()
Expand Down Expand Up @@ -320,6 +325,19 @@ export class QuestionSubmissionFormComponent implements DoCheck {
this.updateSubmissionFormIndexes();
}

filterRecipientsBySearchText(searchText: string, recipients: FeedbackResponseRecipient[])
: FeedbackResponseRecipient[] {
if (!searchText) return recipients;
const searchName = searchText.trim().toLowerCase();
if (searchName.length === 0) return recipients;
if (searchName.includes(' ')) {
return recipients.filter((r) => !this.isRecipientSelected(r)
&& r.recipientName.toLowerCase().includes(searchName));
}
return recipients.filter((r) => !this.isRecipientSelected(r)
&& r.recipientName.split(' ').some((s) => s.toLowerCase().includes(searchName)));
}

private sortRecipientsBySectionTeam(): void {
if (this.recipientLabelType === FeedbackRecipientLabelType.INCLUDE_SECTION) {
this.model.recipientList.sort((firstRecipient, secondRecipient) => {
Expand Down Expand Up @@ -361,6 +379,54 @@ export class QuestionSubmissionFormComponent implements DoCheck {
recipientSubmissionFormModel.recipientIdentifier === recipient.recipientIdentifier);
}

/**
* Triggers the changes of the recipient selection
*/
triggerRecipientIdentifierChange(index: number, data: any): void {
this.searchNameTexts[index] = '';
this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', data);
}

/**
* Update the texts in the recipient selection inputs to fit the Show Section/Team checkbox
*/
updateSearchNameTextByShowSection(): void {
this.searchNameTexts = this.searchNameTexts.map(
(s, i) => {
return this.feedbackRecipients[i] === null ? s : this.getSelectionOptionLabel(this.feedbackRecipients[i]!);
},
);
}

/**
* Triggers when the input is focused
*/
triggerSelectInputFocus(index: number): void {
if (this.feedbackRecipients[index] !== null) {
this.searchNameTexts[index] = '';
this.model.recipientSubmissionForms[index].recipientIdentifier = '';
this.feedbackRecipients[index] = null;
}
}

/**
* Triggers the changes of the recipient search text input
*/
triggerSelectInputChange(index: number): void {
this.model.recipientSubmissionForms[index].recipientIdentifier = '';
this.feedbackRecipients[index] = null;
}

/**
* Triggers the changes of the recipient selection by options's click-events
*/
triggerRecipientOptionSelect(index: number, recipient: FeedbackResponseRecipient, event: any): void {
event.target.blur();
this.searchNameTexts[index] = this.getSelectionOptionLabel(recipient);
this.feedbackRecipients[index] = recipient;
this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', recipient.recipientIdentifier);
}

/**
* Triggers the change of the recipient submission form.
*/
Expand Down Expand Up @@ -523,6 +589,7 @@ export class QuestionSubmissionFormComponent implements DoCheck {
this.isSectionTeamShown = false;
this.sortRecipientsByName();
}
this.updateSearchNameTextByShowSection();
}

/**
Expand Down
Loading