@ -64,11 +64,22 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
const [ delTarget , setDelTarget ] = useState < string | null > ( null ) ;
const [ deleteLocal , setDeleteLocal ] = useState ( false ) ;
const [ showNewProject , setShowNewProject ] = useState ( false ) ;
const [ view , setView ] = useState < "all" | "by-group" > ( "all" ) ;
const { data : projects = [ ] } = useQuery ( {
queryKey : [ "projects" , spaceId ] ,
queryFn : ( ) = > getProjects ( undefined , spaceId ) ,
} ) ;
const { data : groups = [ ] } = useQuery ( {
queryKey : [ "groups" , spaceId ] ,
queryFn : ( ) = > getGroups ( spaceId ) ,
} ) ;
const { data : maps = [ ] } = useQuery ( { queryKey : [ "maps" ] , queryFn : getGroupMaps } ) ;
const getProjectGroupNames = ( projectId : string ) : string [ ] = > {
const groupIds = maps . filter ( ( m ) = > m . project_id === projectId ) . map ( ( m ) = > m . group_id ) ;
return groupIds . map ( ( id ) = > groups . find ( ( g ) = > g . id === id ) ? . name ? ? "" ) . filter ( Boolean ) ;
} ;
const confirmDelete = async ( ) = > {
if ( ! delTarget ) return ;
@ -79,11 +90,29 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
showToast ( "已删除" ) ;
} ;
const PROJECT_VIEWS = [
{ key : "all" , label : "全览表格" } ,
{ key : "by-group" , label : "按产品组分组" } ,
] as const ;
return (
< >
< div className = "flex items-center justify-between mb-4" >
< h2 className = "text-sm font-semibold text-gray-700" > 共 { projects . length } 个 项 目 < / h2 >
< div className = "flex gap-2" >
< div className = "flex items-center gap-2" >
< div className = "flex rounded-lg border border-gray-200 overflow-hidden" >
{ PROJECT_VIEWS . map ( ( { key , label } ) = > (
< button
key = { key }
onClick = { ( ) = > setView ( key ) }
className = { ` px-3 py-1.5 text-xs transition-colors ${
view === key ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50"
} ` }
>
{ label }
< / button >
) ) }
< / div >
< button
onClick = { ( ) = > setShowNewProject ( true ) }
className = "px-3 py-1.5 rounded-lg border border-indigo-200 text-indigo-600 bg-indigo-50 text-sm hover:bg-indigo-100"
@ -99,11 +128,13 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
< / div >
< / div >
{ /* 全览表格视图 */ }
{ view === "all" && (
< div className = "overflow-x-auto rounded-xl border border-gray-200 shadow-sm" >
< table className = "w-full text-sm" >
< thead >
< tr className = "bg-gray-50 border-b border-gray-200" >
{ [ "名称 / 描述" , " 类型 ", "状态" , "优先级" , "技术栈" , "本地路径" , "操作" ] . map ( ( h ) = > (
{ [ "名称 / 描述" , " 产品组 ", "状态" , "优先级" , "技术栈" , "本地路径" , "操作" ] . map ( ( h ) = > (
< th key = { h } className = "px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide whitespace-nowrap" >
{ h }
< / th >
@ -111,7 +142,9 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
< / tr >
< / thead >
< tbody className = "divide-y divide-gray-100" >
{ projects . map ( ( p ) = > (
{ projects . map ( ( p ) = > {
const groupNames = getProjectGroupNames ( p . id ) ;
return (
< tr key = { p . id } className = "bg-white hover:bg-gray-50/60 transition-colors group" >
< td className = "px-5 py-4" >
< div className = "font-semibold text-gray-900" > { p . name } < / div >
@ -120,11 +153,15 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
) }
< / td >
< td className = "px-5 py-4" >
{ p . type && (
< span className = "px-2 py-0.5 rounded-md bg-indigo-50 text-indigo-700 text-xs font-medium border border-indigo-100" >
{ p . type }
< div className = "flex flex-wrap gap-1" >
{ groupNames . length > 0 ? groupNames . map ( ( gName ) = > (
< span key = { gName } className = "px-2 py-0.5 rounded-md bg-blue-50 text-blue-700 text-xs font-medium border border-blue-100" >
{ gName }
< / span >
) ) : (
< span className = "text-xs text-gray-300" > — < / span >
) }
< / div >
< / td >
< td className = "px-5 py-4" > < StatusBadge status = { p . status } / > < / td >
< td className = "px-5 py-4" > < PriorityBadge priority = { p . priority } / > < / td >
@ -162,13 +199,118 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
< / div >
< / td >
< / tr >
) ) }
) ;
} ) }
< / tbody >
< / table >
{ projects . length === 0 && (
< div className = "text-center py-20 text-gray-400 text-sm" > 暂 无 项 目 < / div >
) }
< / div >
) }
{ /* 按产品组分组视图 */ }
{ view === "by-group" && (
< div className = "space-y-6" >
{ groups . map ( ( g ) = > {
const groupProjects = maps
. filter ( ( m ) = > m . group_id === g . id )
. map ( ( m ) = > projects . find ( ( p ) = > p . id === m . project_id ) )
. filter ( ( p ) : p is Project = > p !== undefined ) ;
return (
< div key = { g . id } className = "rounded-xl border border-gray-200 shadow-sm overflow-hidden" >
< div className = "px-5 py-3 bg-gray-50 border-b border-gray-200 flex items-center gap-2" >
< span className = "font-semibold text-gray-700" > { g . name } < / span >
< span className = "text-xs text-gray-400 bg-white border border-gray-200 rounded-full px-2 py-0.5" >
{ groupProjects . length }
< / span >
{ g . description && (
< span className = "text-xs text-gray-400 border-l border-gray-200 pl-2" > { g . description } < / span >
) }
< / div >
< table className = "w-full text-sm" >
< thead >
< tr className = "border-b border-gray-100" >
{ [ "名称 / 描述" , "状态" , "优先级" , "操作" ] . map ( ( h ) = > (
< th key = { h } className = "px-5 py-2.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide whitespace-nowrap" >
{ h }
< / th >
) ) }
< / tr >
< / thead >
< tbody className = "divide-y divide-gray-50" >
{ groupProjects . map ( ( p ) = > (
< tr key = { p . id } className = "bg-white hover:bg-gray-50/60 transition-colors group" >
< td className = "px-5 py-3" >
< div className = "font-medium text-gray-900" > { p . name } < / div >
{ p . description && (
< div className = "text-xs text-gray-400 mt-0.5 max-w-[240px] truncate" > { p . description } < / div >
) }
< / td >
< td className = "px-5 py-3" > < StatusBadge status = { p . status } / > < / td >
< td className = "px-5 py-3" > < PriorityBadge priority = { p . priority } / > < / td >
< td className = "px-5 py-3" >
< div className = "flex gap-2 opacity-60 group-hover:opacity-100 transition-opacity" >
< button onClick = { ( ) = > openEdit ( p ) } className = "px-2.5 py-1 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100" > 编 辑 < / button >
< button onClick = { ( ) = > setDelTarget ( p . id ) } className = "px-2.5 py-1 rounded-lg border border-red-200 text-xs text-red-500 hover:bg-red-50" > 删 除 < / button >
< / div >
< / td >
< / tr >
) ) }
{ groupProjects . length === 0 && (
< tr >
< td colSpan = { 4 } className = "px-5 py-4 text-xs text-gray-300 text-center" > 暂 无 项 目 < / td >
< / tr >
) }
< / tbody >
< / table >
< / div >
) ;
} ) }
{ ( ( ) = > {
const ungrouped = projects . filter ( ( p ) = > ! maps . some ( ( m ) = > m . project_id === p . id ) ) ;
if ( ungrouped . length === 0 ) return null ;
return (
< div className = "rounded-xl border border-dashed border-gray-200 overflow-hidden" >
< div className = "px-5 py-3 bg-gray-50/50 border-b border-gray-200 flex items-center gap-2" >
< span className = "font-semibold text-gray-400" > 未 分 组 < / span >
< span className = "text-xs text-gray-400 bg-white border border-gray-200 rounded-full px-2 py-0.5" > { ungrouped . length } < / span >
< / div >
< table className = "w-full text-sm" >
< thead >
< tr className = "border-b border-gray-100" >
{ [ "名称 / 描述" , "状态" , "优先级" , "操作" ] . map ( ( h ) = > (
< th key = { h } className = "px-5 py-2.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide whitespace-nowrap" > { h } < / th >
) ) }
< / tr >
< / thead >
< tbody className = "divide-y divide-gray-50" >
{ ungrouped . map ( ( p ) = > (
< tr key = { p . id } className = "bg-white hover:bg-gray-50/60 transition-colors group" >
< td className = "px-5 py-3" >
< div className = "font-medium text-gray-900" > { p . name } < / div >
{ p . description && < div className = "text-xs text-gray-400 mt-0.5 max-w-[240px] truncate" > { p . description } < / div > }
< / td >
< td className = "px-5 py-3" > < StatusBadge status = { p . status } / > < / td >
< td className = "px-5 py-3" > < PriorityBadge priority = { p . priority } / > < / td >
< td className = "px-5 py-3" >
< div className = "flex gap-2 opacity-60 group-hover:opacity-100 transition-opacity" >
< button onClick = { ( ) = > openEdit ( p ) } className = "px-2.5 py-1 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100" > 编 辑 < / button >
< button onClick = { ( ) = > setDelTarget ( p . id ) } className = "px-2.5 py-1 rounded-lg border border-red-200 text-xs text-red-500 hover:bg-red-50" > 删 除 < / button >
< / div >
< / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) ;
} ) ( ) }
{ groups . length === 0 && projects . length === 0 && (
< div className = "text-center py-16 text-gray-400 text-sm" > 暂 无 产 品 组 , 请 先 创 建 产 品 组 并 添 加 成 员 < / div >
) }
< / div >
) }
{ delTarget && (
< Dialog title = "确认删除" onClose = { ( ) = > { setDelTarget ( null ) ; setDeleteLocal ( false ) ; } } size = "sm" >
@ -204,6 +346,7 @@ function ProjectsTab({ spaceId }: { spaceId?: string }) {
function GroupsTab ( { spaceId } : { spaceId? : string } ) {
const showToast = useUIStore ( ( s ) = > s . showToast ) ;
const qc = useQueryClient ( ) ;
const [ groupView , setGroupView ] = useState < "overview" | "health" | "detail" > ( "overview" ) ;
const [ groupModal , setGroupModal ] = useState < ProductGroup | null | undefined > ( undefined ) ;
const [ memberTarget , setMemberTarget ] = useState < ProductGroup | null > ( null ) ;
const [ delGroup , setDelGroup ] = useState < string | null > ( null ) ;
@ -250,51 +393,18 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
qc . invalidateQueries ( { queryKey : [ "groups" , spaceId ] } ) ;
} ;
return (
< >
< div className = "flex items-center justify-between mb-4" >
< h2 className = "text-sm font-semibold text-gray-700" > 共 { groups . length } 个 产 品 组 < / h2 >
< button
onClick = { ( ) = > setGroupModal ( null ) }
className = "px-3 py-1.5 rounded-lg bg-blue-600 text-white text-sm hover:bg-blue-700"
>
+ 新 建 产 品 组
< / button >
< / div >
const GROUP_VIEWS = [
{ key : "overview" , label : "产品组概览" } ,
{ key : "health" , label : "健康度" } ,
{ key : "detail" , label : "成员详情" } ,
] as const ;
< div className = "space-y-4" >
{ groups . map ( ( g , idx ) = > {
const members = maps . filter ( ( m ) = > m . group_id === g . id ) ;
return (
< div key = { g . id } className = "bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden" >
< div className = "flex items-center justify-between px-5 py-4 border-b border-gray-100 bg-gray-50/60" >
< div className = "flex items-center gap-3 min-w-0" >
{ /* 排序按钮 */ }
< div className = "flex flex-col gap-0.5 shrink-0" >
< button
onClick = { ( ) = > move ( idx , - 1 ) }
disabled = { idx === 0 }
className = "text-gray-300 hover:text-gray-600 disabled:opacity-20 leading-none text-[10px]"
title = "上移"
> ▲ < / button >
< button
onClick = { ( ) = > move ( idx , 1 ) }
disabled = { idx === groups . length - 1 }
className = "text-gray-300 hover:text-gray-600 disabled:opacity-20 leading-none text-[10px]"
title = "下移"
> ▼ < / button >
< / div >
< span className = "font-semibold text-gray-800" > { g . name } < / span >
< span className = "text-xs font-mono text-gray-400 bg-white border border-gray-200 rounded px-1.5 py-0.5" > { g . id } < / span >
{ g . description && (
< span className = "text-xs text-gray-400 border-l border-gray-200 pl-3 truncate" > { g . description } < / span >
) }
< / div >
< div className = "flex gap-2 shrink-0" >
const renderGroupActions = ( g : ProductGroup ) = > (
< div className = "flex gap-1.5 shrink-0" >
< button
onClick = { ( ) = > handleInspect ( g . id ) }
disabled = { inspecting . has ( g . id ) }
className = "px-3 py-1.5 rounded-lg border border-indigo-200 text-xs text-indigo-600 hover:bg-indigo-50 transition-colors disabled:opacity-50 flex items-center gap-1"
className = "px-2.5 py-1 rounded-lg border border-indigo-200 text-xs text-indigo-600 hover:bg-indigo-50 transition-colors disabled:opacity-50 flex items-center gap-1"
title = "聚合全组导师报告并复制到剪贴板"
>
{ inspecting . has ( g . id ) ? (
@ -303,19 +413,170 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
< path className = "opacity-75" fill = "currentColor" d = "M4 12a8 8 0 018-8v8H4z" / >
< / svg >
) : "🔍" }
巡 检 全 组
巡 检
< / button >
< button onClick = { ( ) = > setMemberTarget ( g ) } className = "px-3 py-1.5 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100 transition-colors" >
+ 成 员
< button onClick = { ( ) = > setMemberTarget ( g ) } className = "px-2.5 py-1 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100" > + 成 员 < / button >
< button onClick = { ( ) = > setGroupModal ( g ) } className = "px-2.5 py-1 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100" > 编 辑 < / button >
< button onClick = { ( ) = > setDelGroup ( g . id ) } className = "px-2.5 py-1 rounded-lg border border-red-200 text-xs text-red-500 hover:bg-red-50" > 删 除 < / button >
< / div >
) ;
return (
< >
< div className = "flex items-center justify-between mb-4" >
< h2 className = "text-sm font-semibold text-gray-700" > 共 { groups . length } 个 产 品 组 < / h2 >
< div className = "flex items-center gap-2" >
< div className = "flex rounded-lg border border-gray-200 overflow-hidden" >
{ GROUP_VIEWS . map ( ( { key , label } ) = > (
< button
key = { key }
onClick = { ( ) = > setGroupView ( key ) }
className = { ` px-3 py-1.5 text-xs transition-colors ${
groupView === key ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50"
} ` }
>
{ label }
< / button >
< button onClick = { ( ) = > setGroupModal ( g ) } className = "px-3 py-1.5 rounded-lg border border-gray-200 text-xs text-gray-600 hover:bg-gray-100 transition-colors" >
编 辑
< / button >
< button onClick = { ( ) = > setDelGroup ( g . id ) } className = "px-3 py-1.5 rounded-lg border border-red-200 text-xs text-red-500 hover:bg-red-50 transition-colors" >
删 除
) ) }
< / div >
< button
onClick = { ( ) = > setGroupModal ( null ) }
className = "px-3 py-1.5 rounded-lg bg-blue-600 text-white text-sm hover:bg-blue-700"
>
+ 新 建 产 品 组
< / button >
< / div >
< / div >
{ /* 产品组概览视图 */ }
{ groupView === "overview" && (
< div className = "overflow-x-auto rounded-xl border border-gray-200 shadow-sm" >
< table className = "w-full text-sm" >
< thead >
< tr className = "bg-gray-50 border-b border-gray-200" >
{ [ "产品组名称" , "项目数量" , "描述" , "操作" ] . map ( ( h ) = > (
< th key = { h } className = "px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide whitespace-nowrap" >
{ h }
< / th >
) ) }
< / tr >
< / thead >
< tbody className = "divide-y divide-gray-100" >
{ groups . map ( ( g ) = > {
const count = maps . filter ( ( m ) = > m . group_id === g . id ) . length ;
return (
< tr key = { g . id } className = "bg-white hover:bg-gray-50/60 transition-colors group" >
< td className = "px-5 py-4" >
< div className = "font-semibold text-gray-900" > { g . name } < / div >
< div className = "text-xs font-mono text-gray-400 mt-0.5" > { g . id } < / div >
< / td >
< td className = "px-5 py-4" >
< span className = "inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-50 text-blue-700 text-sm font-semibold border border-blue-100" >
{ count }
< / span >
< / td >
< td className = "px-5 py-4 max-w-[280px]" >
< span className = "text-sm text-gray-500 truncate block" > { g . description ? ? "—" } < / span >
< / td >
< td className = "px-5 py-4" >
< div className = "opacity-60 group-hover:opacity-100 transition-opacity" >
{ renderGroupActions ( g ) }
< / div >
< / td >
< / tr >
) ;
} ) }
< / tbody >
< / table >
{ groups . length === 0 && (
< div className = "text-center py-20 text-gray-400 text-sm" > 暂 无 产 品 组 < / div >
) }
< / div >
) }
{ /* 健康度视图 */ }
{ groupView === "health" && (
< div className = "overflow-x-auto rounded-xl border border-gray-200 shadow-sm" >
< table className = "w-full text-sm" >
< thead >
< tr className = "bg-gray-50 border-b border-gray-200" >
{ [ "产品组名称" , "进行中" , "维护中" , "暂停" , "已归档" , "合计" ] . map ( ( h ) = > (
< th key = { h } className = "px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide whitespace-nowrap" >
{ h }
< / th >
) ) }
< / tr >
< / thead >
< tbody className = "divide-y divide-gray-100" >
{ groups . map ( ( g ) = > {
const groupProjectIds = maps . filter ( ( m ) = > m . group_id === g . id ) . map ( ( m ) = > m . project_id ) ;
const gps = groupProjectIds
. map ( ( id ) = > projects . find ( ( p ) = > p . id === id ) )
. filter ( ( p ) : p is Project = > p !== undefined ) ;
const counts = {
active : gps.filter ( ( p ) = > p . status === "active" ) . length ,
maintenance : gps.filter ( ( p ) = > p . status === "maintenance" ) . length ,
paused : gps.filter ( ( p ) = > p . status === "paused" ) . length ,
archived : gps.filter ( ( p ) = > p . status === "archived" ) . length ,
} ;
return (
< tr key = { g . id } className = "bg-white hover:bg-gray-50/60 transition-colors" >
< td className = "px-5 py-4 font-semibold text-gray-900" > { g . name } < / td >
< td className = "px-5 py-4" >
{ counts . active > 0
? < span className = "px-2 py-0.5 rounded-full bg-green-100 text-green-800 text-xs font-medium" > { counts . active } < / span >
: < span className = "text-gray-300 text-xs" > — < / span > }
< / td >
< td className = "px-5 py-4" >
{ counts . maintenance > 0
? < span className = "px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800 text-xs font-medium" > { counts . maintenance } < / span >
: < span className = "text-gray-300 text-xs" > — < / span > }
< / td >
< td className = "px-5 py-4" >
{ counts . paused > 0
? < span className = "px-2 py-0.5 rounded-full bg-gray-100 text-gray-600 text-xs font-medium" > { counts . paused } < / span >
: < span className = "text-gray-300 text-xs" > — < / span > }
< / td >
< td className = "px-5 py-4" >
{ counts . archived > 0
? < span className = "px-2 py-0.5 rounded-full bg-red-100 text-red-700 text-xs font-medium" > { counts . archived } < / span >
: < span className = "text-gray-300 text-xs" > — < / span > }
< / td >
< td className = "px-5 py-4" >
< span className = "text-sm font-medium text-gray-600" > { Object . values ( counts ) . reduce ( ( a , b ) = > a + b , 0 ) } < / span >
< / td >
< / tr >
) ;
} ) }
< / tbody >
< / table >
{ groups . length === 0 && (
< div className = "text-center py-20 text-gray-400 text-sm" > 暂 无 产 品 组 < / div >
) }
< / div >
) }
{ /* 成员详情视图 */ }
{ groupView === "detail" && (
< div className = "space-y-4" >
{ groups . map ( ( g , idx ) = > {
const members = maps . filter ( ( m ) = > m . group_id === g . id ) ;
return (
< div key = { g . id } className = "bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden" >
< div className = "flex items-center justify-between px-5 py-4 border-b border-gray-100 bg-gray-50/60" >
< div className = "flex items-center gap-3 min-w-0" >
< div className = "flex flex-col gap-0.5 shrink-0" >
< button onClick = { ( ) = > move ( idx , - 1 ) } disabled = { idx === 0 } className = "text-gray-300 hover:text-gray-600 disabled:opacity-20 leading-none text-[10px]" title = "上移" > ▲ < / button >
< button onClick = { ( ) = > move ( idx , 1 ) } disabled = { idx === groups . length - 1 } className = "text-gray-300 hover:text-gray-600 disabled:opacity-20 leading-none text-[10px]" title = "下移" > ▼ < / button >
< / div >
< span className = "font-semibold text-gray-800" > { g . name } < / span >
< span className = "text-xs font-mono text-gray-400 bg-white border border-gray-200 rounded px-1.5 py-0.5" > { g . id } < / span >
{ g . description && (
< span className = "text-xs text-gray-400 border-l border-gray-200 pl-3 truncate" > { g . description } < / span >
) }
< / div >
{ renderGroupActions ( g ) }
< / div >
< div className = "px-5 py-4 flex flex-wrap gap-2" >
{ members . map ( ( m ) = > {
const p = projects . find ( ( x ) = > x . id === m . project_id ) ;
@ -323,39 +584,24 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
return p ? (
< div key = { m . project_id } className = "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gray-50 border border-gray-200 text-xs" >
< span className = "font-medium text-gray-700" > { p . name } < / span >
{ /* 平台标识 */ }
{ p . platform && (
< span className = { ` px-1.5 py-0.5 rounded border text-xs ${
p . platform === "wsl"
? "bg-orange-50 text-orange-600 border-orange-200"
: "bg-blue-50 text-blue-600 border-blue-200"
p . platform === "wsl" ? "bg-orange-50 text-orange-600 border-orange-200" : "bg-blue-50 text-blue-600 border-blue-200"
} ` }>
{ p . platform === "wsl" ? "WSL" : "Win" }
< / span >
) }
{ /* MCP 注入状态 */ }
{ hasPath ? (
< span
className = "flex items-center gap-1 px-1.5 py-0.5 rounded border bg-green-50 text-green-600 border-green-200"
title = { ` MCP 已注入: ${ p . win_path || p . wsl_path } ` }
>
< span className = "flex items-center gap-1 px-1.5 py-0.5 rounded border bg-green-50 text-green-600 border-green-200" title = { ` MCP 已注入: ${ p . win_path || p . wsl_path } ` } >
< span className = "w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" / >
MCP
< / span >
) : (
< span
className = "px-1.5 py-0.5 rounded border bg-gray-100 text-gray-400 border-gray-200"
title = "项目未配置路径, MCP 注入已跳过"
>
无 路 径
< / span >
< span className = "px-1.5 py-0.5 rounded border bg-gray-100 text-gray-400 border-gray-200" title = "项目未配置路径, MCP 注入已跳过" > 无 路 径 < / span >
) }
{ m . role && < span className = "text-gray-400 border-l border-gray-200 pl-2" > { m . role } < / span > }
< button
onClick = { async ( ) = > {
await removeGroupMember ( m . project_id , g . id ) ;
qc . invalidateQueries ( { queryKey : [ "maps" ] } ) ;
} }
onClick = { async ( ) = > { await removeGroupMember ( m . project_id , g . id ) ; qc . invalidateQueries ( { queryKey : [ "maps" ] } ) ; } }
className = "text-gray-300 hover:text-red-400 transition-colors ml-0.5"
> × < / button >
< / div >
@ -372,8 +618,8 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
< div className = "text-center py-16 text-gray-400 text-sm" > 暂 无 产 品 组 < / div >
) }
< / div >
) }
{ /* Group modal */ }
{ groupModal !== undefined && (
< GroupFormModal
group = { groupModal }
@ -386,8 +632,6 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
} }
/ >
) }
{ /* Add member modal */ }
{ memberTarget && (
< AddMemberModal
group = { memberTarget }
@ -401,7 +645,6 @@ function GroupsTab({ spaceId }: { spaceId?: string }) {
} }
/ >
) }
{ delGroup && (
< Dialog title = "确认删除产品组" onClose = { ( ) = > setDelGroup ( null ) } size = "sm" >
< div className = "px-6 py-4" >