From eeab8d30fb330b36d8ef6bc3727c5d515d1e90b8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 14 Oct 2024 13:10:53 +1100 Subject: [PATCH] [PUI] Table query params (#8279) * Pass more information through in redirect after login - Include query parameters in redirect * InvenTreeTable: Update filters based on URL query parameters * Add button to remove custom URL query filters --- .../components/forms/AuthenticationForm.tsx | 9 +++-- src/frontend/src/components/nav/Layout.tsx | 9 ++++- src/frontend/src/functions/auth.tsx | 19 ++++++++--- src/frontend/src/pages/Auth/Logged-In.tsx | 2 +- src/frontend/src/pages/Auth/Login.tsx | 10 ++++-- src/frontend/src/tables/InvenTreeTable.tsx | 34 ++++++++++++++++++- 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 5f61a60eb02b..37f7e8b5ae72 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -17,7 +17,11 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { api } from '../../App'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { doBasicLogin, doSimpleLogin } from '../../functions/auth'; +import { + doBasicLogin, + doSimpleLogin, + followRedirect +} from '../../functions/auth'; import { showLoginNotification } from '../../functions/notifications'; import { apiUrl, useServerApiState } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -51,8 +55,7 @@ export function AuthenticationForm() { title: t`Login successful`, message: t`Logged in successfully` }); - - navigate(location?.state?.redirectFrom ?? '/home'); + followRedirect(navigate, location?.state); } else { showLoginNotification({ title: t`Login failed`, diff --git a/src/frontend/src/components/nav/Layout.tsx b/src/frontend/src/components/nav/Layout.tsx index b201c7d66913..236e3dd63b72 100644 --- a/src/frontend/src/components/nav/Layout.tsx +++ b/src/frontend/src/components/nav/Layout.tsx @@ -18,7 +18,14 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => { if (!isLoggedIn()) { return ( - + ); } diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 8325bfc8b216..a05b0437555f 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -1,7 +1,7 @@ import { t } from '@lingui/macro'; import { notifications } from '@mantine/notifications'; import axios from 'axios'; -import { NavigateFunction } from 'react-router-dom'; +import { Navigate, NavigateFunction } from 'react-router-dom'; import { api, setApiDefaults } from '../App'; import { ApiEndpoints } from '../enums/ApiEndpoints'; @@ -11,6 +11,17 @@ import { useUserState } from '../states/UserState'; import { fetchGlobalStates } from '../states/states'; import { showLoginNotification } from './notifications'; +export function followRedirect(navigate: NavigateFunction, redirect: any) { + let url = redirect?.redirectUrl ?? '/home'; + + if (redirect?.queryParams) { + // Construct and appand query parameters + url = url + '?' + new URLSearchParams(redirect.queryParams).toString(); + } + + navigate(url); +} + /** * sends a request to the specified url from a form. this will change the window location. * @param {string} path the path to send the post request to @@ -177,7 +188,7 @@ export function handleReset(navigate: any, values: { email: string }) { */ export const checkLoginState = async ( navigate: any, - redirect?: string, + redirect?: any, no_redirect?: boolean ) => { setApiDefaults(); @@ -197,13 +208,13 @@ export const checkLoginState = async ( fetchGlobalStates(); - navigate(redirect ?? '/home'); + followRedirect(navigate, redirect); }; // Callback function when login fails const loginFailure = () => { if (!no_redirect) { - navigate('/login', { state: { redirectFrom: redirect } }); + navigate('/login', { state: redirect }); } }; diff --git a/src/frontend/src/pages/Auth/Logged-In.tsx b/src/frontend/src/pages/Auth/Logged-In.tsx index c4939dd30d13..6aec17440090 100644 --- a/src/frontend/src/pages/Auth/Logged-In.tsx +++ b/src/frontend/src/pages/Auth/Logged-In.tsx @@ -10,7 +10,7 @@ export default function Logged_In() { const location = useLocation(); useEffect(() => { - checkLoginState(navigate, location?.state?.redirectFrom); + checkLoginState(navigate, location?.state); }, [navigate]); return ( diff --git a/src/frontend/src/pages/Auth/Login.tsx b/src/frontend/src/pages/Auth/Login.tsx index a27687dfe846..213963a34cb1 100644 --- a/src/frontend/src/pages/Auth/Login.tsx +++ b/src/frontend/src/pages/Auth/Login.tsx @@ -13,7 +13,11 @@ import { } from '../../components/forms/AuthenticationForm'; import { InstanceOptions } from '../../components/forms/InstanceOptions'; import { defaultHostKey } from '../../defaults/defaultHostList'; -import { checkLoginState, doBasicLogin } from '../../functions/auth'; +import { + checkLoginState, + doBasicLogin, + followRedirect +} from '../../functions/auth'; import { useServerApiState } from '../../states/ApiState'; import { useLocalState } from '../../states/LocalState'; @@ -49,7 +53,7 @@ export default function Login() { ChangeHost(defaultHostKey); } - checkLoginState(navigate, location?.state?.redirectFrom, true); + checkLoginState(navigate, location?.state, true); // check if we got login params (login and password) if (searchParams.has('login') && searchParams.has('password')) { @@ -57,7 +61,7 @@ export default function Login() { searchParams.get('login') ?? '', searchParams.get('password') ?? '' ).then(() => { - navigate(location?.state?.redirectFrom ?? '/home'); + followRedirect(navigate, location?.state); }); } }, []); diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index 4ba9fb718ce7..5ffbc5fd33fc 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -12,6 +12,7 @@ import { import { IconBarcode, IconFilter, + IconFilterCancel, IconRefresh, IconTrash } from '@tabler/icons-react'; @@ -28,7 +29,7 @@ import React, { useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import { api } from '../App'; import { Boundary } from '../components/Boundary'; @@ -155,10 +156,14 @@ export function InvenTreeTable>({ setTableSorting, loader } = useLocalState(); + const [fieldNames, setFieldNames] = useState>({}); const navigate = useNavigate(); + // Extract URL query parameters (e.g. ?active=true&overdue=false) + const [urlQueryParams, setUrlQueryParams] = useSearchParams(); + // Construct table filters - note that we can introspect filter labels from column names const filters: TableFilter[] = useMemo(() => { return ( @@ -361,6 +366,13 @@ export function InvenTreeTable>({ ); } + // Allow override of filters based on URL query parameters + if (urlQueryParams) { + for (let [key, value] of urlQueryParams) { + queryParams[key] = value; + } + } + // Add custom search term if (tableState.searchTerm) { queryParams.search = tableState.searchTerm; @@ -522,6 +534,11 @@ export function InvenTreeTable>({ refetchOnMount: true }); + // Refetch data when the query parameters change + useEffect(() => { + refetch(); + }, [urlQueryParams]); + useEffect(() => { tableState.setIsLoading( isFetching || @@ -699,6 +716,21 @@ export function InvenTreeTable>({ onToggleColumn={toggleColumn} /> )} + {urlQueryParams.size > 0 && ( + + + { + setUrlQueryParams({}); + }} + /> + + + )} {tableProps.enableFilters && filters.length > 0 && (