From 0a881fd180a387f3d9a710d6dfe51b17f5e4dff5 Mon Sep 17 00:00:00 2001 From: William Tso Date: Tue, 1 Apr 2025 19:51:30 +0800 Subject: [PATCH] team selector --- app/(app)/analytics/page.tsx | 34 ++++-- app/components/ui/TeamSelector.tsx | 170 ++++++++++++++++++++++------- 2 files changed, 155 insertions(+), 49 deletions(-) diff --git a/app/(app)/analytics/page.tsx b/app/(app)/analytics/page.tsx index 1a30267..2f8bf15 100644 --- a/app/(app)/analytics/page.tsx +++ b/app/(app)/analytics/page.tsx @@ -13,8 +13,8 @@ export default function AnalyticsPage() { to: today // 今天 }); - // 添加团队选择状态 - const [selectedTeamId, setSelectedTeamId] = useState(); + // 添加团队选择状态 - 使用数组支持多选 + const [selectedTeamIds, setSelectedTeamIds] = useState([]); return (
@@ -23,11 +23,10 @@ export default function AnalyticsPage() {
setSelectedTeamIds(Array.isArray(value) ? value : [value])} + className="w-[250px]" + multiple={true} /> {/* 如果没有选择团队,显示提示信息 */} - {!selectedTeamId && ( + {selectedTeamIds.length === 0 && (

- Please select a team to view analytics + Please select one or more teams to view analytics

)} {/* 如果选择了团队,这里可以显示团队相关的分析数据 */} - {selectedTeamId && ( + {selectedTeamIds.length > 0 && (
- {/* 这里添加实际的分析数据组件 */} +
+

+ Analytics for {selectedTeamIds.length} selected {selectedTeamIds.length === 1 ? 'team' : 'teams'} +

+
+ {/* You can map through selectedTeamIds and display data for each team */} + {selectedTeamIds.map((teamId) => ( +
+

Team ID: {teamId}

+

Team analytics will appear here

+
+ ))} +
+
)}
diff --git a/app/components/ui/TeamSelector.tsx b/app/components/ui/TeamSelector.tsx index 4a66304..f17c839 100644 --- a/app/components/ui/TeamSelector.tsx +++ b/app/components/ui/TeamSelector.tsx @@ -1,41 +1,63 @@ "use client"; import * as React from 'react'; -import { useEffect, useState } 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 { Select } from '@radix-ui/themes'; -import { Loader2 } from 'lucide-react'; +import { Loader2, X, Check } from 'lucide-react'; import { cn } from '@/lib/utils'; type Team = Database['limq']['Tables']['teams']['Row']; -type AccentColor = - | 'ruby' | 'gray' | 'gold' | 'bronze' | 'brown' | 'yellow' | 'amber' | 'orange' - | 'tomato' | 'red' | 'crimson' | 'pink' | 'plum' | 'purple' | 'violet' - | 'iris' | 'indigo' | 'blue' | 'cyan' | 'teal' | 'jade' | 'green' | 'grass' | 'lime'; -// TeamSelector component using Radix Themes +// TeamSelector component with multi-select support export function TeamSelector({ value, onChange, className, - size = "2", - variant = "surface", - color, - radius, + multiple = false, }: { - value?: string; - onChange?: (teamId: string) => void; + value?: string | string[]; + onChange?: (teamId: string | string[]) => void; className?: string; - size?: "1" | "2" | "3"; - variant?: "classic" | "surface" | "soft" | "ghost"; - color?: AccentColor; - radius?: "none" | "small" | "medium" | "large" | "full"; + 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; @@ -106,6 +128,44 @@ export function TeamSelector({ }; }, []); + 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 (
selectedIds.includes(team.id)); + return ( - - - - {teams.map((team) => ( - - {team.name} - - ))} - - +
+
+ {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) && ( + + )} +
+ ))} +
+ )} +
); } \ No newline at end of file