diff --git a/src/components/gameClass/GameClassSelector.tsx b/src/components/gameClass/GameClassSelector.tsx
new file mode 100644
index 0000000..2371b35
--- /dev/null
+++ b/src/components/gameClass/GameClassSelector.tsx
@@ -0,0 +1,73 @@
+import { useGetGameClasses } from "@/hooks/GameClassHooks";
+import { useEffect, useState } from "react";
+import DangerMessage from "../message/DangerMessage";
+
+
+export function GameClassSelector({
+ gameId,
+ gameClassId,
+ onChange
+}:{
+ gameId: string;
+ gameClassId?: string;
+ onChange?: (gameClassId?: string) => void;
+}){
+ const [ selectedGameClassId, setSelectedGameClassId ] = useState(gameClassId);
+ const selectorId = crypto.randomUUID().replaceAll("-", "");
+
+
+ const gameClassesQuery = useGetGameClasses(gameId, 0, 100, undefined);
+
+
+ useEffect(() => {
+ onChange?.(selectedGameClassId);
+ }, [ selectedGameClassId, onChange ]);
+
+
+ if(gameClassesQuery.status === "pending"){
+ return
Loading...
+ }
+ else if(gameClassesQuery.status === "error"){
+ return Error loading Game Classes: {gameClassesQuery.error.message}
+ }
+ else{
+ return (
+
+ {
+ gameClassesQuery.data.map((gameClass) => (
+
+
setSelectedGameClassId(e.target.value)}
+ />
+
+
+ ))
+ }
+
+ );
+ }
+}
diff --git a/src/components/personCharacter/PersonCharacterDisplay.tsx b/src/components/personCharacter/PersonCharacterDisplay.tsx
new file mode 100644
index 0000000..215c288
--- /dev/null
+++ b/src/components/personCharacter/PersonCharacterDisplay.tsx
@@ -0,0 +1,49 @@
+import { useGetPersonCharactersByPersonId } from "@/hooks/PersonCharacterHooks";
+import DangerMessage from "../message/DangerMessage";
+
+
+export default function PersonCharacterDisplay({
+ personId,
+ raidGroupId
+}:{
+ personId: string;
+ raidGroupId: string;
+}){
+ const personCharacterQuery = useGetPersonCharactersByPersonId(personId, raidGroupId);
+
+
+ if(personCharacterQuery.status === "pending"){
+ return (Loading...
);
+ }
+ else if(personCharacterQuery.status === "error"){
+ return (Error loading characters: {personCharacterQuery.error.message});
+ }
+ else{
+ return (
+
+ {
+ personCharacterQuery.data.map((personCharacter) => {
+ return (
+
+

+ {personCharacter.characterName}
+
+ );
+ })
+ }
+ {
+ personCharacterQuery.data.length === 0 &&
+
No characters
+ }
+
+ );
+ }
+}
diff --git a/src/components/personCharacter/RatingSelector.tsx b/src/components/personCharacter/RatingSelector.tsx
new file mode 100644
index 0000000..bc2cab8
--- /dev/null
+++ b/src/components/personCharacter/RatingSelector.tsx
@@ -0,0 +1,43 @@
+import { useEffect, useState } from "react";
+
+export default function RatingSelector({
+ rating,
+ onChange
+}:{
+ rating?: number;
+ onChange?: (rating?: number) => void;
+}){
+ const ratings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ const [ currentRating, setCurrentRating ] = useState(rating);
+
+
+ useEffect(() => {
+ setCurrentRating(rating);
+ }, [ rating, setCurrentRating ]);
+
+ useEffect(() => {
+ onChange?.(currentRating);
+ }, [ currentRating, onChange ]);
+
+
+ return (
+
+
+
+ );
+}
diff --git a/src/hooks/PersonCharacterHooks.ts b/src/hooks/PersonCharacterHooks.ts
new file mode 100644
index 0000000..76f5097
--- /dev/null
+++ b/src/hooks/PersonCharacterHooks.ts
@@ -0,0 +1,88 @@
+import { PersonCharacter } from "@/interface/PersonCharacter";
+import { api } from "@/util/AxiosUtil";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+
+
+export function useGetPersonCharactersByPersonId(personId: string, raidGroupId: string){
+ return useQuery({
+ queryKey: ["personCharacters", personId],
+ queryFn: async () => {
+ const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get person characters");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data as PersonCharacter[];
+ }
+ });
+}
+
+export function useCreatePersonCharacter(raidGroupId: string, personId: string){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["createPersonCharacter"],
+ mutationFn: async (personCharacter: PersonCharacter) => {
+ const response = await api.post(`/raidGroup/${raidGroupId}/person/${personId}/character`, personCharacter);
+
+ if(response.status !== 200){
+ throw new Error("Failed to create person character");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
+ }
+ });
+}
+
+export function useUpdatePersonCharacter(raidGroupId: string, personId: string){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["updatePersonCharacter"],
+ mutationFn: async (personCharacter: PersonCharacter) => {
+ const response = await api.put(`/raidGroup/${raidGroupId}/person/${personId}/character/${personCharacter.personCharacterId}`, personCharacter);
+
+ if(response.status !== 200){
+ throw new Error("Failed to update person character");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
+ }
+ });
+}
+
+export function useDeletePersonCharacter(raidGroupId: string, personId: string){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["deletePersonCharacter"],
+ mutationFn: async (personCharacterId: string) => {
+ const response = await api.delete(`/raidGroup/${raidGroupId}/person/${personId}/character/${personCharacterId}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to delete person character");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
+ }
+ });
+}
diff --git a/src/hooks/PersonHooks.ts b/src/hooks/PersonHooks.ts
new file mode 100644
index 0000000..cfbd177
--- /dev/null
+++ b/src/hooks/PersonHooks.ts
@@ -0,0 +1,138 @@
+import { Person } from "@/interface/Person";
+import { api } from "@/util/AxiosUtil";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+
+
+export function useGetPerson(raidGroupId: string, personId: string, disabled: boolean){
+ return useQuery({
+ queryKey: ["people", raidGroupId, personId],
+ queryFn: async () => {
+ const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get person");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data as Person;
+ },
+ enabled: !disabled
+ });
+}
+
+export function useGetPeopleByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
+ return useQuery({
+ queryKey: ["people", raidGroupId, { page, pageSize, searchTerm }],
+ queryFn: async () => {
+ const searchParams = new URLSearchParams();
+ searchParams.append("page", page.toString());
+ searchParams.append("pageSize", pageSize.toString());
+ if(searchTerm){
+ searchParams.append("searchTerm", searchTerm);
+ }
+
+ const response = await api.get(`/raidGroup/${raidGroupId}/person?${searchParams}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get people");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data as Person[];
+ }
+ });
+}
+
+export function useGetPeopleByRaidGroupCount(raidGroupId: string, searchTerm?: string){
+ const searchParams = new URLSearchParams();
+ if(searchTerm){
+ searchParams.append("searchTerm", searchTerm);
+ }
+
+
+ return useQuery({
+ queryKey: ["people", raidGroupId, "count", searchTerm],
+ queryFn: async () => {
+ const response = await api.get(`/raidGroup/${raidGroupId}/person/count?${searchParams}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get people count");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data.count as number;
+ }
+ });
+}
+
+export function useCreatePerson(){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["createPerson"],
+ mutationFn: async ({raidGroupId, personName, discordId}:{raidGroupId: string; personName: string; discordId?: string;}) => {
+ const response = await api.post(`/raidGroup/${raidGroupId}/person`, {raidGroupId, personName, discordId});
+
+ if(response.status !== 200){
+ throw new Error("Failed to create person");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["people"] });
+ }
+ });
+}
+
+export function useUpdatePerson(){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["updatePerson"],
+ mutationFn: async (person: Person) => {
+ const response = await api.put(`/raidGroup/${person.raidGroupId}/person/${person.personId}`, person);
+
+ if(response.status !== 200){
+ throw new Error("Failed to update person");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["people"] });
+ }
+ });
+}
+
+export function useDeletePerson(){
+ const queryClient = useQueryClient();
+
+
+ return useMutation({
+ mutationKey: ["deletePerson"],
+ mutationFn: async ({raidGroupId, personId}:{raidGroupId: string; personId: string;}) => {
+ const response = await api.delete(`/raidGroup/${raidGroupId}/person/${personId}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to delete person");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["people"] });
+ }
+ });
+}
diff --git a/src/interface/Person.ts b/src/interface/Person.ts
new file mode 100644
index 0000000..5ba607b
--- /dev/null
+++ b/src/interface/Person.ts
@@ -0,0 +1,6 @@
+export interface Person {
+ personId?: string;
+ raidGroupId: string;
+ personName: string;
+ discordId?: string;
+}
diff --git a/src/interface/PersonCharacter.ts b/src/interface/PersonCharacter.ts
new file mode 100644
index 0000000..433875d
--- /dev/null
+++ b/src/interface/PersonCharacter.ts
@@ -0,0 +1,8 @@
+export interface PersonCharacter {
+ personCharacterId?: string;
+ personId: string;
+ gameClassId: string;
+ characterName: string;
+ characterRating?: number;
+ characterComments?: string;
+}
diff --git a/src/pages/protected/RaidGroupPage.tsx b/src/pages/protected/RaidGroupPage.tsx
index 9d611b8..390370a 100644
--- a/src/pages/protected/RaidGroupPage.tsx
+++ b/src/pages/protected/RaidGroupPage.tsx
@@ -3,6 +3,7 @@ import { useGetRaidGroup } from "@/hooks/RaidGroupHooks";
import { RaidGroup } from "@/interface/RaidGroup";
import RaidGroupCalendarDisplay from "@/ui/calendar/RaidGroupCalendarDisplay";
import RaidGroupHeader from "@/ui/calendar/RaidGroupHeader";
+import PersonTab from "@/ui/person/PersonTab";
import { useEffect, useState } from "react";
import { Navigate, useParams } from "react-router";
@@ -40,6 +41,10 @@ export default function RaidGroupPage(){
{
tabHeader: "Calendar",
tabContent:
+ },
+ {
+ tabHeader: "People",
+ tabContent:
}
];
diff --git a/src/ui/account/AdminAccountsTab.tsx b/src/ui/account/AdminAccountsTab.tsx
index 676d4ad..5680d22 100644
--- a/src/ui/account/AdminAccountsTab.tsx
+++ b/src/ui/account/AdminAccountsTab.tsx
@@ -17,7 +17,7 @@ export default function AdminAccountsTab(){
const pageSize = 10;
const modalId = crypto.randomUUID().replaceAll("-", "");
-
+
const accountsCountQuery = useGetAccountsCount(sentSearchTerm);
@@ -53,7 +53,7 @@ export default function AdminAccountsTab(){
className="flex flex-row items-center justify-center w-full"
>
setDisplayCreateAccountModal(true)}
>
Create Account
diff --git a/src/ui/calendar/RaidGroupHeader.tsx b/src/ui/calendar/RaidGroupHeader.tsx
index ef78281..7a413f3 100644
--- a/src/ui/calendar/RaidGroupHeader.tsx
+++ b/src/ui/calendar/RaidGroupHeader.tsx
@@ -31,10 +31,8 @@ export default function RaidGroupHeader({
{
raidGroup.raidGroupIcon &&
}
{raidGroup.raidGroupName}
diff --git a/src/ui/game/GameHeader.tsx b/src/ui/game/GameHeader.tsx
index 534e84d..d475e1d 100644
--- a/src/ui/game/GameHeader.tsx
+++ b/src/ui/game/GameHeader.tsx
@@ -31,10 +31,8 @@ export default function GameHeader({
{
game.gameIcon &&
}
{game.gameName}
diff --git a/src/ui/game/GamesList.tsx b/src/ui/game/GamesList.tsx
index ff3028b..9f62210 100644
--- a/src/ui/game/GamesList.tsx
+++ b/src/ui/game/GamesList.tsx
@@ -47,10 +47,8 @@ export default function GamesList({
className="absolute -my-4"
>
}
diff --git a/src/ui/gameClass/GameClassList.tsx b/src/ui/gameClass/GameClassList.tsx
index 5f26a07..c8bc868 100644
--- a/src/ui/gameClass/GameClassList.tsx
+++ b/src/ui/gameClass/GameClassList.tsx
@@ -43,13 +43,11 @@ export default function GameClassList({
{
gameClass.gameClassIcon &&
}
diff --git a/src/ui/person/PersonAdminButtons.tsx b/src/ui/person/PersonAdminButtons.tsx
new file mode 100644
index 0000000..3580ae1
--- /dev/null
+++ b/src/ui/person/PersonAdminButtons.tsx
@@ -0,0 +1,51 @@
+import { ButtonProps } from "@/components/button/Button";
+import DangerButton from "@/components/button/DangerButton";
+import PrimaryButton from "@/components/button/PrimaryButton";
+import SuccessButton from "@/components/button/SuccessButton";
+import { BsPencilFill, BsPlusLg, BsTrash3 } from "react-icons/bs";
+
+
+export default function PersonAdminButtons({
+ buttonProps,
+ showUpdatePersonModal,
+ showDeletePersonModal,
+ showCreatePersonCharacterModal
+}:{
+ buttonProps: ButtonProps;
+ showUpdatePersonModal: () => void;
+ showDeletePersonModal: () => void;
+ showCreatePersonCharacterModal: () => void;
+}){
+ return (
+
+ );
+}
diff --git a/src/ui/person/PersonList.tsx b/src/ui/person/PersonList.tsx
new file mode 100644
index 0000000..a47cf5a
--- /dev/null
+++ b/src/ui/person/PersonList.tsx
@@ -0,0 +1,122 @@
+import { ButtonProps } from "@/components/button/Button";
+import PersonCharacterDisplay from "@/components/personCharacter/PersonCharacterDisplay";
+import Table from "@/components/table/Table";
+import { Person } from "@/interface/Person";
+import { RaidGroup } from "@/interface/RaidGroup";
+import { useState } from "react";
+import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
+import DeletePersonModal from "./modals/DeletePersonModal";
+import PersonModal from "./modals/PersonModal";
+import PersonAdminButtons from "./PersonAdminButtons";
+
+
+export default function PersonList({
+ people,
+ raidGroup
+}:{
+ people: Person[];
+ raidGroup: RaidGroup;
+}){
+ const [ selectedPerson, setSelectedPerson ] = useState();
+ const [ displayUpdatePersonModal, setDisplayUpdatePersonModal ] = useState(false);
+ const [ displayDeletePersonModal, setDisplayDeletePersonModal ] = useState(false);
+ const [ displayCreatePersonCharacterModal, setDisplayCreatePersonCharacterModal ] = useState(false);
+
+
+ const buttonProps: ButtonProps = {
+ variant: "ghost",
+ size: "md",
+ shape: "square"
+ };
+
+
+ const headElements: React.ReactNode[] = [
+
+ Person Name
+
,
+
+ Discord ID
+
,
+
+ Characters
+
,
+
+ Actions
+
+ ];
+
+ const bodyElements: React.ReactNode[][] = people.map((person) => [
+
+ {person.personName}
+
,
+
+ {person.discordId}
+
,
+ ,
+
+
+
+
+
{
+ setSelectedPerson(person);
+ setDisplayUpdatePersonModal(true);
+ }}
+ showDeletePersonModal={() => {
+ setSelectedPerson(person);
+ setDisplayDeletePersonModal(true);
+ }}
+ showCreatePersonCharacterModal={() => {
+ setSelectedPerson(person);
+ setDisplayCreatePersonCharacterModal(true);
+ }}
+ />
+
+ ]);
+
+
+ return (
+ <>
+
+ {setDisplayUpdatePersonModal(false); setSelectedPerson(undefined);}}
+ person={selectedPerson}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+ {setDisplayDeletePersonModal(false); setSelectedPerson(undefined);}}
+ person={selectedPerson}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+ {setDisplayCreatePersonCharacterModal(false); setSelectedPerson(undefined);}}
+ personId={selectedPerson?.personId ?? ""}
+ personCharacter={undefined}
+ gameId={raidGroup.gameId}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+ >
+ );
+}
diff --git a/src/ui/person/PersonListSkeleton.tsx b/src/ui/person/PersonListSkeleton.tsx
new file mode 100644
index 0000000..e722147
--- /dev/null
+++ b/src/ui/person/PersonListSkeleton.tsx
@@ -0,0 +1,7 @@
+export default function PersonListSkeleton(){
+ return (
+
+ Person List Skeleton
+
+ );
+}
diff --git a/src/ui/person/PersonLoader.tsx b/src/ui/person/PersonLoader.tsx
new file mode 100644
index 0000000..48ccea2
--- /dev/null
+++ b/src/ui/person/PersonLoader.tsx
@@ -0,0 +1,36 @@
+import DangerMessage from "@/components/message/DangerMessage";
+import { useGetPeopleByRaidGroup } from "@/hooks/PersonHooks";
+import { RaidGroup } from "@/interface/RaidGroup";
+import PersonList from "./PersonList";
+import PersonListSkeleton from "./PersonListSkeleton";
+
+
+export default function PersonLoader({
+ raidGroup,
+ page,
+ pageSize,
+ searchTerm
+}:{
+ raidGroup: RaidGroup;
+ page: number;
+ pageSize: number;
+ searchTerm?: string;
+}){
+ const peopleQuery = useGetPeopleByRaidGroup(raidGroup.raidGroupId ?? "", page - 1, pageSize, searchTerm);
+
+
+ if(peopleQuery.status === "pending"){
+ return
+ }
+ else if(peopleQuery.status === "error"){
+ return Error: {peopleQuery.error.message}
+ }
+ else{
+ return (
+
+ );
+ }
+}
diff --git a/src/ui/person/PersonTab.tsx b/src/ui/person/PersonTab.tsx
new file mode 100644
index 0000000..f472c47
--- /dev/null
+++ b/src/ui/person/PersonTab.tsx
@@ -0,0 +1,106 @@
+import PrimaryButton from "@/components/button/PrimaryButton";
+import TextInput from "@/components/input/TextInput";
+import Pagination from "@/components/pagination/Pagination";
+import { useGetPeopleByRaidGroupCount } from "@/hooks/PersonHooks";
+import { RaidGroup } from "@/interface/RaidGroup";
+import { useEffect, useState } from "react";
+import { useDebouncedCallback } from "use-debounce";
+import PersonModal from "./modals/PersonModal";
+import PersonLoader from "./PersonLoader";
+
+
+export default function PersonTab({
+ raidGroup
+}:{
+ raidGroup: RaidGroup;
+}){
+ const [ displayCreatePersonModal, setDisplayCreatePersonModal ] = useState(false);
+ const [ page, setPage ] = useState(1);
+ const [ totalPages, setTotalPages ] = useState(1);
+ const [ searchTerm, setSearchTerm ] = useState("");
+ const [ sentSearchTerm, setSentSearchTerm ] = useState();
+ const pageSize = 10;
+ const modalId = crypto.randomUUID().replaceAll("-", "");
+
+
+ const personCountQuery = useGetPeopleByRaidGroupCount(raidGroup?.raidGroupId ?? "", sentSearchTerm);
+
+
+ const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => {
+ setSentSearchTerm(newSearchTerm);
+ }, 1000);
+
+
+ useEffect(() => {
+ updateSearchTerm(searchTerm ?? "");
+ }, [ searchTerm, updateSearchTerm ]);
+
+
+ useEffect(() => {
+ if(personCountQuery.status === "success"){
+ setTotalPages(Math.ceil(personCountQuery.data / pageSize));
+ }
+ }, [ personCountQuery ]);
+
+
+ return (
+ <>
+
+
+
+
+ {/* Add Person Button */}
+
+
setDisplayCreatePersonModal(true)}
+ >
+ Create Person
+
+
setDisplayCreatePersonModal(false)}
+ person={undefined}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+
+ {/* Person Search Box */}
+
+
+ setSearchTerm(e.target.value)}
+ placeholder="Search"
+ />
+
+
+
+ { /* Person List */}
+
+ {/* Pagination */}
+
+ >
+ );
+}
diff --git a/src/ui/person/modals/DeletePersonModal.tsx b/src/ui/person/modals/DeletePersonModal.tsx
new file mode 100644
index 0000000..76f90ef
--- /dev/null
+++ b/src/ui/person/modals/DeletePersonModal.tsx
@@ -0,0 +1,65 @@
+import DangerButton from "@/components/button/DangerButton";
+import SecondaryButton from "@/components/button/SecondaryButton";
+import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
+import { useDeletePerson } from "@/hooks/PersonHooks";
+import { Person } from "@/interface/Person";
+import { useTimedModal } from "@/providers/TimedModalProvider";
+import { useEffect } from "react";
+
+
+export default function DeletePersonModal({
+ display,
+ close,
+ raidGroupId,
+ person
+}:{
+ display: boolean;
+ close: () => void;
+ raidGroupId: string;
+ person?: Person;
+}){
+ const deletePersonMutate = useDeletePerson();
+ const { addSuccessMessage, addErrorMessage } = useTimedModal();
+
+
+ const deletePerson = () => {
+ deletePersonMutate.mutate({raidGroupId, personId: person?.personId ?? ""});
+ }
+
+ useEffect(() => {
+ if(deletePersonMutate.status === "success"){
+ deletePersonMutate.reset();
+ addSuccessMessage(`Person ${person?.personName} deleted`);
+ close();
+ }
+ else if(deletePersonMutate.status === "error"){
+ deletePersonMutate.reset();
+ addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
+ console.log(deletePersonMutate.error);
+ }
+ })
+
+
+ return (
+
+
+ Delete
+
+
+ Cancel
+
+ >
+ }
+ />
+ );
+}
diff --git a/src/ui/person/modals/PersonModal.tsx b/src/ui/person/modals/PersonModal.tsx
new file mode 100644
index 0000000..bd12398
--- /dev/null
+++ b/src/ui/person/modals/PersonModal.tsx
@@ -0,0 +1,110 @@
+import PrimaryButton from "@/components/button/PrimaryButton";
+import SecondaryButton from "@/components/button/SecondaryButton";
+import TextInput from "@/components/input/TextInput";
+import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
+import { useCreatePerson, useUpdatePerson } from "@/hooks/PersonHooks";
+import { Person } from "@/interface/Person";
+import { useTimedModal } from "@/providers/TimedModalProvider";
+import { useEffect, useState } from "react";
+
+
+export default function PersonModal({
+ display,
+ close,
+ person,
+ raidGroupId
+}:{
+ display: boolean;
+ close: () => void;
+ person: Person | undefined;
+ raidGroupId: string;
+}){
+ const [ personName, setPersonName ] = useState(person?.personName ?? "");
+ const [ discordId, setDiscordId ] = useState(person?.discordId ?? "");
+ const modalId = crypto.randomUUID().replaceAll("-", "");
+
+
+ useEffect(() => {
+ setPersonName(person?.personName ?? "");
+ setDiscordId(person?.discordId ?? "");
+ }, [ person, raidGroupId, setPersonName, setDiscordId ]);
+
+
+ const createPersonMutate = useCreatePerson();
+ const updatePersonMutate = useUpdatePerson();
+ const { addSuccessMessage, addErrorMessage } = useTimedModal();
+
+
+ useEffect(() => {
+ if(createPersonMutate.status === "success"){
+ createPersonMutate.reset();
+ addSuccessMessage("Person created successfully");
+ close();
+ }
+ else if(updatePersonMutate.status === "success"){
+ updatePersonMutate.reset();
+ addSuccessMessage("Person updated successfully");
+ close();
+ }
+ else if(createPersonMutate.status === "error"){
+ createPersonMutate.reset();
+ addErrorMessage(`Error creating person ${personName}: ${createPersonMutate.error.message}`);
+ console.log(createPersonMutate.error);
+ }
+ else if(updatePersonMutate.status === "error"){
+ updatePersonMutate.reset();
+ addErrorMessage(`Error updating person ${personName}: ${updatePersonMutate.error.message}`);
+ console.log(updatePersonMutate.error);
+ }
+ });
+
+
+ const updatePerson = () => {
+ updatePersonMutate.mutate({raidGroupId, personId: person?.personId, personName, discordId} as Person);
+ }
+
+ const createPerson = () => {
+ createPersonMutate.mutate({raidGroupId, personName, discordId} as Person);
+ }
+
+
+ return (
+
+ setPersonName(e.target.value)}
+ />
+ setDiscordId(e.target.value)}
+ />
+
+ }
+ modalFooter={
+ <>
+
+ {person ? "Update" : "Create"}
+
+
+ Cancel
+
+ >
+ }
+ />
+ );
+}
diff --git a/src/ui/personCharacter/modal/PersonCharacterModal.tsx b/src/ui/personCharacter/modal/PersonCharacterModal.tsx
new file mode 100644
index 0000000..bc36328
--- /dev/null
+++ b/src/ui/personCharacter/modal/PersonCharacterModal.tsx
@@ -0,0 +1,131 @@
+import PrimaryButton from "@/components/button/PrimaryButton";
+import SecondaryButton from "@/components/button/SecondaryButton";
+import { GameClassSelector } from "@/components/gameClass/GameClassSelector";
+import TextInput from "@/components/input/TextInput";
+import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
+import RatingSelector from "@/components/personCharacter/RatingSelector";
+import { useCreatePersonCharacter, useUpdatePersonCharacter } from "@/hooks/PersonCharacterHooks";
+import { PersonCharacter } from "@/interface/PersonCharacter";
+import { useTimedModal } from "@/providers/TimedModalProvider";
+import { useEffect, useState } from "react";
+
+
+export default function PersonCharacterModal({
+ display,
+ close,
+ personCharacter,
+ personId,
+ gameId,
+ raidGroupId
+}:{
+ display: boolean;
+ close: () => void;
+ personCharacter?: PersonCharacter;
+ personId: string;
+ gameId: string;
+ raidGroupId: string;
+}){
+ const [ characterName, setCharacterName ] = useState(personCharacter?.characterName ?? "");
+ const [ gameClassId, setGameClassId ] = useState(personCharacter?.gameClassId);
+ const [ characterRating, setCharacterRating ] = useState();
+ const [ characterComments, setCharacterComments ] = useState();
+ const modalId = crypto.randomUUID().replaceAll("-", "");
+
+
+ useEffect(() => {
+ if(personCharacter){
+ setCharacterName(personCharacter.characterName);
+ setGameClassId(personCharacter.gameClassId);
+ setCharacterRating(personCharacter.characterRating);
+ setCharacterComments(personCharacter.characterComments);
+ }
+ }, [ personCharacter, setCharacterName, setGameClassId, setCharacterRating, setCharacterComments ]);
+
+
+ const createPersonCharacterMutate = useCreatePersonCharacter(raidGroupId, personId);
+ const updatePersonCharacterMutate = useUpdatePersonCharacter(raidGroupId, personId);
+ const { addSuccessMessage, addErrorMessage } = useTimedModal();
+
+
+ useEffect(() => {
+ if(createPersonCharacterMutate.status === "success"){
+ createPersonCharacterMutate.reset();
+ addSuccessMessage(`Character ${characterName} created successfully`);
+ close();
+ }
+ else if(updatePersonCharacterMutate.status === "success"){
+ updatePersonCharacterMutate.reset();
+ addSuccessMessage(`Character ${characterName} updated successfully`);
+ close();
+ }
+ else if(createPersonCharacterMutate.status === "error"){
+ createPersonCharacterMutate.reset();
+ addErrorMessage(`Error creating character ${characterName}: ${createPersonCharacterMutate.error.message}`);
+ console.log(createPersonCharacterMutate.error);
+ }
+ else if(updatePersonCharacterMutate.status === "error"){
+ updatePersonCharacterMutate.reset();
+ addErrorMessage(`Error updating character ${characterName}: ${updatePersonCharacterMutate.error.message}`);
+ console.log(updatePersonCharacterMutate.error);
+ }
+ });
+
+
+ const createPersonCharacter = () => {
+ createPersonCharacterMutate.mutate({personId, characterName, gameClassId, characterRating, characterComments} as PersonCharacter);
+ }
+
+ const updatePersonCharacter = () => {
+ updatePersonCharacterMutate.mutate({personCharacterId: personCharacter?.personCharacterId, personId, characterName, gameClassId, characterRating, characterComments} as PersonCharacter);
+ }
+
+
+ return (
+
+ setCharacterName(e.target.value)}
+ />
+
+
+