Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/admin/pga/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ContentLayout } from "@/components/nav/content-layout";
import PgaMatchupForm from "@/components/admin/pga/pga-matchup-form";

export default function Page() {
return (
<ContentLayout title="Create PGA Matchup">
<PgaMatchupForm />
</ContentLayout>
);
}
10 changes: 10 additions & 0 deletions app/admin/pga/events/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ContentLayout } from "@/components/nav/content-layout";
import PgaEventList from "@/components/admin/pga/pga-event-list";

export default function Page() {
return (
<ContentLayout title="PGA Events">
<PgaEventList />
</ContentLayout>
);
}
17 changes: 17 additions & 0 deletions app/admin/pga/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ContentLayout } from "@/components/nav/content-layout";
import AdminPgaMatchupList from "@/components/admin/pga/admin-pga-matchup-list";
import { Button } from "@/components/ui/button";
import Link from "next/link";

export default function Page() {
return (
<ContentLayout title="PGA Matchups">
<div className="flex justify-end mb-4">
<Button asChild>
<Link href="/admin/pga/create">Create New Matchup</Link>
</Button>
</div>
<AdminPgaMatchupList />
</ContentLayout>
);
}
10 changes: 10 additions & 0 deletions app/admin/pga/players/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ContentLayout } from "@/components/nav/content-layout";
import PgaPlayerList from "@/components/admin/pga/pga-player-list";

export default function Page() {
return (
<ContentLayout title="PGA Players">
<PgaPlayerList />
</ContentLayout>
);
}
39 changes: 39 additions & 0 deletions components/admin/pga/admin-pga-columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { Doc } from "@/convex/_generated/dataModel";

export type PgaMatchupWithPlayers = Doc<"pgaMatchups"> & {
golferA: Doc<"pgaPlayers"> | null;
golferB: Doc<"pgaPlayers"> | null;
};

export const AdminPgaColumns: ColumnDef<PgaMatchupWithPlayers>[] = [
{
accessorKey: "golferA.name",
header: "Golfer A",
},
{
accessorKey: "golferB.name",
header: "Golfer B",
},
{
accessorKey: "holes",
header: "Holes",
},
{
accessorKey: "thru",
header: "Thru",
},
{
accessorKey: "startTime",
header: "Start Time",
cell: ({ row }) => {
const date = new Date(row.original.startTime);
return date.toLocaleString();
},
},
{
accessorKey: "status",
header: "Status",
},
];
19 changes: 19 additions & 0 deletions components/admin/pga/admin-pga-matchup-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";
import { api } from "@/convex/_generated/api";
import { useQuery } from "convex/react";
import { DataTable } from "@/components/matchups/admin-datatable";
import { AdminPgaColumns } from "./admin-pga-columns";
import { useMemo } from "react";

const AdminPgaMatchupList = () => {
const matchups = useQuery(api.pga.getAdminPgaMatchups, {});

const memoizedTable = useMemo(
() => matchups && <DataTable columns={AdminPgaColumns} data={matchups} />,
[matchups]
);

return <div className="mt-2">{memoizedTable}</div>;
};

export default AdminPgaMatchupList;
120 changes: 120 additions & 0 deletions components/admin/pga/pga-event-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"use client";
import { api } from "@/convex/_generated/api";
import { useMutation, useQuery } from "convex/react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";

const formSchema = z.object({
name: z.string().min(2, {
message: "Name must be at least 2 characters.",
}),
leaderboardUrl: z.string().url({
message: "Please enter a valid URL.",
}),
externalId: z.string().min(1, {
message: "External ID is required.",
}),
});

const PgaEventList = () => {
const events = useQuery(api.pga.getPgaEvents, {});
const createEvent = useMutation(api.pga.createPgaEvent);
const { toast } = useToast();

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
leaderboardUrl: "",
externalId: "",
},
});

function onSubmit(values: z.infer<typeof formSchema>) {
createEvent(values).then(() => {
toast({
title: "Event created",
description: `${values.name} has been added.`,
});
form.reset();
});
}

return (
<div>
<div className="mb-4">
<h2 className="text-xl font-bold">Add New Event</h2>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Event Name</FormLabel>
<FormControl>
<Input placeholder="The Masters" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="leaderboardUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Leaderboard URL</FormLabel>
<FormControl>
<Input placeholder="https://www.pga.com/leaderboard" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="externalId"
render={({ field }) => (
<FormItem>
<FormLabel>External ID</FormLabel>
<FormControl>
<Input placeholder="evt-123" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Add Event</Button>
</form>
</Form>
</div>
<div>
<h2 className="text-xl font-bold">Existing Events</h2>
<ul className="space-y-2">
{events?.map((event) => (
<li key={event._id} className="border p-2 rounded">
<p className="font-bold">{event.name}</p>
<p className="text-sm text-gray-500">ID: {event.externalId}</p>
<p className="text-sm text-gray-500">URL: {event.leaderboardUrl}</p>
</li>
))}
</ul>
</div>
</div>
);
};

export default PgaEventList;
Loading