feat: scope workspace routes by organization slug

This commit is contained in:
2026-07-02 20:11:57 -07:00
parent fae97a7046
commit 3ab0e3e034
48 changed files with 565 additions and 96 deletions

View File

@@ -17,10 +17,11 @@ export type NavIconKey =
| "users";
export type NavItem = {
href: string;
path: string;
label: string;
icon: NavIconKey;
permission?: Permission;
anyPermissions?: Permission[];
};
export type NavGroup = {
@@ -33,34 +34,40 @@ export const navGroups: NavGroup[] = [
label: "运营",
items: [
{
href: "/app/dashboard",
path: "/dashboard",
label: "运营看板",
icon: "dashboard",
anyPermissions: ["elder:read", "facility:read", "admission:read", "incident:read"],
},
{
href: "/app/elders",
path: "/elders",
label: "老人档案",
icon: "users",
permission: "elder:read",
},
{
href: "/app/beds",
path: "/beds",
label: "床位房间",
icon: "beds",
anyPermissions: ["facility:read", "admission:read"],
},
{
href: "/app/health",
path: "/health",
label: "健康照护",
icon: "health",
permission: "elder:read",
},
{
href: "/app/care",
path: "/care",
label: "护理服务",
icon: "care",
permission: "elder:update",
},
{
href: "/app/emergency",
path: "/emergency",
label: "安全应急",
icon: "emergency",
permission: "incident:read",
},
],
},
@@ -68,62 +75,64 @@ export const navGroups: NavGroup[] = [
label: "协同",
items: [
{
href: "/app/devices",
path: "/devices",
label: "设备运维",
icon: "devices",
permission: "facility:read",
},
{
href: "/app/notices",
path: "/notices",
label: "公告通知",
icon: "notices",
},
{
href: "/app/alerts",
path: "/alerts",
label: "规则预警",
icon: "alerts",
permission: "incident:read",
},
{
href: "/app/family",
path: "/family",
label: "家属服务",
icon: "devices",
},
],
},
{
label: "系统设置",
label: "管理系统",
items: [
{
href: "/app/settings/global",
path: "/settings/global",
label: "全局配置",
icon: "global",
permission: "platform:manage",
},
{
href: "/app/settings/organizations",
path: "/settings/organizations",
label: "机构管理",
icon: "organizations",
permission: "organization:read",
},
{
href: "/app/settings/users",
path: "/settings/users",
label: "用户管理",
icon: "users",
permission: "account:read",
},
{
href: "/app/settings/roles",
path: "/settings/roles",
label: "角色权限",
icon: "roles",
permission: "role:read",
},
{
href: "/app/settings/audit",
path: "/settings/audit",
label: "审计日志",
icon: "audit",
permission: "audit:read",
},
{
href: "/app/settings/status",
path: "/settings/status",
label: "运行状态",
icon: "status",
permission: "incident:read",

View File

@@ -0,0 +1,92 @@
export const APP_ROUTE_PREFIX = "/app";
export const WORKSPACE_SECTION_KEYS = [
"dashboard",
"elders",
"beds",
"health",
"care",
"emergency",
"devices",
"notices",
"alerts",
"family",
"settings",
] as const;
export const RESERVED_WORKSPACE_SLUGS = new Set<string>(WORKSPACE_SECTION_KEYS);
function splitPath(pathname: string): string[] {
const pathOnly = pathname.split(/[?#]/, 1)[0] ?? "";
return pathOnly.split("/").filter(Boolean);
}
export function normalizeWorkspacePath(path: string): string {
const trimmed = path.trim();
if (!trimmed || trimmed === "/" || trimmed === APP_ROUTE_PREFIX) {
return "/dashboard";
}
if (trimmed.startsWith(`${APP_ROUTE_PREFIX}/`) || trimmed === APP_ROUTE_PREFIX) {
return getWorkspacePathFromPathname(trimmed);
}
const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
return withLeadingSlash.replace(/\/{2,}/g, "/");
}
export function getWorkspacePathFromPathname(pathname: string): string {
const segments = splitPath(pathname);
if (segments[0] !== "app") {
return "/dashboard";
}
const appSegments = segments.slice(1);
if (appSegments.length === 0) {
return "/dashboard";
}
const firstSegment = appSegments[0] ?? "";
if (RESERVED_WORKSPACE_SLUGS.has(firstSegment)) {
return `/${appSegments.join("/")}`;
}
const scopedSegments = appSegments.slice(1);
return scopedSegments.length > 0 ? `/${scopedSegments.join("/")}` : "/dashboard";
}
export function getWorkspaceSlugFromPathname(pathname: string): string | undefined {
const segments = splitPath(pathname);
if (segments[0] !== "app") {
return undefined;
}
const candidate = segments[1];
if (!candidate || RESERVED_WORKSPACE_SLUGS.has(candidate)) {
return undefined;
}
return candidate;
}
export function getWorkspaceHref(organizationSlug: string | undefined, path = "/dashboard"): string {
const workspacePath = normalizeWorkspacePath(path);
const slug = organizationSlug?.trim();
if (!slug) {
return `${APP_ROUTE_PREFIX}${workspacePath}`;
}
return `${APP_ROUTE_PREFIX}/${encodeURIComponent(slug)}${workspacePath}`;
}
export function isWorkspacePathActive(pathname: string, itemPath: string): boolean {
const currentPath = getWorkspacePathFromPathname(pathname);
const normalizedItemPath = normalizeWorkspacePath(itemPath);
if (normalizedItemPath === "/dashboard") {
return currentPath === "/dashboard";
}
return currentPath === normalizedItemPath || currentPath.startsWith(`${normalizedItemPath}/`);
}