2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-10 17:28:50 +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:
Oliver 2024-10-14 13:10:53 +11:00 committed by GitHub
parent 7d3eb433d6
commit eeab8d30fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70 additions and 13 deletions

View File

@ -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`,

View File

@ -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
}}
/>
); );
} }

View File

@ -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 });
} }
}; };

View File

@ -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 (

View File

@ -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);
}); });
} }
}, []); }, []);

View File

@ -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"