From 3827d23d7a501034ec657052860b38202857ddf0 Mon Sep 17 00:00:00 2001 From: Lidavic <148764396+Lidavic@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:22:53 +0100 Subject: [PATCH] 1494 add search to admin users page (#1657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * idk man * add ability to search for users at endpoint localhost:8000/users * reverted permissions * update in usersAdminPage for search bar to search by user's name, as well as a fix to the filterDocuments function in saksdokumentadminpage * biome * biome * storybook error * biome * changes in usersadminpage and api.ts as requested in comments. also a bug fix for the search function in utils.py, so that it is possible to search by first+last name * biome * backend test fix? * ruff 🐶 --- backend/samfundet/utils.py | 4 +- .../SaksdokumentAdminPage.tsx | 2 +- .../UsersAdminPage/UsersAdminPage.module.scss | 7 ++ .../UsersAdminPage/UsersAdminPage.tsx | 104 +++++++++--------- frontend/src/api.ts | 4 +- 5 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.module.scss diff --git a/backend/samfundet/utils.py b/backend/samfundet/utils.py index 6964f4003..b70d977ce 100644 --- a/backend/samfundet/utils.py +++ b/backend/samfundet/utils.py @@ -47,7 +47,9 @@ def user_query(*, query: QueryDict, users: QuerySet[User] = None) -> QuerySet[Us users = User.objects.all() search = query.get('search', None) if search: - users = users.filter(Q(username__icontains=search) | Q(first_name__icontains=search) | Q(last_name__icontains=search)) + for name in search.split(): + users = users.filter(Q(username__icontains=name) | Q(first_name__icontains=name) | Q(last_name__icontains=name)) + return users return users diff --git a/frontend/src/PagesAdmin/SaksdokumentAdminPage/SaksdokumentAdminPage.tsx b/frontend/src/PagesAdmin/SaksdokumentAdminPage/SaksdokumentAdminPage.tsx index 5b6e23240..584109752 100644 --- a/frontend/src/PagesAdmin/SaksdokumentAdminPage/SaksdokumentAdminPage.tsx +++ b/frontend/src/PagesAdmin/SaksdokumentAdminPage/SaksdokumentAdminPage.tsx @@ -47,7 +47,7 @@ export function SaksdokumentAdminPage() { // Filter by match all keywords. return documents.filter((doc) => { for (const kw of keywords) { - if (doc.title_nb?.toLowerCase().indexOf(kw) === -1) return false; + if (doc.title_nb?.toLowerCase().indexOf(kw.toLowerCase()) === -1) return false; } return true; }); diff --git a/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.module.scss b/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.module.scss new file mode 100644 index 000000000..09df078ed --- /dev/null +++ b/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.module.scss @@ -0,0 +1,7 @@ +@import 'src/mixins'; + +@import 'src/constants'; + +.table_container { + margin-top: 1.5em; +} \ No newline at end of file diff --git a/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.tsx b/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.tsx index 7fd36c020..f2a2f6693 100644 --- a/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.tsx +++ b/frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.tsx @@ -1,36 +1,38 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'react-toastify'; +import { InputField } from '~/Components'; import { formatDate } from '~/Components/OccupiedForm/utils'; import { Table } from '~/Components/Table'; import { AdminPageLayout } from '~/PagesAdmin/AdminPageLayout/AdminPageLayout'; import { getUsers } from '~/api'; import type { UserDto } from '~/dto'; -import { useTitle } from '~/hooks'; +import { useDebounce } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { getFullName } from '~/utils'; +import styles from './UsersAdminPage.module.scss'; import { ImpersonateButton } from './components'; export function UsersAdminPage() { const { t } = useTranslation(); + const title: string = t(KEY.common_users); - const [users, setUsers] = useState(); - const [loading, setLoading] = useState(true); - const title = t(KEY.common_users); - useTitle(title); + const [searchTerm, setSearchTerm] = useState(''); + const debouncedSearchTerm = useDebounce(searchTerm, 300); - useEffect(() => { - setLoading(true); - getUsers() - .then(setUsers) - .catch((err) => { - toast.error(t(KEY.common_something_went_wrong)); - console.error(err); - }) - .finally(() => setLoading(false)); - }, [t]); + const { data: users = [] } = useQuery({ + queryKey: ['users', debouncedSearchTerm], + queryFn: () => getUsers(debouncedSearchTerm), + enabled: true, + staleTime: 1000 * 60 * 5, + refetchOnWindowFocus: false, + }); - const columns = [ + function handleSearchQuery(searchString: string) { + setSearchTerm(searchString); + } + + const userColumns = [ { content: t(KEY.common_username), sortable: true }, { content: t(KEY.common_name), sortable: true }, { content: t(KEY.common_email), sortable: true }, @@ -39,42 +41,40 @@ export function UsersAdminPage() { { content: '' }, ]; - const data = useMemo(() => { - if (!users) return []; - return users.map((u) => { - return { - cells: [ - { - content: u.username, - value: u.username, - }, - { - content: getFullName(u), - value: getFullName(u), - }, - { - content: u.email, - value: u.email, - }, - { - content: u.is_active ? t(KEY.common_yes) : '', - value: u.is_active, - }, - { - content: u.last_login ? formatDate(new Date(u.last_login)) : '', - value: u.last_login || undefined, - }, - { - content: , - }, - ], - }; - }); - }, [t, users]); + function userTableRow(user: UserDto) { + return [ + { + content: user.username, + value: user.username, + }, + { + content: getFullName(user), + value: getFullName(user), + }, + { + content: user.email, + value: user.email, + }, + { + content: user.is_active ? t(KEY.common_yes) : '', + value: user.is_active, + }, + { + content: user.last_login ? formatDate(new Date(user.last_login)) : '', + value: user.last_login || undefined, + }, + { + content: , + }, + ]; + } return ( - - + + +
+
({ cells: userTableRow(user) }))} columns={userColumns} /> + ); } diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 1a1782a80..220e124ad 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -113,8 +113,8 @@ export async function impersonateUser(userId: number): Promise { return response.status === 200; } -export async function getUsers(): Promise { - const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__users; +export async function getUsers(search?: string): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__users + (search ? `?search=${search}` : ''); const response = await axios.get(url, { withCredentials: true }); return response.data; }