diff --git a/app/(app)/AppLayoutClient.tsx b/app/(app)/AppLayoutClient.tsx index 6686067..b0a665f 100644 --- a/app/(app)/AppLayoutClient.tsx +++ b/app/(app)/AppLayoutClient.tsx @@ -32,6 +32,12 @@ export default function AppLayoutClient({ > Dashboard + + Analytics + (); + + return ( +
+
+

Analytics

+ +
+ + +
+
+ + {/* 如果没有选择团队,显示提示信息 */} + {!selectedTeamId && ( +
+

+ Please select a team to view analytics +

+
+ )} + + {/* 如果选择了团队,这里可以显示团队相关的分析数据 */} + {selectedTeamId && ( +
+ {/* 这里添加实际的分析数据组件 */} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/app/api/teams/list/route.ts b/app/api/teams/list/route.ts new file mode 100644 index 0000000..cb2d078 --- /dev/null +++ b/app/api/teams/list/route.ts @@ -0,0 +1,41 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; +import { cookies } from 'next/headers'; +import { NextResponse } from 'next/server'; + +export async function GET() { + try { + const supabase = createRouteHandlerClient({ cookies }); + + // 获取当前用户 + const { data: { user }, error: userError } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // 获取用户所属的所有团队 + const { data: teams, error: teamsError } = await supabase + .from('teams') + .select(` + id, + name, + description, + avatar_url + `) + .innerJoin('team_membership', 'teams.id = team_membership.team_id') + .eq('team_membership.user_id', user.id) + .is('teams.deleted_at', null); + + if (teamsError) { + console.error('Error fetching teams:', teamsError); + return NextResponse.json({ error: 'Failed to fetch teams' }, { status: 500 }); + } + + return NextResponse.json(teams); + } catch (error) { + console.error('Error in /api/teams/list:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/components/ui/Select.tsx b/app/components/ui/Select.tsx new file mode 100644 index 0000000..7448aca --- /dev/null +++ b/app/components/ui/Select.tsx @@ -0,0 +1,88 @@ +"use client"; + +import * as React from 'react'; +import { ChevronDown } from 'lucide-react'; + +interface SelectOption { + value: string; + label: string; + icon?: string; +} + +interface SelectProps { + value?: string; + onChange?: (value: string) => void; + options: SelectOption[]; + placeholder?: string; + className?: string; +} + +export function Select({ value, onChange, options, placeholder, className = '' }: SelectProps) { + const [isOpen, setIsOpen] = React.useState(false); + const containerRef = React.useRef(null); + + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const selectedOption = options.find(option => option.value === value); + + return ( +
+ + + {isOpen && ( +
+
+ {options.map((option) => ( + + ))} +
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/app/components/ui/TeamSelector.tsx b/app/components/ui/TeamSelector.tsx new file mode 100644 index 0000000..dd031a4 --- /dev/null +++ b/app/components/ui/TeamSelector.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { useEffect, useState } from 'react'; +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'; +import type { Database } from '@/types/supabase'; +import { Select } from './Select'; + +type Team = Database['public']['Tables']['teams']['Row']; + +interface TeamSelectorProps { + value?: string; + onChange?: (teamId: string) => void; + className?: string; +} + +export function TeamSelector({ value, onChange, className }: TeamSelectorProps) { + const [teams, setTeams] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const supabase = createClientComponentClient(); + + useEffect(() => { + let isMounted = true; + + const fetchTeams = async (userId: string) => { + if (!isMounted) return; + + console.log('Starting to fetch teams for user:', userId); + setLoading(true); + setError(null); + + try { + // 获取用户的团队成员关系 + console.log('Fetching team memberships for user:', userId); + const { data: memberships, error: membershipError } = await supabase + .from('team_membership') + .select('team_id') + .eq('user_id', userId); + + console.log('Team memberships result:', { memberships, membershipError }); + + if (membershipError) { + console.error('Membership error:', membershipError); + throw membershipError; + } + + if (!memberships || memberships.length === 0) { + console.log('No team memberships found'); + if (isMounted) { + setTeams([]); + } + return; + } + + // 获取团队详细信息 + const teamIds = memberships.map(m => m.team_id); + console.log('Fetching teams with IDs:', teamIds); + const { data: teamsData, error: teamsError } = await supabase + .from('teams') + .select('*') + .in('id', teamIds) + .is('deleted_at', null); + + console.log('Teams result:', { teamsData, teamsError }); + + if (teamsError) { + console.error('Teams error:', teamsError); + throw teamsError; + } + + if (isMounted && teamsData) { + setTeams(teamsData); + } + } catch (err) { + console.error('Error fetching teams:', err); + if (isMounted) { + setError(err instanceof Error ? err.message : 'Failed to load teams'); + } + } finally { + if (isMounted) { + setLoading(false); + } + } + }; + + // 设置认证状态监听器 + const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { + console.log('Auth state changed:', event, session?.user); + if (event === 'SIGNED_IN' && session?.user?.id) { + fetchTeams(session.user.id); + } else if (event === 'SIGNED_OUT') { + setTeams([]); + setError(null); + } + }); + + // 初始检查session + supabase.auth.getSession().then(({ data: { session } }) => { + console.log('Initial session check:', session?.user); + if (session?.user?.id) { + fetchTeams(session.user.id); + } + }); + + return () => { + isMounted = false; + subscription.unsubscribe(); + }; + }, [supabase]); + + if (loading) { + return
; + } + + if (error) { + return
{error}
; + } + + if (teams.length === 0) { + return
No teams available
; + } + + return ( +