diff --git a/src/components/gameClass/GameClassCellDisplay.tsx b/src/components/gameClass/GameClassCellDisplay.tsx
new file mode 100644
index 0000000..1729adc
--- /dev/null
+++ b/src/components/gameClass/GameClassCellDisplay.tsx
@@ -0,0 +1,32 @@
+import { useGetGameClass } from "@/hooks/GameClassHooks";
+import DangerMessage from "../message/DangerMessage";
+
+
+export default function GameClassCellDisplay({
+ gameClassId
+}:{
+ gameClassId: string;
+}){
+ const gameClassQuery = useGetGameClass(gameClassId);
+
+
+ if(gameClassQuery.status === "pending"){
+ return
Loading...
;
+ }
+ else if(gameClassQuery.status === "error"){
+ return {
+ const response = await api.get(`/gameClass/${gameClassId}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get game class");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data as GameClass;
+ },
+ enabled: !!gameClassId
+ })
+}
+
export function useGetGameClasses(gameId: string, page: number, pageSize: number, searchTerm?: string){
return useQuery({
queryKey: ["gameClasses", gameId, { page, pageSize, searchTerm }],
diff --git a/src/hooks/PersonCharacterHooks.ts b/src/hooks/PersonCharacterHooks.ts
index 76f5097..d4b0990 100644
--- a/src/hooks/PersonCharacterHooks.ts
+++ b/src/hooks/PersonCharacterHooks.ts
@@ -21,6 +21,58 @@ export function useGetPersonCharactersByPersonId(personId: string, raidGroupId:
});
}
+export function useGetPersonCharactersByPersonIdSearch(personId: string, raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
+ const params = new URLSearchParams();
+ params.append("page", page.toString());
+ params.append("pageSize", pageSize.toString());
+ if(searchTerm){
+ params.append("searchTerm", searchTerm);
+ }
+
+
+ return useQuery({
+ queryKey: ["personCharacters", personId, { page, pageSize, searchTerm }],
+ queryFn: async () => {
+ const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character/page?${params}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get person characters");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ console.log("person characters");
+ console.log(response.data);
+
+ return response.data as PersonCharacter[];
+ }
+ });
+}
+
+export function useGetPersonCharactersCountByPersonIdSearch(personId: string, raidGroupId: string, searchTerm?: string){
+ const params = new URLSearchParams();
+ if(searchTerm){
+ params.append("searchTerm", searchTerm);
+ }
+
+ return useQuery({
+ queryKey: ["personCharactersCount", personId, { searchTerm }],
+ queryFn: async () => {
+ const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character/count?${params}`);
+
+ if(response.status !== 200){
+ throw new Error("Failed to get person characters count");
+ }
+ else if(response.data.errors){
+ throw new Error(response.data.errors.join(", "));
+ }
+
+ return response.data.count as number;
+ }
+ });
+}
+
export function useCreatePersonCharacter(raidGroupId: string, personId: string){
const queryClient = useQueryClient();
diff --git a/src/pages/protected/PersonPage.tsx b/src/pages/protected/PersonPage.tsx
index 6a65013..8e25ca5 100644
--- a/src/pages/protected/PersonPage.tsx
+++ b/src/pages/protected/PersonPage.tsx
@@ -1,10 +1,72 @@
+import DangerMessage from "@/components/message/DangerMessage";
+import { useGetPerson } from "@/hooks/PersonHooks";
+import { useGetRaidGroup } from "@/hooks/RaidGroupHooks";
+import { Person } from "@/interface/Person";
+import { RaidGroup } from "@/interface/RaidGroup";
+import PersonDisplay from "@/ui/person/PersonDisplay";
+import { PersonHeader } from "@/ui/person/PersonHeader";
+import { useEffect, useState } from "react";
+import { useParams } from "react-router";
+
+
export default function PersonPage(){
- //TODO:
+ const { raidGroupId, personId } = useParams();
+ const [ raidGroup, setRaidGroup ] = useState
();
+ const [ person, setPerson ] = useState();
- return (
-
- Person Page
-
- );
+ const raidGroupQuery = useGetRaidGroup(raidGroupId ?? "", false);
+ const personQuery = useGetPerson(raidGroupId ?? "", personId ?? "", false);
+ useEffect(() => {
+ if(raidGroupQuery.status === "success"){
+ setRaidGroup(raidGroupQuery.data);
+ }
+ }, [ raidGroupQuery ]);
+ useEffect(() => {
+ if(personQuery.status === "success"){
+ setPerson(personQuery.data);
+ }
+ }, [ personQuery ]);
+
+ if(raidGroupQuery.status === "pending" || personQuery.status === "pending"){
+ return (
+ Loading...
+ );
+ }
+ else if(raidGroupQuery.status === "error"){
+ return (
+ Error: {raidGroupQuery.error.message}
+ );
+ }
+ else if (personQuery.status === "error"){
+ return (
+ Error: {personQuery.error.message}
+ );
+ }
+ else if(raidGroupQuery.status === "success" && raidGroupQuery.data === undefined){
+ return (
+ Raid Group not found
+ );
+ }
+ else if(personQuery.status === "success" && personQuery.data === undefined){
+ return (
+ Person not found
+ );
+ }
+ else if(raidGroup && person){
+ return (
+
+
+
+
+ );
+ }
}
diff --git a/src/ui/account/AdminAccountsTab.tsx b/src/ui/account/AdminAccountsTab.tsx
index 5680d22..fbcb7e1 100644
--- a/src/ui/account/AdminAccountsTab.tsx
+++ b/src/ui/account/AdminAccountsTab.tsx
@@ -41,7 +41,7 @@ export default function AdminAccountsTab(){
return (
<>
setDisplayCreateAccountModal(true)}
>
Create Account
diff --git a/src/ui/classGroup/ClassGroupsTab.tsx b/src/ui/classGroup/ClassGroupsTab.tsx
index 3c84d33..03c4ffb 100644
--- a/src/ui/classGroup/ClassGroupsTab.tsx
+++ b/src/ui/classGroup/ClassGroupsTab.tsx
@@ -46,7 +46,7 @@ export default function ClassGroupsTab({
return (
<>
setDisplayCreateClassGroupModal(true)}
>
Create Class Group
diff --git a/src/ui/person/PersonAdminButtons.tsx b/src/ui/person/PersonAdminButtons.tsx
index 3580ae1..c8b603c 100644
--- a/src/ui/person/PersonAdminButtons.tsx
+++ b/src/ui/person/PersonAdminButtons.tsx
@@ -14,22 +14,25 @@ export default function PersonAdminButtons({
buttonProps: ButtonProps;
showUpdatePersonModal: () => void;
showDeletePersonModal: () => void;
- showCreatePersonCharacterModal: () => void;
+ showCreatePersonCharacterModal?: () => void;
}){
return (
-
-
-
+ {
+ showCreatePersonCharacterModal &&
+
+
+
+ }
();
+ const pageSize = 10;
+ const modalId = crypto.randomUUID().replaceAll("-", "");
+
+ const navigate = useNavigate();
+
+
+ const characterCountQuery = useGetPersonCharactersCountByPersonIdSearch(person.personId ?? "", raidGroup.raidGroupId ?? "", sentSearchTerm);
+
+
+ const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => {
+ setSentSearchTerm(newSearchTerm);
+ }, 1000);
+
+
+ useEffect(() => {
+ updateSearchTerm(searchTerm ?? "");
+ }, [ searchTerm, updateSearchTerm ]);
+
+
+ useEffect(() => {
+ if(characterCountQuery.status === "success"){
+ setTotalPages(Math.ceil(characterCountQuery.data / pageSize));
+ }
+ }, [ characterCountQuery ]);
+
+
+ return (
+ <>
+
+
+
navigate(`/raidGroup/${raidGroup.raidGroupId}`)}
+ >
+
+
+
+ {/* Add Character Button */}
+
+
setDisplayCreatePersonCharacterModal(true)}
+ >
+ Create Character
+
+
setDisplayCreatePersonCharacterModal(false)}
+ personId={person.personId ?? ""}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ gameId={raidGroup.gameId}
+ />
+
+ {/* Character Search Box */}
+
+
+ setSearchTerm(e.target.value)}
+ placeholder="Search"
+ />
+
+
+
+ {/* Character List */}
+
+ {/* Pagination */}
+
+ >
+ );
+}
diff --git a/src/ui/person/PersonHeader.tsx b/src/ui/person/PersonHeader.tsx
new file mode 100644
index 0000000..5d4de7e
--- /dev/null
+++ b/src/ui/person/PersonHeader.tsx
@@ -0,0 +1,57 @@
+import { ButtonProps } from "@/components/button/Button";
+import { Person } from "@/interface/Person";
+import { RaidGroup } from "@/interface/RaidGroup";
+import { useState } from "react";
+import PersonAdminButtons from "./PersonAdminButtons";
+import DeletePersonModal from "./modals/DeletePersonModal";
+import PersonModal from "./modals/PersonModal";
+
+
+export function PersonHeader({
+ raidGroup,
+ person
+}:{
+ raidGroup: RaidGroup;
+ person: Person;
+}){
+ const [ displayEditPersonModal, setDisplayEditPersonModal ] = useState(false);
+ const [ displayDeletePersonModal, setDisplayDeletePersonModal ] = useState(false);
+
+ const buttonProps: ButtonProps = {
+ variant: "ghost",
+ size: "md",
+ shape: "square"
+ };
+
+
+ return (
+
+
+ {person.personName}
+
+
+
setDisplayEditPersonModal(true)}
+ showDeletePersonModal={() => setDisplayDeletePersonModal(true)}
+ />
+
+ setDisplayEditPersonModal(false)}
+ person={person}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+ setDisplayDeletePersonModal(false)}
+ person={person}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+
+ );
+}
diff --git a/src/ui/person/PersonList.tsx b/src/ui/person/PersonList.tsx
index a47cf5a..3100553 100644
--- a/src/ui/person/PersonList.tsx
+++ b/src/ui/person/PersonList.tsx
@@ -4,6 +4,7 @@ import Table from "@/components/table/Table";
import { Person } from "@/interface/Person";
import { RaidGroup } from "@/interface/RaidGroup";
import { useState } from "react";
+import { Link } from "react-router";
import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
import DeletePersonModal from "./modals/DeletePersonModal";
import PersonModal from "./modals/PersonModal";
@@ -52,18 +53,22 @@ export default function PersonList({
];
const bodyElements: React.ReactNode[][] = people.map((person) => [
-
+
{person.personName}
-
,
+ ,
{person.discordId}
,
- ,
+ ,
diff --git a/src/ui/person/PersonListSkeleton.tsx b/src/ui/person/PersonListSkeleton.tsx
index bf069b3..bcc46a0 100644
--- a/src/ui/person/PersonListSkeleton.tsx
+++ b/src/ui/person/PersonListSkeleton.tsx
@@ -4,6 +4,7 @@ import { elementBg } from "@/util/SkeletonUtil";
import React from "react";
import PersonAdminButtons from "./PersonAdminButtons";
+
export default function PersonListSkeleton(){
const headElements: React.ReactNode[] = [
diff --git a/src/ui/person/PersonTab.tsx b/src/ui/person/PersonTab.tsx
index f472c47..4fa5265 100644
--- a/src/ui/person/PersonTab.tsx
+++ b/src/ui/person/PersonTab.tsx
@@ -46,7 +46,7 @@ export default function PersonTab({
return (
<>
setDisplayCreatePersonModal(true)}
>
Create Person
diff --git a/src/ui/person/modals/DeletePersonModal.tsx b/src/ui/person/modals/DeletePersonModal.tsx
index 76f90ef..1ce071c 100644
--- a/src/ui/person/modals/DeletePersonModal.tsx
+++ b/src/ui/person/modals/DeletePersonModal.tsx
@@ -37,7 +37,7 @@ export default function DeletePersonModal({
addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
console.log(deletePersonMutate.error);
}
- })
+ });
return (
diff --git a/src/ui/personCharacter/PersonCharacterAdminButtons.tsx b/src/ui/personCharacter/PersonCharacterAdminButtons.tsx
new file mode 100644
index 0000000..3f40509
--- /dev/null
+++ b/src/ui/personCharacter/PersonCharacterAdminButtons.tsx
@@ -0,0 +1,38 @@
+import { ButtonProps } from "@/components/button/Button";
+import DangerButton from "@/components/button/DangerButton";
+import PrimaryButton from "@/components/button/PrimaryButton";
+import { BsPencilFill, BsTrash3 } from "react-icons/bs";
+
+
+export default function PersonCharacterAdminButtons({
+ buttonProps,
+ showUpdatePersonCharacterModal,
+ showDeletePersonCharacterModal
+}:{
+ buttonProps: ButtonProps;
+ showUpdatePersonCharacterModal: () => void;
+ showDeletePersonCharacterModal: () => void;
+}){
+ return (
+
+ );
+}
diff --git a/src/ui/personCharacter/PersonCharacterList.tsx b/src/ui/personCharacter/PersonCharacterList.tsx
new file mode 100644
index 0000000..9ffbcac
--- /dev/null
+++ b/src/ui/personCharacter/PersonCharacterList.tsx
@@ -0,0 +1,125 @@
+import { ButtonProps } from "@/components/button/Button";
+import GameClassCellDisplay from "@/components/gameClass/GameClassCellDisplay";
+import Table from "@/components/table/Table";
+import { Person } from "@/interface/Person";
+import { PersonCharacter } from "@/interface/PersonCharacter";
+import { RaidGroup } from "@/interface/RaidGroup";
+import { useState } from "react";
+import DeletePersonCharacterModal from "./modal/DeletePersonCharacterModal";
+import PersonCharacterModal from "./modal/PersonCharacterModal";
+import PersonCharacterAdminButtons from "./PersonCharacterAdminButtons";
+
+
+export default function PersonCharacterList({
+ personCharacters,
+ person,
+ raidGroup
+}:{
+ personCharacters: PersonCharacter[];
+ person: Person;
+ raidGroup: RaidGroup;
+}){
+ const [ selectedCharacter, setSelectedCharacter ] = useState();
+ const [ displayUpdatePersonCharacterModal, setDisplayUpdatePersonCharacterModal ] = useState(false);
+ const [ displayDeletePersonCharacterModal, setDisplayDeletePersonCharacterModal ] = useState(false);
+
+
+ const buttonProps: ButtonProps = {
+ variant: "ghost",
+ size: "md",
+ shape: "square"
+ };
+
+
+ const headElements: React.ReactNode[] = [
+
+ Character Name
+
,
+
+ Class
+
,
+
+ Rating
+
,
+
+ Comments
+
,
+
+ Actions
+
+ ];
+
+ const bodyElements: React.ReactNode[][] = personCharacters.map((personCharacter) => [
+
+ {personCharacter.characterName}
+
,
+
+
+
,
+
+ {
+ personCharacter.characterRating && personCharacter.characterRating > 0 ?
+ personCharacter.characterRating :
+ <> >
+ }
+
,
+
+ {
+ personCharacter.characterComments || <> >
+ }
+
,
+
+
+
+
+
{
+ setSelectedCharacter(personCharacter);
+ setDisplayUpdatePersonCharacterModal(true);
+ }}
+ showDeletePersonCharacterModal={() => {
+ setSelectedCharacter(personCharacter);
+ setDisplayDeletePersonCharacterModal(true);
+ }}
+ />
+
+ ]);
+
+
+ return (
+ <>
+
+ {setDisplayUpdatePersonCharacterModal(false); setSelectedCharacter(undefined);}}
+ personCharacter={selectedCharacter}
+ personId={person.personId ?? ""}
+ gameId={raidGroup.gameId}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ />
+ {setDisplayDeletePersonCharacterModal(false); setSelectedCharacter(undefined);}}
+ personCharacter={selectedCharacter}
+ raidGroupId={raidGroup.raidGroupId ?? ""}
+ personId={person.personId ?? ""}
+ />
+ >
+ );
+}
diff --git a/src/ui/personCharacter/PersonCharacterListSkeleton.tsx b/src/ui/personCharacter/PersonCharacterListSkeleton.tsx
new file mode 100644
index 0000000..da1abe1
--- /dev/null
+++ b/src/ui/personCharacter/PersonCharacterListSkeleton.tsx
@@ -0,0 +1,84 @@
+import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button";
+import Table from "@/components/table/Table";
+import { elementBg } from "@/util/SkeletonUtil";
+import PersonCharacterAdminButtons from "./PersonCharacterAdminButtons";
+
+
+export default function PersonCharacterListSkeleton(){
+ const headElements: React.ReactNode[] = [
+
+ Character Name
+
,
+
+ Class
+
,
+
+ Rating
+
,
+
+ Comments
+
,
+
+ Actions
+
+ ];
+
+ const bodyElements: React.ReactNode[][] = [
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton(),
+ PersonCharacterSkeleton()
+ ];
+
+
+ return (
+
+ );
+}
+
+function PersonCharacterSkeleton(): React.ReactNode[]{
+ const buttonsProps = {
+ buttonProps: {
+ variant: "ghost" as ButtonVariant,
+ size: "md" as ButtonSizeType,
+ shape: "square" as ButtonShape,
+ disabled: true
+ },
+ showUpdatePersonCharacterModal: () => {},
+ showDeletePersonCharacterModal: () => {}
+ };
+
+ const elements: React.ReactNode[] = [
+ ,
+ ,
+ ,
+ ,
+
+ ];
+
+ return elements;
+}
diff --git a/src/ui/personCharacter/PersonCharacterLoader.tsx b/src/ui/personCharacter/PersonCharacterLoader.tsx
new file mode 100644
index 0000000..36bb5cd
--- /dev/null
+++ b/src/ui/personCharacter/PersonCharacterLoader.tsx
@@ -0,0 +1,40 @@
+import DangerMessage from "@/components/message/DangerMessage";
+import { useGetPersonCharactersByPersonIdSearch } from "@/hooks/PersonCharacterHooks";
+import { Person } from "@/interface/Person";
+import { RaidGroup } from "@/interface/RaidGroup";
+import PersonCharacterList from "./PersonCharacterList";
+import PersonCharacterListSkeleton from "./PersonCharacterListSkeleton";
+
+
+export default function PersonCharacterLoader({
+ raidGroup,
+ person,
+ page,
+ pageSize,
+ searchTerm
+}:{
+ raidGroup: RaidGroup;
+ person: Person;
+ page: number;
+ pageSize: number;
+ searchTerm?: string;
+}){
+ const personCharactersQuery = useGetPersonCharactersByPersonIdSearch(person.personId ?? "", raidGroup.raidGroupId ?? "", page - 1, pageSize, searchTerm);
+
+
+ if(personCharactersQuery.status === "pending"){
+
+ }
+ else if(personCharactersQuery.status === "error"){
+ return Error: {personCharactersQuery.error.message}
+ }
+ else{
+ return (
+
+ );
+ }
+}
diff --git a/src/ui/personCharacter/modal/DeletePersonCharacterModal.tsx b/src/ui/personCharacter/modal/DeletePersonCharacterModal.tsx
new file mode 100644
index 0000000..48b3869
--- /dev/null
+++ b/src/ui/personCharacter/modal/DeletePersonCharacterModal.tsx
@@ -0,0 +1,67 @@
+import DangerButton from "@/components/button/DangerButton";
+import SecondaryButton from "@/components/button/SecondaryButton";
+import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
+import { useDeletePersonCharacter } from "@/hooks/PersonCharacterHooks";
+import { PersonCharacter } from "@/interface/PersonCharacter";
+import { useTimedModal } from "@/providers/TimedModalProvider";
+import { useEffect } from "react";
+
+
+export default function DeletePersonCharacterModal({
+ display,
+ close,
+ personCharacter,
+ raidGroupId,
+ personId
+}:{
+ display: boolean;
+ close: () => void;
+ personCharacter?: PersonCharacter;
+ raidGroupId: string;
+ personId: string;
+}){
+ const deletePersonCharacterMutate = useDeletePersonCharacter(raidGroupId, personId);
+ const { addSuccessMessage, addErrorMessage } = useTimedModal();
+
+
+ const deletePersonCharacter = () => {
+ deletePersonCharacterMutate.mutate(personCharacter?.personCharacterId ?? "");
+ }
+
+ useEffect(() => {
+ if(deletePersonCharacterMutate.status === "success"){
+ deletePersonCharacterMutate.reset();
+ addSuccessMessage("Person Character Deleted");
+ close();
+ }
+ else if(deletePersonCharacterMutate.status === "error"){
+ deletePersonCharacterMutate.reset();
+ addErrorMessage("Failed to delete person character");
+ console.log(deletePersonCharacterMutate.error);
+ }
+ });
+
+
+ return (
+
+
+ Delete
+
+
+ Cancel
+
+ >
+ }
+ />
+ );
+}
diff --git a/src/ui/raidLayout/RaidLayoutTab.tsx b/src/ui/raidLayout/RaidLayoutTab.tsx
index e3dd00c..950a777 100644
--- a/src/ui/raidLayout/RaidLayoutTab.tsx
+++ b/src/ui/raidLayout/RaidLayoutTab.tsx
@@ -45,7 +45,7 @@ export default function RaidLayoutTab({
return (
<>
setDisplayCreateRaidLayoutModal(true)}
>
Create Raid Layout