import React, { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardHeader, CardTitle, CardContent, CardFooter, CardDescription, } from "@/components/ui/card"; import { Plus, Minus, Trash2, ArrowLeft, Pencil } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Progress } from "@/components/ui/progress"; interface ParticipantSetup { id: number; name: string; } interface Prize { id: string; name: string; points: number; quantity: number; } interface Participant { id: number; name: string; pointsAvailable: number; prizes: Prize[]; } interface EditingPrize { participantId: number; prizeId: string; name: string; points: string; quantity: string; } interface StoredData { stage: Stage; numParticipants: number; participantSetup: ParticipantSetup[]; pointInputs: number[]; participants: Participant[]; totalPoints: number; } type Stage = "setup" | "points" | "tracking"; const STORAGE_KEY = "arcadeTrackerData"; const ArcadeTracker: React.FC = () => { const [stage, setStage] = useState("setup"); const [numParticipants, setNumParticipants] = useState(3); const [pointInputs, setPointInputs] = useState([]); const [participants, setParticipants] = useState([]); const [totalPoints, setTotalPoints] = useState(0); const [newPrizeName, setNewPrizeName] = useState(""); const [newPrizePoints, setNewPrizePoints] = useState(""); const [newPrizeQuantity, setNewPrizeQuantity] = useState("1"); const [participantSetup, setParticipantSetup] = useState( [] ); const [showResetDialog, setShowResetDialog] = useState(false); const [editingParticipantId, setEditingParticipantId] = useState< number | null >(null); const [editingPrize, setEditingPrize] = useState(null); useEffect(() => { // Initialize participant setup when component mounts if (participantSetup.length === 0) { setParticipantSetup(Array(numParticipants) .fill(null) .map((_, i) => ({ id: i + 1, name: `Player ${i + 1}`, })) ); } }, [participantSetup.length, numParticipants]); useEffect(() => { const savedData = localStorage.getItem(STORAGE_KEY); if (savedData) { try { const parsed = JSON.parse(savedData) as StoredData; setStage(parsed.stage); setNumParticipants(parsed.numParticipants); setParticipantSetup(parsed.participantSetup || []); setPointInputs(parsed.pointInputs); setParticipants(parsed.participants); setTotalPoints(parsed.totalPoints); } catch (error) { console.error("Error parsing saved data:", error); } } }, []); useEffect(() => { const dataToSave: StoredData = { stage, numParticipants, participantSetup, pointInputs, participants, totalPoints, }; localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave)); }, [ stage, numParticipants, pointInputs, participants, totalPoints, participantSetup, ]); // Add participantSetup const handleParticipantNameChange = (id: number, name: string) => { setParticipantSetup((prev) => prev.map((p) => (p.id === id ? { ...p, name } : p)) ); }; const handleNumParticipantsChange = (change: number) => { const newNum = Math.max(2, Math.min(10, numParticipants + change)); setNumParticipants(newNum); // Update participant setup array setParticipantSetup((prev) => { const newSetup = Array(newNum) .fill(null) .map((_, i) => ({ id: i + 1, name: prev[i]?.name || `Player ${i + 1}`, })); return newSetup; }); }; const handleUpdatePoints = () => { const total = pointInputs.reduce((sum, points) => sum + points, 0); const pointsPerPerson = Math.floor(total / numParticipants); // Update each participant's points while preserving prizes const updatedParticipants = participantSetup.map((setup) => { const existingParticipant = participants.find((p) => p.id === setup.id); const currentPrizePoints = existingParticipant?.prizes.reduce( (sum, prize) => sum + prize.points, 0 ) || 0; return { id: setup.id, name: setup.name, pointsAvailable: pointsPerPerson - currentPrizePoints, prizes: existingParticipant?.prizes || [], }; }); setTotalPoints(total); setParticipants(updatedParticipants); setStage("tracking"); }; const handleAddPrize = (participantId: number) => { if (editingPrize) { // We're editing an existing prize const points = parseInt(newPrizePoints) || 0; const quantity = parseInt(newPrizeQuantity) || 1; setParticipants((current) => current.map((p) => { if (p.id === participantId) { const oldPrize = p.prizes.find((prize) => prize.id === editingPrize.prizeId); const oldPoints = (oldPrize?.points || 0) * (oldPrize?.quantity || 1); const newPoints = points * quantity; return { ...p, pointsAvailable: p.pointsAvailable + oldPoints - newPoints, prizes: p.prizes.map((prize) => prize.id === editingPrize.prizeId ? { ...prize, name: newPrizeName || "Prize", points, quantity, } : prize ), }; } return p; }) ); } else { // We're adding a new prize const points = parseInt(newPrizePoints) || 0; const quantity = parseInt(newPrizeQuantity) || 1; setParticipants((current) => current.map((p) => { if (p.id === participantId) { return { ...p, pointsAvailable: p.pointsAvailable - points * quantity, prizes: [ ...p.prizes, { id: Date.now().toString(), name: newPrizeName || "Prize", points, quantity, }, ], }; } return p; }) ); } // Clear inputs and close dialog setNewPrizeName(""); setNewPrizePoints(""); setNewPrizeQuantity("1"); setEditingParticipantId(null); setEditingPrize(null); }; const removePrize = (participantId: number, prizeId: string) => { setParticipants( participants.map((p) => { if (p.id === participantId) { const prizeToRemove = p.prizes.find((prize) => prize.id === prizeId); return { ...p, pointsAvailable: p.pointsAvailable + (prizeToRemove?.points || 0) * (prizeToRemove?.quantity || 1), prizes: p.prizes.filter((prize) => prize.id !== prizeId), }; } return p; }) ); }; const handlePointInput = (index: number, value: string) => { const newInputs = [...pointInputs]; newInputs[index] = value === "" ? 0 : parseInt(value) || 0; setPointInputs(newInputs); }; const resetApp = () => { localStorage.removeItem(STORAGE_KEY); setStage("setup"); setNumParticipants(3); setParticipantSetup([ { id: 1, name: "Player 1" }, { id: 2, name: "Player 2" }, { id: 3, name: "Player 3" }, ]); setPointInputs([]); setParticipants([]); setTotalPoints(0); setShowResetDialog(false); }; const renderSetupStage = () => (
Arcade Point Tracker
Number of participants:
{numParticipants}
Enter participant names:
{participantSetup.map((p, index) => (
handleParticipantNameChange(p.id, e.target.value) } placeholder={`Player ${index + 1}`} onClick={(e) => (e.target as HTMLInputElement).select()} className="transition-colors focus:bg-accent focus:text-accent-foreground" />
))}
); const renderPointsStage = () => (
Enter Points Input the points on each participant's card
{pointInputs.map((points, index) => (
handlePointInput(index, e.target.value)} className="mt-1" placeholder="Enter points" />
))}
{" "}
); const renderTrackingStage = () => ( <>
Total Points: {totalPoints}
Points Used:{" "} {participants.reduce( (total, p) => total + p.prizes.reduce((sum, prize) => sum + prize.points * prize.quantity, 0), 0 )}
total + p.prizes.reduce((sum, prize) => sum + prize.points * prize.quantity, 0), 0 ) / totalPoints) * 100 } className={`h-2 ${ participants.reduce( (total, p) => total + p.prizes.reduce((sum, prize) => sum + prize.points * prize.quantity, 0), 0 ) > totalPoints ? "[&>div]:bg-destructive" : "" }`} /> total + p.prizes.reduce((sum, prize) => sum + prize.points * prize.quantity, 0), 0 ) > totalPoints ? "text-destructive font-medium" : "" }`} > {( (participants.reduce( (total, p) => total + p.prizes.reduce((sum, prize) => sum + prize.points * prize.quantity, 0), 0 ) / totalPoints) * 100 ).toFixed(1)} % used
{/* Spacer for fixed header */}
Reset Application Are you sure you want to reset all data? This action cannot be undone. {participants.map((participant, index) => (
{participant.name}
Points Available: {participant.pointsAvailable} /{" "} {Math.floor(totalPoints / participants.length)} {( (participant.pointsAvailable / (totalPoints / participants.length)) * 100 ).toFixed(1)} %
div]:bg-destructive" : "" }`} />
{participant.prizes.map((prize) => ( {editingPrize?.prizeId === prize.id ? (
setEditingPrize({ ...editingPrize, name: e.target.value }) } className="h-8" />
setEditingPrize({ ...editingPrize, points: e.target.value }) } className="h-8" />
{editingPrize.quantity}
) : (
{prize.name}
{prize.quantity > 1 ? `${prize.quantity}x ` : ''}{prize.points} points {prize.quantity > 1 ? ` (${prize.points * prize.quantity} total)` : ''}
)}
))}
{!participant.prizes.length && ( No prizes added yet )} { if (!open) { setEditingParticipantId(null); setEditingPrize(null); setNewPrizeName(""); setNewPrizePoints(""); setNewPrizeQuantity("1"); } }} > {editingPrize ? 'Edit' : 'Add'} Prize for {participant.name} Available Points: {participant.pointsAvailable}
setNewPrizeName(e.target.value) } />
setNewPrizePoints(e.target.value) } />
{newPrizeQuantity}
))}
); switch (stage) { case "setup": return renderSetupStage(); case "points": return renderPointsStage(); case "tracking": return renderTrackingStage(); default: return null; } }; export default ArcadeTracker;