102 lines
3.7 KiB
JavaScript
102 lines
3.7 KiB
JavaScript
import React from 'react'
|
|
import StatusBadge from '../Badges/StatusBadge'
|
|
import RisingBadge from '../Badges/RisingBadge'
|
|
|
|
function getStatus(art) {
|
|
if (art.deleted_at) return 'archived'
|
|
if (!art.is_public) return 'draft'
|
|
return 'published'
|
|
}
|
|
|
|
function statItem(icon, value) {
|
|
return (
|
|
<span className="flex items-center gap-1 text-xs text-slate-400">
|
|
<span>{icon}</span>
|
|
<span>{typeof value === 'number' ? value.toLocaleString() : value}</span>
|
|
</span>
|
|
)
|
|
}
|
|
|
|
export default function StudioGridCard({ artwork, selected, onSelect, onAction }) {
|
|
const status = getStatus(artwork)
|
|
|
|
return (
|
|
<div
|
|
className={`group relative bg-nova-900/60 border rounded-2xl overflow-hidden transition-all duration-300 hover:-translate-y-0.5 hover:shadow-xl hover:shadow-accent/5 ${
|
|
selected ? 'border-accent/60 ring-2 ring-accent/20' : 'border-white/10 hover:border-white/20'
|
|
}`}
|
|
>
|
|
{/* Selection checkbox */}
|
|
<label className="absolute top-3 left-3 z-10 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={selected}
|
|
onChange={() => onSelect(artwork.id)}
|
|
className="w-4 h-4 rounded-sm bg-transparent border border-white/20 accent-accent focus:ring-accent/50 cursor-pointer"
|
|
/>
|
|
</label>
|
|
|
|
{/* Thumbnail */}
|
|
<div className="relative aspect-[4/3] bg-nova-800 overflow-hidden">
|
|
<img
|
|
src={artwork.thumb_url}
|
|
alt={artwork.title}
|
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
|
loading="lazy"
|
|
/>
|
|
|
|
{/* Hover actions */}
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
<div className="absolute bottom-3 right-3 flex gap-1.5">
|
|
<ActionBtn icon="fa-eye" title="View public" onClick={() => window.open(`/artworks/${artwork.slug}`, '_blank')} />
|
|
<ActionBtn icon="fa-pen" title="Edit" onClick={() => onAction('edit', artwork)} />
|
|
{status !== 'archived' ? (
|
|
<ActionBtn icon="fa-box-archive" title="Archive" onClick={() => onAction('archive', artwork)} />
|
|
) : (
|
|
<ActionBtn icon="fa-rotate-left" title="Unarchive" onClick={() => onAction('unarchive', artwork)} />
|
|
)}
|
|
<ActionBtn icon="fa-trash" title="Delete" onClick={() => onAction('delete', artwork)} danger />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="p-3 space-y-2">
|
|
<h3 className="text-sm font-semibold text-white truncate" title={artwork.title}>
|
|
{artwork.title}
|
|
</h3>
|
|
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<StatusBadge status={status} />
|
|
<RisingBadge heatScore={artwork.heat_score} rankingScore={artwork.ranking_score} />
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
{statItem('👁', artwork.views)}
|
|
{statItem('❤️', artwork.favourites)}
|
|
{statItem('🔗', artwork.shares)}
|
|
{statItem('💬', artwork.comments)}
|
|
{statItem('⬇', artwork.downloads)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ActionBtn({ icon, title, onClick, danger }) {
|
|
return (
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); onClick() }}
|
|
title={title}
|
|
className={`w-8 h-8 rounded-lg flex items-center justify-center text-sm transition-all backdrop-blur-sm ${
|
|
danger
|
|
? 'bg-red-500/20 text-red-400 hover:bg-red-500/40'
|
|
: 'bg-white/10 text-white hover:bg-white/20'
|
|
}`}
|
|
aria-label={title}
|
|
>
|
|
<i className={`fa-solid ${icon}`} />
|
|
</button>
|
|
)
|
|
}
|