diff --git a/components/arcade.tsx b/components/arcade.tsx index f70b530..232a103 100644 --- a/components/arcade.tsx +++ b/components/arcade.tsx @@ -1,8 +1,15 @@ -import React, { useState, useEffect } from 'react'; +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 } from "lucide-react"; +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 { @@ -26,6 +33,7 @@ interface Prize { id: string; name: string; points: number; + quantity: number; } interface Participant { @@ -35,6 +43,14 @@ interface Participant { prizes: Prize[]; } +interface EditingPrize { + participantId: number; + prizeId: string; + name: string; + points: string; + quantity: string; +} + interface StoredData { stage: Stage; numParticipants: number; @@ -44,31 +60,40 @@ interface StoredData { totalPoints: number; } -type Stage = 'setup' | 'points' | 'tracking'; +type Stage = "setup" | "points" | "tracking"; -const STORAGE_KEY = 'arcadeTrackerData'; +const STORAGE_KEY = "arcadeTrackerData"; const ArcadeTracker: React.FC = () => { - const [stage, setStage] = useState('setup'); - const [numParticipants, setNumParticipants] = useState(2); + 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 [participantSetup, setParticipantSetup] = useState([]); + 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(null); + const [editingParticipantId, setEditingParticipantId] = useState< + number | null + >(null); + const [editingPrize, setEditingPrize] = useState(null); useEffect(() => { // Initialize participant setup when component mounts if (participantSetup.length === 0) { - setParticipantSetup([ - { id: 1, name: 'Player 1' }, - { id: 2, name: 'Player 2' } - ]); + setParticipantSetup(Array(numParticipants) + .fill(null) + .map((_, i) => ({ + id: i + 1, + name: `Player ${i + 1}`, + })) + ); } - }, [participantSetup.length]); // Add the dependency + }, [participantSetup.length, numParticipants]); useEffect(() => { const savedData = localStorage.getItem(STORAGE_KEY); @@ -82,7 +107,7 @@ const ArcadeTracker: React.FC = () => { setParticipants(parsed.participants); setTotalPoints(parsed.totalPoints); } catch (error) { - console.error('Error parsing saved data:', error); + console.error("Error parsing saved data:", error); } } }, []); @@ -97,24 +122,33 @@ const ArcadeTracker: React.FC = () => { totalPoints, }; localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave)); - }, [stage, numParticipants, pointInputs, participants, totalPoints, participantSetup]); // Add participantSetup - + }, [ + 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) + 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}` - })); + setParticipantSetup((prev) => { + const newSetup = Array(newNum) + .fill(null) + .map((_, i) => ({ + id: i + 1, + name: prev[i]?.name || `Player ${i + 1}`, + })); return newSetup; }); }; @@ -122,81 +156,124 @@ const ArcadeTracker: React.FC = () => { 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; - + 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 || [] + prizes: existingParticipant?.prizes || [], }; }); setTotalPoints(total); setParticipants(updatedParticipants); - setStage('tracking'); + setStage("tracking"); }; const handleAddPrize = (participantId: number) => { - // Just add the prize, no validation - const points = parseInt(newPrizePoints) || 0; - - setParticipants(current => - current.map(p => { + 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 - points, - prizes: [ - ...p.prizes, - { - id: Date.now().toString(), - name: newPrizeName || 'Prize', - points - } - ] + pointsAvailable: p.pointsAvailable + (prizeToRemove?.points || 0) * (prizeToRemove?.quantity || 1), + prizes: p.prizes.filter((prize) => prize.id !== prizeId), }; } return p; }) ); - - // Clear inputs and close dialog - setNewPrizeName(''); - setNewPrizePoints(''); - setEditingParticipantId(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), - 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; + newInputs[index] = value === "" ? 0 : parseInt(value) || 0; setPointInputs(newInputs); }; const resetApp = () => { localStorage.removeItem(STORAGE_KEY); - setStage('setup'); - setNumParticipants(2); + setStage("setup"); + setNumParticipants(3); setParticipantSetup([ - { id: 1, name: 'Player 1' }, - { id: 2, name: 'Player 2' } + { id: 1, name: "Player 1" }, + { id: 2, name: "Player 2" }, + { id: 3, name: "Player 3" }, ]); setPointInputs([]); setParticipants([]); @@ -208,13 +285,12 @@ const ArcadeTracker: React.FC = () => {
- Arcade Point Tracker - Set up your group's point tracking + Arcade Point Tracker
- +
Number of participants:
-
+
{numParticipants}
- +
Enter participant names:
{participantSetup.map((p, index) => (
handleParticipantNameChange(p.id, e.target.value)} + onChange={(e) => + 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" @@ -251,13 +329,13 @@ const ArcadeTracker: React.FC = () => {
))}
- @@ -271,20 +349,19 @@ const ArcadeTracker: React.FC = () => {
- Enter Points - Input the points from each card + 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" @@ -294,247 +371,443 @@ const ArcadeTracker: React.FC = () => {
- - + {" "} +
); - - const renderTrackingStage = () => ( - <> -
-
- - -
- -
Total Points: {totalPoints}
-
- Points Used: {participants.reduce((total, p) => - total + p.prizes.reduce((sum, prize) => sum + prize.points, 0) - , 0)} -
-
-
- - -
+ + 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, 0) - , 0) / totalPoints) * 100} - className={`h-2 ${(participants.reduce((total, p) => - total + p.prizes.reduce((sum, prize) => sum + prize.points, 0) - , 0) > totalPoints) ? '[&>div]:bg-destructive' : ''}`} - /> - - total + p.prizes.reduce((sum, prize) => sum + prize.points, 0) - , 0) > totalPoints) ? 'text-destructive font-medium' : ''}`}> - {((participants.reduce((total, p) => - total + p.prizes.reduce((sum, prize) => sum + prize.points, 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) => ( - +
+ + +
+
+
+ + 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. Click "Add Prize" to get started." - - - )} - - { - if (!open) setEditingParticipantId(null); - }}> - - - - - - Add Prize for {participant.name} - - Available Points: {participant.pointsAvailable} - - -
-
- - setNewPrizeName(e.target.value)} - /> -
-
- - setNewPrizePoints(e.target.value)} - /> -
+ + + + ))} + + + {!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': + case "setup": return renderSetupStage(); - case 'points': + case "points": return renderPointsStage(); - case 'tracking': + case "tracking": return renderTrackingStage(); default: return null; } }; -export default ArcadeTracker; \ No newline at end of file +export default ArcadeTracker;