Added support for redirecting to a specific control
This commit is contained in:
parent
c7428be21b
commit
49bac1091a
@ -12,6 +12,7 @@ import LoginForm from "./modules/frame/components/LoginForm";
|
|||||||
import Redirect from "./components/common/Redirect";
|
import Redirect from "./components/common/Redirect";
|
||||||
import Mainframe from "./modules/frame/components/Mainframe";
|
import Mainframe from "./modules/frame/components/Mainframe";
|
||||||
import EmailUserAction from "./modules/frame/components/EmailUserAction";
|
import EmailUserAction from "./modules/frame/components/EmailUserAction";
|
||||||
|
import { HashNavigationProvider } from "./utils/HashNavigationContext";
|
||||||
|
|
||||||
import HomePage from "./modules/homepage/HomePage";
|
import HomePage from "./modules/homepage/HomePage";
|
||||||
import Profile from "./modules/profile/Profile";
|
import Profile from "./modules/profile/Profile";
|
||||||
@ -458,6 +459,7 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
|
<HashNavigationProvider>
|
||||||
<Helmet htmlAttributes={htmlAttributes}>
|
<Helmet htmlAttributes={htmlAttributes}>
|
||||||
<title>{config.applicationName}</title>
|
<title>{config.applicationName}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
@ -494,6 +496,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</main>
|
</main>
|
||||||
|
</HashNavigationProvider>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,53 @@
|
|||||||
import React, { useEffect, useState, useCallback, useMemo } from "react";
|
import React, { useEffect, useState, useCallback, useMemo } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useHashSegment } from "../../utils/HashNavigationContext";
|
||||||
import TabHeader from "./TabHeader";
|
import TabHeader from "./TabHeader";
|
||||||
|
|
||||||
interface HorizontalTabsProps {
|
interface HorizontalTabsProps {
|
||||||
children: JSX.Element[];
|
children: JSX.Element[];
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
hashSegment?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({
|
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({
|
||||||
children,
|
children,
|
||||||
initialTab,
|
initialTab,
|
||||||
|
hashSegment,
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
const hashValue = useHashSegment(
|
||||||
|
hashSegment !== undefined ? hashSegment : -1,
|
||||||
|
);
|
||||||
const [activeTab, setActiveTab] = useState<string>("");
|
const [activeTab, setActiveTab] = useState<string>("");
|
||||||
|
|
||||||
// Set initial tab on mount
|
// Set initial tab on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
// Check for hash in URL first, then fall back to initialTab prop
|
// Only use hash if hashSegment was explicitly provided
|
||||||
const hashTab = location.hash.slice(1); // Remove the # character
|
const useHash = hashSegment !== undefined;
|
||||||
const tabToSelect = hashTab || initialTab || children[0].props.id;
|
|
||||||
|
// Validate that the hash matches one of our tab IDs
|
||||||
|
const hashMatchesTab =
|
||||||
|
useHash &&
|
||||||
|
hashValue &&
|
||||||
|
children.some((child) => child.props.id === hashValue);
|
||||||
|
|
||||||
|
// Use id if available, otherwise fall back to label
|
||||||
|
const firstTabId = children[0].props.id || children[0].props.label;
|
||||||
|
|
||||||
|
const tabToSelect =
|
||||||
|
(hashMatchesTab ? hashValue : initialTab) || firstTabId;
|
||||||
setActiveTab(tabToSelect);
|
setActiveTab(tabToSelect);
|
||||||
}
|
}
|
||||||
}, [children, initialTab, location.hash]);
|
}, [children, initialTab, hashValue, hashSegment]);
|
||||||
|
|
||||||
const onClickTabItem = useCallback((tab: string) => {
|
const onClickTabItem = useCallback((tab: string) => {
|
||||||
setActiveTab((prev) => (prev !== tab ? tab : prev));
|
setActiveTab((prev) => (prev !== tab ? tab : prev));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const activeTabChildren = useMemo(() => {
|
const activeTabChildren = useMemo(() => {
|
||||||
const match = children.find((child) => child.props.id === activeTab);
|
const match = children.find((child) => {
|
||||||
|
const tabId = child.props.id || child.props.label;
|
||||||
|
return tabId === activeTab;
|
||||||
|
});
|
||||||
return match ? match.props.children : <></>;
|
return match ? match.props.children : <></>;
|
||||||
}, [children, activeTab]);
|
}, [children, activeTab]);
|
||||||
|
|
||||||
@ -43,12 +61,13 @@ const HorizontalTabs: React.FC<HorizontalTabsProps> = ({
|
|||||||
<ul className="tab-list">
|
<ul className="tab-list">
|
||||||
{children.map((child) => {
|
{children.map((child) => {
|
||||||
const { id, label } = child.props;
|
const { id, label } = child.props;
|
||||||
|
const tabId = id || label;
|
||||||
return (
|
return (
|
||||||
<TabHeader
|
<TabHeader
|
||||||
key={label}
|
key={label}
|
||||||
label={label}
|
label={label}
|
||||||
isActive={id === activeTab}
|
isActive={tabId === activeTab}
|
||||||
onClick={() => onClickTabItem(id)}
|
onClick={() => onClickTabItem(tabId)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -53,7 +53,9 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ isEditMode }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>{heading}</h1>
|
<h1>{heading}</h1>
|
||||||
<HorizontalTabs initialTab={initialTab}>{tabs}</HorizontalTabs>
|
<HorizontalTabs initialTab={initialTab} hashSegment={0}>
|
||||||
|
{tabs}
|
||||||
|
</HorizontalTabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,9 +53,9 @@ const AddUserToRole: React.FC<LocAddUserToRoleProps> = ({ isEditMode }) => {
|
|||||||
if (response) {
|
if (response) {
|
||||||
toast.info(t("UserAddedToRole"));
|
toast.info(t("UserAddedToRole"));
|
||||||
|
|
||||||
if (buttonName === labelSave)
|
if (buttonName === "save")
|
||||||
form.setState({
|
form.setState({
|
||||||
redirect: `/domains/edit/${domainId}?tab=SecurityRoles&roleId=${roleId}&innerTab=Users`,
|
redirect: `/domains/edit/${domainId}#securityRoles/${roleId}/users`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import RolesTable from "./RolesTable";
|
|||||||
import Button, { ButtonType } from "../../../../components/common/Button";
|
import Button, { ButtonType } from "../../../../components/common/Button";
|
||||||
import Loading from "../../../../components/common/Loading";
|
import Loading from "../../../../components/common/Loading";
|
||||||
import Permission from "../../../../components/common/Permission";
|
import Permission from "../../../../components/common/Permission";
|
||||||
|
import { useHashSegment } from "../../../../utils/HashNavigationContext";
|
||||||
|
|
||||||
const initialPagedData: Paginated<GetRoleResponse> = {
|
const initialPagedData: Paginated<GetRoleResponse> = {
|
||||||
page: 1,
|
page: 1,
|
||||||
@ -24,6 +25,7 @@ interface RolesEditorProps {
|
|||||||
onSelectRole?: (keyValue: any) => void;
|
onSelectRole?: (keyValue: any) => void;
|
||||||
onUnselectRole?: () => void;
|
onUnselectRole?: () => void;
|
||||||
initialRoleId?: string;
|
initialRoleId?: string;
|
||||||
|
hashSegment?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RolesEditor: React.FC<RolesEditorProps> = ({
|
const RolesEditor: React.FC<RolesEditorProps> = ({
|
||||||
@ -31,8 +33,12 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
|
|||||||
onSelectRole,
|
onSelectRole,
|
||||||
onUnselectRole,
|
onUnselectRole,
|
||||||
initialRoleId,
|
initialRoleId,
|
||||||
|
hashSegment,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation<typeof Namespaces.Common>();
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
|
const hashRoleId = useHashSegment(
|
||||||
|
hashSegment !== undefined ? hashSegment : -1,
|
||||||
|
);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [pagedData, setPagedData] =
|
const [pagedData, setPagedData] =
|
||||||
useState<Paginated<GetRoleResponse>>(initialPagedData);
|
useState<Paginated<GetRoleResponse>>(initialPagedData);
|
||||||
@ -79,17 +85,20 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
|
|||||||
void loadInitial();
|
void loadInitial();
|
||||||
}, [changePage]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [changePage]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
// Auto-select role when initialRoleId is provided
|
// Auto-select role when initialRoleId or hashRoleId is provided
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialRoleId && pagedData.data.length > 0 && onSelectRole) {
|
const roleIdToSelect =
|
||||||
|
hashSegment !== undefined ? hashRoleId : initialRoleId;
|
||||||
|
|
||||||
|
if (roleIdToSelect && pagedData.data.length > 0 && onSelectRole) {
|
||||||
const roleToSelect = pagedData.data.find(
|
const roleToSelect = pagedData.data.find(
|
||||||
(role) => role.id.toString() === initialRoleId,
|
(role) => role.id.toString() === roleIdToSelect,
|
||||||
);
|
);
|
||||||
if (roleToSelect) {
|
if (roleToSelect) {
|
||||||
onSelectRole(roleToSelect);
|
onSelectRole(roleToSelect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialRoleId, pagedData.data, onSelectRole]);
|
}, [hashRoleId, initialRoleId, pagedData.data, onSelectRole, hashSegment]);
|
||||||
|
|
||||||
const onSort = async (nextSortColumn: Column<GetRoleResponse>) => {
|
const onSort = async (nextSortColumn: Column<GetRoleResponse>) => {
|
||||||
const { page, pageSize } = pagedData;
|
const { page, pageSize } = pagedData;
|
||||||
|
|||||||
@ -38,14 +38,14 @@ const SecurityRolesTab: React.FC<SecurityRolesTabProps> = ({
|
|||||||
|
|
||||||
if (canViewRoleAccess) {
|
if (canViewRoleAccess) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<Tab key={1} label={t("RoleAccess")}>
|
<Tab id="roleAccess" key={1} label={t("RoleAccess")}>
|
||||||
<RoleAccessEditor role={selectedRole} />
|
<RoleAccessEditor role={selectedRole} />
|
||||||
</Tab>,
|
</Tab>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (canViewRoleUsers) {
|
if (canViewRoleUsers) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<Tab key={2} label={t("Users")}>
|
<Tab id="users" key={2} label={t("Users")}>
|
||||||
<UserRoleEditor role={selectedRole} />
|
<UserRoleEditor role={selectedRole} />
|
||||||
</Tab>,
|
</Tab>,
|
||||||
);
|
);
|
||||||
@ -59,12 +59,15 @@ const SecurityRolesTab: React.FC<SecurityRolesTabProps> = ({
|
|||||||
onSelectRole={onSelectRow}
|
onSelectRole={onSelectRow}
|
||||||
onUnselectRole={onUnselectRow}
|
onUnselectRole={onUnselectRow}
|
||||||
initialRoleId={initialRoleId}
|
initialRoleId={initialRoleId}
|
||||||
|
hashSegment={1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{selectedRole !== undefined &&
|
{selectedRole !== undefined &&
|
||||||
(canViewRoleAccess || canViewRoleUsers) && (
|
(canViewRoleAccess || canViewRoleUsers) && (
|
||||||
<HorizontalTabs initialTab={initialInnerTab}>{tabs}</HorizontalTabs>
|
<HorizontalTabs initialTab={initialInnerTab} hashSegment={2}>
|
||||||
|
{tabs}
|
||||||
|
</HorizontalTabs>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
44
src/utils/HashNavigationContext.tsx
Normal file
44
src/utils/HashNavigationContext.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React, { createContext, useContext, useMemo } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
interface HashNavigationContextValue {
|
||||||
|
segments: string[];
|
||||||
|
getSegment: (index: number) => string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HashNavigationContext = createContext<HashNavigationContextValue>({
|
||||||
|
segments: [],
|
||||||
|
getSegment: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const HashNavigationProvider: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ children }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
// Parse hash into segments, removing the leading #
|
||||||
|
const hash = location.hash.slice(1);
|
||||||
|
const segments = hash ? hash.split("/") : [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
segments,
|
||||||
|
getSegment: (index: number) => segments[index],
|
||||||
|
};
|
||||||
|
}, [location.hash]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HashNavigationContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</HashNavigationContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHashNavigation = () => {
|
||||||
|
return useContext(HashNavigationContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHashSegment = (index: number): string | undefined => {
|
||||||
|
const { getSegment } = useHashNavigation();
|
||||||
|
return getSegment(index);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user