links info

This commit is contained in:
2025-04-07 22:17:53 +08:00
parent 0c4a67e769
commit f782dba0c9
2 changed files with 110 additions and 21 deletions

View File

@@ -30,7 +30,7 @@ export async function GET(request: NextRequest) {
} }
if (team) { if (team) {
whereConditions.push(`hasToken(teams, 'team_id', '${team}')`); whereConditions.push(`arrayExists(x -> JSONExtractString(x, 'team_id') = '${team}', JSONExtractArrayRaw(teams))`);
} }
const whereClause = whereConditions.join(' AND '); const whereClause = whereConditions.join(' AND ');

View File

@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { getSupabaseClient } from '../utils/supabase'; import { getSupabaseClient } from '../utils/supabase';
import { AuthChangeEvent } from '@supabase/supabase-js'; import { AuthChangeEvent } from '@supabase/supabase-js';
import { Loader2, ExternalLink, Search } from 'lucide-react'; import { Loader2, ExternalLink, Search } from 'lucide-react';
import { TeamSelector } from '@/app/components/ui/TeamSelector';
// Define attribute type to avoid using 'any' // Define attribute type to avoid using 'any'
interface LinkAttributes { interface LinkAttributes {
@@ -136,28 +137,75 @@ export default function LinksPage() {
console.error('Error parsing attributes:', e); console.error('Error parsing attributes:', e);
} }
// Get team name // Get team names
let teamName = ''; const teamNames: string[] = [];
try { try {
if (link.teams) { if (link.teams) {
const teams = typeof link.teams === 'string' const teams = typeof link.teams === 'string'
? JSON.parse(link.teams) ? JSON.parse(link.teams)
: link.teams || []; : link.teams || [];
if (Array.isArray(teams) && teams.length > 0 && teams[0].team_name) { if (Array.isArray(teams)) {
teamName = teams[0].team_name; teams.forEach(team => {
if (team.team_name) {
teamNames.push(team.team_name);
}
});
} }
} }
} catch (e) { } catch (e) {
console.error('Error parsing teams:', e); console.error('Error parsing teams:', e);
} }
// Get project names
const projectNames: string[] = [];
try {
if (link.projects) {
const projects = typeof link.projects === 'string'
? JSON.parse(link.projects)
: link.projects || [];
if (Array.isArray(projects)) {
projects.forEach(project => {
if (project.project_name) {
projectNames.push(project.project_name);
}
});
}
}
} catch (e) {
console.error('Error parsing projects:', e);
}
// Get tag names
const tagNames: string[] = [];
try {
if (link.tags) {
const tags = typeof link.tags === 'string'
? JSON.parse(link.tags)
: link.tags || [];
if (Array.isArray(tags)) {
tags.forEach(tag => {
if (tag.tag_name) {
tagNames.push(tag.tag_name);
}
});
}
}
} catch (e) {
console.error('Error parsing tags:', e);
}
return { return {
title: link.title || attributes.title || 'Untitled', title: link.title || attributes.title || 'Untitled',
slug: link.slug || attributes.slug || '', slug: link.slug || attributes.slug || '',
domain: domain, domain: domain,
originalUrl: link.original_url || attributes.originalUrl || attributes.original_url || '', originalUrl: link.original_url || attributes.originalUrl || attributes.original_url || '',
teamName: teamName, teamNames: teamNames,
projectNames: projectNames,
tagNames: tagNames,
teamName: teamNames[0] || '', // Keep for backward compatibility
createdAt: new Date(link.created_at).toLocaleDateString(), createdAt: new Date(link.created_at).toLocaleDateString(),
visits: link.click_count || 0 visits: link.click_count || 0
}; };
@@ -168,6 +216,9 @@ export default function LinksPage() {
slug: '', slug: '',
domain: 'shorturl.example.com', domain: 'shorturl.example.com',
originalUrl: '', originalUrl: '',
teamNames: [],
projectNames: [],
tagNames: [],
teamName: '', teamName: '',
createdAt: '', createdAt: '',
visits: 0 visits: 0
@@ -305,19 +356,22 @@ export default function LinksPage() {
/> />
</div> </div>
<select <div className="flex items-center gap-2">
<TeamSelector
value={teamFilter || ''} value={teamFilter || ''}
onChange={(e) => { onChange={(value) => {
setTeamFilter(e.target.value || null); // 如果是多选模式,值将是数组。对于空数组,设置为 null
if (Array.isArray(value)) {
setTeamFilter(value.length > 0 ? value[0] : null);
} else {
setTeamFilter(value || null);
}
setCurrentPage(1); // Reset to page 1 when filtering setCurrentPage(1); // Reset to page 1 when filtering
}} }}
className="rounded-md border border-gray-300 py-2 pl-3 pr-10 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" className="w-64"
> multiple={true}
<option value="">All Teams</option> />
{teams.map(team => ( </div>
<option key={team.id} value={team.id}>{team.name}</option>
))}
</select>
</div> </div>
{/* Links table */} {/* Links table */}
@@ -339,9 +393,20 @@ export default function LinksPage() {
return ( return (
<tr key={link.id} className="hover:bg-gray-50"> <tr key={link.id} className="hover:bg-gray-50">
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex flex-col"> <div className="flex flex-col space-y-1">
<span className="font-medium text-gray-900">{metadata.title}</span> <span className="font-medium text-gray-900">{metadata.title}</span>
<span className="text-xs text-blue-500">{shortUrl}</span> <span className="text-xs text-blue-500">{shortUrl}</span>
{/* Tags */}
{metadata.tagNames.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-1">
{metadata.tagNames.map((tag, index) => (
<span key={index} className="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800">
{tag}
</span>
))}
</div>
)}
</div> </div>
</td> </td>
<td className="px-6 py-4 text-sm text-gray-500"> <td className="px-6 py-4 text-sm text-gray-500">
@@ -356,7 +421,31 @@ export default function LinksPage() {
</a> </a>
</td> </td>
<td className="px-6 py-4 text-sm text-gray-500"> <td className="px-6 py-4 text-sm text-gray-500">
{metadata.teamName} <div className="flex flex-col space-y-1">
{/* Teams */}
{metadata.teamNames.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{metadata.teamNames.map((team, index) => (
<span key={index} className="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-800">
{team}
</span>
))}
</div>
) : (
<span>-</span>
)}
{/* Projects */}
{metadata.projectNames.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-1">
{metadata.projectNames.map((project, index) => (
<span key={index} className="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
{project}
</span>
))}
</div>
)}
</div>
</td> </td>
<td className="px-6 py-4 text-sm text-gray-500"> <td className="px-6 py-4 text-sm text-gray-500">
{metadata.createdAt} {metadata.createdAt}