Updating
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { EmptyState } from "@/components/EmptyState";
|
||||
import { PlayerForm, emptyPlayerForm, toPlayerForm } from "@/components/PlayerForm";
|
||||
import { SectionCard } from "@/components/SectionCard";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { fetchJson } from "@/lib/client";
|
||||
import { formatPlayerName } from "@/lib/utils";
|
||||
import type { PitchingAppearanceRecord, PlayerInput, PlayerRecord } from "@/lib/types";
|
||||
|
||||
export function RosterClient() {
|
||||
const [players, setPlayers] = useState<PlayerRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [editingPlayerId, setEditingPlayerId] = useState<string | null>(null);
|
||||
const [playerForm, setPlayerForm] = useState<PlayerInput>(emptyPlayerForm());
|
||||
const [appearanceForm, setAppearanceForm] = useState({
|
||||
playerId: "",
|
||||
gameDate: new Date().toISOString().slice(0, 10),
|
||||
pitchCount: 20,
|
||||
notes: ""
|
||||
});
|
||||
|
||||
const editingPlayer = useMemo(
|
||||
() => players.find((player) => player._id === editingPlayerId) ?? null,
|
||||
[editingPlayerId, players]
|
||||
);
|
||||
|
||||
async function loadPlayers() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetchJson<PlayerRecord[]>("/api/players");
|
||||
setPlayers(response);
|
||||
if (response.length > 0 && !appearanceForm.playerId) {
|
||||
setAppearanceForm((current) => ({ ...current, playerId: response[0]._id }));
|
||||
}
|
||||
} catch (loadError) {
|
||||
setError(loadError instanceof Error ? loadError.message : "Failed to load roster.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadPlayers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingPlayer) {
|
||||
setPlayerForm(toPlayerForm(editingPlayer));
|
||||
} else {
|
||||
setPlayerForm(emptyPlayerForm());
|
||||
}
|
||||
}, [editingPlayer]);
|
||||
|
||||
async function submitPlayer() {
|
||||
try {
|
||||
const url = editingPlayerId ? `/api/players/${editingPlayerId}` : "/api/players";
|
||||
const method = editingPlayerId ? "PATCH" : "POST";
|
||||
await fetchJson<PlayerRecord>(url, { method, body: JSON.stringify(playerForm) });
|
||||
setMessage(editingPlayerId ? "Player updated." : "Player added.");
|
||||
setEditingPlayerId(null);
|
||||
setPlayerForm(emptyPlayerForm());
|
||||
await loadPlayers();
|
||||
} catch (submitError) {
|
||||
setError(submitError instanceof Error ? submitError.message : "Unable to save player.");
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePlayer(player: PlayerRecord) {
|
||||
if (!window.confirm(`Remove ${formatPlayerName(player.firstName, player.lastName)} from the roster?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetchJson(`/api/players/${player._id}`, { method: "DELETE" });
|
||||
setMessage("Player removed.");
|
||||
await loadPlayers();
|
||||
} catch (deleteError) {
|
||||
setError(deleteError instanceof Error ? deleteError.message : "Unable to delete player.");
|
||||
}
|
||||
}
|
||||
|
||||
async function logAppearance() {
|
||||
try {
|
||||
await fetchJson<PitchingAppearanceRecord>("/api/pitching-appearances", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(appearanceForm)
|
||||
});
|
||||
setMessage("Pitching appearance recorded.");
|
||||
await loadPlayers();
|
||||
} catch (submitError) {
|
||||
setError(submitError instanceof Error ? submitError.message : "Unable to save pitching appearance.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
title="Roster Management"
|
||||
subtitle="Add players, update availability, and keep pitching history current from one screen."
|
||||
>
|
||||
{message ? <div className="banner banner-success">{message}</div> : null}
|
||||
{error ? <div className="banner banner-error">{error}</div> : null}
|
||||
|
||||
<SectionCard
|
||||
title={editingPlayerId ? "Edit Player" : "Add Player"}
|
||||
description="Keep player details simple so a coach can update them quickly before game time."
|
||||
>
|
||||
<PlayerForm value={playerForm} onChange={setPlayerForm} onSubmit={() => void submitPlayer()} submitLabel={editingPlayerId ? "Save Changes" : "Add Player"} />
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard title="Pitch Count Log" description="Record the latest outing to keep rest recommendations accurate.">
|
||||
<div className="form-grid">
|
||||
<label>
|
||||
<span>Player</span>
|
||||
<select
|
||||
value={appearanceForm.playerId}
|
||||
onChange={(event) => setAppearanceForm((current) => ({ ...current, playerId: event.target.value }))}
|
||||
>
|
||||
{players.map((player) => (
|
||||
<option key={player._id} value={player._id}>
|
||||
{formatPlayerName(player.firstName, player.lastName)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Game Date</span>
|
||||
<input
|
||||
type="date"
|
||||
value={appearanceForm.gameDate}
|
||||
onChange={(event) => setAppearanceForm((current) => ({ ...current, gameDate: event.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Pitches Thrown</span>
|
||||
<input
|
||||
type="number"
|
||||
value={appearanceForm.pitchCount}
|
||||
onChange={(event) => setAppearanceForm((current) => ({ ...current, pitchCount: Number(event.target.value) }))}
|
||||
/>
|
||||
</label>
|
||||
<label className="full-width">
|
||||
<span>Notes</span>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={appearanceForm.notes}
|
||||
onChange={(event) => setAppearanceForm((current) => ({ ...current, notes: event.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
<div className="full-width">
|
||||
<button className="button" onClick={() => void logAppearance()} disabled={players.length === 0}>
|
||||
Save Pitching Appearance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard title="Current Roster" description="Tap edit for fast changes and check pitcher status at a glance.">
|
||||
{loading ? (
|
||||
<p className="muted-copy">Loading roster...</p>
|
||||
) : players.length > 0 ? (
|
||||
<div className="card-grid">
|
||||
{players.map((player) => (
|
||||
<article key={player._id} className="player-card">
|
||||
<div className="player-card-head">
|
||||
<div>
|
||||
<h3>
|
||||
#{player.jerseyNumber} {formatPlayerName(player.firstName, player.lastName)}
|
||||
</h3>
|
||||
<p>
|
||||
{player.primaryPosition}
|
||||
{player.secondaryPosition ? ` / ${player.secondaryPosition}` : ""}
|
||||
{player.tertiaryPosition ? ` / ${player.tertiaryPosition}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge status={player.pitcherAvailability.status} />
|
||||
</div>
|
||||
<dl className="player-meta">
|
||||
<div>
|
||||
<dt>Bats/Throws</dt>
|
||||
<dd>{player.batsThrows}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Status</dt>
|
||||
<dd>{player.isActive ? "Active" : "Inactive"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Pitching</dt>
|
||||
<dd>
|
||||
{player.pitcherAvailability.lastPitchedDate
|
||||
? `${player.pitcherAvailability.lastPitchCount} pitches on ${player.pitcherAvailability.lastPitchedDate}`
|
||||
: "No history"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{player.notes ? <p className="player-notes">{player.notes}</p> : null}
|
||||
<div className="action-row">
|
||||
<button className="button button-secondary" onClick={() => setEditingPlayerId(player._id)}>
|
||||
Edit
|
||||
</button>
|
||||
<button className="button button-danger" onClick={() => void deletePlayer(player)}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState title="No players yet" description="Add your first player above or load sample data from the dashboard." />
|
||||
)}
|
||||
</SectionCard>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user