Skip to content

Commit

Permalink
Add Fuzzy Search in Drop Down (#691)
Browse files Browse the repository at this point in the history
* install fuse and implement fuzzy search results

* util to update supported major sort method

* remove unused debounce promise

* update prop for plan select - fuzzy search

* test commit

* yarn lint fix

* let to const
  • Loading branch information
KobeZ123 authored Feb 4, 2024
1 parent 6b29b5a commit 6c81c22
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@babel/plugin-proposal-decorators": "^7.17.8",
"@nestjs/throttler": "^5.0.1",
"cross-env": "^7.0.3",
"fuse.js": "^7.0.0",
"nodemailer": "^6.9.1"
},
"devDependencies": {
Expand Down
16 changes: 16 additions & 0 deletions packages/common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,19 @@ export const isStrongPassword = (password: string): boolean => {
const containsLettersAndNumbersRegex = /^(?=.*[a-zA-Z])(?=.*[0-9])/;
return password.length >= 8 && containsLettersAndNumbersRegex.test(password);
};

/**
* Comparator function for sorting majors by name Criteria: ignores spacing and
* special characters when sorting
*/
export const majorNameComparator = (a: string, b: string) => {
const trimmedA = a
.replace(/[^A-Z0-9]/gi, "")
.trim()
.toLowerCase();
const trimmedB = b
.replace(/[^A-Z0-9]/gi, "")
.trim()
.toLowerCase();
return trimmedB.localeCompare(trimmedA);
};
25 changes: 25 additions & 0 deletions packages/frontend/components/Form/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
FormErrorMessage,
FormHelperText,
} from "@chakra-ui/react";
import Fuse from "fuse.js";
import { Control, FieldError, useController } from "react-hook-form";
import Select from "react-select";
import { FilterOptionOption } from "react-select/dist/declarations/src/filters";

type PlanSelectProps = {
error?: FieldError;
Expand All @@ -25,6 +27,8 @@ type PlanSelectProps = {
isSearchable?: boolean;
/** An option in the select dropdown that indicates "no selection". */
noValueOptionLabel?: string;
/** Fuzzy options to use */
useFuzzySearch?: boolean;
};

export const PlanSelect: React.FC<PlanSelectProps> = ({
Expand All @@ -38,7 +42,27 @@ export const PlanSelect: React.FC<PlanSelectProps> = ({
isNumeric,
isSearchable,
noValueOptionLabel,
useFuzzySearch,
}) => {
const filterOptions = useFuzzySearch
? (option: FilterOptionOption<any>, inputValue: string) => {
if (inputValue.length !== 0) {
const list = new Fuse(options, {
isCaseSensitive: false,
shouldSort: true,
ignoreLocation: true,
findAllMatches: true,
includeScore: true,
threshold: 0.4,
}).search(inputValue);

return list.map((element) => element.item).includes(option.label);
} else {
return true;
}
}
: null;

const {
field: { onChange: onChangeUpdateValue, value, ...fieldRest },
fieldState: { error },
Expand Down Expand Up @@ -90,6 +114,7 @@ export const PlanSelect: React.FC<PlanSelectProps> = ({
value={selectedOption}
isSearchable={isSearchable}
defaultValue={noValueOption}
filterOption={filterOptions}
{...fieldRest}
/>
{helperText && <FormHelperText>{helperText}</FormHelperText>}
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/components/Plan/AddPlanModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export const AddPlanModal: React.FC<AddPlanModalProps> = ({
rules={{ required: "Major is required." }}
helperText='First select your catalog year. If you still cannot find your major, select "No Major" above.'
isSearchable
useFuzzySearch
/>
<PlanConcentrationsSelect
catalogYear={catalogYear}
Expand Down
9 changes: 7 additions & 2 deletions packages/frontend/utils/plan/supportedMajors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { GetSupportedMajorsResponse } from "@graduate/common";
import {
GetSupportedMajorsResponse,
majorNameComparator,
} from "@graduate/common";

export const extractSupportedMajorYears = (
supportedMajorsData?: GetSupportedMajorsResponse
Expand All @@ -12,5 +15,7 @@ export const extractSupportedMajorNames = (
if (!catalogYear) {
return [];
}
return Object.keys(supportedMajorsData?.supportedMajors[catalogYear] ?? {});
return Object.keys(
supportedMajorsData?.supportedMajors[catalogYear] ?? {}
).sort(majorNameComparator);
};
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7840,6 +7840,13 @@ __metadata:
languageName: node
linkType: hard

"fuse.js@npm:^7.0.0":
version: 7.0.0
resolution: "fuse.js@npm:7.0.0"
checksum: d15750efec1808370c0cae92ec9473aa7261c59bca1f15f1cf60039ba6f804b8f95340b5cabd83a4ef55839c1034764856e0128e443921f072aa0d8a20e4cacf
languageName: node
linkType: hard

"gauge@npm:^4.0.3":
version: 4.0.4
resolution: "gauge@npm:4.0.4"
Expand Down Expand Up @@ -8062,6 +8069,7 @@ __metadata:
eslint-plugin-monorepo-cop: ^1.0.2
eslint-plugin-no-only-tests: ^2.6.0
express: ^4.17.3
fuse.js: ^7.0.0
http-proxy-middleware: ^2.0.3
husky: ^8.0.0
nodemailer: ^6.9.1
Expand Down

0 comments on commit 6c81c22

Please sign in to comment.