145 lines
6.7 KiB
JavaScript
145 lines
6.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'
|
|
}
|
|
|
|
export default function StudioTable({ artworks, selectedIds, onSelect, onSelectAll, onAction, onSort, currentSort }) {
|
|
const allSelected = artworks.length > 0 && artworks.every((a) => selectedIds.includes(a.id))
|
|
|
|
const columns = [
|
|
{ key: 'title', label: 'Title', sortable: false },
|
|
{ key: 'status', label: 'Status', sortable: false },
|
|
{ key: 'category', label: 'Category', sortable: false },
|
|
{ key: 'created_at', label: 'Created', sortable: true, sort: 'created_at' },
|
|
{ key: 'views', label: 'Views', sortable: true, sort: 'views' },
|
|
{ key: 'favourites', label: 'Favs', sortable: true, sort: 'favorites_count' },
|
|
{ key: 'shares', label: 'Shares', sortable: true, sort: 'shares_count' },
|
|
{ key: 'comments', label: 'Comments', sortable: true, sort: 'comments_count' },
|
|
{ key: 'downloads', label: 'Downloads', sortable: true, sort: 'downloads' },
|
|
{ key: 'ranking_score', label: 'Rank', sortable: true, sort: 'ranking_score' },
|
|
{ key: 'heat_score', label: 'Heat', sortable: true, sort: 'heat_score' },
|
|
]
|
|
|
|
const handleSort = (col) => {
|
|
if (!col.sortable) return
|
|
const field = col.sort
|
|
const [currentField, currentDir] = (currentSort || '').split(':')
|
|
const dir = currentField === field && currentDir === 'desc' ? 'asc' : 'desc'
|
|
onSort(`${field}:${dir}`)
|
|
}
|
|
|
|
const getSortIcon = (col) => {
|
|
if (!col.sortable) return null
|
|
const [currentField, currentDir] = (currentSort || '').split(':')
|
|
if (currentField !== col.sort) return <i className="fa-solid fa-sort text-slate-600 ml-1 text-[10px]" />
|
|
return <i className={`fa-solid fa-sort-${currentDir === 'asc' ? 'up' : 'down'} text-accent ml-1 text-[10px]`} />
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-x-auto rounded-2xl border border-white/10 bg-nova-900/40">
|
|
<table className="w-full text-sm text-left">
|
|
<thead className="sticky top-0 z-10 bg-nova-900/90 backdrop-blur-sm border-b border-white/10">
|
|
<tr>
|
|
<th className="p-3 w-10">
|
|
<input
|
|
type="checkbox"
|
|
checked={allSelected}
|
|
onChange={onSelectAll}
|
|
className="w-4 h-4 rounded-sm bg-transparent border border-white/20 accent-accent focus:ring-accent/50 cursor-pointer"
|
|
/>
|
|
</th>
|
|
<th className="p-3 w-12"></th>
|
|
{columns.map((col) => (
|
|
<th
|
|
key={col.key}
|
|
className={`p-3 text-xs font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap ${col.sortable ? 'cursor-pointer hover:text-white select-none' : ''}`}
|
|
onClick={() => handleSort(col)}
|
|
>
|
|
{col.label}
|
|
{getSortIcon(col)}
|
|
</th>
|
|
))}
|
|
<th className="p-3 w-20 text-xs font-semibold text-slate-400 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-white/5">
|
|
{artworks.map((art) => (
|
|
<tr
|
|
key={art.id}
|
|
className={`transition-colors ${selectedIds.includes(art.id) ? 'bg-accent/5' : 'hover:bg-white/[0.02]'}`}
|
|
>
|
|
<td className="p-3">
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedIds.includes(art.id)}
|
|
onChange={() => onSelect(art.id)}
|
|
className="w-4 h-4 rounded-sm bg-transparent border border-white/20 accent-accent focus:ring-accent/50 cursor-pointer"
|
|
/>
|
|
</td>
|
|
<td className="p-3">
|
|
<img
|
|
src={art.thumb_url}
|
|
alt=""
|
|
className="w-10 h-10 rounded-lg object-cover bg-nova-800"
|
|
loading="lazy"
|
|
/>
|
|
</td>
|
|
<td className="p-3">
|
|
<span className="text-white font-medium truncate block max-w-[200px]" title={art.title}>{art.title}</span>
|
|
</td>
|
|
<td className="p-3"><StatusBadge status={getStatus(art)} /></td>
|
|
<td className="p-3 text-slate-400">{art.category || '—'}</td>
|
|
<td className="p-3 text-slate-400 whitespace-nowrap">{art.created_at ? new Date(art.created_at).toLocaleDateString() : '—'}</td>
|
|
<td className="p-3 text-slate-300 tabular-nums">{art.views.toLocaleString()}</td>
|
|
<td className="p-3 text-slate-300 tabular-nums">{art.favourites.toLocaleString()}</td>
|
|
<td className="p-3 text-slate-300 tabular-nums">{art.shares.toLocaleString()}</td>
|
|
<td className="p-3 text-slate-300 tabular-nums">{art.comments.toLocaleString()}</td>
|
|
<td className="p-3 text-slate-300 tabular-nums">{art.downloads.toLocaleString()}</td>
|
|
<td className="p-3">
|
|
<RisingBadge heatScore={0} rankingScore={art.ranking_score} />
|
|
<span className="text-slate-400 text-xs">{art.ranking_score.toFixed(1)}</span>
|
|
</td>
|
|
<td className="p-3">
|
|
<RisingBadge heatScore={art.heat_score} rankingScore={0} />
|
|
<span className="text-slate-400 text-xs">{art.heat_score.toFixed(1)}</span>
|
|
</td>
|
|
<td className="p-3">
|
|
<div className="flex items-center gap-1">
|
|
<button
|
|
onClick={() => onAction('edit', art)}
|
|
className="w-7 h-7 rounded-lg flex items-center justify-center text-xs text-slate-400 hover:text-white hover:bg-white/10 transition-all"
|
|
title="Edit"
|
|
aria-label={`Edit ${art.title}`}
|
|
>
|
|
<i className="fa-solid fa-pen" />
|
|
</button>
|
|
<button
|
|
onClick={() => onAction('delete', art)}
|
|
className="w-7 h-7 rounded-lg flex items-center justify-center text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 transition-all"
|
|
title="Delete"
|
|
aria-label={`Delete ${art.title}`}
|
|
>
|
|
<i className="fa-solid fa-trash" />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
{artworks.length === 0 && (
|
|
<tr>
|
|
<td colSpan={14} className="p-12 text-center text-slate-500">
|
|
No artworks found
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|