Skip to content

Commit

Permalink
manage locations
Browse files Browse the repository at this point in the history
  • Loading branch information
philipstubbs13 committed Sep 24, 2024
1 parent 90d492d commit a8fdbb0
Show file tree
Hide file tree
Showing 22 changed files with 334 additions and 54 deletions.
27 changes: 27 additions & 0 deletions app/actions/addRaceLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use server";
import { db } from "@/lib/db";
import { getSessionUser } from "@/utils/getSessionUser";
import { Routes } from "@/utils/router/Routes.constants";
import { revalidatePath } from "next/cache";

async function addRaceLocation(formData: FormData): Promise<void> {
const sessionUser = await getSessionUser();

if (!sessionUser || !sessionUser.userId) {
throw new Error("User ID is required");
}

const { userId } = sessionUser;

await db.raceLocation.create({
data: {
city: formData.get("city") as string,
state: formData.get("state") as string,
userId,
},
});

revalidatePath(Routes.ManageRaceLocations, "layout");
}

export default addRaceLocation;
10 changes: 8 additions & 2 deletions app/actions/addRaceResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ async function addRaceResult(formData: FormData): Promise<void> {
time,
};

const locationId = formData.get("location") as string;
const location = await db.raceLocation.findFirst({
where: { id: locationId },
});

await db.raceResult.create({
data: {
city: (formData.get("city") as string) || "",
city: location?.city || "",
date: new Date(raceResultData.date).toISOString(),
race: raceResultData.race,
raceDistance: formData.get("distance") as string,
state: (formData.get("state") as string) || "",
raceLocationId: locationId,
state: location?.state || "",
time: raceResultData.time,
userId,
},
Expand Down
31 changes: 31 additions & 0 deletions app/actions/deleteRaceLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use server";
import { db } from "@/lib/db";
import { getSessionUser } from "../../utils/getSessionUser";
import { revalidatePath } from "next/cache";
import { Routes } from "@/utils/router/Routes.constants";

async function deleteRaceLocation(raceLocationId: string): Promise<void> {
const sessionUser = await getSessionUser();

if (!sessionUser || !sessionUser.userId) {
throw new Error("User ID is required");
}

const { userId } = sessionUser;

const raceLocation = await db.raceLocation.findFirst({
where: { id: raceLocationId },
});

if (!raceLocation) throw new Error("Race Location Not Found");

if (raceLocation.userId.toString() !== userId) {
throw new Error("Unauthorized");
}

await db.raceLocation.delete({ where: { id: raceLocationId } });

revalidatePath(Routes.ManageRaceLocations, "layout");
}

export default deleteRaceLocation;
10 changes: 8 additions & 2 deletions app/actions/editRaceResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ async function editRaceResult(
const time = `${hours}:${minutes}:${seconds}`;
const date = formData.get("date") as string;

const locationId = formData.get("location") as string;
const location = await db.raceLocation.findFirst({
where: { id: locationId },
});

await db.raceResult.update({
where: { id: raceResultId },
data: {
city: (formData.get("city") as string) || "",
city: location?.city || "",
date: new Date(date).toISOString(),
race: formData.get("race") as string,
raceDistance: formData.get("distance") as string,
state: (formData.get("state") as string) || "",
raceLocationId: locationId,
state: location?.state || "",
time,
},
});
Expand Down
35 changes: 35 additions & 0 deletions app/settings/manage-locations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import { Routes } from "@/utils/router/Routes.constants";
import { TabCard } from "@/components/tab-card/TabCard";
import { Tab } from "@/components/tabs/Tabs.constants";
import { db } from "@/lib/db";
import { getSessionUser } from "@/utils/getSessionUser";
import { LayoutContainer } from "@/components/layout-container/LayoutContainer";
import { LocationsList } from "@/components/locations-list/LocationsList";

export default async function ManageLocationsPage() {
const session = await getSessionUser();
const locations = await db.raceLocation.findMany({
where: { userId: session?.userId },
});

return (
<LayoutContainer>
<Link
href={Routes.Results}
className="inline-flex items-center text-blue-600 hover:text-blue-800 mb-4"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Dashboard
</Link>
<TabCard tab={Tab.ManageLocations}>
<div className="space-y-4 mt-4">
<div className="space-y-2">
<LocationsList locations={locations} />
</div>
</div>
</TabCard>
</LayoutContainer>
);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
"use client";

import {
Bar,
BarChart,
CartesianGrid,
LabelList,
XAxis,
YAxis,
} from "recharts";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";

import {
Card,
Expand Down Expand Up @@ -67,15 +60,7 @@ export const DistanceComparisonChart = (props: IProps) => {
formatter={(value) => minutesToTime(Number(value))}
/>
<ChartLegend content={<ChartLegendContent />} />
<Bar dataKey="averageTime" fill="#10B981" radius={8}>
<LabelList
position="top"
offset={12}
className="fill-foreground"
fontSize={12}
formatter={(value: string) => minutesToTime(Number(value))}
/>
</Bar>
<Bar dataKey="averageTime" fill="#10B981" radius={8} />
</BarChart>
</ChartContainer>
</CardContent>
Expand Down
6 changes: 6 additions & 0 deletions components/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export const Footer = () => {
label="Upcoming Races"
/>
</li>
<li>
<FooterQuickLink
href={Routes.ManageRaceLocations}
label="Manage Race Locations"
/>
</li>
</ul>
<ul className="space-y-2">
<li>
Expand Down
12 changes: 10 additions & 2 deletions components/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { Button } from "@/components/ui/button";
import { LogOut, Route, Settings } from "lucide-react";
import { LogOut, MapPinHouse, Route, Settings } from "lucide-react";
import { signOut, useSession } from "next-auth/react";
import { useGlobalContext } from "@/context/global-context/GlobalContext";
import { Tab } from "@/components/tabs/Tabs.constants";
Expand Down Expand Up @@ -36,7 +36,7 @@ export const Header = () => {
};

return (
<div className="flex justify-between items-center mb-6 pt-4">
<div className="flex justify-around sm:justify-between items-center mb-6 pt-4">
<PageHeading activeTab={activeTab} />
<div className="flex items-center space-x-4">
<Sheet>
Expand Down Expand Up @@ -91,6 +91,14 @@ export const Header = () => {
<Route className="mr-2 h-4 w-4" />
Manage Race Distances
</Button>
<Button
onClick={() => router.push(Routes.ManageRaceLocations)}
variant="outline"
className="w-full mb-2"
>
<MapPinHouse className="mr-2 h-4 w-4" />
Manage Race Locations
</Button>
<Button
onClick={handleLogout}
className="w-full bg-pink-500 hover:bg-pink-600 text-white font-bold py-3 px-4 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 flex items-center justify-center"
Expand Down
120 changes: 120 additions & 0 deletions components/locations-list/LocationsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"use client";

import { Button } from "@/components/ui/button";
import { Plus, Trash2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { useRef, useState } from "react";
import { useToast } from "@/hooks/use-toast";
import { ConfirmationDialog } from "@/components/confirmation-dialog/ConfirmationDialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { states } from "../personal-results/PersonalResults.constants";
import addRaceLocation from "@/app/actions/addRaceLocation";
import { IRaceLocation } from "./LocationsList.types";
import deleteRaceLocation from "@/app/actions/deleteRaceLocation";

interface IProps {
locations: IRaceLocation[];
}

export const LocationsList = (props: IProps) => {
const formRef = useRef<HTMLFormElement | null>(null);
const selectRef = useRef<HTMLSelectElement | null>(null);
const { toast } = useToast();
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] =
useState<boolean>(false);

const handleRemoveLocation = async (locationId: string) => {
await deleteRaceLocation(locationId);
toast({
title: "Successfully deleted race location",
});
setIsConfirmationDialogOpen(false);
};

const handleAddLocation = async (formData: FormData) => {
await addRaceLocation(formData);
toast({
title: "Successfully added race location",
});

formRef.current?.reset();
if (selectRef.current) {
selectRef.current.value = "";
}
};

return (
<>
<form action={handleAddLocation} className="flex space-x-2" ref={formRef}>
<Input
name="city"
placeholder="Enter city"
className="flex-grow"
required={true}
/>
<Select name="state" required={true} defaultValue="">
<SelectTrigger className="border-blue-300 focus:border-blue-500">
<SelectValue
placeholder="Select State"
defaultValue=""
ref={selectRef}
/>
</SelectTrigger>
<SelectContent className="bg-white">
{states.map((state) => (
<SelectItem key={state.value} value={state.value}>
{state.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
className="bg-blue-500 hover:bg-blue-600 text-white"
type="submit"
>
<Plus className="h-4 w-4 mr-2" />
Add
</Button>
</form>
<div className="space-y-2">
{props.locations.map((location, index) => (
<div
key={index}
className="flex justify-between items-center bg-gray-100 p-2 rounded"
>
<span>
{location.city}, {location.state}
</span>
<div>
{/* <EditRaceDistanceDialog
distance={distance.distance}
id={distance.id}
/> */}
<Button
variant="ghost"
size="sm"
onClick={() => setIsConfirmationDialogOpen(true)}
className="text-red-500 hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>
<ConfirmationDialog
description={`This will permanently delete ${location.city}, ${location.state}.`}
isOpen={isConfirmationDialogOpen}
title="Are you sure you want to delete?"
onClose={() => setIsConfirmationDialogOpen(false)}
onConfirm={() => handleRemoveLocation(location.id)}
/>
</div>
</div>
))}
</div>
</>
);
};
7 changes: 7 additions & 0 deletions components/locations-list/LocationsList.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface IRaceLocation {
createdAt: Date;
city: string;
id: string;
state: string;
userId: string;
}
11 changes: 9 additions & 2 deletions components/personal-results/PersonalResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ export const PersonalResults = async () => {
const distances = await db.raceDistance.findMany({
where: { userId: sessionUser?.userId },
});
const locations = await db.raceLocation.findMany({
where: { userId: sessionUser?.userId },
});
const hasResults = results.length > 0;

return (
<div className="space-y-4">
{hasResults && (
<PersonalResultsList distances={distances} results={results} />
<PersonalResultsList
distances={distances}
locations={locations}
results={results}
/>
)}
{!hasResults && (
<NoResults
Expand All @@ -35,7 +42,7 @@ export const PersonalResults = async () => {
/>
)}
<div className="flex flex-col md:flex-row md:items-center gap-4">
<AddResultDialog distances={distances} />
<AddResultDialog distances={distances} locations={locations} />
<Link href={Routes.ManageRaceDistances}>
<Button
variant="outline"
Expand Down
1 change: 1 addition & 0 deletions components/personal-results/PersonalResults.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface IPersonalResult {
id: string;
race: string;
raceDistance: string;
raceLocationId: string | null;
state?: string;
time: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const IsEditingResult: Story = {
id: "3",
race: "Twin Cities Marathon",
raceDistance: "Marathon",
raceLocationId: "1",
time: "03:30:15",
},
},
Expand Down
Loading

0 comments on commit a8fdbb0

Please sign in to comment.