mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	[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
This commit is contained in:
		| @@ -17,7 +17,11 @@ import { useLocation, useNavigate } from 'react-router-dom'; | |||||||
|  |  | ||||||
| import { api } from '../../App'; | import { api } from '../../App'; | ||||||
| import { ApiEndpoints } from '../../enums/ApiEndpoints'; | import { ApiEndpoints } from '../../enums/ApiEndpoints'; | ||||||
| import { doBasicLogin, doSimpleLogin } from '../../functions/auth'; | import { | ||||||
|  |   doBasicLogin, | ||||||
|  |   doSimpleLogin, | ||||||
|  |   followRedirect | ||||||
|  | } from '../../functions/auth'; | ||||||
| import { showLoginNotification } from '../../functions/notifications'; | import { showLoginNotification } from '../../functions/notifications'; | ||||||
| import { apiUrl, useServerApiState } from '../../states/ApiState'; | import { apiUrl, useServerApiState } from '../../states/ApiState'; | ||||||
| import { useUserState } from '../../states/UserState'; | import { useUserState } from '../../states/UserState'; | ||||||
| @@ -51,8 +55,7 @@ export function AuthenticationForm() { | |||||||
|             title: t`Login successful`, |             title: t`Login successful`, | ||||||
|             message: t`Logged in successfully` |             message: t`Logged in successfully` | ||||||
|           }); |           }); | ||||||
|  |           followRedirect(navigate, location?.state); | ||||||
|           navigate(location?.state?.redirectFrom ?? '/home'); |  | ||||||
|         } else { |         } else { | ||||||
|           showLoginNotification({ |           showLoginNotification({ | ||||||
|             title: t`Login failed`, |             title: t`Login failed`, | ||||||
|   | |||||||
| @@ -18,7 +18,14 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => { | |||||||
|  |  | ||||||
|   if (!isLoggedIn()) { |   if (!isLoggedIn()) { | ||||||
|     return ( |     return ( | ||||||
|       <Navigate to="/logged-in" state={{ redirectFrom: location.pathname }} /> |       <Navigate | ||||||
|  |         to="/logged-in" | ||||||
|  |         state={{ | ||||||
|  |           redirectUrl: location.pathname, | ||||||
|  |           queryParams: location.search, | ||||||
|  |           anchor: location.hash | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { t } from '@lingui/macro'; | import { t } from '@lingui/macro'; | ||||||
| import { notifications } from '@mantine/notifications'; | import { notifications } from '@mantine/notifications'; | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import { NavigateFunction } from 'react-router-dom'; | import { Navigate, NavigateFunction } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { api, setApiDefaults } from '../App'; | import { api, setApiDefaults } from '../App'; | ||||||
| import { ApiEndpoints } from '../enums/ApiEndpoints'; | import { ApiEndpoints } from '../enums/ApiEndpoints'; | ||||||
| @@ -11,6 +11,17 @@ import { useUserState } from '../states/UserState'; | |||||||
| import { fetchGlobalStates } from '../states/states'; | import { fetchGlobalStates } from '../states/states'; | ||||||
| import { showLoginNotification } from './notifications'; | 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. |  * 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 |  * @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 ( | export const checkLoginState = async ( | ||||||
|   navigate: any, |   navigate: any, | ||||||
|   redirect?: string, |   redirect?: any, | ||||||
|   no_redirect?: boolean |   no_redirect?: boolean | ||||||
| ) => { | ) => { | ||||||
|   setApiDefaults(); |   setApiDefaults(); | ||||||
| @@ -197,13 +208,13 @@ export const checkLoginState = async ( | |||||||
|  |  | ||||||
|     fetchGlobalStates(); |     fetchGlobalStates(); | ||||||
|  |  | ||||||
|     navigate(redirect ?? '/home'); |     followRedirect(navigate, redirect); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Callback function when login fails |   // Callback function when login fails | ||||||
|   const loginFailure = () => { |   const loginFailure = () => { | ||||||
|     if (!no_redirect) { |     if (!no_redirect) { | ||||||
|       navigate('/login', { state: { redirectFrom: redirect } }); |       navigate('/login', { state: redirect }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ export default function Logged_In() { | |||||||
|   const location = useLocation(); |   const location = useLocation(); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     checkLoginState(navigate, location?.state?.redirectFrom); |     checkLoginState(navigate, location?.state); | ||||||
|   }, [navigate]); |   }, [navigate]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|   | |||||||
| @@ -13,7 +13,11 @@ import { | |||||||
| } from '../../components/forms/AuthenticationForm'; | } from '../../components/forms/AuthenticationForm'; | ||||||
| import { InstanceOptions } from '../../components/forms/InstanceOptions'; | import { InstanceOptions } from '../../components/forms/InstanceOptions'; | ||||||
| import { defaultHostKey } from '../../defaults/defaultHostList'; | import { defaultHostKey } from '../../defaults/defaultHostList'; | ||||||
| import { checkLoginState, doBasicLogin } from '../../functions/auth'; | import { | ||||||
|  |   checkLoginState, | ||||||
|  |   doBasicLogin, | ||||||
|  |   followRedirect | ||||||
|  | } from '../../functions/auth'; | ||||||
| import { useServerApiState } from '../../states/ApiState'; | import { useServerApiState } from '../../states/ApiState'; | ||||||
| import { useLocalState } from '../../states/LocalState'; | import { useLocalState } from '../../states/LocalState'; | ||||||
|  |  | ||||||
| @@ -49,7 +53,7 @@ export default function Login() { | |||||||
|       ChangeHost(defaultHostKey); |       ChangeHost(defaultHostKey); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     checkLoginState(navigate, location?.state?.redirectFrom, true); |     checkLoginState(navigate, location?.state, true); | ||||||
|  |  | ||||||
|     // check if we got login params (login and password) |     // check if we got login params (login and password) | ||||||
|     if (searchParams.has('login') && searchParams.has('password')) { |     if (searchParams.has('login') && searchParams.has('password')) { | ||||||
| @@ -57,7 +61,7 @@ export default function Login() { | |||||||
|         searchParams.get('login') ?? '', |         searchParams.get('login') ?? '', | ||||||
|         searchParams.get('password') ?? '' |         searchParams.get('password') ?? '' | ||||||
|       ).then(() => { |       ).then(() => { | ||||||
|         navigate(location?.state?.redirectFrom ?? '/home'); |         followRedirect(navigate, location?.state); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }, []); |   }, []); | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import { | |||||||
| import { | import { | ||||||
|   IconBarcode, |   IconBarcode, | ||||||
|   IconFilter, |   IconFilter, | ||||||
|  |   IconFilterCancel, | ||||||
|   IconRefresh, |   IconRefresh, | ||||||
|   IconTrash |   IconTrash | ||||||
| } from '@tabler/icons-react'; | } from '@tabler/icons-react'; | ||||||
| @@ -28,7 +29,7 @@ import React, { | |||||||
|   useMemo, |   useMemo, | ||||||
|   useState |   useState | ||||||
| } from 'react'; | } from 'react'; | ||||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate, useSearchParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { api } from '../App'; | import { api } from '../App'; | ||||||
| import { Boundary } from '../components/Boundary'; | import { Boundary } from '../components/Boundary'; | ||||||
| @@ -155,10 +156,14 @@ export function InvenTreeTable<T extends Record<string, any>>({ | |||||||
|     setTableSorting, |     setTableSorting, | ||||||
|     loader |     loader | ||||||
|   } = useLocalState(); |   } = useLocalState(); | ||||||
|  |  | ||||||
|   const [fieldNames, setFieldNames] = useState<Record<string, string>>({}); |   const [fieldNames, setFieldNames] = useState<Record<string, string>>({}); | ||||||
|  |  | ||||||
|   const navigate = useNavigate(); |   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 |   // Construct table filters - note that we can introspect filter labels from column names | ||||||
|   const filters: TableFilter[] = useMemo(() => { |   const filters: TableFilter[] = useMemo(() => { | ||||||
|     return ( |     return ( | ||||||
| @@ -361,6 +366,13 @@ export function InvenTreeTable<T extends Record<string, any>>({ | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Allow override of filters based on URL query parameters | ||||||
|  |     if (urlQueryParams) { | ||||||
|  |       for (let [key, value] of urlQueryParams) { | ||||||
|  |         queryParams[key] = value; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Add custom search term |     // Add custom search term | ||||||
|     if (tableState.searchTerm) { |     if (tableState.searchTerm) { | ||||||
|       queryParams.search = tableState.searchTerm; |       queryParams.search = tableState.searchTerm; | ||||||
| @@ -522,6 +534,11 @@ export function InvenTreeTable<T extends Record<string, any>>({ | |||||||
|     refetchOnMount: true |     refetchOnMount: true | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   // Refetch data when the query parameters change | ||||||
|  |   useEffect(() => { | ||||||
|  |     refetch(); | ||||||
|  |   }, [urlQueryParams]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     tableState.setIsLoading( |     tableState.setIsLoading( | ||||||
|       isFetching || |       isFetching || | ||||||
| @@ -699,6 +716,21 @@ export function InvenTreeTable<T extends Record<string, any>>({ | |||||||
|                   onToggleColumn={toggleColumn} |                   onToggleColumn={toggleColumn} | ||||||
|                 /> |                 /> | ||||||
|               )} |               )} | ||||||
|  |               {urlQueryParams.size > 0 && ( | ||||||
|  |                 <ActionIcon | ||||||
|  |                   variant="transparent" | ||||||
|  |                   color="red" | ||||||
|  |                   aria-label="table-clear-query-filters" | ||||||
|  |                 > | ||||||
|  |                   <Tooltip label={t`Clear custom query filters`}> | ||||||
|  |                     <IconFilterCancel | ||||||
|  |                       onClick={() => { | ||||||
|  |                         setUrlQueryParams({}); | ||||||
|  |                       }} | ||||||
|  |                     /> | ||||||
|  |                   </Tooltip> | ||||||
|  |                 </ActionIcon> | ||||||
|  |               )} | ||||||
|               {tableProps.enableFilters && filters.length > 0 && ( |               {tableProps.enableFilters && filters.length > 0 && ( | ||||||
|                 <Indicator |                 <Indicator | ||||||
|                   size="xs" |                   size="xs" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user