feat: connect search and project settings
This commit is contained in:
@@ -11,15 +11,18 @@ import {
|
||||
createTask,
|
||||
fetchProjectWorkspace,
|
||||
fetchProjects,
|
||||
updateProject,
|
||||
updateTask,
|
||||
uploadSource,
|
||||
} from '../api/projects'
|
||||
import type { SearchResultDTO } from '../api/search'
|
||||
import { LoginPage } from '../pages/login'
|
||||
import { ProjectActionModals, type CronDraft, type ProjectActionModal, type ProjectDraft, type SourceDraft, type TaskDraft } from '../pages/projects/project-action-modals'
|
||||
import type { ProjectSettingsUpdate } from '../pages/projects/project-sidebar'
|
||||
import { ProjectPage } from '../pages/workspace-home'
|
||||
import type { WorkspaceTaskUpdate } from '../pages/workspace-body'
|
||||
import type { ChannelKey, Project, ProjectWorkspace, Screen, Theme, WorkbenchView } from '../pages/projects/project-types'
|
||||
import { useWorkbenchSearch } from './use-workbench-search'
|
||||
|
||||
function App() {
|
||||
const [screen, setScreen] = useState<Screen>('login')
|
||||
@@ -34,6 +37,7 @@ function App() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [actionLoading, setActionLoading] = useState(false)
|
||||
const [activeModal, setActiveModal] = useState<ProjectActionModal>(null)
|
||||
const workspaceSearch = useWorkbenchSearch(session)
|
||||
|
||||
const dark = theme === 'dark'
|
||||
const activeWorkspace = workspaces.find((workspace) => workspace.project.id === activeProjectID) ?? workspaces[0]
|
||||
@@ -210,23 +214,35 @@ function App() {
|
||||
}, '任务已更新')
|
||||
}
|
||||
|
||||
function handleUpdateProject(update: ProjectSettingsUpdate) {
|
||||
setWorkspaces((current) =>
|
||||
current.map((workspace) => {
|
||||
if (workspace.project.id !== update.projectId) return workspace
|
||||
const nextProject = {
|
||||
...workspace.project,
|
||||
name: update.name,
|
||||
identifier: update.identifier,
|
||||
icon: update.icon,
|
||||
background: update.background,
|
||||
description: update.description,
|
||||
short: projectIconLabel(update.icon, update.identifier || update.name.slice(0, 2)),
|
||||
color: projectColor(update.background, workspace.project.color),
|
||||
}
|
||||
return { ...workspace, project: nextProject }
|
||||
}),
|
||||
)
|
||||
async function handleUpdateProject(update: ProjectSettingsUpdate) {
|
||||
const currentSession = requireSession()
|
||||
await updateProject(currentSession, update.projectId, {
|
||||
name: update.name,
|
||||
identifier: update.identifier,
|
||||
icon: update.icon,
|
||||
background: update.background,
|
||||
description: update.description,
|
||||
})
|
||||
await loadWorkspaces(currentSession, update.projectId)
|
||||
Message.success('项目设置已更新')
|
||||
}
|
||||
|
||||
function handleSelectSearchResult(result: SearchResultDTO) {
|
||||
const owningWorkspace = workspaces.find((workspace) => workspace.project.id === result.projectId)
|
||||
if (!owningWorkspace) {
|
||||
Message.warning('该搜索结果所在项目当前不可访问')
|
||||
return
|
||||
}
|
||||
const target = searchResultTarget(result.type)
|
||||
if (!target) {
|
||||
Message.warning('该搜索结果暂不支持导航')
|
||||
return
|
||||
}
|
||||
setActiveProjectID(owningWorkspace.project.id)
|
||||
setActiveView('project')
|
||||
setActiveChannel(target.channel)
|
||||
setActiveTaskID(target.openTask ? result.id : null)
|
||||
if (result.type === 'note') setSelectedItem(result.title)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -267,6 +283,13 @@ function App() {
|
||||
onUpdateWorkspaceTask={handleUpdateWorkspaceTask}
|
||||
onUpdateProject={handleUpdateProject}
|
||||
onCreateProjectTag={handleCreateProjectTag}
|
||||
searchQuery={workspaceSearch.query}
|
||||
searchLoading={workspaceSearch.loading}
|
||||
searchSearched={workspaceSearch.searched}
|
||||
searchResults={workspaceSearch.results}
|
||||
onSearchQueryChange={workspaceSearch.onQueryChange}
|
||||
onSearch={() => void workspaceSearch.onSearch()}
|
||||
onSelectSearchResult={handleSelectSearchResult}
|
||||
/>
|
||||
) : (
|
||||
<Spin loading />
|
||||
@@ -286,18 +309,11 @@ function App() {
|
||||
)
|
||||
}
|
||||
|
||||
function projectIconLabel(icon: string, fallback: string) {
|
||||
const trimmed = icon.trim()
|
||||
if (!trimmed) return fallback
|
||||
const chars = Array.from(trimmed)
|
||||
if (chars.length <= 2) return trimmed
|
||||
return chars.slice(0, 2).join('').toUpperCase()
|
||||
}
|
||||
|
||||
function projectColor(background: string, fallback: string) {
|
||||
const trimmed = background.trim()
|
||||
if (trimmed.startsWith('#') || trimmed.startsWith('rgb') || trimmed.startsWith('hsl')) return trimmed
|
||||
return fallback
|
||||
function searchResultTarget(type: string): { channel: ChannelKey; openTask: boolean } | null {
|
||||
if (type === 'project') return { channel: 'overview', openTask: false }
|
||||
if (type === 'task') return { channel: 'tasks', openTask: true }
|
||||
if (type === 'note') return { channel: 'notes', openTask: false }
|
||||
return null
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
58
apps/web_v1/src/app/use-workbench-search.ts
Normal file
58
apps/web_v1/src/app/use-workbench-search.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { Message } from '@arco-design/web-react'
|
||||
import type { ApiSession } from '../api/client'
|
||||
import { searchWorkspace, type SearchResultDTO } from '../api/search'
|
||||
|
||||
export function useWorkbenchSearch(session: ApiSession | null) {
|
||||
const [query, setQuery] = useState('')
|
||||
const [results, setResults] = useState<SearchResultDTO[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [searched, setSearched] = useState(false)
|
||||
const requestSequence = useRef(0)
|
||||
|
||||
function changeQuery(value: string) {
|
||||
requestSequence.current += 1
|
||||
setQuery(value)
|
||||
setLoading(false)
|
||||
setSearched(false)
|
||||
}
|
||||
|
||||
async function submitSearch() {
|
||||
const requestID = ++requestSequence.current
|
||||
const trimmedQuery = query.trim()
|
||||
if (!trimmedQuery) {
|
||||
setResults([])
|
||||
setSearched(false)
|
||||
Message.warning('请输入搜索关键词')
|
||||
return
|
||||
}
|
||||
if (!session) {
|
||||
Message.error('未登录或登录已失效')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setSearched(false)
|
||||
try {
|
||||
const response = await searchWorkspace(session, trimmedQuery)
|
||||
if (requestID !== requestSequence.current) return
|
||||
setResults(response.items)
|
||||
setSearched(true)
|
||||
} catch (error) {
|
||||
if (requestID !== requestSequence.current) return
|
||||
setResults([])
|
||||
Message.error(error instanceof Error ? error.message : '搜索失败,请稍后重试')
|
||||
} finally {
|
||||
if (requestID === requestSequence.current) setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
results,
|
||||
loading,
|
||||
searched,
|
||||
onQueryChange: changeQuery,
|
||||
onSearch: submitSearch,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user