From 195b2f75d48485dfbeceebc4c1088f0598b75238 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 16:59:35 -0500 Subject: [PATCH 1/7] Fix comparator case for ILIKE in getSequelizeOpForComparator --- .../resolvers/list-query-args/getSequelizeOpForComparator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/resolvers/list-query-args/getSequelizeOpForComparator.ts b/packages/server/src/resolvers/list-query-args/getSequelizeOpForComparator.ts index 5e8509336..8dd7324e7 100644 --- a/packages/server/src/resolvers/list-query-args/getSequelizeOpForComparator.ts +++ b/packages/server/src/resolvers/list-query-args/getSequelizeOpForComparator.ts @@ -28,7 +28,7 @@ export function getSequelizeOpForComparator( return negated ? Op.notLike : Op.like; } case Comparator.ILIKE: { - return negated ? Op.notLike : Op.like; + return negated ? Op.notILike : Op.iLike; } case Comparator.REGEX: { return negated ? Op.notRegexp : Op.regexp; From 489f872f4a3d128593d2373371c4ffd784e6d99b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 16:59:42 -0500 Subject: [PATCH 2/7] Add FilterSearchDropdown component --- .../elements/components/FilterDropdown.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 packages/portal/src/elements/components/FilterDropdown.tsx diff --git a/packages/portal/src/elements/components/FilterDropdown.tsx b/packages/portal/src/elements/components/FilterDropdown.tsx new file mode 100644 index 000000000..fa7780fdd --- /dev/null +++ b/packages/portal/src/elements/components/FilterDropdown.tsx @@ -0,0 +1,35 @@ +import type { StringFilterItemInterface } from "@ukdanceblue/common"; +import { StringComparator } from "@ukdanceblue/common"; +import Search from "antd/es/input/Search"; + +export function FilterSearchDropdown({ + updateFilter, + clearFilter, + field, + placeholderText = `Search ${field}`, +}: { + updateFilter: ( + field: Field, + filter: StringFilterItemInterface + ) => void; + clearFilter: (field: Field) => void; + field: Field; + placeholderText?: string | false; +}) { + return ( + { + if (value) { + updateFilter(field, { + comparison: StringComparator.ILIKE, + field, + value: `%${value}%`, + }); + } else { + clearFilter(field); + } + }} + /> + ); +} From 2612621a0f6c367adc90cbd15da294bdb3bb973b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 17:00:04 -0500 Subject: [PATCH 3/7] Add linkblue field to PersonResolverStringFilterKeys and ListPeopleArgs --- packages/common/lib/graphql-client-admin/graphql.ts | 1 + packages/server/schema.graphql | 1 + packages/server/src/resolvers/PersonResolver.ts | 9 ++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/common/lib/graphql-client-admin/graphql.ts b/packages/common/lib/graphql-client-admin/graphql.ts index 6df46e88f..2f59599ef 100644 --- a/packages/common/lib/graphql-client-admin/graphql.ts +++ b/packages/common/lib/graphql-client-admin/graphql.ts @@ -889,6 +889,7 @@ export const PersonResolverStringFilterKeys = { CommitteeRole: 'committeeRole', DbRole: 'dbRole', Email: 'email', + Linkblue: 'linkblue', Name: 'name' } as const; diff --git a/packages/server/schema.graphql b/packages/server/schema.graphql index fc7af028f..7e2475c65 100644 --- a/packages/server/schema.graphql +++ b/packages/server/schema.graphql @@ -780,6 +780,7 @@ enum PersonResolverStringFilterKeys { committeeRole dbRole email + linkblue name } diff --git a/packages/server/src/resolvers/PersonResolver.ts b/packages/server/src/resolvers/PersonResolver.ts index 43f68d9bd..ed70a297f 100644 --- a/packages/server/src/resolvers/PersonResolver.ts +++ b/packages/server/src/resolvers/PersonResolver.ts @@ -83,7 +83,14 @@ class ListPeopleArgs extends FilteredListQueryArgs("PersonResolver", { "committeeRole", "committeeName", ], - string: ["name", "email", "dbRole", "committeeRole", "committeeName"], + string: [ + "name", + "email", + "linkblue", + "dbRole", + "committeeRole", + "committeeName", + ], }) {} @InputType() class CreatePersonInput { From 2dc71cb83686f7d6e580bc2f833a09765e46cd1b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 18:57:35 -0500 Subject: [PATCH 4/7] Add useMakeSearchFilterProps hook --- .../src/hooks/useMakeSearchFilterProps.tsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/portal/src/hooks/useMakeSearchFilterProps.tsx diff --git a/packages/portal/src/hooks/useMakeSearchFilterProps.tsx b/packages/portal/src/hooks/useMakeSearchFilterProps.tsx new file mode 100644 index 000000000..a2c3f85c2 --- /dev/null +++ b/packages/portal/src/hooks/useMakeSearchFilterProps.tsx @@ -0,0 +1,39 @@ +import { SearchOutlined } from "@ant-design/icons"; +import { FilterSearchDropdown } from "@elements/components/FilterDropdown"; +import type { StringFilterItemInterface } from "@ukdanceblue/common"; +import type { InputRef } from "antd"; +import { useRef } from "react"; + +export function useMakeSearchFilterProps( + field: Field, + updateFilter: ( + field: Field, + filter: StringFilterItemInterface + ) => void, + clearFilter: (field: Field) => void, + placeholderText?: string | false +) { + const focusRef = useRef(undefined); + + return { + filterDropdown: () => ( + { + focusRef.current = inputRef; + }} + /> + ), + filterIcon: (filtered: boolean) => ( + + ), + onFilterDropdownOpenChange: () => { + setTimeout(() => { + focusRef.current?.focus(); + }, 100); + }, + }; +} From 6fea2353e0e1a1fc0a6c926e7062ff3209a0c306 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 18:57:40 -0500 Subject: [PATCH 5/7] Add inputRef prop to FilterSearchDropdown component --- packages/portal/src/elements/components/FilterDropdown.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/portal/src/elements/components/FilterDropdown.tsx b/packages/portal/src/elements/components/FilterDropdown.tsx index fa7780fdd..a4a87bd19 100644 --- a/packages/portal/src/elements/components/FilterDropdown.tsx +++ b/packages/portal/src/elements/components/FilterDropdown.tsx @@ -1,5 +1,6 @@ import type { StringFilterItemInterface } from "@ukdanceblue/common"; import { StringComparator } from "@ukdanceblue/common"; +import type { InputRef } from "antd"; import Search from "antd/es/input/Search"; export function FilterSearchDropdown({ @@ -7,6 +8,7 @@ export function FilterSearchDropdown({ clearFilter, field, placeholderText = `Search ${field}`, + inputRef, }: { updateFilter: ( field: Field, @@ -14,7 +16,8 @@ export function FilterSearchDropdown({ ) => void; clearFilter: (field: Field) => void; field: Field; - placeholderText?: string | false; + placeholderText?: string | undefined | false; + inputRef?: (ref: InputRef) => void; }) { return ( ({ clearFilter(field); } }} + ref={inputRef} /> ); } From 2b04e62938f1fecacb9ba58c221e929aeaf6f8e0 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 18:57:46 -0500 Subject: [PATCH 6/7] Refactor table components and add search filters --- .../src/elements/tables/PeopleTable.tsx | 110 +++++++++++++----- .../portal/src/elements/tables/TeamsTable.tsx | 28 +---- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/packages/portal/src/elements/tables/PeopleTable.tsx b/packages/portal/src/elements/tables/PeopleTable.tsx index f1d1c5106..0b0626f07 100644 --- a/packages/portal/src/elements/tables/PeopleTable.tsx +++ b/packages/portal/src/elements/tables/PeopleTable.tsx @@ -1,8 +1,12 @@ import { EditOutlined, EyeOutlined } from "@ant-design/icons"; import { useListQuery } from "@hooks/useListQuery"; +import { useMakeSearchFilterProps } from "@hooks/useMakeSearchFilterProps"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useNavigate } from "@tanstack/react-router"; +import type { CommitteeIdentifier } from "@ukdanceblue/common"; import { + CommitteeRole, + DbRole, SortDirection, committeeNames, stringifyDbRole, @@ -59,35 +63,36 @@ const peopleTableDocument = graphql(/* GraphQL */ ` export const PeopleTable = () => { const navigate = useNavigate(); - const { queryOptions, updatePagination, clearSorting, pushSorting } = - useListQuery( - { - initPage: 1, - initPageSize: 20, - initSorting: [], - }, - { - allFields: [ - "name", - "email", - "linkblue", - "dbRole", - "committeeRole", - "committeeName", - ], - dateFields: [], - isNullFields: [], - numericFields: [], - oneOfFields: [], - stringFields: [ - "name", - "email", - "dbRole", - "committeeRole", - "committeeName", - ], - } - ); + const { + queryOptions, + updatePagination, + clearSorting, + pushSorting, + clearFilters, + updateFilter, + clearFilter, + } = useListQuery( + { + initPage: 1, + initPageSize: 20, + initSorting: [], + }, + { + allFields: [ + "name", + "email", + "linkblue", + "dbRole", + "committeeRole", + "committeeName", + ], + dateFields: [], + isNullFields: [], + numericFields: [], + oneOfFields: ["dbRole", "committeeRole", "committeeName"], + stringFields: ["name", "email", "linkblue"], + } + ); const [{ fetching, error, data: peopleDocument }] = useQuery({ query: peopleTableDocument, @@ -109,7 +114,7 @@ export const PeopleTable = () => { { + onChange={(pagination, filters, sorter, _extra) => { updatePagination({ page: pagination.current, pageSize: pagination.pageSize, @@ -133,6 +138,36 @@ export const PeopleTable = () => { : SortDirection.DESCENDING, }); } + clearFilters(); + for (const key of Object.keys(filters)) { + const value = filters[key]; + if (!value) { + continue; + } + switch (key) { + case "dbRole": { + updateFilter("dbRole", { + field: "dbRole", + value: value.map((v) => v.toString()), + }); + break; + } + case "committeeRole": { + updateFilter("committeeRole", { + field: "committeeRole", + value: value.map((v) => v.toString()), + }); + break; + } + case "committeeName": { + updateFilter("committeeName", { + field: "committeeName", + value: value.map((v) => v.toString()), + }); + break; + } + } + } }} pagination={ peopleDocument @@ -152,18 +187,21 @@ export const PeopleTable = () => { dataIndex: "name", sorter: true, sortDirections: ["ascend", "descend"], + ...useMakeSearchFilterProps("name", updateFilter, clearFilter), }, { title: "Email", dataIndex: "email", sorter: true, sortDirections: ["ascend", "descend"], + ...useMakeSearchFilterProps("email", updateFilter, clearFilter), }, { title: "Linkblue", dataIndex: "linkblue", sorter: true, sortDirections: ["ascend", "descend"], + ...useMakeSearchFilterProps("linkblue", updateFilter, clearFilter), }, { title: "Role", @@ -173,6 +211,10 @@ export const PeopleTable = () => { }, sorter: true, sortDirections: ["ascend", "descend"], + filters: Object.values(DbRole).map((role) => ({ + text: stringifyDbRole(role), + value: role, + })), }, { title: "Committee Role", @@ -182,6 +224,10 @@ export const PeopleTable = () => { }, sorter: true, sortDirections: ["ascend", "descend"], + filters: Object.values(CommitteeRole).map((role) => ({ + text: role, + value: role, + })), }, { title: "Committee Name", @@ -193,6 +239,10 @@ export const PeopleTable = () => { }, sorter: true, sortDirections: ["ascend", "descend"], + filters: Object.keys(committeeNames).map((committeeIdentifier) => ({ + text: committeeNames[committeeIdentifier as CommitteeIdentifier], + value: committeeIdentifier, + })), }, { title: "Actions", diff --git a/packages/portal/src/elements/tables/TeamsTable.tsx b/packages/portal/src/elements/tables/TeamsTable.tsx index f2d5acf2f..221174aeb 100644 --- a/packages/portal/src/elements/tables/TeamsTable.tsx +++ b/packages/portal/src/elements/tables/TeamsTable.tsx @@ -1,14 +1,14 @@ -import { EditOutlined, EyeOutlined, SearchOutlined } from "@ant-design/icons"; +import { EditOutlined, EyeOutlined } from "@ant-design/icons"; import { useListQuery } from "@hooks/useListQuery"; +import { useMakeSearchFilterProps } from "@hooks/useMakeSearchFilterProps"; import { useQueryStatusWatcher } from "@hooks/useQueryStatusWatcher"; import { useNavigate } from "@tanstack/react-router"; -import { SortDirection, StringComparator } from "@ukdanceblue/common"; +import { SortDirection } from "@ukdanceblue/common"; import { getFragmentData, graphql, } from "@ukdanceblue/common/graphql-client-admin"; import { Button, Flex, Table } from "antd"; -import Search from "antd/es/input/Search"; import { useQuery } from "urql"; const teamsTableQueryDocument = graphql(/* GraphQL */ ` @@ -103,27 +103,7 @@ export const TeamsTable = () => { title: "Name", dataIndex: "name", sorter: true, - filterDropdown: () => ( - { - if (value) { - updateFilter("name", { - comparison: StringComparator.ILIKE, - field: "name", - value: `%${value}%`, - }); - } else { - clearFilter("name"); - } - }} - /> - ), - filterIcon: (filtered) => ( - - ), + ...useMakeSearchFilterProps("name", updateFilter, clearFilter), }, { title: "Type", From b1448b6262efad7bc289aa0d0658ff41efe851e1 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Wed, 22 Nov 2023 19:03:08 -0500 Subject: [PATCH 7/7] Update image names for app-portal and app-server --- .github/workflows/docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index fde9a56c1..6660f9ab4 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -9,8 +9,8 @@ on: env: REGISTRY: ghcr.io - PORTAL_IMAGE_NAME: app-portal - SERVER_IMAGE_NAME: app-server + PORTAL_IMAGE_NAME: ukdanceblue/app-portal + SERVER_IMAGE_NAME: ukdanceblue/app-server jobs: build-and-push-portal-image: