[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:
Oliver 2023-12-13 21:37:41 +11:00 committed by GitHub
parent 6cb66a7a70
commit 902eafcc75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 101 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
import { CategoryPage } from './CategoryDetail';
export default function PartIndex({}: {}) {
return <CategoryPage />;
}

View File

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

View File

@ -0,0 +1,5 @@
import { LocationPage } from './LocationDetail';
export default function StockIndex({}: {}) {
return <LocationPage />;
}

View File

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

View File

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