2025-11-22 19:28:55 +08:00

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>
);
}