Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/frontend/lib/functions/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@
return '';
}

/**
* Add URL-persisted list context for previous/next navigation on a detail page.
*/
export function getDetailUrlWithNavigation(
model: ModelType,
pk: number | string,
endpoint: string,
filters: Record<string, unknown>,
index: number,
absolute?: boolean
): string {
const detailUrl = getDetailUrl(model, pk, absolute);
if (!detailUrl || !endpoint || index < 0) return detailUrl;

const query = new URLSearchParams();
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
query.set(key, String(value));

Check warning on line 79 in src/frontend/lib/functions/Navigation.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'value' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=inventree_InvenTree&issues=AaAH4DcDRXPx87p8_ubK&open=AaAH4DcDRXPx87p8_ubK&pullRequest=12644
}
});

const separator = detailUrl.includes('?') ? '&' : '?';
return `${detailUrl}${separator}_navApi=${encodeURIComponent(endpoint)}&${
`_nav=${encodeURIComponent(query.toString())}&_navIndex=${index}`
}`;
}

/**
* Returns the API detail URL for a given model type.
*/
Expand Down
61 changes: 59 additions & 2 deletions src/frontend/src/components/nav/PageDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Group, Paper, Space, Stack, Text } from '@mantine/core';
import { ActionIcon, Group, Paper, Space, Stack, Text, Tooltip } from '@mantine/core';

import { StylishText } from '@lib/components/StylishText';
import { useInvenTreeHotkeys } from '@lib/functions/Events';
import { shortenString } from '@lib/functions/String';
import { t } from '@lingui/core/macro';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { Fragment, type ReactNode, useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { useApi } from '../../contexts/ApiContext';
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
import { useUserSettingsState } from '../../states/SettingsStates';
import PrimaryActionButton from '../buttons/PrimaryActionButton';
Expand All @@ -30,6 +33,59 @@
editEnabled?: boolean;
}

function DetailNavigation() {
const api = useApi();
const navigate = useNavigate();
const location = useLocation();
const [searchParams] = useSearchParams();
const endpoint = searchParams.get('_navApi');
const encodedFilters = searchParams.get('_nav');
const currentIndex = Number(searchParams.get('_navIndex'));
const currentPk = location.pathname.split('/').filter(Boolean).at(-1);

Check warning on line 44 in src/frontend/src/components/nav/PageDetail.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `.findLast(…)` over `.filter(…).at(-1)`.

See more on https://sonarcloud.io/project/issues?id=inventree_InvenTree&issues=AaAH4DbcRXPx87p8_ubI&open=AaAH4DbcRXPx87p8_ubI&pullRequest=12644

const query = useQuery({
enabled: !!endpoint && encodedFilters !== null && Number.isInteger(currentIndex) && currentIndex >= 0,
queryKey: ['detail-navigation', endpoint, encodedFilters, currentIndex],
queryFn: async () => {
const params = new URLSearchParams(encodedFilters ?? '');
params.set('limit', '3');
params.set('offset', String(Math.max(0, currentIndex - 1)));
const response = await api.get(endpoint!, { params });
return response.data?.results ?? response.data ?? [];
}
});

if (!Array.isArray(query.data)) return null;
const localIndex = query.data.findIndex((record: any) => String(record.pk) === String(currentPk));
if (localIndex < 0) return null;

const navigateToRecord = (record: any, offset: number) => {
const params = new URLSearchParams(searchParams);
params.set('_navIndex', String(offset));
const path = location.pathname.replace(/[^/]+\/?$/, `${record.pk}/`);

Check warning on line 65 in src/frontend/src/components/nav/PageDetail.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Simplify this regular expression to reduce its runtime, as it has super-linear performance due to backtracking.

See more on https://sonarcloud.io/project/issues?id=inventree_InvenTree&issues=AaAH4DbcRXPx87p8_ubJ&open=AaAH4DbcRXPx87p8_ubJ&pullRequest=12644
navigate(`${path}?${params.toString()}`);
};
const previous = localIndex > 0 ? query.data[localIndex - 1] : undefined;
const next = localIndex + 1 < query.data.length ? query.data[localIndex + 1] : undefined;

return (
<Group gap={2} wrap='nowrap'>
<Tooltip label={t`Previous`}>
<ActionIcon aria-label={t`Previous`} variant='subtle' disabled={!previous}
onClick={() => previous && navigateToRecord(previous, currentIndex - 1)}>
<IconChevronLeft size={18} />
</ActionIcon>
</Tooltip>
<Tooltip label={t`Next`}>
<ActionIcon aria-label={t`Next`} variant='subtle' disabled={!next}
onClick={() => next && navigateToRecord(next, currentIndex + 1)}>
<IconChevronRight size={18} />
</ActionIcon>
</Tooltip>
</Group>
);
}

/**
* Construct a "standard" page detail for common display between pages.
*
Expand Down Expand Up @@ -184,6 +240,7 @@
</Group>
)}
</Group>
<DetailNavigation />
{computedActions && (
<Group gap={5} justify='right' wrap='nowrap' align='flex-start'>
{computedActions.map((action, idx) => (
Expand Down
25 changes: 20 additions & 5 deletions src/frontend/src/components/tables/InvenTreeTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import { resolveItem } from '@lib/functions/Conversion';
import { cancelEvent } from '@lib/functions/Events';
import { mapFields } from '@lib/functions/Forms';
import { eventModified, getDetailUrl } from '@lib/functions/Navigation';
import {
eventModified,
getDetailUrlWithNavigation
} from '@lib/functions/Navigation';

Check warning on line 11 in src/frontend/src/components/tables/InvenTreeTable.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'@lib/functions/Navigation' imported multiple times.

See more on https://sonarcloud.io/project/issues?id=inventree_InvenTree&issues=AaAH4DYgRXPx87p8_ubH&open=AaAH4DYgRXPx87p8_ubH&pullRequest=12644
import { navigateToLink } from '@lib/functions/Navigation';
import { useStoredTableState } from '@lib/states/StoredTableState';
import type { TableFilter } from '@lib/types/Filters';
Expand Down Expand Up @@ -748,10 +751,16 @@
if (pk) {
cancelEvent(event);
// If a model type is provided, navigate to the detail view for that model
const url = getDetailUrl(tableProps.modelType, pk);
const detailUrl = getDetailUrlWithNavigation(
tableProps.modelType,
pk,
url ?? '',
getTableFilters(false),
(tableState.page - 1) * pageSize + index
);

if (!showPreviewPanel || eventModified(event as any)) {
navigateToLink(url, navigate, event);
navigateToLink(detailUrl, navigate, event);
} else {
showRowPreview(pk);
}
Expand Down Expand Up @@ -802,7 +811,13 @@
// Add action to navigate to the detail view
const accessor = props.modelField ?? 'pk';
const pk = resolveItem(record, accessor);
const url = getDetailUrl(props.modelType, pk);
const detailUrl = getDetailUrlWithNavigation(
props.modelType,
pk,
url ?? '',
getTableFilters(false),
(tableState.page - 1) * pageSize + (tableState.records as any[]).indexOf(record)
);

const model: string | undefined =
ModelInformationDict[props.modelType]?.label?.();
Expand All @@ -820,7 +835,7 @@
onClick: (event: any) => {
cancelEvent(event);
if (!showPreviewPanel || eventModified(event as any)) {
navigateToLink(url, navigate, event);
navigateToLink(detailUrl, navigate, event);
} else {
showRowPreview(pk);
}
Expand Down