This commit is contained in:
Zeeshaun
2026-05-06 15:33:36 +00:00
parent 0de593c7d3
commit 9e56dae161
21 changed files with 1663 additions and 955 deletions
+114 -74
View File
@@ -1,5 +1,6 @@
"use client";
import { Alert, Box, Button, Card, CardContent, Checkbox, Divider, FormControlLabel, MenuItem, Stack, TextField, Typography } from "@mui/material";
import { useEffect, useMemo, useState } from "react";
import { AppShell } from "@/components/AppShell";
import { EmptyState } from "@/components/EmptyState";
@@ -94,73 +95,102 @@ export function LineupClient() {
title="Generate Lineup"
subtitle="Choose the active group, pick an eligible starter, then adjust the suggested lineup as needed."
>
{message ? <div className="banner banner-success">{message}</div> : null}
{error ? <div className="banner banner-error">{error}</div> : null}
{message ? <Alert severity="success">{message}</Alert> : null}
{error ? <Alert severity="error">{error}</Alert> : null}
<SectionCard title="Game Setup" description="Start with the players who are available today.">
<div className="form-grid">
<label>
<span>Game Date</span>
<input type="date" value={gameDate} onChange={(event) => setGameDate(event.target.value)} />
</label>
<label>
<span>Starting Pitcher</span>
<select value={startingPitcherId} onChange={(event) => setStartingPitcherId(event.target.value)}>
{selectablePitchers.map((player) => (
<option key={player._id} value={player._id}>
{formatPlayerName(player.firstName, player.lastName)}
</option>
))}
</select>
</label>
<label className="checkbox-row">
<input type="checkbox" checked={showAllPitchers} onChange={(event) => setShowAllPitchers(event.target.checked)} />
<span>Show pitchers who need rest</span>
</label>
<div className="full-width action-row">
<button className="button" disabled={selectedPlayerIds.length === 0} onClick={() => void generate()}>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "repeat(2, minmax(0, 1fr))" }, gap: 2 }}>
<TextField
label="Game Date"
type="date"
value={gameDate}
onChange={(event) => setGameDate(event.target.value)}
slotProps={{ inputLabel: { shrink: true } }}
/>
<TextField select label="Starting Pitcher" value={startingPitcherId} onChange={(event) => setStartingPitcherId(event.target.value)}>
{selectablePitchers.map((player) => (
<MenuItem key={player._id} value={player._id}>
{formatPlayerName(player.firstName, player.lastName)}
</MenuItem>
))}
</TextField>
<Box sx={{ gridColumn: "1 / -1" }}>
<FormControlLabel
control={<Checkbox checked={showAllPitchers} onChange={(event) => setShowAllPitchers(event.target.checked)} />}
label="Show pitchers who need rest"
/>
</Box>
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.25} sx={{ gridColumn: "1 / -1" }}>
<Button variant="contained" disabled={selectedPlayerIds.length === 0} onClick={() => void generate()}>
Generate Suggested Lineup
</button>
<button className="button button-secondary" disabled={!generated} onClick={() => void save()}>
</Button>
<Button variant="outlined" color="inherit" disabled={!generated} onClick={() => void save()}>
Save Lineup
</button>
</div>
</div>
</Button>
</Stack>
</Box>
<div className="picker-grid">
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", lg: "repeat(2, 1fr)" }, gap: 1.5, mt: 3 }}>
{players.map((player) => (
<label key={player._id} className="picker-card">
<input
type="checkbox"
checked={selectedPlayerIds.includes(player._id)}
onChange={(event) =>
setSelectedPlayerIds((current) =>
event.target.checked ? [...current, player._id] : current.filter((id) => id !== player._id)
)
}
/>
<div>
<strong>{formatPlayerName(player.firstName, player.lastName)}</strong>
<p>
#{player.jerseyNumber} {player.primaryPosition}
</p>
</div>
<StatusBadge status={player.pitcherAvailability.status} />
</label>
<Card
key={player._id}
elevation={0}
sx={{
border: selectedPlayerIds.includes(player._id) ? "1px solid" : "1px solid rgba(15, 38, 47, 0.08)",
borderColor: selectedPlayerIds.includes(player._id) ? "primary.main" : "rgba(15, 38, 47, 0.08)",
backgroundColor: selectedPlayerIds.includes(player._id) ? "rgba(15, 92, 77, 0.05)" : "transparent"
}}
>
<CardContent
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "row" },
justifyContent: "space-between",
alignItems: { xs: "flex-start", sm: "center" },
gap: 1.5
}}
>
<Stack direction="row" spacing={1.25} sx={{ alignItems: "flex-start" }}>
<Checkbox
checked={selectedPlayerIds.includes(player._id)}
onChange={(event) =>
setSelectedPlayerIds((current) =>
event.target.checked ? [...current, player._id] : current.filter((id) => id !== player._id)
)
}
/>
<Box>
<Typography variant="h6">{formatPlayerName(player.firstName, player.lastName)}</Typography>
<Typography color="text.secondary">
#{player.jerseyNumber} {player.primaryPosition}
</Typography>
</Box>
</Stack>
<StatusBadge status={player.pitcherAvailability.status} />
</CardContent>
</Card>
))}
</div>
</Box>
</SectionCard>
{generated ? (
<>
<SectionCard title="Batting Order" description="Adjust any slot manually before saving.">
<div className="list-stack">
<Stack divider={<Divider flexItem />} spacing={0}>
{generated.battingOrder.map((slot, index) => (
<div key={`${slot.playerId}-${index}`} className="list-row">
<div>
<h3>Spot {index + 1}</h3>
</div>
<select
<Box
key={`${slot.playerId}-${index}`}
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", md: "180px 1fr" },
alignItems: "center",
gap: 2,
py: 2
}}
>
<Typography variant="h6">Spot {index + 1}</Typography>
<TextField
select
value={slot.playerId}
onChange={(event) => {
const nextPlayer = selectedPlayers.find((player) => player._id === event.target.value);
@@ -180,26 +210,34 @@ export function LineupClient() {
}}
>
{selectedPlayers.map((player) => (
<option key={player._id} value={player._id}>
<MenuItem key={player._id} value={player._id}>
{formatPlayerName(player.firstName, player.lastName)}
</option>
</MenuItem>
))}
</select>
</div>
</TextField>
</Box>
))}
</div>
</Stack>
</SectionCard>
<SectionCard title="Defensive Alignment" description="Assignments follow position preferences, but every field is editable.">
<div className="list-stack">
<Stack divider={<Divider flexItem />} spacing={0}>
{FIELD_POSITIONS.map((position) => {
const assignment = generated.defensiveAssignments.find((entry) => entry.position === position);
return (
<div key={position} className="list-row">
<div>
<h3>{position}</h3>
</div>
<select
<Box
key={position}
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", md: "180px 1fr" },
alignItems: "center",
gap: 2,
py: 2
}}
>
<Typography variant="h6">{position}</Typography>
<TextField
select
value={assignment?.playerId ?? ""}
onChange={(event) => {
const nextPlayer = selectedPlayers.find((player) => player._id === event.target.value);
@@ -216,23 +254,25 @@ export function LineupClient() {
});
}}
>
<option value="">Unassigned</option>
<MenuItem value="">Unassigned</MenuItem>
{selectedPlayers.map((player) => (
<option key={player._id} value={player._id}>
<MenuItem key={player._id} value={player._id}>
{formatPlayerName(player.firstName, player.lastName)}
</option>
</MenuItem>
))}
</select>
</div>
</TextField>
</Box>
);
})}
</div>
</Stack>
{generated.notes.length > 0 ? (
<div className="note-block">
<Box sx={{ mt: 2, pl: 2, borderLeft: "4px solid", borderColor: "warning.main" }}>
{generated.notes.map((note) => (
<p key={note}>{note}</p>
<Typography key={note} color="text.secondary">
{note}
</Typography>
))}
</div>
</Box>
) : null}
</SectionCard>
</>