"use client"; import * as React from 'react'; import { useEffect, useState, useRef } from 'react'; import type { Database } from '@/types/supabase'; import { getSupabaseClient } from '../../utils/supabase'; import { AuthChangeEvent, Session } from '@supabase/supabase-js'; import { Loader2, X, Check } from 'lucide-react'; import { cn } from '@/lib/utils'; import { limqRequest } from '@/lib/api'; type Team = Database['limq']['Tables']['teams']['Row']; // TeamSelector component with multi-select support export function TeamSelector({ value, onChange, className, multiple = false, }: { value?: string | string[]; onChange?: (teamId: string | string[]) => void; className?: string; multiple?: boolean; }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [teams, setTeams] = useState([]); const [selectedIds, setSelectedIds] = useState([]); const [isOpen, setIsOpen] = useState(false); const selectorRef = useRef(null); // Initialize selected teams based on value prop useEffect(() => { if (value) { if (Array.isArray(value)) { setSelectedIds(value); } else { setSelectedIds(value ? [value] : []); } } else { setSelectedIds([]); } }, [value]); // Add click outside listener to close dropdown useEffect(() => { function handleClickOutside(event: MouseEvent) { if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) { setIsOpen(false); } } // Only add the event listener if the dropdown is open if (isOpen) { document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; } }, [isOpen]); useEffect(() => { let isMounted = true; const fetchTeams = async (userId: string) => { if (!isMounted) return; setLoading(true); setError(null); console.log(`开始获取团队数据,用户ID: ${userId}`); try { const supabase = getSupabaseClient(); console.log('Supabase客户端已创建,准备获取团队数据'); // 先尝试直接获取团队数据,不等待create-default try { const { data: memberships, error: membershipError } = await supabase .from('team_membership') .select('team_id') .eq('user_id', userId); console.log(`团队成员关系查询结果:`, memberships ? `找到${memberships.length}个` : '无数据', membershipError ? `错误: ${membershipError.message}` : '无错误'); if (membershipError) throw membershipError; if (!memberships || memberships.length === 0) { console.log('未找到团队成员关系,尝试创建默认团队'); // 尝试创建默认团队和项目(如果用户还没有) try { const response = await limqRequest('team/create-default', 'POST'); console.log('默认团队创建成功:', response); // 创建默认团队后重新获取团队列表 const { data: refreshedMemberships, error: refreshError } = await supabase .from('team_membership') .select('team_id') .eq('user_id', userId); console.log(`刷新后的团队成员关系:`, refreshedMemberships ? `找到${refreshedMemberships.length}个` : '无数据'); if (refreshError) throw refreshError; if (!refreshedMemberships || refreshedMemberships.length === 0) { if (isMounted) { console.log('创建默认团队后仍未找到团队,设置空团队列表'); setTeams([]); } return; } const teamIds = refreshedMemberships.map(m => m.team_id); console.log('获取到团队IDs:', teamIds); const { data: teamsData, error: teamsError } = await supabase .from('teams') .select('*') .in('id', teamIds) .is('deleted_at', null); console.log(`团队数据查询结果:`, teamsData ? `找到${teamsData.length}个` : '无数据'); if (teamsError) throw teamsError; if (isMounted && teamsData) { setTeams(teamsData); } return; } catch (teamError) { console.error('创建默认团队失败:', teamError); // 创建失败也继续,返回空列表 if (isMounted) setTeams([]); return; } } const teamIds = memberships.map(m => m.team_id); console.log('获取到团队IDs:', teamIds); const { data: teamsData, error: teamsError } = await supabase .from('teams') .select('*') .in('id', teamIds) .is('deleted_at', null); console.log(`团队数据查询结果:`, teamsData ? `找到${teamsData.length}个` : '无数据'); if (teamsError) throw teamsError; if (isMounted && teamsData) { setTeams(teamsData); } } catch (dataError) { console.error('获取团队数据失败:', dataError); throw dataError; } } catch (err) { console.error('获取团队数据出错:', err); if (isMounted) { setError(err instanceof Error ? err.message : '获取团队数据失败'); } } finally { if (isMounted) { setLoading(false); } } }; // 获取Supabase客户端实例并订阅认证状态变化 try { const supabase = getSupabaseClient(); console.log('注册认证状态变化监听器'); const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => { console.log(`认证状态变化: ${event}`, session ? `用户ID: ${session.user.id}` : '无会话'); if (event === 'SIGNED_IN' && session?.user?.id) { fetchTeams(session.user.id); } else if (event === 'SIGNED_OUT') { setTeams([]); setError(null); } }); // 初始化时获取当前会话 console.log('获取当前会话状态'); supabase.auth.getSession().then(({ data: { session } }) => { console.log('当前会话状态:', session ? `用户已登录,ID: ${session.user.id}` : '用户未登录'); if (session?.user?.id) { fetchTeams(session.user.id); } else { // 如果没有会话但组件需要初始化,可以设置加载完成 setLoading(false); } }).catch(err => { console.error('获取会话状态失败:', err); // 确保即使获取会话失败也停止加载状态 setLoading(false); }); return () => { console.log('TeamSelector组件卸载,清理订阅'); isMounted = false; subscription.unsubscribe(); }; } catch (initError) { console.error('初始化TeamSelector出错:', initError); // 确保在初始化出错时也停止加载状态 setLoading(false); setError('初始化失败,请刷新页面重试'); return () => { isMounted = false; }; } }, []); const handleToggle = () => { if (!loading && !error && teams.length > 0) { setIsOpen(!isOpen); } }; const handleTeamSelect = (teamId: string) => { let newSelected: string[]; if (multiple) { // For multi-select: toggle team in/out of selection if (selectedIds.includes(teamId)) { newSelected = selectedIds.filter(id => id !== teamId); } else { newSelected = [...selectedIds, teamId]; } } else { // For single-select: replace selection with the new team newSelected = [teamId]; setIsOpen(false); } setSelectedIds(newSelected); if (onChange) { onChange(multiple ? newSelected : newSelected[0] || ''); } }; const removeTeam = (e: React.MouseEvent, teamId: string) => { e.stopPropagation(); const newSelected = selectedIds.filter(id => id !== teamId); setSelectedIds(newSelected); if (onChange) { onChange(multiple ? newSelected : newSelected[0] || ''); } }; if (loading) { return (
); } if (error) { return (
{error}
); } if (teams.length === 0) { return (
No teams available
); } const selectedTeams = teams.filter(team => selectedIds.includes(team.id)); return (
{selectedTeams.length > 0 ? (
{selectedTeams.map(team => (
{team.name} {multiple && ( removeTeam(e, team.id)} /> )}
))}
) : (
Select a team
)}
{isOpen && (
{teams.map(team => (
handleTeamSelect(team.id)} > {team.name} {selectedIds.includes(team.id) && ( )}
))}
)}
); }