Skip to content

Commit

Permalink
1494 add search to admin users page (#1657)
Browse files Browse the repository at this point in the history
* 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 🐶
  • Loading branch information
Lidavic authored Feb 6, 2025
1 parent 39f65fc commit 3827d23
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 56 deletions.
4 changes: 3 additions & 1 deletion backend/samfundet/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import 'src/mixins';

@import 'src/constants';

.table_container {
margin-top: 1.5em;
}
104 changes: 52 additions & 52 deletions frontend/src/PagesAdmin/UsersAdminPage/UsersAdminPage.tsx
Original file line number Diff line number Diff line change
@@ -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<UserDto[]>();
const [loading, setLoading] = useState(true);
const title = t(KEY.common_users);
useTitle(title);
const [searchTerm, setSearchTerm] = useState<string>('');
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 },
Expand All @@ -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: <ImpersonateButton userId={u.id} />,
},
],
};
});
}, [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: <ImpersonateButton userId={user.id} />,
},
];
}

return (
<AdminPageLayout title={title} loading={loading}>
<Table data={data} columns={columns} />
<AdminPageLayout title={title}>
<InputField icon="mdi:search" onChange={handleSearchQuery} />
<div className={styles.table_container}>
<Table data={users.map((user) => ({ cells: userTableRow(user) }))} columns={userColumns} />
</div>
</AdminPageLayout>
);
}
4 changes: 2 additions & 2 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export async function impersonateUser(userId: number): Promise<boolean> {
return response.status === 200;
}

export async function getUsers(): Promise<UserDto[]> {
const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__users;
export async function getUsers(search?: string): Promise<UserDto[]> {
const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__users + (search ? `?search=${search}` : '');
const response = await axios.get<UserDto[]>(url, { withCredentials: true });
return response.data;
}
Expand Down

0 comments on commit 3827d23

Please sign in to comment.