refactor(web): move the workbench shell out of App
Session state stays in App. The shell, rights dialogs, and project modals now render from WorkbenchView, so App no longer mounts AppShell itself. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+149
-250
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useEffect, useRef, useCallback } from 'react';
|
||||
import { AppShell } from './components/layout/AppShell';
|
||||
import { WorkbenchView } from './components/layout/WorkbenchView';
|
||||
import { DocumentPane } from './components/doc/DocumentPane';
|
||||
import { ChatColumn } from './components/chat/ChatColumn';
|
||||
import { fetchFreeformDoc } from './api/freeformDoc';
|
||||
@@ -34,16 +34,9 @@ import {
|
||||
import { DocumentLineage } from './types/projectWorkflow';
|
||||
import { StaleDocumentNotice } from './components/workflow/StaleDocumentNotice';
|
||||
import { StageAdvanceBanner } from './components/workflow/StageAdvanceBanner';
|
||||
import { UserProjectRightsModal } from './components/user/UserProjectRightsModal';
|
||||
import { UserSettingsModal } from './components/user/UserSettingsModal';
|
||||
import { OnDemand } from './components/lazy/OnDemand';
|
||||
import { DocVisibilityModal } from './components/doc/DocVisibilityModal';
|
||||
import { CreateProjectModal } from './components/project/CreateProjectModal';
|
||||
import { ProjectMembersModal } from './components/project/ProjectMembersModal';
|
||||
import { ProjectSettingsModal } from './components/project/ProjectSettingsModal';
|
||||
import { fetchProjectSettings } from './api/projectSettings';
|
||||
import { LoginView } from './components/auth/LoginView';
|
||||
import { FirstLoginPasswordModal } from './components/auth/FirstLoginPasswordModal';
|
||||
import { authClient } from './api/auth';
|
||||
import { LoginResult, SystemAuthConfig } from './types/auth';
|
||||
import { markdownToBlockAST } from './engine/markdownToBlock';
|
||||
@@ -1496,247 +1489,153 @@ export default function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppShell
|
||||
sessions={visibleSessions}
|
||||
activeSessionId={activeSessionId}
|
||||
onSelectSession={(sessId) => {
|
||||
if (window.location.hash) {
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search);
|
||||
}
|
||||
setActiveSessionId(sessId);
|
||||
const targetSess = sessions.find((s) => s.sessionId === sessId);
|
||||
if (targetSess && targetSess.projectId !== currentProjectId) {
|
||||
const targetProjDocs = deliverables.filter((d) => d.projectId === targetSess.projectId);
|
||||
if (targetProjDocs.length > 0) {
|
||||
setActiveDocId(targetProjDocs[0].docId);
|
||||
setActiveCategory(targetProjDocs[0].category);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onNewSession={() => setCreateProjectModalOpen(true)}
|
||||
canCreateProject={canCreateProject}
|
||||
currentUser={currentUser}
|
||||
currentRole={currentDocRole}
|
||||
currentUserName={currentUser.name}
|
||||
onChangeRole={(_, __) => {}}
|
||||
onOpenRightsModal={() => setRightsModalOpen(true)}
|
||||
onOpenUserSettings={() => setUserSettingsOpen(true)}
|
||||
onOpenAdminSettings={() => setView('admin')}
|
||||
onOpenProjectMembers={() => setProjectMembersModalOpen(true)}
|
||||
activeWorkflowKey={currentWfState.activeKey}
|
||||
workflowVersion={currentWfState.version}
|
||||
lifecycleStatus={currentWfState.lifecycleStatus}
|
||||
isCreator={currentWfState.creatorId === currentUser.id}
|
||||
governanceEnabled={workflowGovernanceEnabled}
|
||||
onOpenProjectSettings={() => setProjectSettingsOpen(true)}
|
||||
onWorkflowChanged={(newKey, newVer) => {
|
||||
setProjectWorkflowsState((prev) => ({
|
||||
...prev,
|
||||
[currentProjectId]: {
|
||||
...currentWfState,
|
||||
activeKey: newKey,
|
||||
version: newVer,
|
||||
lifecycleStatus: 'active',
|
||||
},
|
||||
}));
|
||||
}}
|
||||
workflowBanner={workflowBanner}
|
||||
chatContent={
|
||||
<ChatColumn
|
||||
activeSessionId={activeSessionId}
|
||||
categoryStats={categoryStats}
|
||||
chatWindow={chatWindow}
|
||||
isCalendarOpen={isCalendarOpen}
|
||||
setIsCalendarOpen={setIsCalendarOpen}
|
||||
isSearchOpen={isSearchOpen}
|
||||
setIsSearchOpen={setIsSearchOpen}
|
||||
isDigestOpen={isDigestOpen}
|
||||
setIsDigestOpen={setIsDigestOpen}
|
||||
isExportOpen={isExportOpen}
|
||||
setIsExportOpen={setIsExportOpen}
|
||||
unreadTotal={unreadTotal}
|
||||
dateStats={dateStats}
|
||||
unreadMarks={unreadMarks}
|
||||
unreadMessageIds={unreadMessageIds}
|
||||
isAllReadCompleted={isAllReadCompleted}
|
||||
consumeNextUnread={consumeNextUnread}
|
||||
handleSelectDoc={handleSelectDoc}
|
||||
handleCardAction={handleCardAction}
|
||||
handleErrorAction={handleErrorAction}
|
||||
handleQuoteMessage={handleQuoteMessage}
|
||||
handleSaveMessageToPrivate={handleSaveMessageToPrivate}
|
||||
handleExportReplyDocx={handleExportReplyDocx}
|
||||
pendingProposal={pendingProposal}
|
||||
proposalError={proposalError}
|
||||
handleAcceptProposal={handleAcceptProposal}
|
||||
handleRejectProposal={handleRejectProposal}
|
||||
handleSendMessage={handleSendMessage}
|
||||
isThinking={isThinking}
|
||||
isStreamingActive={isStreamingActive}
|
||||
handleStopStreaming={handleStopStreaming}
|
||||
activeQuotes={activeQuotes}
|
||||
handleRemoveQuote={handleRemoveQuote}
|
||||
isCurrentProjectExited={isCurrentProjectExited}
|
||||
/>
|
||||
}
|
||||
docContent={
|
||||
<DocumentPane
|
||||
isCurrentProjectExited={isCurrentProjectExited}
|
||||
activeDocId={activeDocId}
|
||||
deliverables={deliverables}
|
||||
visibleDeliverables={visibleDeliverables}
|
||||
activeCategory={activeCategory}
|
||||
setActiveCategory={setActiveCategory}
|
||||
handleSelectDoc={handleSelectDoc}
|
||||
deckDrawerOpen={deckDrawerOpen}
|
||||
setDeckDrawerOpen={setDeckDrawerOpen}
|
||||
handleExportDocx={handleExportDocx}
|
||||
handleExportXlsx={handleExportXlsx}
|
||||
handleExportMarkdown={handleExportMarkdown}
|
||||
docRevOverlayMode={docRevOverlayMode}
|
||||
setDocRevOverlayMode={setDocRevOverlayMode}
|
||||
setDocRevOverlay={setDocRevOverlay}
|
||||
docRevRefreshKey={docRevRefreshKey}
|
||||
docRevConflict={docRevConflict}
|
||||
docRevOverlay={docRevOverlay}
|
||||
currentUser={currentUser}
|
||||
handleShareToProject={handleShareToProject}
|
||||
handleRevokeShare={handleRevokeShare}
|
||||
handleDeleteDoc={handleDeleteDoc}
|
||||
setTargetDocForVisibility={setTargetDocForVisibility}
|
||||
setDocVisibilityModalOpen={setDocVisibilityModalOpen}
|
||||
docLoadErrors={docLoadErrors}
|
||||
docLoadErrorInfo={docLoadErrorInfo}
|
||||
retryControlledDocLoad={retryControlledDocLoad}
|
||||
docAST={docAST}
|
||||
currentDocRole={currentDocRole}
|
||||
openBlockEditor={openBlockEditor}
|
||||
handleInsertBlock={handleInsertBlock}
|
||||
handleMoveBlock={handleMoveBlock}
|
||||
handleDeleteBlock={handleDeleteBlock}
|
||||
setCommentingBlock={setCommentingBlock}
|
||||
handleSendMessage={handleSendMessage}
|
||||
highlightedBlockId={highlightedBlockId}
|
||||
canOverrideVisibility={canOverrideVisibility}
|
||||
handleClaim={handleClaim}
|
||||
handleSubmitReview={handleSubmitReview}
|
||||
handlePassReview={handlePassReview}
|
||||
handleRejectReview={handleRejectReview}
|
||||
handleApprove={handleApprove}
|
||||
handleRejectApprove={handleRejectApprove}
|
||||
editorSession={editorSession}
|
||||
handleSaveBlockEdit={handleSaveBlockEdit}
|
||||
closeBlockEditor={closeBlockEditor}
|
||||
commentingBlock={commentingBlock}
|
||||
handleAddComment={handleAddComment}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 用户在当前激活项目中的法定职责与权限矩阵弹窗 */}
|
||||
<UserProjectRightsModal
|
||||
isOpen={rightsModalOpen}
|
||||
onClose={() => setRightsModalOpen(false)}
|
||||
currentUser={currentUser}
|
||||
currentProjectTitle={sessions.find((s) => s.sessionId === activeSessionId)?.title || '当前项目'}
|
||||
deliverables={visibleDeliverables}
|
||||
activeDocId={activeDocId}
|
||||
canAssignLead={canAssignLead}
|
||||
onSelectDoc={handleSelectDoc}
|
||||
onRoleChangeForDoc={handleRoleChangeForDoc}
|
||||
/>
|
||||
|
||||
{/* 首次登录强制改密阻断弹窗 */}
|
||||
<FirstLoginPasswordModal
|
||||
isOpen={!!session?.mustChangePassword}
|
||||
user={currentUser}
|
||||
onPasswordChanged={() => {
|
||||
const updated = { ...session, mustChangePassword: false };
|
||||
setSession(updated);
|
||||
authClient.saveSession(updated);
|
||||
}}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
|
||||
{/* 用户个人设置与偏好中心 (全员可用) */}
|
||||
<UserSettingsModal
|
||||
isOpen={userSettingsOpen}
|
||||
onClose={() => setUserSettingsOpen(false)}
|
||||
currentUser={currentUser}
|
||||
roles={roles}
|
||||
onUpdateProfile={(updatedProfile) => {
|
||||
setAllUsers((prev) =>
|
||||
prev.map((u) => (u.id === currentUser.id ? { ...u, ...updatedProfile } : u))
|
||||
);
|
||||
setCurrentUser((prev) => ({ ...prev, ...updatedProfile }));
|
||||
}}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
|
||||
{/* 单文档可见性覆盖弹窗 */}
|
||||
<DocVisibilityModal
|
||||
isOpen={docVisibilityModalOpen}
|
||||
onClose={() => setDocVisibilityModalOpen(false)}
|
||||
doc={targetDocForVisibility}
|
||||
roles={roles}
|
||||
currentUserName={currentUser.name}
|
||||
onSaveRule={handleSaveDocVisibilityRule}
|
||||
/>
|
||||
|
||||
{/* 发起医疗器械新项目立项弹窗 */}
|
||||
<CreateProjectModal
|
||||
isOpen={createProjectModalOpen}
|
||||
onClose={() => {
|
||||
if (createProjectSubmitting) return;
|
||||
setCreateProjectModalOpen(false);
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
onSubmit={handleCreateProjectConfirm}
|
||||
submitError={createProjectError}
|
||||
submitting={createProjectSubmitting}
|
||||
submitPhase={createProjectSubmitPhase}
|
||||
currentUser={currentUser}
|
||||
availableUsers={allUsers}
|
||||
/>
|
||||
|
||||
{/* 项目成员与职责分配管理弹窗 */}
|
||||
<ProjectMembersModal
|
||||
isOpen={projectMembersModalOpen}
|
||||
onClose={() => setProjectMembersModalOpen(false)}
|
||||
project={currentProject}
|
||||
currentUser={currentUser}
|
||||
availableUsers={allUsers}
|
||||
canManage={canManageProjectMembers}
|
||||
membersError={projectMembersError}
|
||||
onAddMember={handleAddProjectMember}
|
||||
onUpdateMemberRole={handleUpdateProjectMemberRole}
|
||||
onRemoveMember={handleRemoveProjectMember}
|
||||
onRestoreMember={handleRestoreProjectMember}
|
||||
/>
|
||||
|
||||
<ProjectSettingsModal
|
||||
open={projectSettingsOpen}
|
||||
projectId={currentProjectId}
|
||||
projectName={currentActiveSession?.title || '当前项目'}
|
||||
members={(currentProject?.members || [])
|
||||
.filter((m) => m.status === 'active')
|
||||
.map((m) => ({
|
||||
userId: m.userId,
|
||||
name: allUsers.find((u) => u.id === m.userId)?.name || `用户 ${m.userId}`,
|
||||
}))}
|
||||
onClose={() => setProjectSettingsOpen(false)}
|
||||
onActivated={(activeKey) => {
|
||||
setProjectWorkflowsState((prev) => ({
|
||||
...prev,
|
||||
[currentProjectId]: {
|
||||
...currentWfState,
|
||||
activeKey,
|
||||
lifecycleStatus: 'active',
|
||||
},
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<WorkbenchView
|
||||
visibleSessions={visibleSessions}
|
||||
activeSessionId={activeSessionId}
|
||||
sessions={sessions}
|
||||
deliverables={deliverables}
|
||||
currentProjectId={currentProjectId}
|
||||
setActiveSessionId={setActiveSessionId}
|
||||
setActiveDocId={setActiveDocId}
|
||||
setActiveCategory={setActiveCategory}
|
||||
setCreateProjectModalOpen={setCreateProjectModalOpen}
|
||||
canCreateProject={canCreateProject}
|
||||
currentUser={currentUser}
|
||||
currentDocRole={currentDocRole}
|
||||
setRightsModalOpen={setRightsModalOpen}
|
||||
setUserSettingsOpen={setUserSettingsOpen}
|
||||
setView={setView}
|
||||
setProjectMembersModalOpen={setProjectMembersModalOpen}
|
||||
currentWfState={currentWfState}
|
||||
workflowGovernanceEnabled={workflowGovernanceEnabled}
|
||||
setProjectSettingsOpen={setProjectSettingsOpen}
|
||||
setProjectWorkflowsState={setProjectWorkflowsState}
|
||||
workflowBanner={workflowBanner}
|
||||
chatContent={
|
||||
<ChatColumn
|
||||
activeSessionId={activeSessionId}
|
||||
categoryStats={categoryStats}
|
||||
chatWindow={chatWindow}
|
||||
isCalendarOpen={isCalendarOpen}
|
||||
setIsCalendarOpen={setIsCalendarOpen}
|
||||
isSearchOpen={isSearchOpen}
|
||||
setIsSearchOpen={setIsSearchOpen}
|
||||
isDigestOpen={isDigestOpen}
|
||||
setIsDigestOpen={setIsDigestOpen}
|
||||
isExportOpen={isExportOpen}
|
||||
setIsExportOpen={setIsExportOpen}
|
||||
unreadTotal={unreadTotal}
|
||||
dateStats={dateStats}
|
||||
unreadMarks={unreadMarks}
|
||||
unreadMessageIds={unreadMessageIds}
|
||||
isAllReadCompleted={isAllReadCompleted}
|
||||
consumeNextUnread={consumeNextUnread}
|
||||
handleSelectDoc={handleSelectDoc}
|
||||
handleCardAction={handleCardAction}
|
||||
handleErrorAction={handleErrorAction}
|
||||
handleQuoteMessage={handleQuoteMessage}
|
||||
handleSaveMessageToPrivate={handleSaveMessageToPrivate}
|
||||
handleExportReplyDocx={handleExportReplyDocx}
|
||||
pendingProposal={pendingProposal}
|
||||
proposalError={proposalError}
|
||||
handleAcceptProposal={handleAcceptProposal}
|
||||
handleRejectProposal={handleRejectProposal}
|
||||
handleSendMessage={handleSendMessage}
|
||||
isThinking={isThinking}
|
||||
isStreamingActive={isStreamingActive}
|
||||
handleStopStreaming={handleStopStreaming}
|
||||
activeQuotes={activeQuotes}
|
||||
handleRemoveQuote={handleRemoveQuote}
|
||||
isCurrentProjectExited={isCurrentProjectExited}
|
||||
/>
|
||||
}
|
||||
docContent={
|
||||
<DocumentPane
|
||||
isCurrentProjectExited={isCurrentProjectExited}
|
||||
activeDocId={activeDocId}
|
||||
deliverables={deliverables}
|
||||
visibleDeliverables={visibleDeliverables}
|
||||
activeCategory={activeCategory}
|
||||
setActiveCategory={setActiveCategory}
|
||||
handleSelectDoc={handleSelectDoc}
|
||||
deckDrawerOpen={deckDrawerOpen}
|
||||
setDeckDrawerOpen={setDeckDrawerOpen}
|
||||
handleExportDocx={handleExportDocx}
|
||||
handleExportXlsx={handleExportXlsx}
|
||||
handleExportMarkdown={handleExportMarkdown}
|
||||
docRevOverlayMode={docRevOverlayMode}
|
||||
setDocRevOverlayMode={setDocRevOverlayMode}
|
||||
setDocRevOverlay={setDocRevOverlay}
|
||||
docRevRefreshKey={docRevRefreshKey}
|
||||
docRevConflict={docRevConflict}
|
||||
docRevOverlay={docRevOverlay}
|
||||
currentUser={currentUser}
|
||||
handleShareToProject={handleShareToProject}
|
||||
handleRevokeShare={handleRevokeShare}
|
||||
handleDeleteDoc={handleDeleteDoc}
|
||||
setTargetDocForVisibility={setTargetDocForVisibility}
|
||||
setDocVisibilityModalOpen={setDocVisibilityModalOpen}
|
||||
docLoadErrors={docLoadErrors}
|
||||
docLoadErrorInfo={docLoadErrorInfo}
|
||||
retryControlledDocLoad={retryControlledDocLoad}
|
||||
docAST={docAST}
|
||||
currentDocRole={currentDocRole}
|
||||
openBlockEditor={openBlockEditor}
|
||||
handleInsertBlock={handleInsertBlock}
|
||||
handleMoveBlock={handleMoveBlock}
|
||||
handleDeleteBlock={handleDeleteBlock}
|
||||
setCommentingBlock={setCommentingBlock}
|
||||
handleSendMessage={handleSendMessage}
|
||||
highlightedBlockId={highlightedBlockId}
|
||||
canOverrideVisibility={canOverrideVisibility}
|
||||
handleClaim={handleClaim}
|
||||
handleSubmitReview={handleSubmitReview}
|
||||
handlePassReview={handlePassReview}
|
||||
handleRejectReview={handleRejectReview}
|
||||
handleApprove={handleApprove}
|
||||
handleRejectApprove={handleRejectApprove}
|
||||
editorSession={editorSession}
|
||||
handleSaveBlockEdit={handleSaveBlockEdit}
|
||||
closeBlockEditor={closeBlockEditor}
|
||||
commentingBlock={commentingBlock}
|
||||
handleAddComment={handleAddComment}
|
||||
/>
|
||||
}
|
||||
rightsModalOpen={rightsModalOpen}
|
||||
visibleDeliverables={visibleDeliverables}
|
||||
activeDocId={activeDocId}
|
||||
canAssignLead={canAssignLead}
|
||||
handleSelectDoc={handleSelectDoc}
|
||||
handleRoleChangeForDoc={handleRoleChangeForDoc}
|
||||
session={session}
|
||||
setSession={setSession}
|
||||
handleLogout={handleLogout}
|
||||
userSettingsOpen={userSettingsOpen}
|
||||
roles={roles}
|
||||
setAllUsers={setAllUsers}
|
||||
setCurrentUser={setCurrentUser}
|
||||
docVisibilityModalOpen={docVisibilityModalOpen}
|
||||
setDocVisibilityModalOpen={setDocVisibilityModalOpen}
|
||||
targetDocForVisibility={targetDocForVisibility}
|
||||
handleSaveDocVisibilityRule={handleSaveDocVisibilityRule}
|
||||
createProjectModalOpen={createProjectModalOpen}
|
||||
createProjectSubmitting={createProjectSubmitting}
|
||||
setCreateProjectError={setCreateProjectError}
|
||||
handleCreateProjectConfirm={handleCreateProjectConfirm}
|
||||
createProjectError={createProjectError}
|
||||
createProjectSubmitPhase={createProjectSubmitPhase}
|
||||
allUsers={allUsers}
|
||||
projectMembersModalOpen={projectMembersModalOpen}
|
||||
currentProject={currentProject}
|
||||
canManageProjectMembers={canManageProjectMembers}
|
||||
projectMembersError={projectMembersError}
|
||||
handleAddProjectMember={handleAddProjectMember}
|
||||
handleUpdateProjectMemberRole={handleUpdateProjectMemberRole}
|
||||
handleRemoveProjectMember={handleRemoveProjectMember}
|
||||
handleRestoreProjectMember={handleRestoreProjectMember}
|
||||
projectSettingsOpen={projectSettingsOpen}
|
||||
currentActiveSession={currentActiveSession}
|
||||
/>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
|
||||
test('workbench shell lives outside App', () => {
|
||||
const app = readFileSync(new URL('../../App.tsx', import.meta.url), 'utf8');
|
||||
const view = readFileSync(new URL('./WorkbenchView.tsx', import.meta.url), 'utf8');
|
||||
assert.equal((app.match(/<WorkbenchView\b/g) || []).length, 1);
|
||||
assert.equal(app.includes('<AppShell'), false);
|
||||
assert.equal(view.includes('<AppShell'), true);
|
||||
assert.equal(view.includes('<ProjectSettingsModal'), true);
|
||||
assert.equal(view.includes('<FirstLoginPasswordModal'), true);
|
||||
});
|
||||
@@ -0,0 +1,294 @@
|
||||
import type { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||
import { AppShell } from './AppShell';
|
||||
import { UserProjectRightsModal } from '../user/UserProjectRightsModal';
|
||||
import { UserSettingsModal } from '../user/UserSettingsModal';
|
||||
import { DocVisibilityModal } from '../doc/DocVisibilityModal';
|
||||
import { CreateProjectModal, type CreateProjectConfirmData } from '../project/CreateProjectModal';
|
||||
import { ProjectMembersModal } from '../project/ProjectMembersModal';
|
||||
import { ProjectSettingsModal } from '../project/ProjectSettingsModal';
|
||||
import { FirstLoginPasswordModal } from '../auth/FirstLoginPasswordModal';
|
||||
import { authClient } from '../../api/auth';
|
||||
import type { LoginResult } from '../../types/auth';
|
||||
import type { DocPermissionRole, UserProfile, UserRole } from '../../types/docgov';
|
||||
import type { DocVisibilityRule, RoleDefinition } from '../../types/rbac';
|
||||
import type { CreateProjectSubmitPhase } from '../../lib/createProjectSubmitGuard';
|
||||
import type {
|
||||
DeliverableCategory,
|
||||
DeliverableItem,
|
||||
ProjectInfo,
|
||||
ProjectMember,
|
||||
ProjectMemberRole,
|
||||
ProjectSession,
|
||||
} from '../../types/workbench';
|
||||
|
||||
export type WorkbenchWorkflowState = {
|
||||
activeKey: string;
|
||||
version: number;
|
||||
creatorId: number;
|
||||
lifecycleStatus: string;
|
||||
};
|
||||
|
||||
export interface WorkbenchViewProps {
|
||||
visibleSessions: ProjectSession[];
|
||||
activeSessionId: string;
|
||||
sessions: ProjectSession[];
|
||||
deliverables: DeliverableItem[];
|
||||
currentProjectId: number;
|
||||
setActiveSessionId: (id: string) => void;
|
||||
setActiveDocId: (id: string) => void;
|
||||
setActiveCategory: (category: DeliverableCategory) => void;
|
||||
setCreateProjectModalOpen: (open: boolean) => void;
|
||||
canCreateProject: boolean;
|
||||
currentUser: UserProfile;
|
||||
currentDocRole: UserRole;
|
||||
setRightsModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setUserSettingsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setView: (view: 'workbench' | 'admin') => void;
|
||||
setProjectMembersModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
currentWfState: WorkbenchWorkflowState;
|
||||
workflowGovernanceEnabled: boolean;
|
||||
setProjectSettingsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setProjectWorkflowsState: Dispatch<SetStateAction<Record<number, WorkbenchWorkflowState>>>;
|
||||
workflowBanner: ReactNode;
|
||||
chatContent: ReactNode;
|
||||
docContent: ReactNode;
|
||||
rightsModalOpen: boolean;
|
||||
visibleDeliverables: DeliverableItem[];
|
||||
activeDocId: string;
|
||||
canAssignLead: boolean;
|
||||
handleSelectDoc: (id: string) => void;
|
||||
handleRoleChangeForDoc: (docId: string, role: DocPermissionRole) => void;
|
||||
session: LoginResult | null;
|
||||
setSession: Dispatch<SetStateAction<LoginResult | null>>;
|
||||
handleLogout: () => void;
|
||||
userSettingsOpen: boolean;
|
||||
roles: RoleDefinition[];
|
||||
setAllUsers: Dispatch<SetStateAction<UserProfile[]>>;
|
||||
setCurrentUser: Dispatch<SetStateAction<UserProfile>>;
|
||||
docVisibilityModalOpen: boolean;
|
||||
setDocVisibilityModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
targetDocForVisibility: DeliverableItem | null;
|
||||
handleSaveDocVisibilityRule: (docId: string, rule: DocVisibilityRule | undefined) => void;
|
||||
createProjectModalOpen: boolean;
|
||||
createProjectSubmitting: boolean;
|
||||
setCreateProjectError: Dispatch<SetStateAction<string | null>>;
|
||||
handleCreateProjectConfirm: (data: CreateProjectConfirmData) => void | Promise<void>;
|
||||
createProjectError: string | null;
|
||||
createProjectSubmitPhase: CreateProjectSubmitPhase | null;
|
||||
allUsers: UserProfile[];
|
||||
projectMembersModalOpen: boolean;
|
||||
currentProject: ProjectInfo | undefined;
|
||||
canManageProjectMembers: boolean;
|
||||
projectMembersError: string | null;
|
||||
handleAddProjectMember: (member: Omit<ProjectMember, 'joinedAt' | 'status'>) => void | Promise<void>;
|
||||
handleUpdateProjectMemberRole: (userId: number, role: ProjectMemberRole) => void | Promise<void>;
|
||||
handleRemoveProjectMember: (userId: number) => void | Promise<void>;
|
||||
handleRestoreProjectMember: (userId: number) => void | Promise<void>;
|
||||
projectSettingsOpen: boolean;
|
||||
currentActiveSession: ProjectSession | undefined;
|
||||
}
|
||||
|
||||
export function WorkbenchView({
|
||||
visibleSessions,
|
||||
activeSessionId,
|
||||
sessions,
|
||||
deliverables,
|
||||
currentProjectId,
|
||||
setActiveSessionId,
|
||||
setActiveDocId,
|
||||
setActiveCategory,
|
||||
setCreateProjectModalOpen,
|
||||
canCreateProject,
|
||||
currentUser,
|
||||
currentDocRole,
|
||||
setRightsModalOpen,
|
||||
setUserSettingsOpen,
|
||||
setView,
|
||||
setProjectMembersModalOpen,
|
||||
currentWfState,
|
||||
workflowGovernanceEnabled,
|
||||
setProjectSettingsOpen,
|
||||
setProjectWorkflowsState,
|
||||
workflowBanner,
|
||||
chatContent,
|
||||
docContent,
|
||||
rightsModalOpen,
|
||||
visibleDeliverables,
|
||||
activeDocId,
|
||||
canAssignLead,
|
||||
handleSelectDoc,
|
||||
handleRoleChangeForDoc,
|
||||
session,
|
||||
setSession,
|
||||
handleLogout,
|
||||
userSettingsOpen,
|
||||
roles,
|
||||
setAllUsers,
|
||||
setCurrentUser,
|
||||
docVisibilityModalOpen,
|
||||
setDocVisibilityModalOpen,
|
||||
targetDocForVisibility,
|
||||
handleSaveDocVisibilityRule,
|
||||
createProjectModalOpen,
|
||||
createProjectSubmitting,
|
||||
setCreateProjectError,
|
||||
handleCreateProjectConfirm,
|
||||
createProjectError,
|
||||
createProjectSubmitPhase,
|
||||
allUsers,
|
||||
projectMembersModalOpen,
|
||||
currentProject,
|
||||
canManageProjectMembers,
|
||||
projectMembersError,
|
||||
handleAddProjectMember,
|
||||
handleUpdateProjectMemberRole,
|
||||
handleRemoveProjectMember,
|
||||
handleRestoreProjectMember,
|
||||
projectSettingsOpen,
|
||||
currentActiveSession,
|
||||
}: WorkbenchViewProps) {
|
||||
return (
|
||||
<>
|
||||
<AppShell
|
||||
sessions={visibleSessions}
|
||||
activeSessionId={activeSessionId}
|
||||
onSelectSession={(sessId) => {
|
||||
if (window.location.hash) {
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search);
|
||||
}
|
||||
setActiveSessionId(sessId);
|
||||
const targetSess = sessions.find((s) => s.sessionId === sessId);
|
||||
if (targetSess && targetSess.projectId !== currentProjectId) {
|
||||
const targetProjDocs = deliverables.filter((d) => d.projectId === targetSess.projectId);
|
||||
if (targetProjDocs.length > 0) {
|
||||
setActiveDocId(targetProjDocs[0].docId);
|
||||
setActiveCategory(targetProjDocs[0].category);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onNewSession={() => setCreateProjectModalOpen(true)}
|
||||
canCreateProject={canCreateProject}
|
||||
currentUser={currentUser}
|
||||
currentRole={currentDocRole}
|
||||
currentUserName={currentUser.name}
|
||||
onChangeRole={(_, __) => {}}
|
||||
onOpenRightsModal={() => setRightsModalOpen(true)}
|
||||
onOpenUserSettings={() => setUserSettingsOpen(true)}
|
||||
onOpenAdminSettings={() => setView('admin')}
|
||||
onOpenProjectMembers={() => setProjectMembersModalOpen(true)}
|
||||
activeWorkflowKey={currentWfState.activeKey}
|
||||
workflowVersion={currentWfState.version}
|
||||
lifecycleStatus={currentWfState.lifecycleStatus}
|
||||
isCreator={currentWfState.creatorId === currentUser.id}
|
||||
governanceEnabled={workflowGovernanceEnabled}
|
||||
onOpenProjectSettings={() => setProjectSettingsOpen(true)}
|
||||
onWorkflowChanged={(newKey, newVer) => {
|
||||
setProjectWorkflowsState((prev) => ({
|
||||
...prev,
|
||||
[currentProjectId]: {
|
||||
...currentWfState,
|
||||
activeKey: newKey,
|
||||
version: newVer,
|
||||
lifecycleStatus: 'active',
|
||||
},
|
||||
}));
|
||||
}}
|
||||
workflowBanner={workflowBanner}
|
||||
chatContent={chatContent}
|
||||
docContent={docContent}
|
||||
/>
|
||||
<UserProjectRightsModal
|
||||
isOpen={rightsModalOpen}
|
||||
onClose={() => setRightsModalOpen(false)}
|
||||
currentUser={currentUser}
|
||||
currentProjectTitle={sessions.find((s) => s.sessionId === activeSessionId)?.title || '当前项目'}
|
||||
deliverables={visibleDeliverables}
|
||||
activeDocId={activeDocId}
|
||||
canAssignLead={canAssignLead}
|
||||
onSelectDoc={handleSelectDoc}
|
||||
onRoleChangeForDoc={handleRoleChangeForDoc}
|
||||
/>
|
||||
<FirstLoginPasswordModal
|
||||
isOpen={!!session?.mustChangePassword}
|
||||
user={currentUser}
|
||||
onPasswordChanged={() => {
|
||||
if (!session) return;
|
||||
const updated = { ...session, mustChangePassword: false };
|
||||
setSession(updated);
|
||||
authClient.saveSession(updated);
|
||||
}}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
<UserSettingsModal
|
||||
isOpen={userSettingsOpen}
|
||||
onClose={() => setUserSettingsOpen(false)}
|
||||
currentUser={currentUser}
|
||||
roles={roles}
|
||||
onUpdateProfile={(updatedProfile) => {
|
||||
setAllUsers((prev) =>
|
||||
prev.map((u) => (u.id === currentUser.id ? { ...u, ...updatedProfile } : u))
|
||||
);
|
||||
setCurrentUser((prev) => ({ ...prev, ...updatedProfile }));
|
||||
}}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
<DocVisibilityModal
|
||||
isOpen={docVisibilityModalOpen}
|
||||
onClose={() => setDocVisibilityModalOpen(false)}
|
||||
doc={targetDocForVisibility}
|
||||
roles={roles}
|
||||
currentUserName={currentUser.name}
|
||||
onSaveRule={handleSaveDocVisibilityRule}
|
||||
/>
|
||||
<CreateProjectModal
|
||||
isOpen={createProjectModalOpen}
|
||||
onClose={() => {
|
||||
if (createProjectSubmitting) return;
|
||||
setCreateProjectModalOpen(false);
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
onSubmit={handleCreateProjectConfirm}
|
||||
submitError={createProjectError}
|
||||
submitting={createProjectSubmitting}
|
||||
submitPhase={createProjectSubmitPhase}
|
||||
currentUser={currentUser}
|
||||
availableUsers={allUsers}
|
||||
/>
|
||||
<ProjectMembersModal
|
||||
isOpen={projectMembersModalOpen}
|
||||
onClose={() => setProjectMembersModalOpen(false)}
|
||||
project={currentProject}
|
||||
currentUser={currentUser}
|
||||
availableUsers={allUsers}
|
||||
canManage={canManageProjectMembers}
|
||||
membersError={projectMembersError}
|
||||
onAddMember={handleAddProjectMember}
|
||||
onUpdateMemberRole={handleUpdateProjectMemberRole}
|
||||
onRemoveMember={handleRemoveProjectMember}
|
||||
onRestoreMember={handleRestoreProjectMember}
|
||||
/>
|
||||
<ProjectSettingsModal
|
||||
open={projectSettingsOpen}
|
||||
projectId={currentProjectId}
|
||||
projectName={currentActiveSession?.title || '当前项目'}
|
||||
members={(currentProject?.members || [])
|
||||
.filter((m) => m.status === 'active')
|
||||
.map((m) => ({
|
||||
userId: m.userId,
|
||||
name: allUsers.find((u) => u.id === m.userId)?.name || `用户 ${m.userId}`,
|
||||
}))}
|
||||
onClose={() => setProjectSettingsOpen(false)}
|
||||
onActivated={(activeKey) => {
|
||||
setProjectWorkflowsState((prev) => ({
|
||||
...prev,
|
||||
[currentProjectId]: {
|
||||
...currentWfState,
|
||||
activeKey,
|
||||
lifecycleStatus: 'active',
|
||||
},
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user