46f488c8ae
- 重构 DesktopPreferences 为分栏式设置面板,整合连接、外观、通知、更新与诊断信息分区,并补充过渡动效与暗色主题样式 - DesktopLayout 侧边栏导航分组支持展开折叠,调整管理/项目区块顺序并统一图标与标题 - 新增 fileTaskFeedback 工具,统一 pickFiles/saveFile/openFile 的成功/取消提示,替换审计导出、权限日志、附件、文档、线程、项目配置等处的直接调用 - desktopUpdateManager 暴露更新状态快照与状态变更监听,区分检查中、安装中、已推迟、失败等状态 - DesktopServerSettings 增加连接诊断信息(检查时间、健康地址、耗时、HTTP 状态) - unified-page.css 与 ProjectMilestones 引入 CSS 变量以适配暗色主题 - WebLayout 将服务器设置入口改为打开系统偏好面板,管理菜单中邮件服务归入系统设置分组 - ProfileSettings 移除已迁入偏好面板的桌面端专属区块 - 补充 Layout.desktop 布局与偏好面板契约测试
308 lines
9.2 KiB
TypeScript
308 lines
9.2 KiB
TypeScript
import { TEXT } from "../../locales";
|
|
|
|
export type LayoutNavigationIcon =
|
|
| "audit"
|
|
| "calendar"
|
|
| "check"
|
|
| "coin"
|
|
| "dashboard"
|
|
| "document"
|
|
| "flag"
|
|
| "folder"
|
|
| "key"
|
|
| "monitor"
|
|
| "notebook"
|
|
| "project"
|
|
| "settings"
|
|
| "subject"
|
|
| "users";
|
|
|
|
export type LayoutNavigationItem = {
|
|
label: string;
|
|
path: string;
|
|
group: string;
|
|
icon: LayoutNavigationIcon;
|
|
keywords?: string[];
|
|
children?: LayoutNavigationItem[];
|
|
};
|
|
|
|
export const getActiveLayoutPath = (path: string) => {
|
|
if (path.startsWith("/project/milestones")) return "/project/milestones";
|
|
if (path.startsWith("/project/")) return "/project/overview";
|
|
if (path.startsWith("/fees/contracts")) return "/fees/contracts";
|
|
if (path.startsWith("/drug/shipments")) return "/drug/shipments";
|
|
if (path.startsWith("/materials/equipment")) return "/materials/equipment";
|
|
if (path.startsWith("/file-versions") || path.startsWith("/trial/") || path.startsWith("/documents/")) return "/file-versions";
|
|
if (path.startsWith("/startup/feasibility") || path.startsWith("/startup/ethics")) return "/startup/feasibility-ethics";
|
|
if (path.startsWith("/startup/meeting-auth") || path.startsWith("/startup/kickoff") || path.startsWith("/startup/training")) {
|
|
return "/startup/meeting-auth";
|
|
}
|
|
if (path.startsWith("/subjects")) return "/subjects";
|
|
if (path.startsWith("/risk-issues/sae")) return "/risk-issues/sae";
|
|
if (path.startsWith("/risk-issues/pd")) return "/risk-issues/pd";
|
|
if (path.startsWith("/risk-issues/monitoring-visits")) return "/risk-issues/monitoring-visits";
|
|
if (path.startsWith("/risk-issues")) return "/risk-issues/sae";
|
|
if (path.startsWith("/etmf")) return "/etmf";
|
|
if (path.startsWith("/knowledge/medical-consult")) return "/knowledge/medical-consult";
|
|
if (path.startsWith("/knowledge/precautions")) return "/knowledge/precautions";
|
|
if (path.startsWith("/knowledge/support-files")) return "/knowledge/support-files";
|
|
if (path.startsWith("/knowledge/instruction-files")) return "/knowledge/instruction-files";
|
|
if (path.startsWith("/projects/")) return "/admin/projects";
|
|
if (path.startsWith("/admin/projects/")) return "/admin/projects";
|
|
if (path.startsWith("/admin/system-monitoring") || path.startsWith("/admin/permission-monitoring")) return "/admin/system-monitoring";
|
|
if (path.startsWith("/admin/permissions/")) return path;
|
|
return path;
|
|
};
|
|
|
|
export const buildAdminNavigationItems = (options: {
|
|
hasUser: boolean;
|
|
isAdmin: boolean;
|
|
canAccessAdminPermissions: boolean;
|
|
}): LayoutNavigationItem[] => {
|
|
if (!options.hasUser) return [];
|
|
|
|
const items: LayoutNavigationItem[] = [];
|
|
if (options.isAdmin) {
|
|
items.push({
|
|
label: TEXT.menu.accountManagement,
|
|
path: "/admin/users",
|
|
group: TEXT.menu.admin,
|
|
icon: "users",
|
|
keywords: ["user", "account"],
|
|
});
|
|
}
|
|
items.push({
|
|
label: TEXT.menu.projectManagement,
|
|
path: "/admin/projects",
|
|
group: TEXT.menu.admin,
|
|
icon: "project",
|
|
keywords: ["project"],
|
|
});
|
|
if (options.isAdmin) {
|
|
items.push(
|
|
{
|
|
label: TEXT.menu.auditLogs,
|
|
path: "/admin/audit-logs",
|
|
group: TEXT.menu.admin,
|
|
icon: "audit",
|
|
keywords: ["audit"],
|
|
},
|
|
{
|
|
label: TEXT.menu.systemMonitoring,
|
|
path: "/admin/system-monitoring",
|
|
group: TEXT.menu.admin,
|
|
icon: "monitor",
|
|
keywords: ["monitoring"],
|
|
},
|
|
{
|
|
label: "系统设置",
|
|
path: "/admin/email-settings",
|
|
group: TEXT.menu.admin,
|
|
icon: "settings",
|
|
keywords: ["settings"],
|
|
children: [
|
|
{
|
|
label: "邮件服务",
|
|
path: "/admin/email-settings",
|
|
group: "系统设置",
|
|
icon: "settings",
|
|
keywords: ["email", "settings"],
|
|
},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
if (options.canAccessAdminPermissions) {
|
|
items.push({
|
|
label: TEXT.menu.permissionManagement,
|
|
path: "/admin/permissions/system",
|
|
group: TEXT.menu.admin,
|
|
icon: "key",
|
|
keywords: ["permission"],
|
|
children: [
|
|
{
|
|
label: "系统级权限",
|
|
path: "/admin/permissions/system",
|
|
group: TEXT.menu.permissionManagement,
|
|
icon: "key",
|
|
keywords: ["permission", "system"],
|
|
},
|
|
{
|
|
label: "项目权限配置",
|
|
path: "/admin/permissions/project",
|
|
group: TEXT.menu.permissionManagement,
|
|
icon: "key",
|
|
keywords: ["permission", "project"],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
return items;
|
|
};
|
|
|
|
export const buildProjectNavigationItems = (options: {
|
|
hasCurrentStudy: boolean;
|
|
hasAnyProjectModuleAccess: boolean;
|
|
canAccessProjectPath: (path: string) => boolean;
|
|
}): LayoutNavigationItem[] => {
|
|
if (!options.hasCurrentStudy || !options.hasAnyProjectModuleAccess) return [];
|
|
|
|
const items: LayoutNavigationItem[] = [
|
|
{
|
|
label: TEXT.menu.projectOverview,
|
|
path: "/project/overview",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "dashboard",
|
|
keywords: ["overview"],
|
|
},
|
|
{
|
|
label: TEXT.menu.projectMilestones,
|
|
path: "/project/milestones",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "calendar",
|
|
keywords: ["milestone"],
|
|
},
|
|
{
|
|
label: TEXT.menu.feeContracts,
|
|
path: "/fees/contracts",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "coin",
|
|
keywords: ["fee", "contract"],
|
|
},
|
|
{
|
|
label: TEXT.menu.materialManagement,
|
|
path: "/drug/shipments",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "folder",
|
|
children: [
|
|
{
|
|
label: TEXT.menu.drugShipments,
|
|
path: "/drug/shipments",
|
|
group: TEXT.menu.materialManagement,
|
|
icon: "folder",
|
|
keywords: ["drug", "shipment"],
|
|
},
|
|
{
|
|
label: TEXT.menu.materialEquipment,
|
|
path: "/materials/equipment",
|
|
group: TEXT.menu.materialManagement,
|
|
icon: "folder",
|
|
keywords: ["material", "equipment"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: TEXT.menu.fileVersionManagement,
|
|
path: "/file-versions",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "document",
|
|
keywords: ["document", "file", "version"],
|
|
},
|
|
{
|
|
label: TEXT.menu.startupFeasibilityEthics,
|
|
path: "/startup/feasibility-ethics",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "calendar",
|
|
keywords: ["startup", "ethics"],
|
|
},
|
|
{
|
|
label: TEXT.menu.startupMeetingAuth,
|
|
path: "/startup/meeting-auth",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "check",
|
|
keywords: ["meeting", "training"],
|
|
},
|
|
{
|
|
label: TEXT.menu.subjects,
|
|
path: "/subjects",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "subject",
|
|
keywords: ["subject"],
|
|
},
|
|
{
|
|
label: TEXT.menu.riskIssues,
|
|
path: "/risk-issues/sae",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "flag",
|
|
children: [
|
|
{
|
|
label: TEXT.menu.riskIssueSae,
|
|
path: "/risk-issues/sae",
|
|
group: TEXT.menu.riskIssues,
|
|
icon: "flag",
|
|
keywords: ["sae", "risk"],
|
|
},
|
|
{
|
|
label: TEXT.menu.riskIssuePd,
|
|
path: "/risk-issues/pd",
|
|
group: TEXT.menu.riskIssues,
|
|
icon: "flag",
|
|
keywords: ["pd", "risk"],
|
|
},
|
|
{
|
|
label: TEXT.menu.riskIssueMonitoringVisits,
|
|
path: "/risk-issues/monitoring-visits",
|
|
group: TEXT.menu.riskIssues,
|
|
icon: "flag",
|
|
keywords: ["monitoring", "visit"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: TEXT.menu.etmf,
|
|
path: "/etmf",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "folder",
|
|
keywords: ["etmf"],
|
|
},
|
|
{
|
|
label: TEXT.menu.sharedLibrary,
|
|
path: "/knowledge/medical-consult",
|
|
group: TEXT.menu.currentProject,
|
|
icon: "notebook",
|
|
children: [
|
|
{
|
|
label: TEXT.menu.knowledgeMedicalConsult,
|
|
path: "/knowledge/medical-consult",
|
|
group: TEXT.menu.sharedLibrary,
|
|
icon: "notebook",
|
|
keywords: ["knowledge", "medical"],
|
|
},
|
|
{
|
|
label: TEXT.menu.knowledgeNotes,
|
|
path: "/knowledge/precautions",
|
|
group: TEXT.menu.sharedLibrary,
|
|
icon: "notebook",
|
|
keywords: ["knowledge", "note"],
|
|
},
|
|
{
|
|
label: TEXT.menu.knowledgeSupportFiles,
|
|
path: "/knowledge/support-files",
|
|
group: TEXT.menu.sharedLibrary,
|
|
icon: "notebook",
|
|
keywords: ["knowledge", "support"],
|
|
},
|
|
{
|
|
label: TEXT.menu.knowledgeInstructionFiles,
|
|
path: "/knowledge/instruction-files",
|
|
group: TEXT.menu.sharedLibrary,
|
|
icon: "notebook",
|
|
keywords: ["knowledge", "instruction"],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
return items
|
|
.map((item) => {
|
|
if (!item.children) return options.canAccessProjectPath(item.path) ? item : null;
|
|
const children = item.children.filter((child) => options.canAccessProjectPath(child.path));
|
|
if (!children.length) return null;
|
|
return { ...item, path: children[0].path, children };
|
|
})
|
|
.filter(Boolean) as LayoutNavigationItem[];
|
|
};
|
|
|
|
export const flattenLayoutNavigationItems = (items: LayoutNavigationItem[]) =>
|
|
items.flatMap((item) => item.children?.length ? item.children : [item]);
|