Studio: make grid checkbox rectangular and commit table changes
This commit is contained in:
144
resources/js/components/Studio/StudioTable.jsx
Normal file
144
resources/js/components/Studio/StudioTable.jsx
Normal file
@@ -0,0 +1,144 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user