mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] URl / panel fix (#6078)
* Add debug messages for auth - trying to track down issues * Add explicit PartIndex page * Remove debug statements * Simplify panel state management - Do not encode in URL - Reduce number of component loads - Ensure a valid panel is always selected * Implement StockIndex page
This commit is contained in:
parent
6cb66a7a70
commit
902eafcc75
@ -15,8 +15,14 @@ export function setApiDefaults() {
|
||||
const token = useSessionState.getState().token;
|
||||
|
||||
api.defaults.baseURL = host;
|
||||
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
||||
|
||||
if (token) {
|
||||
api.defaults.headers.common['Authorization'] = `Token ${token}`;
|
||||
} else {
|
||||
api.defaults.headers.common['Authorization'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const queryClient = new QueryClient();
|
||||
|
||||
function checkMobile() {
|
||||
|
@ -10,15 +10,8 @@ import {
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarRightCollapse
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Navigate,
|
||||
Route,
|
||||
Routes,
|
||||
useNavigate,
|
||||
useParams
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { PlaceholderPanel } from '../items/Placeholder';
|
||||
@ -44,61 +37,57 @@ export type PanelProps = {
|
||||
collapsible?: boolean;
|
||||
};
|
||||
|
||||
function BasePanelGroup({
|
||||
export function PanelGroup({
|
||||
pageKey,
|
||||
panels,
|
||||
onPanelChange,
|
||||
selectedPanel,
|
||||
collapsible = true
|
||||
}: PanelProps): ReactNode {
|
||||
const navigate = useNavigate();
|
||||
const { panel } = useParams();
|
||||
const localState = useLocalState();
|
||||
|
||||
const [panel, setPanel] = useState<string>(selectedPanel ?? '');
|
||||
|
||||
// Keep a list of active panels (hidden and disabled panels are not active)
|
||||
const activePanels = useMemo(
|
||||
() => panels.filter((panel) => !panel.hidden && !panel.disabled),
|
||||
[panels]
|
||||
);
|
||||
|
||||
const setLastUsedPanel = useLocalState((state) =>
|
||||
state.setLastUsedPanel(pageKey)
|
||||
);
|
||||
|
||||
// Set selected panel when component is initially loaded, or when the selected panel changes
|
||||
useEffect(() => {
|
||||
if (panel) {
|
||||
setLastUsedPanel(panel);
|
||||
let first_panel: string = activePanels[0]?.name ?? '';
|
||||
let active_panel: string =
|
||||
useLocalState.getState().getLastUsedPanel(pageKey)() ?? '';
|
||||
|
||||
let panelName = selectedPanel || active_panel || first_panel;
|
||||
|
||||
if (panelName != panel) {
|
||||
setPanel(panelName);
|
||||
}
|
||||
// panel is intentionally no dependency as this should only run on initial render
|
||||
}, [setLastUsedPanel]);
|
||||
|
||||
if (panelName != active_panel) {
|
||||
useLocalState.getState().setLastUsedPanel(pageKey)(panelName);
|
||||
}
|
||||
}, [activePanels, panels, selectedPanel]);
|
||||
|
||||
// Callback when the active panel changes
|
||||
function handlePanelChange(panel: string) {
|
||||
if (activePanels.findIndex((p) => p.name === panel) === -1) {
|
||||
setLastUsedPanel('');
|
||||
return navigate('../');
|
||||
}
|
||||
const handlePanelChange = useCallback(
|
||||
(panelName: string) => {
|
||||
// Ensure that the panel name is valid
|
||||
if (!activePanels.some((panel) => panel.name == panelName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`../${panel}`);
|
||||
setPanel(panelName);
|
||||
localState.setLastUsedPanel(pageKey)(panelName);
|
||||
|
||||
// Optionally call external callback hook
|
||||
if (onPanelChange) {
|
||||
onPanelChange(panel);
|
||||
}
|
||||
}
|
||||
|
||||
// if the selected panel state changes update the current panel
|
||||
useEffect(() => {
|
||||
if (selectedPanel && selectedPanel !== panel) {
|
||||
handlePanelChange(selectedPanel);
|
||||
}
|
||||
}, [selectedPanel, panel]);
|
||||
|
||||
// Update the active panel when panels changes and the active is no longer available
|
||||
useEffect(() => {
|
||||
if (activePanels.findIndex((p) => p.name === panel) === -1) {
|
||||
setLastUsedPanel('');
|
||||
return navigate('../');
|
||||
}
|
||||
}, [activePanels, panel]);
|
||||
if (onPanelChange) {
|
||||
onPanelChange(panelName);
|
||||
}
|
||||
},
|
||||
[onPanelChange, pageKey]
|
||||
);
|
||||
|
||||
const [expanded, setExpanded] = useState<boolean>(true);
|
||||
|
||||
@ -169,38 +158,3 @@ function BasePanelGroup({
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
|
||||
const lastUsedPanel = useLocalState((state) => {
|
||||
const panelName =
|
||||
selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
|
||||
|
||||
if (
|
||||
panels.findIndex(
|
||||
(p) => p.name === panelName && !p.disabled && !p.hidden
|
||||
) === -1
|
||||
) {
|
||||
return panels[0]?.name;
|
||||
}
|
||||
|
||||
return panelName;
|
||||
});
|
||||
|
||||
return <Navigate to={lastUsedPanel} replace />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a panel group. The current panel will be appended to the current url.
|
||||
* The last opened panel will be stored in local storage and opened if no panel is provided via url param
|
||||
* @param panels - The list of panels to display
|
||||
* @param onPanelChange - Callback when the active panel changes
|
||||
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
||||
*/
|
||||
export function PanelGroup(props: PanelProps) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route index element={<IndexPanelComponent {...props} />} />
|
||||
<Route path="/:panel/*" element={<BasePanelGroup {...props} />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
@ -27,7 +27,9 @@ export const doClassicLogin = async (username: string, password: string) => {
|
||||
name: 'inventree-web-app'
|
||||
}
|
||||
})
|
||||
.then((response) => response.data.token)
|
||||
.then((response) => {
|
||||
return response.status == 200 ? response.data?.token : undefined;
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotification({
|
||||
title: t`Login failed`,
|
||||
@ -37,7 +39,7 @@ export const doClassicLogin = async (username: string, password: string) => {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (token === false) return token;
|
||||
if (!token) return false;
|
||||
|
||||
// log in with token
|
||||
doTokenLogin(token);
|
||||
@ -51,6 +53,7 @@ export const doClassicLogout = async () => {
|
||||
// TODO @matmair - logout from the server session
|
||||
// Set token in context
|
||||
const { setToken } = useSessionState.getState();
|
||||
|
||||
setToken(undefined);
|
||||
|
||||
notifications.show({
|
||||
|
@ -22,9 +22,11 @@ import { useInstance } from '../../hooks/UseInstance';
|
||||
*
|
||||
* Note: If no category ID is supplied, this acts as the top-level part category page
|
||||
*/
|
||||
export default function CategoryDetail({}: {}) {
|
||||
const { id } = useParams();
|
||||
|
||||
export function CategoryPage({
|
||||
categoryId
|
||||
}: {
|
||||
categoryId?: string | undefined;
|
||||
}) {
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
|
||||
const {
|
||||
@ -33,7 +35,8 @@ export default function CategoryDetail({}: {}) {
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiPaths.category_list,
|
||||
pk: id,
|
||||
pk: categoryId,
|
||||
hasPrimaryKey: true,
|
||||
params: {
|
||||
path_detail: true
|
||||
}
|
||||
@ -49,7 +52,7 @@ export default function CategoryDetail({}: {}) {
|
||||
<PartListTable
|
||||
props={{
|
||||
params: {
|
||||
category: id
|
||||
category: categoryId
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -62,7 +65,7 @@ export default function CategoryDetail({}: {}) {
|
||||
content: (
|
||||
<PartCategoryTable
|
||||
params={{
|
||||
parent: id
|
||||
parent: categoryId ?? 'null'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@ -74,7 +77,7 @@ export default function CategoryDetail({}: {}) {
|
||||
content: <PlaceholderPanel />
|
||||
}
|
||||
],
|
||||
[category, id]
|
||||
[categoryId]
|
||||
);
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
@ -110,3 +113,13 @@ export default function CategoryDetail({}: {}) {
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail page for a specific Part Category instance.
|
||||
* Uses the :id parameter in the URL to determine which category to display.admin in
|
||||
*/
|
||||
export default function CategoryDetail({}: {}) {
|
||||
const { id } = useParams();
|
||||
|
||||
return <CategoryPage categoryId={id} />;
|
||||
}
|
||||
|
5
src/frontend/src/pages/part/PartIndex.tsx
Normal file
5
src/frontend/src/pages/part/PartIndex.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { CategoryPage } from './CategoryDetail';
|
||||
|
||||
export default function PartIndex({}: {}) {
|
||||
return <CategoryPage />;
|
||||
}
|
@ -12,9 +12,11 @@ import { StockLocationTable } from '../../components/tables/stock/StockLocationT
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
|
||||
export default function Stock() {
|
||||
const { id } = useParams();
|
||||
|
||||
export function LocationPage({
|
||||
locationId
|
||||
}: {
|
||||
locationId?: string | undefined;
|
||||
}) {
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
|
||||
const {
|
||||
@ -23,7 +25,8 @@ export default function Stock() {
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiPaths.stock_location_list,
|
||||
pk: id,
|
||||
pk: locationId,
|
||||
hasPrimaryKey: true,
|
||||
params: {
|
||||
path_detail: true
|
||||
}
|
||||
@ -38,7 +41,7 @@ export default function Stock() {
|
||||
content: (
|
||||
<StockItemTable
|
||||
params={{
|
||||
location: id
|
||||
location: locationId
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@ -50,13 +53,13 @@ export default function Stock() {
|
||||
content: (
|
||||
<StockLocationTable
|
||||
params={{
|
||||
parent: id
|
||||
parent: locationId ?? 'null'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
}, [location, id]);
|
||||
}, [locationId]);
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
() => [
|
||||
@ -91,3 +94,13 @@ export default function Stock() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail page for a specific Stock Location instance
|
||||
* Uses the :id parameter in the URL to determine which location to display
|
||||
*/
|
||||
export default function LocationDetail({}: {}) {
|
||||
const { id } = useParams();
|
||||
|
||||
return <LocationPage locationId={id} />;
|
||||
}
|
||||
|
5
src/frontend/src/pages/stock/StockIndex.tsx
Normal file
5
src/frontend/src/pages/stock/StockIndex.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { LocationPage } from './LocationDetail';
|
||||
|
||||
export default function StockIndex({}: {}) {
|
||||
return <LocationPage />;
|
||||
}
|
@ -28,9 +28,12 @@ export const ManufacturerDetail = Loadable(
|
||||
lazy(() => import('./pages/company/ManufacturerDetail'))
|
||||
);
|
||||
|
||||
export const PartIndex = Loadable(lazy(() => import('./pages/part/PartIndex')));
|
||||
|
||||
export const CategoryDetail = Loadable(
|
||||
lazy(() => import('./pages/part/CategoryDetail'))
|
||||
);
|
||||
|
||||
export const PartDetail = Loadable(
|
||||
lazy(() => import('./pages/part/PartDetail'))
|
||||
);
|
||||
@ -39,6 +42,10 @@ export const LocationDetail = Loadable(
|
||||
lazy(() => import('./pages/stock/LocationDetail'))
|
||||
);
|
||||
|
||||
export const StockIndex = Loadable(
|
||||
lazy(() => import('./pages/stock/StockIndex'))
|
||||
);
|
||||
|
||||
export const StockDetail = Loadable(
|
||||
lazy(() => import('./pages/stock/StockDetail'))
|
||||
);
|
||||
@ -119,13 +126,13 @@ export const routes = (
|
||||
<Route path="user/*" element={<UserSettings />} />
|
||||
</Route>
|
||||
<Route path="part/">
|
||||
<Route index element={<Navigate to="category/" />} />
|
||||
<Route path="category/:id?/*" element={<CategoryDetail />} />
|
||||
<Route index element={<PartIndex />} />
|
||||
<Route path="category/:id/*" element={<CategoryDetail />} />
|
||||
<Route path=":id/*" element={<PartDetail />} />
|
||||
</Route>
|
||||
<Route path="stock/">
|
||||
<Route index element={<Navigate to="location/" />} />
|
||||
<Route path="location/:id?/*" element={<LocationDetail />} />
|
||||
<Route index element={<StockIndex />} />
|
||||
<Route path="location/:id/*" element={<LocationDetail />} />
|
||||
<Route path="item/:id/*" element={<StockDetail />} />
|
||||
</Route>
|
||||
<Route path="build/">
|
||||
|
@ -24,6 +24,7 @@ interface LocalStateProps {
|
||||
loader: LoaderType;
|
||||
lastUsedPanels: Record<string, string>;
|
||||
setLastUsedPanel: (panelKey: string) => (value: string) => void;
|
||||
getLastUsedPanel: (panelKey: string) => () => string | undefined;
|
||||
}
|
||||
|
||||
export const useLocalState = create<LocalStateProps>()(
|
||||
@ -55,6 +56,9 @@ export const useLocalState = create<LocalStateProps>()(
|
||||
lastUsedPanels: { ...get().lastUsedPanels, [panelKey]: value }
|
||||
});
|
||||
}
|
||||
},
|
||||
getLastUsedPanel(panelKey) {
|
||||
return () => get().lastUsedPanels[panelKey];
|
||||
}
|
||||
}),
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user