2026-07-20 10:50:42 +08:00
|
|
|
|
import { useState } from 'react'
|
2026-07-20 13:02:26 +08:00
|
|
|
|
import { ConfigProvider, Message, Spin } from '@arco-design/web-react'
|
2026-07-20 10:50:42 +08:00
|
|
|
|
import '@arco-design/web-react/dist/css/arco.css'
|
|
|
|
|
|
import '../App.css'
|
2026-07-20 13:02:26 +08:00
|
|
|
|
import { login, setApiBaseUrl, type ApiSession } from '../api/client'
|
|
|
|
|
|
import { mapWorkspace } from '../api/mappers'
|
|
|
|
|
|
import { fetchProjectWorkspace, fetchProjects } from '../api/projects'
|
2026-07-20 10:50:42 +08:00
|
|
|
|
import { LoginPage } from '../pages/login'
|
2026-07-20 18:25:04 +08:00
|
|
|
|
import { ProjectPage } from '../pages/workspace-home'
|
|
|
|
|
|
import type { ChannelKey, Project, ProjectWorkspace, Screen, Theme, WorkbenchView } from '../pages/projects/project-types'
|
2026-07-20 10:50:42 +08:00
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
|
const [screen, setScreen] = useState<Screen>('login')
|
|
|
|
|
|
const [theme, setTheme] = useState<Theme>('light')
|
2026-07-20 11:40:45 +08:00
|
|
|
|
const [activeView, setActiveView] = useState<WorkbenchView>('workspace')
|
2026-07-20 13:02:26 +08:00
|
|
|
|
const [session, setSession] = useState<ApiSession | null>(null)
|
|
|
|
|
|
const [workspaces, setWorkspaces] = useState<ProjectWorkspace[]>([])
|
|
|
|
|
|
const [activeProjectID, setActiveProjectID] = useState<string>('')
|
2026-07-20 10:50:42 +08:00
|
|
|
|
const [activeChannel, setActiveChannel] = useState<ChannelKey>('overview')
|
2026-07-20 18:25:04 +08:00
|
|
|
|
const [activeTaskID, setActiveTaskID] = useState<string | null>(null)
|
2026-07-20 12:45:13 +08:00
|
|
|
|
const [, setSelectedItem] = useState('Inbox 待处理(36)')
|
2026-07-20 13:02:26 +08:00
|
|
|
|
const [loading, setLoading] = useState(false)
|
2026-07-20 10:50:42 +08:00
|
|
|
|
|
|
|
|
|
|
const dark = theme === 'dark'
|
2026-07-20 13:02:26 +08:00
|
|
|
|
const activeWorkspace = workspaces.find((workspace) => workspace.project.id === activeProjectID) ?? workspaces[0]
|
|
|
|
|
|
|
|
|
|
|
|
async function handleLogin(input: { server: string; email: string; password: string }) {
|
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
setApiBaseUrl(input.server)
|
|
|
|
|
|
const nextSession = await login(input.email, input.password)
|
|
|
|
|
|
const backendProjects = await fetchProjects(nextSession)
|
|
|
|
|
|
const backendWorkspaces = await Promise.all(
|
|
|
|
|
|
backendProjects.map((project, index) => fetchProjectWorkspace(nextSession, project.ID ?? project.id ?? '').then((workspace) => mapWorkspace(workspace, index))),
|
|
|
|
|
|
)
|
|
|
|
|
|
setSession(nextSession)
|
|
|
|
|
|
setWorkspaces(backendWorkspaces)
|
|
|
|
|
|
setActiveProjectID(backendWorkspaces[0]?.project.id ?? '')
|
|
|
|
|
|
setActiveChannel('overview')
|
|
|
|
|
|
setActiveView('workspace')
|
|
|
|
|
|
setScreen('workbench')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
Message.error(error instanceof Error ? error.message : '登录失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-20 10:50:42 +08:00
|
|
|
|
|
2026-07-20 18:25:04 +08:00
|
|
|
|
function openTask(project: Project, taskID: string) {
|
|
|
|
|
|
setActiveProjectID(project.id)
|
|
|
|
|
|
setActiveView('project')
|
|
|
|
|
|
setActiveChannel('tasks')
|
|
|
|
|
|
setActiveTaskID(taskID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-20 10:50:42 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<ConfigProvider>
|
|
|
|
|
|
<main className={dark ? 'app theme-dark' : 'app'}>
|
|
|
|
|
|
{screen === 'login' ? (
|
2026-07-20 13:02:26 +08:00
|
|
|
|
<Spin loading={loading} style={{ width: '100%' }}>
|
|
|
|
|
|
<LoginPage onLogin={handleLogin} />
|
|
|
|
|
|
</Spin>
|
|
|
|
|
|
) : activeWorkspace && session ? (
|
2026-07-20 10:50:42 +08:00
|
|
|
|
<ProjectPage
|
2026-07-20 11:40:45 +08:00
|
|
|
|
activeView={activeView}
|
2026-07-20 13:02:26 +08:00
|
|
|
|
activeWorkspace={activeWorkspace}
|
|
|
|
|
|
workspaces={workspaces}
|
2026-07-20 10:50:42 +08:00
|
|
|
|
activeChannel={activeChannel}
|
2026-07-20 18:25:04 +08:00
|
|
|
|
activeTaskID={activeTaskID}
|
2026-07-20 10:50:42 +08:00
|
|
|
|
theme={theme}
|
2026-07-20 11:40:45 +08:00
|
|
|
|
onSelectWorkspace={() => setActiveView('workspace')}
|
2026-07-20 18:25:04 +08:00
|
|
|
|
onSelectWorkspaceInbox={() => setActiveView('workspace-inbox')}
|
2026-07-20 11:40:45 +08:00
|
|
|
|
onSelectProject={(project) => {
|
2026-07-20 13:02:26 +08:00
|
|
|
|
setActiveProjectID(project.id)
|
2026-07-20 11:40:45 +08:00
|
|
|
|
setActiveView('project')
|
2026-07-20 13:02:26 +08:00
|
|
|
|
setActiveChannel('overview')
|
2026-07-20 18:25:04 +08:00
|
|
|
|
setActiveTaskID(null)
|
|
|
|
|
|
}}
|
|
|
|
|
|
onSelectChannel={(channel) => {
|
|
|
|
|
|
setActiveChannel(channel)
|
|
|
|
|
|
setActiveTaskID(null)
|
2026-07-20 11:40:45 +08:00
|
|
|
|
}}
|
2026-07-20 10:50:42 +08:00
|
|
|
|
onSelectItem={setSelectedItem}
|
2026-07-20 18:25:04 +08:00
|
|
|
|
onOpenTask={openTask}
|
|
|
|
|
|
onCloseTask={() => setActiveTaskID(null)}
|
2026-07-20 10:50:42 +08:00
|
|
|
|
onToggleTheme={() => setTheme(dark ? 'light' : 'dark')}
|
|
|
|
|
|
/>
|
2026-07-20 13:02:26 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
<Spin loading />
|
2026-07-20 10:50:42 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</ConfigProvider>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default App
|