diff --git a/src/components/accessories/table/Table.tsx b/src/components/accessories/table/Table.tsx index 324886ace..d05f97b5d 100644 --- a/src/components/accessories/table/Table.tsx +++ b/src/components/accessories/table/Table.tsx @@ -20,14 +20,8 @@ import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; -import moment from "moment"; -import React, { - FunctionComponent, - useCallback, - useEffect, - useMemo, - useState, -} from "react"; +import { filterData } from "libraries/tableUtils"; +import React, { FunctionComponent, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import warningIcon from "../../../assets/warning-icon.png"; import { @@ -255,95 +249,19 @@ const Table: FunctionComponent = ({ setExpanded(!expanded); }; - const removeRowWhere = useCallback( - ( - values: Record[], - predicate: (row: Record) => boolean - ) => { - return values.filter((entry) => { - const row = (rawData ?? rowData).find( - (item) => item[rowKey ?? ""] === entry[rowKey ?? ""] - ); - return !!row ? !predicate(row) : false; - }); - }, - [rawData, rowData, rowKey] + const filteredData = useMemo( + () => + filterData( + rawData, + rowData, + rowKey, + filterColumns, + filters, + manualFilter + ), + [filterColumns, filters, manualFilter, rowData] ); - const filteredData = useMemo(() => { - if ((filterColumns?.length ?? 0) === 0 || manualFilter) { - return rowData; - } - let result = rowData; - filterColumns.forEach((field) => { - const filter = filters[field.key]; - if (filter) { - switch (field.type) { - case "boolean": - result = removeRowWhere(result, (row) => - filter.value === undefined - ? false - : (row[field.key] ?? false) !== filter.value - ); - break; - case "number": - result = removeRowWhere( - result, - (row) => - (filter.value === undefined - ? false - : row[field.key] !== filter.value) || - (filter.min === undefined - ? false - : row[field.key] < filter.min) || - (filter.max === undefined ? false : row[field.key] > filter.max) - ); - break; - case "text": - result = removeRowWhere(result, (row) => - filter.value === undefined - ? false - : !row[field.key] - ?.toString() - .toLowerCase() - .includes(filter.value.toString().toLowerCase()) - ); - break; - - case "select": - result = removeRowWhere(result, (row) => - filter.value === undefined - ? false - : row[field.key] !== filter.value - ); - break; - - default: - result = removeRowWhere( - result, - (row) => - (filter.value === undefined - ? false - : !moment(row[field.key]).isSame( - moment(filter.value as string) - )) || - (filter.min === undefined - ? false - : moment(row[field.key]).isBefore( - moment(filter.min as string) - )) || - (filter.max === undefined - ? false - : moment(row[field.key]).isAfter( - moment(filter.max as string) - )) - ); - } - } - }); - return result; - }, [filterColumns, filters, manualFilter, removeRowWhere, rowData]); - useEffect(() => { if (onFilterChange && !manualFilter) { onFilterChange(filters); diff --git a/src/libraries/tableUtils/filterUtils.test.ts b/src/libraries/tableUtils/filterUtils.test.ts new file mode 100644 index 000000000..9dd157ff9 --- /dev/null +++ b/src/libraries/tableUtils/filterUtils.test.ts @@ -0,0 +1,333 @@ +import { TFilterField } from "components/accessories/table/filter/types"; +import { filterData } from "./filterUtils"; + +const people = [ + { + id: 1, + firstName: "John", + lastName: "Doe", + sex: "M", + age: 29, + email: "john.doe@example.com", + }, + { + id: 2, + firstName: "Jane", + lastName: "Smith", + sex: "F", + age: 34, + email: "jane.smith@example.com", + }, + { + id: 3, + firstName: "Michael", + lastName: "Johnson", + sex: "M", + age: 40, + email: "michael.johnson@example.com", + }, + { + id: 4, + firstName: "Emily", + lastName: "Davis", + sex: "F", + age: 27, + email: "emily.davis@example.com", + }, + { + id: 5, + firstName: "Robert", + lastName: "Brown", + sex: "M", + age: 22, + email: "robert.brown@example.com", + }, + { + id: 6, + firstName: "Linda", + lastName: "Williams", + sex: "F", + age: 48, + email: "linda.williams@example.com", + }, + { + id: 7, + firstName: "James", + lastName: "Jones", + sex: "M", + age: 36, + email: "james.jones@example.com", + }, + { + id: 8, + firstName: "Patricia", + lastName: "Garcia", + sex: "F", + age: 30, + email: "patricia.garcia@example.com", + }, + { + id: 9, + firstName: "David", + lastName: "Martinez", + sex: "M", + age: 31, + email: "david.martinez@example.com", + }, + { + id: 10, + firstName: "Mary", + lastName: "Rodriguez", + sex: "F", + age: 26, + email: "mary.rodriguez@example.com", + }, + { + id: 11, + firstName: "Christopher", + lastName: "Miller", + sex: "M", + age: 50, + email: "christopher.miller@example.com", + }, + { + id: 12, + firstName: "Barbara", + lastName: "Wilson", + sex: "F", + age: 38, + email: "barbara.wilson@example.com", + }, + { + id: 13, + firstName: "Daniel", + lastName: "Anderson", + sex: "M", + age: 24, + email: "daniel.anderson@example.com", + }, + { + id: 14, + firstName: "Jennifer", + lastName: "Taylor", + sex: "F", + age: 45, + email: "jennifer.taylor@example.com", + }, + { + id: 15, + firstName: "Matthew", + lastName: "Thomas", + sex: "M", + age: 32, + email: "matthew.thomas@example.com", + }, + { + id: 16, + firstName: "Susan", + lastName: "Moore", + sex: "F", + age: 37, + email: "susan.moore@example.com", + }, + { + id: 17, + firstName: "Joseph", + lastName: "Jackson", + sex: "M", + age: 43, + email: "joseph.jackson@example.com", + }, + { + id: 18, + firstName: "Karen", + lastName: "White", + sex: "F", + age: 28, + email: "karen.white@example.com", + }, + { + id: 19, + firstName: "Mark", + lastName: "Harris", + sex: "M", + age: 52, + email: "mark.harris@example.com", + }, + { + id: 20, + firstName: "Nancy", + lastName: "Martin", + sex: "F", + age: 33, + email: "nancy.martin@example.com", + }, +]; + +const filterColumns: TFilterField[] = [ + { key: "firstName", label: "Firstname", type: "text" }, + { key: "age", label: "Age", type: "number" }, + { + key: "sex", + label: "Sex", + type: "select", + options: [ + { label: "Male", value: "M" }, + { label: "Female", value: "F" }, + ], + }, +]; + +describe("Filter people", () => { + it("Should ignore internal filter when manual filter enabled", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + age: { value: 33 }, + }, + true + ); + + expect(data.length).toBe(people.length); + }); + it("Should filter people with age equal to 33", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + age: { value: 33 }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => person.age === 33).length + ); + }); + it("Should filter people with age less or equal to 30", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + age: { max: 30 }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => person.age <= 30).length + ); + }); + + it("Should filter people with age greater or equal to 30", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + age: { min: 30 }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => person.age >= 30).length + ); + }); + + it("Should filter people with age greater or equal to 30 and firstname include 'a' ignoring case", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + age: { min: 30 }, + firstName: { value: "a" }, + }, + false + ); + + expect(data.length).toBe( + people.filter( + (person) => + person.age >= 30 && person.firstName.toLowerCase().includes("a") + ).length + ); + }); + + it("Should filter people where firstname is 'Nancy' ignoring case", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + firstName: { value: "Nancy" }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => + person.firstName.toLowerCase().includes("nancy") + ).length + ); + }); + it("Should filter people where firstname contanins 'm' ignoring case", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + firstName: { value: "m" }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => person.firstName.toLowerCase().includes("m")) + .length + ); + }); + + it("Should filter female people", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + sex: { value: "F" }, + }, + false + ); + + expect(data.length).toBe( + people.filter((person) => person.sex === "F").length + ); + }); + it("Should not found any person", () => { + const data = filterData( + people, + people, + "id", + filterColumns, + { + sex: { value: "D" }, + }, + false + ); + + expect(data.length).toBe(0); + }); +}); diff --git a/src/libraries/tableUtils/filterUtils.ts b/src/libraries/tableUtils/filterUtils.ts new file mode 100644 index 000000000..e894697b6 --- /dev/null +++ b/src/libraries/tableUtils/filterUtils.ts @@ -0,0 +1,103 @@ +import { + TFilterField, + TFilterValues, +} from "components/accessories/table/filter/types"; +import moment from "moment"; + +const removeRowWhere = ( + rawData: Record[] | undefined, + rowData: Record[], + rowKey: string, + values: Record[], + predicate: (row: Record) => boolean +) => { + return values.filter((entry) => { + const row = (rawData ?? rowData).find( + (item) => item[rowKey ?? ""] === entry[rowKey ?? ""] + ); + return !!row ? !predicate(row) : false; + }); +}; + +export const filterData = ( + rawData: Record[] | undefined, + rowData: Record[], + rowKey: string, + filterColumns: TFilterField[], + filters: Record, + manualFilter: boolean +) => { + if ((filterColumns?.length ?? 0) === 0 || manualFilter) { + return rowData; + } + let result = rowData; + filterColumns.forEach((field) => { + const filter = filters[field.key]; + if (filter) { + switch (field.type) { + case "boolean": + result = removeRowWhere(rawData, rowData, rowKey, result, (row) => + filter.value === undefined + ? false + : (row[field.key] ?? false) !== filter.value + ); + break; + case "number": + result = removeRowWhere( + rawData, + rowData, + rowKey, + result, + (row) => + (filter.value === undefined + ? false + : row[field.key] !== filter.value) || + (filter.min === undefined + ? false + : row[field.key] < filter.min) || + (filter.max === undefined ? false : row[field.key] > filter.max) + ); + break; + case "text": + result = removeRowWhere(rawData, rowData, rowKey, result, (row) => + filter.value === undefined + ? false + : !row[field.key] + ?.toString() + .toLowerCase() + .includes(filter.value.toString().toLowerCase()) + ); + break; + + case "select": + result = removeRowWhere(rawData, rowData, rowKey, result, (row) => + filter.value === undefined ? false : row[field.key] !== filter.value + ); + break; + + default: + result = removeRowWhere( + rawData, + rowData, + rowKey, + result, + (row) => + (filter.value === undefined + ? false + : !moment(row[field.key]).isSame( + moment(filter.value as string) + )) || + (filter.min === undefined + ? false + : moment(row[field.key]).isBefore( + moment(filter.min as string) + )) || + (filter.max === undefined + ? false + : moment(row[field.key]).isAfter(moment(filter.max as string))) + ); + } + } + }); + return result; +}; diff --git a/src/libraries/tableUtils/index.ts b/src/libraries/tableUtils/index.ts new file mode 100644 index 000000000..abe760393 --- /dev/null +++ b/src/libraries/tableUtils/index.ts @@ -0,0 +1 @@ +export * from "./filterUtils";