// SONARA — Producer Catalog (owner view: Beat + Servizi) const CIcons = window.SonaraIcons; const BEATS = [ { id: 1, title: "Notturno", bpm: 142, key: "F# min", grad: "b1", status: "published", price: 45, exclusive: 350, sold: 3, plays: 2100, views: 4200, revenue: 458, updated: "2gg fa", exclusiveAvail: true, genre: "Trap" }, { id: 2, title: "Asfalto Bagnato", bpm: 138, key: "G min", grad: "b2", status: "published", price: 50, exclusive: 400, sold: 1, plays: 1400, views: 2800, revenue: 168, updated: "1 sett. fa", exclusiveAvail: true, genre: "Trap" }, { id: 3, title: "Lupo Solitario", bpm: 145, key: "D min", grad: "b3", status: "published", price: 40, exclusive: 300, sold: 5, plays: 3200, views: 6100, revenue: 545, updated: "3gg fa", exclusiveAvail: true, genre: "Drill" }, { id: 4, title: "Quartiere Nord", bpm: 140, key: "A min", grad: "b4", status: "published", price: 55, exclusive: 450, sold: 2, plays: 1800, views: 3400, revenue: 220, updated: "2 sett. fa", exclusiveAvail: true, genre: "Drill" }, { id: 5, title: "Settembre", bpm: 132, key: "E min", grad: "b5", status: "published", price: 60, exclusive: 500, sold: 0, plays: 892, views: 1900, revenue: 0, updated: "5gg fa", exclusiveAvail: true, genre: "R&B" }, { id: 6, title: "Cassetta Vuota", bpm: 148, key: "B min", grad: "b6", status: "published", price: 45, exclusive: 380, sold: 1, plays: 1100, views: 2300, revenue: 120, updated: "4gg fa", exclusiveAvail: true, genre: "Trap" }, { id: 7, title: "Mezzanotte", bpm: 144, key: "C# min", grad: "b1", status: "sold_exclusive", price: 50, exclusive: 420, sold: 1, plays: 4200, views: 8200, revenue: 420, updated: "1 mese fa", exclusiveAvail: false, genre: "Trap", soldTo: "RAZZA" }, { id: 8, title: "Veleno", bpm: 150, key: "F min", grad: "b2", status: "draft", price: 0, exclusive: 0, sold: 0, plays: 0, views: 0, revenue: 0, updated: "Ieri", exclusiveAvail: true, genre: "Drill" }, { id: 9, title: "Solo Per Te", bpm: 90, key: "D maj", grad: "b3", status: "hidden", price: 50, exclusive: 400, sold: 0, plays: 320, views: 800, revenue: 0, updated: "2 mesi fa", exclusiveAvail: true, genre: "R&B" }, { id: 10, title: "Doppia Faccia", bpm: 146, key: "G# min", grad: "b4", status: "published", price: 45, exclusive: 380, sold: 2, plays: 1600, views: 3100, revenue: 218, updated: "1 sett. fa", exclusiveAvail: true, genre: "Trap" }, { id: 11, title: "Cobra", bpm: 152, key: "E min", grad: "b5", status: "published", price: 50, exclusive: 420, sold: 1, plays: 980, views: 1800, revenue: 105, updated: "2 sett. fa", exclusiveAvail: true, genre: "Drill" }, { id: 12, title: "Sirene", bpm: 128, key: "A min", grad: "b6", status: "published", price: 65, exclusive: 550, sold: 0, plays: 420, views: 1200, revenue: 0, updated: "3gg fa", exclusiveAvail: true, genre: "Hip-hop" }, ]; const SERVICES = [ { id: 1, title: "Mix professionale trap/hip-hop", type: "Mix stereo", price: 120, delivery: "3 gg", orders: 24, rating: 4.9, status: "published", views: 1840, conv: 4.2, revenue: 2640, grad: "g3", revisions: 2, addons: 3, active: 2 }, { id: 2, title: "Mix + Master pacchetto completo", type: "Mix + Master", price: 180, delivery: "4 gg", orders: 18, rating: 4.8, status: "published", views: 2210, conv: 3.8, revenue: 2880, grad: "g4", revisions: 2, addons: 4, active: 1 }, { id: 3, title: "Custom production su brief", type: "Custom production", price: 350, delivery: "7 gg", orders: 8, rating: 5.0, status: "published", views: 1120, conv: 2.1, revenue: 2464, grad: "g1", revisions: 3, addons: 2, active: 1 }, { id: 4, title: "Vocal production + tuning", type: "Vocal production", price: 90, delivery: "2 gg", orders: 15, rating: 4.7, status: "published", views: 940, conv: 5.1, revenue: 1188, grad: "g5", revisions: 2, addons: 2, active: 0 }, ]; /* ─────── ICONS ─────── */ const PlayIc = ({ size = 12 }) => ; const EyeIc = ({ size = 14 }) => ; const EditIc = ({ size = 14 }) => ; const DotsIc = ({ size = 14 }) => ; const PlusIc = ({ size = 14 }) => ; const StarIc = ({ size = 12, filled = true }) => ; const SearchIc = ({ size = 14 }) => ; const ChevDownIc = ({ size = 12 }) => ; const PauseIc = ({ size = 12 }) => ; const STATUS = { published: { label: "Pubblicato", cls: "ok" }, draft: { label: "Bozza", cls: "draft" }, sold_exclusive: { label: "Venduto esclusiva", cls: "exclusive" }, hidden: { label: "Nascosto", cls: "hidden" }, }; /* ─────── BEAT TABLE ROW ─────── */ function BeatRow({ b, view }) { const [playing, setPlaying] = React.useState(false); if (view === "card") { return (
{STATUS[b.status].label} {b.status === "published" && b.sold > 0 && ( {b.sold} venduti )}
{b.title}
{b.bpm} BPM· {b.key}· {b.genre}
Plays{b.plays.toLocaleString("it-IT")}
Visite{b.views.toLocaleString("it-IT")}
Revenue€{b.revenue}
€{b.price} {b.exclusiveAvail && €{b.exclusive} excl.} {b.status === "sold_exclusive" && → {b.soldTo}}
); } // Table row return (
{b.title}
{b.bpm} BPM · {b.key} · {b.genre}
{STATUS[b.status].label}
{b.plays.toLocaleString("it-IT")}
{b.views.toLocaleString("it-IT")}
{b.sold}
€{b.revenue}
€{b.price} {b.exclusiveAvail && €{b.exclusive}}
); } /* ─────── BEAT TAB ─────── */ function BeatTab() { const [filter, setFilter] = React.useState("all"); // all, published, draft, sold_exclusive, hidden const [sort, setSort] = React.useState("updated"); // updated, plays, sold, revenue const [view, setView] = React.useState("table"); // card | table const [search, setSearch] = React.useState(""); let beats = BEATS; if (filter !== "all") beats = beats.filter(b => b.status === filter); if (search) beats = beats.filter(b => b.title.toLowerCase().includes(search.toLowerCase())); beats = [...beats].sort((a, b) => { if (sort === "plays") return b.plays - a.plays; if (sort === "sold") return b.sold - a.sold; if (sort === "revenue") return b.revenue - a.revenue; return 0; // updated = order in array }); return ( <>
setSearch(e.target.value)} placeholder="Cerca beat per titolo…"/>
{[ { v: "all", l: "Tutti", c: BEATS.length }, { v: "published", l: "Pubblicati", c: BEATS.filter(b => b.status === "published").length }, { v: "draft", l: "Bozze", c: BEATS.filter(b => b.status === "draft").length }, { v: "sold_exclusive", l: "Venduti excl.", c: BEATS.filter(b => b.status === "sold_exclusive").length }, { v: "hidden", l: "Nascosti", c: BEATS.filter(b => b.status === "hidden").length }, ].map(f => ( ))}
Ordina
{view === "table" ? (
Beat
Stato
Plays
Visite
Venduti
Revenue
Prezzo
{beats.map(b => )}
Carica nuovo beat
Wizard 3-step · audio + metadata + licenze
) : (
{beats.map(b => )}
Carica nuovo beat
WAV / MP3 / AIFF · max 50MB · wizard 3-step
)} {beats.length === 0 && (
Nessun beat in questa categoria
Cambia filtro o carica il primo beat.
Carica beat
)} ); } /* ─────── SERVICE CARD ─────── */ function ServiceCard({ s }) { return (
{s.type} {STATUS[s.status].label}
{s.title}
{s.rating} · {s.orders} ordini · Consegna {s.delivery} {s.active > 0 && <>·{s.active} in corso}
Visite{s.views.toLocaleString("it-IT")}
Conv.{s.conv}%
Revisioni incl.{s.revisions}
Add-on{s.addons}
Revenue lifetime€{s.revenue}
A partire da €{s.price}
); } /* ─────── SERVICES TAB ─────── */ function ServicesTab() { return ( <>
{SERVICES.map(s => )}
Crea nuovo listing
Mix, master, custom production, vocal prod o altro
); } /* ─────── CATALOG PAGE ─────── */ function CatalogPage() { const [tab, setTab] = React.useState("beats"); const revLifetime = BEATS.reduce((a, b) => a + b.revenue, 0) + SERVICES.reduce((a, s) => a + s.revenue, 0); return (

Catalog

{BEATS.length} beat · {SERVICES.length} servizi · {revLifetime.toLocaleString("it-IT")} revenue lifetime
Carica beat Crea listing servizio
{tab === "beats" ? : }
); } window.CatalogPage = CatalogPage;