"use client"; import { useState, useEffect, useRef } from "react"; import { Card, CardContent } from "./ui/card"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { SOUND_CATEGORIES, EMOJIS } from "@/lib/bingo-data"; import { Badge } from "./ui/badge"; type Item = { id: string; text: string; emoji: string; soundCategory: string; soundUrl?: string | null; gridIndex: number; }; type Campaign = { id: string; name: string; gridSize: number; }; function EmojiPicker({ value, onChange }: { value: string; onChange: (v: string) => void }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, []); return (
{open && (
{EMOJIS.map(e => ( ))}
)}
); } export function ItemEditor({ campaign }: { campaign: Campaign }) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [editText, setEditText] = useState(""); const [editEmoji, setEditEmoji] = useState("💀"); const [editSound, setEditSound] = useState("horn"); const [editSoundUrl, setEditSoundUrl] = useState(null); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const fetchItems = async () => { const res = await fetch(`/api/campaigns/${campaign.id}/items`); const data = await res.json(); const sorted = [...data].sort((a: Item, b: Item) => a.gridIndex - b.gridIndex); setItems(sorted); setLoading(false); }; useEffect(() => { fetchItems(); }, [campaign.id]); const updateItem = async (item: Item) => { await fetch(`/api/campaigns/${campaign.id}/items`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: item.id, text: item.text, emoji: item.emoji, soundCategory: item.soundCategory, soundUrl: item.soundUrl, gridIndex: item.gridIndex, }), }); await fetchItems(); }; const deleteItem = async (itemId: string) => { await fetch(`/api/campaigns/${campaign.id}/items?itemId=${itemId}`, { method: "DELETE" }); await fetchItems(); }; const addItem = async () => { if (!editText) return; const maxIdx = items.reduce((max, it) => Math.max(max, it.gridIndex), -1); const totalCells = campaign.gridSize * campaign.gridSize; if (maxIdx + 1 >= totalCells) { alert("Grid is full! Delete some items first."); return; } await fetch(`/api/campaigns/${campaign.id}/items`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: editText, emoji: editEmoji, soundCategory: editSound, soundUrl: editSoundUrl, gridIndex: maxIdx + 1, }), }); setEditText(""); setEditEmoji("💀"); setEditSound("horn"); setEditSoundUrl(null); await fetchItems(); }; const uploadSound = async (file: File) => { setUploading(true); const formData = new FormData(); formData.append("file", file); const res = await fetch("/api/upload/sound", { method: "POST", body: formData }); const data = await res.json(); if (data.url) setEditSoundUrl(data.url); setUploading(false); }; const totalCells = campaign.gridSize * campaign.gridSize; if (loading) { return
Loading items...
; } return (

+ Add New Cell

setEditText(e.target.value)} className="font-mono text-sm" />
{editSound === "custom" && (
{ const f = e.target.files?.[0]; if (f) uploadSound(f); }} /> {editSoundUrl && ( )}
)}
{Array.from({ length: totalCells }).map((_, idx) => { const item = items.find(it => it.gridIndex === idx); if (!item) { return (
✖
); } return ( {item.emoji} {item.text} {item.soundUrl ? "🔊" : item.soundCategory}
); })}

All Items

{items.map(item => (
{item.emoji} { setItems(prev => prev.map(it => it.id === item.id ? { ...it, text: e.target.value } : it)); }} onBlur={() => updateItem(item)} /> #{item.gridIndex}
))}
); }