Skip to content

Commit

Permalink
#554 - feat: add frontend support for filtering destruction lists o…
Browse files Browse the repository at this point in the history
…n name, status, author, reviewer and assignee
  • Loading branch information
svenvandescheur committed Dec 20, 2024
1 parent ec9d877 commit eec7128
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 39 deletions.
18 changes: 17 additions & 1 deletion frontend/.storybook/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
} from "../src/fixtures/destructionList";
import { FIXTURE_DESTRUCTION_LIST_ITEM } from "../src/fixtures/destructionListItem";
import { FIXTURE_SELECTIELIJSTKLASSE_CHOICES } from "../src/fixtures/selectieLijstKlasseChoices";
import { userFactory, usersFactory } from "../src/fixtures/user";
import {
recordManagerFactory,
userFactory,
usersFactory,
} from "../src/fixtures/user";
import { zaaktypeChoicesFactory } from "../src/fixtures/zaaktypeChoices";

export const MOCKS = {
Expand Down Expand Up @@ -56,6 +60,18 @@ export const MOCKS = {
loginUrl: "http://www.example.com",
},
},
USERS: {
url: "http://localhost:8000/api/v1/users/",
method: "GET",
status: 200,
response: usersFactory(),
},
RECORD_MANAGERS: {
url: "http://localhost:8000/api/v1/record-managers/",
method: "GET",
status: 200,
response: [recordManagerFactory()],
},
REVIEWERS: {
url: "http://localhost:8000/api/v1/reviewers/",
method: "GET",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export * from "./useFilter";
export * from "./useLatestReviewResponse";
export * from "./usePage";
export * from "./usePoll";
export * from "./useRecordManagers";
export * from "./useReviewers";
export * from "./useSelectielijstKlasseChoices";
export * from "./useSort";
export * from "./useSubmitAction";
export * from "./useUsers";
export * from "./useWhoAmI";
export * from "./useZaakReviewStatusBadges";
export * from "./useZaakReviewStatuses";
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/hooks/useRecordManagers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from "react";

import { User } from "../lib/api/auth";
import { listRecordManagers } from "../lib/api/recordManagers";
import { useAlertOnError } from "./useAlertOnError";

/**
* Hook resolving recordManagers
*/
export function useRecordManagers(): User[] {
const alertOnError = useAlertOnError(
"Er is een fout opgetreden bij het ophalen van record managers!",
);

const [recordManagersState, setRecordManagersState] = useState<User[]>([]);
useEffect(() => {
listRecordManagers()
.then((r) => setRecordManagersState(r))
.catch(alertOnError);
}, []);

return recordManagersState;
}
23 changes: 23 additions & 0 deletions frontend/src/hooks/useUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from "react";

import { User } from "../lib/api/auth";
import { listUsers } from "../lib/api/users";
import { useAlertOnError } from "./useAlertOnError";

/**
* Hook resolving users
*/
export function useUsers(): User[] {
const alertOnError = useAlertOnError(
"Er is een fout opgetreden bij het ophalen van gebruikers!",
);

const [usersState, setUsersState] = useState<User[]>([]);
useEffect(() => {
listUsers()
.then((r) => setUsersState(r))
.catch(alertOnError);
}, []);

return usersState;
}
11 changes: 10 additions & 1 deletion frontend/src/lib/api/destructionLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,16 @@ export async function getDestructionList(uuid: string) {
* List destruction lists.
*/
export async function listDestructionLists(
params?: URLSearchParams | { ordering?: string },
params?:
| URLSearchParams
| {
name: string;
status: DestructionListStatus;
author: number;
reviewer: number;
assignee: number;
ordering?: string;
},
) {
const response = await request("GET", "/destruction-lists/", params);
const promise: Promise<DestructionList[]> = response.json();
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/lib/api/recordManagers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { cacheMemo } from "../cache/cache";
import { User } from "./auth";
import { request } from "./request";

/**
* List all the users that have the permission to review destruction lists.
*/
export async function listRecordManagers() {
return cacheMemo("listRecordManagers", async () => {
const response = await request("GET", "/record-managers/");
const promise: Promise<User[]> = response.json();
return promise;
});
}
5 changes: 0 additions & 5 deletions frontend/src/lib/api/reviewers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { cacheMemo } from "../cache/cache";
import { User } from "./auth";
import { request } from "./request";

export type Assignee = {
user: User;
order: number;
};

/**
* List all the users that have the permission to review destruction lists.
*/
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/lib/api/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { cacheMemo } from "../cache/cache";
import { User } from "./auth";
import { request } from "./request";

/**
* List all the users that have the permission to review destruction lists.
*/
export async function listUsers() {
return cacheMemo("listUsers", async () => {
const response = await request("GET", "/users/");
const promise: Promise<User[]> = response.json();
return promise;
});
}
14 changes: 7 additions & 7 deletions frontend/src/pages/landing/Landing.loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ export interface LandingContext {
export const landingLoader = loginRequired(
async ({ request }): Promise<LandingContext> => {
const url = new URL(request.url);
const queryParams = url.searchParams;
const orderQuery = queryParams.get("ordering");
const statusMap = await getStatusMap(orderQuery);
const urlSearchParams = url.searchParams;
const statusMap = await getStatusMap(urlSearchParams);
const user = await whoAmI();

return {
Expand All @@ -28,10 +27,11 @@ export const landingLoader = loginRequired(
},
);

export const getStatusMap = async (orderQuery: string | null) => {
const lists = await listDestructionLists({
ordering: orderQuery ?? "-created",
});
export const getStatusMap = async (urlSearchParams: URLSearchParams) => {
if (!urlSearchParams.has("ordering")) {
urlSearchParams.set("ordering", "-created");
}
const lists = await listDestructionLists(urlSearchParams);
return STATUSES.reduce((acc, val) => {
const status = val[0] || "";
const destructionLists = lists.filter(
Expand Down
144 changes: 119 additions & 25 deletions frontend/src/pages/landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@ import {
P,
Solid,
Tooltip,
string2Title,
} from "@maykin-ui/admin-ui";
import { useLoaderData, useNavigate, useRevalidator } from "react-router-dom";
import {
useLoaderData,
useNavigate,
useRevalidator,
useSearchParams,
} from "react-router-dom";

import { ProcessingStatusBadge } from "../../components/ProcessingStatusBadge";
import {
useCoReviewers,
useCombinedSearchParams,
useRecordManagers,
useReviewers,
useUsers,
} from "../../hooks";
import { usePoll } from "../../hooks/usePoll";
import { User } from "../../lib/api/auth";
import { DestructionList } from "../../lib/api/destructionLists";
import {
DESTRUCTION_LIST_STATUSES,
DestructionList,
} from "../../lib/api/destructionLists";
import { ProcessingStatus } from "../../lib/api/processingStatus";
import {
canCoReviewDestructionList,
Expand Down Expand Up @@ -90,12 +106,13 @@ export const Landing = () => {
const { statusMap, user } = useLoaderData() as LandingContext;
const navigate = useNavigate();
const revalidator = useRevalidator();
const [searchParams, setSearchParams] = useCombinedSearchParams();
const recordManagers = useRecordManagers();
const reviewers = useReviewers();
const users = useUsers();

usePoll(async () => {
const orderQuery = new URLSearchParams(window.location.search).get(
"ordering",
);
const _statusMap = await getStatusMap(orderQuery);
const _statusMap = await getStatusMap(searchParams);
const equal = JSON.stringify(_statusMap) === JSON.stringify(statusMap);
if (!equal) {
revalidator.revalidate();
Expand Down Expand Up @@ -199,22 +216,15 @@ export const Landing = () => {
}),
);

const sortOptions = [
{ label: "Nieuwste eerst", value: "-created" },
{ label: "Oudste eerst", value: "created" },
];

const selectedSort =
new URLSearchParams(window.location.search).get("ordering") || "-created";

const sortedOptions = sortOptions.map((option) => ({
...option,
selected: option.value === selectedSort,
}));

const onChangeSort = (event: React.ChangeEvent<HTMLSelectElement>) => {
// update the query string
navigate(`?ordering=${event.target.value}`);
/**
* Updates the search params when the user changes a filter/order input.
* @param target
*/
const handleFilter = ({
target,
}: React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent) => {
const { name, value } = target as HTMLInputElement;
setSearchParams({ ...searchParams, [name]: value });
};

return (
Expand All @@ -226,11 +236,95 @@ export const Landing = () => {
toolbarProps: {
items: [
{
icon: <Outline.MagnifyingGlassIcon />,
name: "name",
placeholder: "Zoeken op…",
title: "Zoeken",
type: "search",
value: searchParams.get("name") || "",
onBlur: handleFilter,
onKeyUp: (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
handleFilter(e);
}
},
},
{
icon: <Outline.DocumentIcon />,
name: "status",
options: DESTRUCTION_LIST_STATUSES.map((status) => ({
label: string2Title(STATUS_MAPPING[status]),
value: status,
})),
placeholder: "Status…",
required: false,
title: "Status",
value: searchParams.get("status") || "",
onChange: handleFilter,
},
{
icon: <Outline.DocumentPlusIcon />,
name: "author",
options: [
...recordManagers.map((rm) => {
return {
label: formatUser(rm, { showUsername: false }),
value: rm.pk,
};
}),
],
placeholder: "Auteur…",
required: false,
title: "Auteur",
value: searchParams.get("author") || "",
onChange: handleFilter,
},
{
icon: <Outline.DocumentArrowUpIcon />,
name: "reviewer",
options: [
...reviewers.map((rm) => {
return {
label: formatUser(rm, { showUsername: false }),
value: rm.pk,
};
}),
],
placeholder: "Beoordelaar…",
required: false,
title: "Beoordelaar",
value: searchParams.get("reviewer") || "",
onChange: handleFilter,
},
{
icon: <Outline.UserIcon />,
name: "assignee",
options: [
...users.map((rm) => {
return {
label: formatUser(rm, { showUsername: false }),
value: rm.pk,
};
}),
],
placeholder: "Toegewezen aan…",
required: false,
title: "Toegewezen aan",
value: searchParams.get("assignee") || "",
onChange: handleFilter,
},
{
icon: <Outline.ChevronUpDownIcon />,
direction: "horizontal",
label: "Sorteren",
name: "ordering",
options: [
{ label: "Nieuwste eerst", value: "-created" },
{ label: "Oudste eerst", value: "created" },
],
required: true,
options: sortedOptions,
onChange: onChangeSort,
title: "Sorteren",
value: searchParams.get("ordering") || "-created",
onChange: handleFilter,
},
"spacer",
{
Expand Down

0 comments on commit eec7128

Please sign in to comment.