Files
storycove/frontend/src/components/collections/CollectionCard.tsx
2025-07-25 14:15:23 +02:00

203 lines
7.2 KiB
TypeScript

'use client';
import { Collection } from '../../types/api';
import { getImageUrl } from '../../lib/api';
import Link from 'next/link';
interface CollectionCardProps {
collection: Collection;
viewMode: 'grid' | 'list';
onUpdate?: () => void;
}
export default function CollectionCard({ collection, viewMode, onUpdate }: CollectionCardProps) {
const formatReadingTime = (minutes: number): string => {
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
};
const renderRatingStars = (rating?: number) => {
if (!rating) return null;
return (
<div className="flex items-center">
{[1, 2, 3, 4, 5].map((star) => (
<span
key={star}
className={`text-sm ${
star <= rating ? 'text-yellow-400' : 'text-gray-300'
}`}
>
</span>
))}
</div>
);
};
if (viewMode === 'grid') {
return (
<Link href={`/collections/${collection.id}`}>
<div className="theme-card p-4 hover:border-gray-400 transition-colors cursor-pointer">
{/* Cover Image or Placeholder */}
<div className="aspect-[3/4] mb-3 relative overflow-hidden rounded-lg bg-gray-100">
{collection.coverImagePath ? (
<img
src={getImageUrl(collection.coverImagePath)}
alt={`${collection.name} cover`}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-blue-100 to-purple-100">
<div className="text-center p-4">
<div className="text-2xl font-bold theme-text mb-1">
{collection.storyCount}
</div>
<div className="text-xs theme-text opacity-60">
{collection.storyCount === 1 ? 'story' : 'stories'}
</div>
</div>
</div>
)}
{collection.isArchived && (
<div className="absolute top-2 right-2 bg-yellow-500 text-white px-2 py-1 rounded text-xs">
Archived
</div>
)}
</div>
{/* Collection Info */}
<div className="space-y-2">
<h3 className="font-semibold theme-header line-clamp-2">
{collection.name}
</h3>
{collection.description && (
<p className="text-sm theme-text opacity-70 line-clamp-2">
{collection.description}
</p>
)}
<div className="flex items-center justify-between text-xs theme-text opacity-60">
<span>{collection.storyCount} stories</span>
<span>{collection.estimatedReadingTime ? formatReadingTime(collection.estimatedReadingTime) : '—'}</span>
</div>
{collection.rating && (
<div className="flex justify-center">
{renderRatingStars(collection.rating)}
</div>
)}
{/* Tags */}
{collection.tags && collection.tags.length > 0 && (
<div className="flex flex-wrap gap-1">
{collection.tags.slice(0, 3).map((tag) => (
<span
key={tag.id}
className="inline-block px-2 py-1 text-xs rounded-full theme-accent-bg text-white"
>
{tag.name}
</span>
))}
{collection.tags.length > 3 && (
<span className="text-xs theme-text opacity-60">
+{collection.tags.length - 3} more
</span>
)}
</div>
)}
</div>
</div>
</Link>
);
}
// List view
return (
<Link href={`/collections/${collection.id}`}>
<div className="theme-card p-4 hover:border-gray-400 transition-colors cursor-pointer">
<div className="flex gap-4">
{/* Cover Image */}
<div className="w-16 h-20 flex-shrink-0 rounded overflow-hidden bg-gray-100">
{collection.coverImagePath ? (
<img
src={getImageUrl(collection.coverImagePath)}
alt={`${collection.name} cover`}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-blue-100 to-purple-100">
<div className="text-center">
<div className="text-sm font-bold theme-text">
{collection.storyCount}
</div>
</div>
</div>
)}
</div>
{/* Collection Details */}
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<h3 className="font-semibold theme-header line-clamp-1">
{collection.name}
{collection.isArchived && (
<span className="ml-2 inline-block bg-yellow-500 text-white px-2 py-1 rounded text-xs">
Archived
</span>
)}
</h3>
{collection.description && (
<p className="text-sm theme-text opacity-70 line-clamp-2 mt-1">
{collection.description}
</p>
)}
<div className="flex items-center gap-4 mt-2 text-sm theme-text opacity-60">
<span>{collection.storyCount} stories</span>
<span>{collection.estimatedReadingTime ? formatReadingTime(collection.estimatedReadingTime) : '—'} reading</span>
{collection.averageStoryRating && collection.averageStoryRating > 0 && (
<span> {collection.averageStoryRating.toFixed(1)} avg</span>
)}
</div>
{/* Tags */}
{collection.tags && collection.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{collection.tags.slice(0, 5).map((tag) => (
<span
key={tag.id}
className="inline-block px-2 py-1 text-xs rounded-full theme-accent-bg text-white"
>
{tag.name}
</span>
))}
{collection.tags.length > 5 && (
<span className="text-xs theme-text opacity-60">
+{collection.tags.length - 5} more
</span>
)}
</div>
)}
</div>
{collection.rating && (
<div className="flex-shrink-0 ml-4">
{renderRatingStars(collection.rating)}
</div>
)}
</div>
</div>
</div>
</div>
</Link>
);
}