fix filter

This commit is contained in:
2025-04-02 20:05:33 +08:00
parent 8054b0235d
commit a6f7172ec4
6 changed files with 106 additions and 61 deletions

View File

@@ -6,11 +6,20 @@ export async function GET(request: NextRequest) {
try { try {
const searchParams = request.nextUrl.searchParams; const searchParams = request.nextUrl.searchParams;
// 获取团队、项目和标签筛选参数
const teamIds = searchParams.getAll('teamId');
const projectIds = searchParams.getAll('projectId');
const tagIds = searchParams.getAll('tagId');
const data = await getGeoAnalytics({ const data = await getGeoAnalytics({
startTime: searchParams.get('startTime') || undefined, startTime: searchParams.get('startTime') || undefined,
endTime: searchParams.get('endTime') || undefined, endTime: searchParams.get('endTime') || undefined,
linkId: searchParams.get('linkId') || undefined, linkId: searchParams.get('linkId') || undefined,
groupBy: (searchParams.get('groupBy') || 'country') as 'country' | 'city' groupBy: (searchParams.get('groupBy') || 'country') as 'country' | 'city',
// 添加团队、项目和标签筛选
teamIds: teamIds.length > 0 ? teamIds : undefined,
projectIds: projectIds.length > 0 ? projectIds : undefined,
tagIds: tagIds.length > 0 ? tagIds : undefined
}); });
const response: ApiResponse<typeof data> = { const response: ApiResponse<typeof data> = {

View File

@@ -35,10 +35,8 @@ export async function GET(request: NextRequest) {
linkId, linkId,
linkSlug, linkSlug,
userId, userId,
teamId: teamIds.length > 0 ? teamIds[0] : undefined, teamIds: teamIds.length > 0 ? teamIds : undefined,
teamIds: teamIds.length > 1 ? teamIds : undefined, projectIds: projectIds.length > 0 ? projectIds : undefined,
projectId: projectIds.length > 0 ? projectIds[0] : undefined,
projectIds: projectIds.length > 1 ? projectIds : undefined,
tagIds: tagIds.length > 0 ? tagIds : undefined, tagIds: tagIds.length > 0 ? tagIds : undefined,
startTime, startTime,
endTime, endTime,

View File

@@ -35,12 +35,14 @@ export function ProjectSelector({
className, className,
multiple = false, multiple = false,
teamId, teamId,
teamIds,
}: { }: {
value?: string | string[]; value?: string | string[];
onChange?: (projectId: string | string[]) => void; onChange?: (projectId: string | string[]) => void;
className?: string; className?: string;
multiple?: boolean; multiple?: boolean;
teamId?: string; // Optional team ID to filter projects by team teamId?: string; // Optional team ID to filter projects by team
teamIds?: string[]; // Optional array of team IDs to filter projects by multiple teams
}) { }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -49,6 +51,16 @@ export function ProjectSelector({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const selectorRef = useRef<HTMLDivElement>(null); const selectorRef = useRef<HTMLDivElement>(null);
// Normalize team IDs to ensure we're always working with an array
const effectiveTeamIds = React.useMemo(() => {
if (teamIds && teamIds.length > 0) {
return teamIds;
} else if (teamId) {
return [teamId];
}
return undefined;
}, [teamId, teamIds]);
// Initialize selected projects based on value prop // Initialize selected projects based on value prop
useEffect(() => { useEffect(() => {
if (value) { if (value) {
@@ -90,38 +102,59 @@ export function ProjectSelector({
try { try {
const supabase = getSupabaseClient(); const supabase = getSupabaseClient();
let projectsQuery; if (effectiveTeamIds && effectiveTeamIds.length > 0) {
// If team IDs are provided, get projects for those teams
if (teamId) { const { data: projectsData, error: projectsError } = await supabase
// 如果提供了teamId获取该团队的项目
projectsQuery = supabase
.from('team_projects') .from('team_projects')
.select('project_id, projects:project_id(*), teams:team_id(name)') .select('project_id, projects:project_id(*), teams:team_id(name)')
.eq('team_id', teamId) .in('team_id', effectiveTeamIds)
.is('projects.deleted_at', null); .is('projects.deleted_at', null);
if (projectsError) throw projectsError;
if (!projectsData || projectsData.length === 0) {
if (isMounted) setProjects([]);
return;
}
// Extract projects from response with team info
if (isMounted) {
const projectList: Project[] = [];
for (const item of projectsData as ProjectWithTeam[]) {
if (item.projects && typeof item.projects === 'object' && 'id' in item.projects && 'name' in item.projects) {
const project = item.projects as Project;
if (item.teams && 'name' in item.teams) {
project.team_name = item.teams.name;
}
// Avoid duplicate projects from different teams
if (!projectList.some(p => p.id === project.id)) {
projectList.push(project);
}
}
}
setProjects(projectList);
}
} else { } else {
// 否则,获取用户所属的所有项目及其所属团队 // If no team IDs, get all user's projects
projectsQuery = supabase const { data: projectsData, error: projectsError } = await supabase
.from('user_projects') .from('user_projects')
.select('project_id, projects:project_id(*)') .select('project_id, projects:project_id(*)')
.eq('user_id', userId) .eq('user_id', userId)
.is('projects.deleted_at', null); .is('projects.deleted_at', null);
}
const { data: projectsData, error: projectsError } = await projectsQuery; if (projectsError) throw projectsError;
if (projectsError) throw projectsError; if (!projectsData || projectsData.length === 0) {
if (isMounted) setProjects([]);
return;
}
if (!projectsData || projectsData.length === 0) { // Fetch team info for these projects
if (isMounted) setProjects([]);
return;
}
// 如果没有提供teamId需要单独获取每个项目对应的团队信息
if (!teamId && projectsData.length > 0) {
const projectIds = projectsData.map(item => item.project_id); const projectIds = projectsData.map(item => item.project_id);
// 获取项目所属的团队信息 // Get team info for each project
const { data: teamProjectsData, error: teamProjectsError } = await supabase const { data: teamProjectsData, error: teamProjectsError } = await supabase
.from('team_projects') .from('team_projects')
.select('project_id, teams:team_id(name)') .select('project_id, teams:team_id(name)')
@@ -129,7 +162,7 @@ export function ProjectSelector({
if (teamProjectsError) throw teamProjectsError; if (teamProjectsError) throw teamProjectsError;
// 创建项目ID到团队名称的映射 // Create project ID to team name mapping
const projectTeamMap: Record<string, string> = {}; const projectTeamMap: Record<string, string> = {};
if (teamProjectsData) { if (teamProjectsData) {
teamProjectsData.forEach(item => { teamProjectsData.forEach(item => {
@@ -139,7 +172,7 @@ export function ProjectSelector({
}); });
} }
// 提取项目数据,并添加团队名称 // Extract projects with team names
if (isMounted && projectsData) { if (isMounted && projectsData) {
const projectList: Project[] = []; const projectList: Project[] = [];
@@ -151,23 +184,6 @@ export function ProjectSelector({
} }
} }
setProjects(projectList);
}
} else {
// 如果提供了teamId直接从查询结果中提取项目和团队信息
if (isMounted && projectsData) {
const projectList: Project[] = [];
for (const item of projectsData as ProjectWithTeam[]) {
if (item.projects && typeof item.projects === 'object' && 'id' in item.projects && 'name' in item.projects) {
const project = item.projects as Project;
if (item.teams && 'name' in item.teams) {
project.team_name = item.teams.name;
}
projectList.push(project);
}
}
setProjects(projectList); setProjects(projectList);
} }
} }
@@ -203,7 +219,7 @@ export function ProjectSelector({
isMounted = false; isMounted = false;
subscription.unsubscribe(); subscription.unsubscribe();
}; };
}, [teamId]); }, [effectiveTeamIds]);
const handleToggle = () => { const handleToggle = () => {
if (!loading && !error && projects.length > 0) { if (!loading && !error && projects.length > 0) {

View File

@@ -30,14 +30,14 @@ export function TagSelector({
className, className,
multiple = false, multiple = false,
teamId, teamId,
tagType, teamIds,
}: { }: {
value?: string | string[]; value?: string | string[];
onChange?: (tagId: string | string[]) => void; onChange?: (tagIds: string | string[]) => void;
className?: string; className?: string;
multiple?: boolean; multiple?: boolean;
teamId?: string; // Optional team ID to filter tags by team teamId?: string; // Optional single team ID
tagType?: string; // Optional tag type for filtering teamIds?: string[]; // Optional array of team IDs
}) { }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -46,6 +46,16 @@ export function TagSelector({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const selectorRef = useRef<HTMLDivElement>(null); const selectorRef = useRef<HTMLDivElement>(null);
// Normalize team IDs to ensure we're always working with an array
const effectiveTeamIds = React.useMemo(() => {
if (teamIds && teamIds.length > 0) {
return teamIds;
} else if (teamId) {
return [teamId];
}
return undefined;
}, [teamId, teamIds]);
// 标签名称与ID的映射函数 // 标签名称与ID的映射函数
const getTagIdByName = (name: string): string | undefined => { const getTagIdByName = (name: string): string | undefined => {
const tag = tags.find(t => t.name === name); const tag = tags.find(t => t.name === name);
@@ -119,13 +129,8 @@ export function TagSelector({
let query = supabase.from('tags').select('*').is('deleted_at', null); let query = supabase.from('tags').select('*').is('deleted_at', null);
// Filter by team if teamId is provided // Filter by team if teamId is provided
if (teamId) { if (effectiveTeamIds) {
query = query.eq('team_id', teamId); query = query.in('team_id', effectiveTeamIds);
}
// Filter by tag type if provided
if (tagType) {
query = query.eq('type', tagType);
} }
const { data: tagsData, error: tagsError } = await query; const { data: tagsData, error: tagsError } = await query;
@@ -170,7 +175,7 @@ export function TagSelector({
isMounted = false; isMounted = false;
subscription.unsubscribe(); subscription.unsubscribe();
}; };
}, [teamId, tagType]); }, [effectiveTeamIds]);
const handleToggle = () => { const handleToggle = () => {
if (!loading && !error && tags.length > 0) { if (!loading && !error && tags.length > 0) {

View File

@@ -248,7 +248,18 @@ export default function HomePage() {
<div className="flex flex-col gap-4 md:flex-row md:items-center"> <div className="flex flex-col gap-4 md:flex-row md:items-center">
<TeamSelector <TeamSelector
value={selectedTeamIds} value={selectedTeamIds}
onChange={(value) => setSelectedTeamIds(Array.isArray(value) ? value : [value])} onChange={(value) => {
const newTeamIds = Array.isArray(value) ? value : [value];
// Check if team selection has changed
if (JSON.stringify(newTeamIds) !== JSON.stringify(selectedTeamIds)) {
// Clear project selection when team changes
setSelectedProjectIds([]);
// Update team selection
setSelectedTeamIds(newTeamIds);
}
}}
className="w-[250px]" className="w-[250px]"
multiple={true} multiple={true}
/> />
@@ -257,14 +268,14 @@ export default function HomePage() {
onChange={(value) => setSelectedProjectIds(Array.isArray(value) ? value : [value])} onChange={(value) => setSelectedProjectIds(Array.isArray(value) ? value : [value])}
className="w-[250px]" className="w-[250px]"
multiple={true} multiple={true}
teamId={selectedTeamIds.length === 1 ? selectedTeamIds[0] : undefined} teamIds={selectedTeamIds.length > 0 ? selectedTeamIds : undefined}
/> />
<TagSelector <TagSelector
value={selectedTagIds} value={selectedTagIds}
onChange={(value) => setSelectedTagIds(Array.isArray(value) ? value : [value])} onChange={(value) => setSelectedTagIds(Array.isArray(value) ? value : [value])}
className="w-[250px]" className="w-[250px]"
multiple={true} multiple={true}
teamId={selectedTeamIds.length === 1 ? selectedTeamIds[0] : undefined} teamIds={selectedTeamIds.length > 0 ? selectedTeamIds : undefined}
/> />
<DateRangePicker <DateRangePicker
value={dateRange} value={dateRange}

View File

@@ -181,6 +181,9 @@ export async function getTimeSeriesData(params: {
endTime: string; endTime: string;
linkId?: string; linkId?: string;
granularity: 'hour' | 'day' | 'week' | 'month'; granularity: 'hour' | 'day' | 'week' | 'month';
teamIds?: string[];
projectIds?: string[];
tagIds?: string[];
}): Promise<TimeSeriesData[]> { }): Promise<TimeSeriesData[]> {
const filter = buildFilter(params); const filter = buildFilter(params);
@@ -213,9 +216,12 @@ export async function getGeoAnalytics(params: {
endTime?: string; endTime?: string;
linkId?: string; linkId?: string;
groupBy?: 'country' | 'city'; groupBy?: 'country' | 'city';
teamIds?: string[];
projectIds?: string[];
tagIds?: string[];
}): Promise<GeoData[]> { }): Promise<GeoData[]> {
const filter = buildFilter(params); const filter = buildFilter(params);
const groupByField = 'ip_address'; // 暂时按 IP 地址分组 const groupByField = params.groupBy === 'city' ? 'city' : 'country';
const query = ` const query = `
SELECT SELECT
@@ -228,7 +234,7 @@ export async function getGeoAnalytics(params: {
GROUP BY ${groupByField} GROUP BY ${groupByField}
HAVING location != '' HAVING location != ''
ORDER BY visits DESC ORDER BY visits DESC
LIMIT 10 LIMIT 20
`; `;
return executeQuery<GeoData>(query); return executeQuery<GeoData>(query);