131 lines
5.1 KiB
TypeScript
131 lines
5.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Star, RefreshCcw, Loader2 } from 'lucide-react';
|
|
import { ScreenRecord } from '../types';
|
|
import { formatDate } from '../utils';
|
|
import { useStarToggle } from '../hooks/useStarToggle';
|
|
|
|
interface StarredTabProps {
|
|
hostname: string;
|
|
onViewRecord: (record: ScreenRecord) => void;
|
|
}
|
|
|
|
export default function StarredTab({ hostname, onViewRecord }: StarredTabProps) {
|
|
const [starredRecords, setStarredRecords] = useState<ScreenRecord[]>([]);
|
|
const [loadingStarred, setLoadingStarred] = useState(false);
|
|
const { updatingStars, toggleStar } = useStarToggle();
|
|
|
|
const fetchStarredRecords = async () => {
|
|
try {
|
|
setLoadingStarred(true);
|
|
const response = await fetch(`/hosts/${hostname}/starred`);
|
|
if (!response.ok) throw new Error('获取星标记录失败');
|
|
const data = await response.json();
|
|
setStarredRecords(data.records);
|
|
} catch (error) {
|
|
console.error('获取星标记录失败:', error);
|
|
} finally {
|
|
setLoadingStarred(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchStarredRecords();
|
|
}, [hostname]);
|
|
|
|
const handleToggleStar = async (recordId: string) => {
|
|
const newStatus = await toggleStar(recordId);
|
|
if (newStatus === false) {
|
|
// Removed from starred
|
|
setStarredRecords(prev => prev.filter(r => r.id !== recordId));
|
|
} else if (newStatus === true) {
|
|
// Shouldn't happen in this view usually, but if it does...
|
|
fetchStarredRecords();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h2 className="text-xl font-medium text-gray-900 dark:text-white flex items-center">
|
|
<Star className="h-6 w-6 mr-2 text-yellow-500" />
|
|
星标记录
|
|
</h2>
|
|
<button
|
|
onClick={fetchStarredRecords}
|
|
className="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded focus:outline-none flex items-center"
|
|
>
|
|
<RefreshCcw className={`h-4 w-4 mr-1 ${loadingStarred ? 'animate-spin' : ''}`} />
|
|
刷新
|
|
</button>
|
|
</div>
|
|
|
|
{loadingStarred ? (
|
|
<div className="flex justify-center py-12">
|
|
<div className="animate-pulse flex flex-col items-center">
|
|
<Loader2 className="h-8 w-8 text-blue-600 animate-spin" />
|
|
<p className="mt-2 text-gray-500 dark:text-gray-400">加载星标记录...</p>
|
|
</div>
|
|
</div>
|
|
) : starredRecords.length === 0 ? (
|
|
<div className="py-12 flex flex-col items-center justify-center">
|
|
<Star className="h-12 w-12 text-gray-400 mb-3" />
|
|
<p className="text-gray-500 dark:text-gray-400 mb-1">还没有星标记录</p>
|
|
<p className="text-sm text-gray-400 dark:text-gray-500">在截图时间线中点击星标按钮来收藏重要记录</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{starredRecords.map((record) => (
|
|
<div key={record.id} className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden hover:shadow-md transition-shadow">
|
|
{record.screenshots.length > 0 && (
|
|
<div className="aspect-video bg-gray-100 dark:bg-gray-700">
|
|
<img
|
|
src={`/screenshots/${record.screenshots[0].fileId}`}
|
|
alt={record.screenshots[0].monitorName}
|
|
className="w-full h-full object-contain"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="p-4">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
|
{formatDate(record.timestamp, 'short')}
|
|
</div>
|
|
<button
|
|
onClick={() => handleToggleStar(record.id)}
|
|
disabled={updatingStars.has(record.id)}
|
|
className="text-yellow-500 hover:text-yellow-600 p-1"
|
|
>
|
|
{updatingStars.has(record.id) ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Star className="h-4 w-4 fill-current" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
{record.screenshots.length} 个截图 • {record.windows.length} 个窗口
|
|
</div>
|
|
|
|
{record.windows.length > 0 && (
|
|
<div className="text-xs text-gray-600 dark:text-gray-400 truncate">
|
|
主要窗口: {record.windows[0].title}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={() => onViewRecord(record)}
|
|
className="mt-3 w-full px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded focus:outline-none"
|
|
>
|
|
查看详情
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|