Skip to content

Commit

Permalink
Merge pull request #7 from UKDanceBlue/sort-filter-people
Browse files Browse the repository at this point in the history
Add filtering to the people table
  • Loading branch information
jthoward64 authored Nov 23, 2023
2 parents 83394ed + b1448b6 commit 8937568
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 58 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions packages/common/lib/graphql-client-admin/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ export const PersonResolverStringFilterKeys = {
CommitteeRole: 'committeeRole',
DbRole: 'dbRole',
Email: 'email',
Linkblue: 'linkblue',
Name: 'name'
} as const;

Expand Down
39 changes: 39 additions & 0 deletions packages/portal/src/elements/components/FilterDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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<Field extends string>({
updateFilter,
clearFilter,
field,
placeholderText = `Search ${field}`,
inputRef,
}: {
updateFilter: (
field: Field,
filter: StringFilterItemInterface<Field>
) => void;
clearFilter: (field: Field) => void;
field: Field;
placeholderText?: string | undefined | false;
inputRef?: (ref: InputRef) => void;
}) {
return (
<Search
placeholder={placeholderText || undefined}
onSearch={(value) => {
if (value) {
updateFilter(field, {
comparison: StringComparator.ILIKE,
field,
value: `%${value}%`,
});
} else {
clearFilter(field);
}
}}
ref={inputRef}
/>
);
}
110 changes: 80 additions & 30 deletions packages/portal/src/elements/tables/PeopleTable.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -109,7 +114,7 @@ export const PeopleTable = () => {
<Table
dataSource={listPeopleData ?? undefined}
loading={fetching}
onChange={(pagination, _filters, sorter, _extra) => {
onChange={(pagination, filters, sorter, _extra) => {
updatePagination({
page: pagination.current,
pageSize: pagination.pageSize,
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
28 changes: 4 additions & 24 deletions packages/portal/src/elements/tables/TeamsTable.tsx
Original file line number Diff line number Diff line change
@@ -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 */ `
Expand Down Expand Up @@ -103,27 +103,7 @@ export const TeamsTable = () => {
title: "Name",
dataIndex: "name",
sorter: true,
filterDropdown: () => (
<Search
placeholder="Search name"
onSearch={(value) => {
if (value) {
updateFilter("name", {
comparison: StringComparator.ILIKE,
field: "name",
value: `%${value}%`,
});
} else {
clearFilter("name");
}
}}
/>
),
filterIcon: (filtered) => (
<SearchOutlined
style={{ color: filtered ? "#1890ff" : undefined }}
/>
),
...useMakeSearchFilterProps("name", updateFilter, clearFilter),
},
{
title: "Type",
Expand Down
39 changes: 39 additions & 0 deletions packages/portal/src/hooks/useMakeSearchFilterProps.tsx
Original file line number Diff line number Diff line change
@@ -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 extends string>(
field: Field,
updateFilter: (
field: Field,
filter: StringFilterItemInterface<Field>
) => void,
clearFilter: (field: Field) => void,
placeholderText?: string | false
) {
const focusRef = useRef<InputRef | undefined>(undefined);

return {
filterDropdown: () => (
<FilterSearchDropdown
updateFilter={updateFilter}
clearFilter={clearFilter}
field={field}
placeholderText={placeholderText}
inputRef={(inputRef) => {
focusRef.current = inputRef;
}}
/>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? "#1890ff" : undefined }} />
),
onFilterDropdownOpenChange: () => {
setTimeout(() => {
focusRef.current?.focus();
}, 100);
},
};
}
1 change: 1 addition & 0 deletions packages/server/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ enum PersonResolverStringFilterKeys {
committeeRole
dbRole
email
linkblue
name
}

Expand Down
9 changes: 8 additions & 1 deletion packages/server/src/resolvers/PersonResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 8937568

Please sign in to comment.