From e43fd17de4dde66b052c6252dfefde7e866f261d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 00:31:51 +0000 Subject: [PATCH] Add inline edit for participant names on admin sports season page Adds a pencil icon button next to each participant's delete button. Clicking it switches the row into edit mode with an input field, a save (checkmark) and cancel (X) button. Escape also cancels. Submits via intent="update-name" and validates uniqueness within the season before persisting. https://claude.ai/code/session_01Q94DWQ1HZEB9PJhGMArU8D --- .../admin.sports-seasons.$id.participants.tsx | 131 +++++++++++++++--- 1 file changed, 115 insertions(+), 16 deletions(-) diff --git a/app/routes/admin.sports-seasons.$id.participants.tsx b/app/routes/admin.sports-seasons.$id.participants.tsx index 70d2c29..da8fbd4 100644 --- a/app/routes/admin.sports-seasons.$id.participants.tsx +++ b/app/routes/admin.sports-seasons.$id.participants.tsx @@ -1,3 +1,4 @@ +import { useState, useEffect } from "react"; import { Form, Link } from "react-router"; import type { Route } from "./+types/admin.sports-seasons.$id.participants"; @@ -8,6 +9,7 @@ import { findParticipantByName, createParticipant, deleteParticipant, + updateParticipant, } from "~/models/participant"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; @@ -28,7 +30,7 @@ import { TableHeader, TableRow, } from "~/components/ui/table"; -import { Plus, Trash2, ArrowLeft } from "lucide-react"; +import { Plus, Trash2, ArrowLeft, Pencil, Check, X } from "lucide-react"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `Participants — ${data?.sportsSeason?.name ?? "Sports Season"} - Brackt Admin` }]; @@ -62,6 +64,33 @@ export async function action({ request, params }: Route.ActionArgs) { return { success: true }; } + if (intent === "update-name") { + const participantId = formData.get("participantId"); + const newName = formData.get("newName"); + + if (typeof participantId !== "string") { + return { error: "Invalid participant" }; + } + + if (typeof newName !== "string" || !newName.trim()) { + return { error: "Name is required" }; + } + + const trimmedName = newName.trim(); + const existing = await findParticipantByName(params.id, trimmedName); + if (existing && existing.id !== participantId) { + return { error: `"${trimmedName}" already exists in this season.` }; + } + + try { + await updateParticipant(participantId, { name: trimmedName }); + return { success: true, intent: "update-name" }; + } catch (error) { + logger.error("Error updating participant:", error); + return { error: "Failed to update participant. Please try again." }; + } + } + // Bulk add participants if (intent === "bulk") { const bulkNames = formData.get("bulkNames"); @@ -142,10 +171,19 @@ export async function action({ request, params }: Route.ActionArgs) { export default function ManageParticipants({ loaderData, actionData }: Route.ComponentProps) { const { sportsSeason, participants } = loaderData; - + const [editingId, setEditingId] = useState(null); + const [editingName, setEditingName] = useState(""); + // Use success state to reset forms by changing the key const formKey = actionData?.success ? Date.now() : 'static'; + useEffect(() => { + if (actionData?.success && actionData?.intent === "update-name") { + setEditingId(null); + setEditingName(""); + } + }, [actionData]); + return (
@@ -273,26 +311,87 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com Name - + {participants.map((participant) => ( - {participant.name} + + {editingId === participant.id ? ( + setEditingName(e.target.value)} + className="h-8" + autoFocus + onKeyDown={(e) => { + if (e.key === "Escape") { + setEditingId(null); + setEditingName(""); + } + }} + /> + ) : ( + participant.name + )} + -
- - - -
+ {editingId === participant.id ? ( +
+
+ + + + +
+ +
+ ) : ( +
+ +
+ + + +
+
+ )}
))}