diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index c9b7e19def..5edddfece2 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -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() { diff --git a/src/frontend/src/components/nav/PanelGroup.tsx b/src/frontend/src/components/nav/PanelGroup.tsx index a56d9bb5ef..8ac898da8d 100644 --- a/src/frontend/src/components/nav/PanelGroup.tsx +++ b/src/frontend/src/components/nav/PanelGroup.tsx @@ -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(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(true); @@ -169,38 +158,3 @@ function BasePanelGroup({ ); } - -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 ; -} - -/** - * 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 ( - - } /> - } /> - - ); -} diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 58e9d8e063..561d883e00 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -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({ diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx index 30f63b6ba4..291c230979 100644 --- a/src/frontend/src/pages/part/CategoryDetail.tsx +++ b/src/frontend/src/pages/part/CategoryDetail.tsx @@ -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({}: {}) { @@ -62,7 +65,7 @@ export default function CategoryDetail({}: {}) { content: ( ) @@ -74,7 +77,7 @@ export default function CategoryDetail({}: {}) { content: } ], - [category, id] + [categoryId] ); const breadcrumbs = useMemo( @@ -110,3 +113,13 @@ export default function CategoryDetail({}: {}) { ); } + +/** + * 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 ; +} diff --git a/src/frontend/src/pages/part/PartIndex.tsx b/src/frontend/src/pages/part/PartIndex.tsx new file mode 100644 index 0000000000..cd60d701fd --- /dev/null +++ b/src/frontend/src/pages/part/PartIndex.tsx @@ -0,0 +1,5 @@ +import { CategoryPage } from './CategoryDetail'; + +export default function PartIndex({}: {}) { + return ; +} diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index af55680e8a..e28544e245 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -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: ( ) @@ -50,13 +53,13 @@ export default function Stock() { content: ( ) } ]; - }, [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 ; +} diff --git a/src/frontend/src/pages/stock/StockIndex.tsx b/src/frontend/src/pages/stock/StockIndex.tsx new file mode 100644 index 0000000000..c548d65b38 --- /dev/null +++ b/src/frontend/src/pages/stock/StockIndex.tsx @@ -0,0 +1,5 @@ +import { LocationPage } from './LocationDetail'; + +export default function StockIndex({}: {}) { + return ; +} diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index 45713dbe38..fc10016f06 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -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 = ( } /> - } /> - } /> + } /> + } /> } /> - } /> - } /> + } /> + } /> } /> diff --git a/src/frontend/src/states/LocalState.tsx b/src/frontend/src/states/LocalState.tsx index 1eda3fcceb..a4640de5df 100644 --- a/src/frontend/src/states/LocalState.tsx +++ b/src/frontend/src/states/LocalState.tsx @@ -24,6 +24,7 @@ interface LocalStateProps { loader: LoaderType; lastUsedPanels: Record; setLastUsedPanel: (panelKey: string) => (value: string) => void; + getLastUsedPanel: (panelKey: string) => () => string | undefined; } export const useLocalState = create()( @@ -55,6 +56,9 @@ export const useLocalState = create()( lastUsedPanels: { ...get().lastUsedPanels, [panelKey]: value } }); } + }, + getLastUsedPanel(panelKey) { + return () => get().lastUsedPanels[panelKey]; } }), {