Studio: make grid checkbox rectangular and commit table changes

This commit is contained in:
2026-03-01 08:43:48 +01:00
parent 211dc58884
commit e3ca845a6d
89 changed files with 7323 additions and 475 deletions

View File

@@ -0,0 +1,141 @@
import React from 'react'
import { usePage, Link } from '@inertiajs/react'
import StudioLayout from '../../Layouts/StudioLayout'
const kpiConfig = [
{ key: 'total_artworks', label: 'Total Artworks', icon: 'fa-images', color: 'text-blue-400', link: '/studio/artworks' },
{ key: 'views_30d', label: 'Views (30d)', icon: 'fa-eye', color: 'text-emerald-400', link: null },
{ key: 'favourites_30d', label: 'Favourites (30d)', icon: 'fa-heart', color: 'text-pink-400', link: null },
{ key: 'shares_30d', label: 'Shares (30d)', icon: 'fa-share-nodes', color: 'text-amber-400', link: null },
{ key: 'followers', label: 'Followers', icon: 'fa-user-group', color: 'text-purple-400', link: null },
]
function KpiCard({ config, value }) {
const content = (
<div className="bg-nova-900/60 border border-white/10 rounded-2xl p-5 hover:border-white/20 hover:shadow-lg hover:shadow-accent/5 transition-all duration-300 cursor-pointer group">
<div className="flex items-center gap-3 mb-3">
<div className={`w-10 h-10 rounded-xl bg-white/5 flex items-center justify-center ${config.color} group-hover:scale-110 transition-transform`}>
<i className={`fa-solid ${config.icon}`} />
</div>
<span className="text-xs font-medium text-slate-400 uppercase tracking-wider">{config.label}</span>
</div>
<p className="text-3xl font-bold text-white tabular-nums">
{typeof value === 'number' ? value.toLocaleString() : value}
</p>
</div>
)
if (config.link) {
return <Link href={config.link}>{content}</Link>
}
return content
}
function TopPerformerCard({ artwork }) {
return (
<div className="bg-nova-900/60 border border-white/10 rounded-2xl p-4 hover:border-white/20 hover:shadow-lg hover:shadow-accent/5 transition-all duration-300 group">
<div className="flex items-start gap-3">
{artwork.thumb_url && (
<img
src={artwork.thumb_url}
alt={artwork.title}
className="w-16 h-16 rounded-xl object-cover bg-nova-800 flex-shrink-0 group-hover:scale-105 transition-transform"
loading="lazy"
/>
)}
<div className="min-w-0 flex-1">
<h4 className="text-sm font-semibold text-white truncate" title={artwork.title}>
{artwork.title}
</h4>
<div className="flex flex-wrap items-center gap-3 mt-1.5">
<span className="text-xs text-slate-400">
{artwork.favourites?.toLocaleString()}
</span>
<span className="text-xs text-slate-400">
🔗 {artwork.shares?.toLocaleString()}
</span>
</div>
{artwork.heat_score > 5 && (
<span className="inline-flex items-center gap-1 mt-2 px-2 py-0.5 rounded-md text-[10px] font-medium bg-orange-500/20 text-orange-400 border border-orange-500/30">
<i className="fa-solid fa-fire" /> Rising
</span>
)}
</div>
</div>
</div>
)
}
function RecentComment({ comment }) {
return (
<div className="flex items-start gap-3 py-3 border-b border-white/5 last:border-0">
<div className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center text-xs text-slate-400 flex-shrink-0">
<i className="fa-solid fa-comment" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm text-white">
<span className="font-medium text-accent">{comment.author_name}</span>
{' '}on{' '}
<span className="text-slate-300">{comment.artwork_title}</span>
</p>
<p className="text-xs text-slate-500 mt-0.5 line-clamp-2">{comment.body}</p>
<p className="text-[10px] text-slate-600 mt-1">
{new Date(comment.created_at).toLocaleDateString()}
</p>
</div>
</div>
)
}
export default function StudioDashboard() {
const { props } = usePage()
const { kpis, topPerformers, recentComments } = props
return (
<StudioLayout title="Studio Overview">
{/* KPI Cards */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
{kpiConfig.map((config) => (
<KpiCard key={config.key} config={config} value={kpis?.[config.key] ?? 0} />
))}
</div>
{/* Top Performers */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-white">Your Top Performers</h2>
<span className="text-xs text-slate-500">Last 7 days</span>
</div>
{topPerformers?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{topPerformers.map((art) => (
<TopPerformerCard key={art.id} artwork={art} />
))}
</div>
) : (
<div className="bg-nova-900/40 border border-white/10 rounded-2xl p-8 text-center">
<p className="text-slate-500 text-sm">No artworks yet. Upload your first creation!</p>
<Link
href="/upload"
className="inline-flex items-center gap-2 mt-4 px-5 py-2.5 rounded-xl bg-accent hover:bg-accent/90 text-white text-sm font-semibold transition-all shadow-lg shadow-accent/25"
>
<i className="fa-solid fa-cloud-arrow-up" /> Upload
</Link>
</div>
)}
</div>
{/* Recent Comments */}
<div>
<h2 className="text-lg font-bold text-white mb-4">Recent Comments</h2>
<div className="bg-nova-900/40 border border-white/10 rounded-2xl p-4">
{recentComments?.length > 0 ? (
recentComments.map((c) => <RecentComment key={c.id} comment={c} />)
) : (
<p className="text-slate-500 text-sm text-center py-4">No comments yet</p>
)}
</div>
</div>
</StudioLayout>
)
}