Person page working

This commit is contained in:
2025-03-10 22:55:16 -04:00
parent 5a2c8a8936
commit c9ceeea3b4
21 changed files with 757 additions and 47 deletions

View File

@@ -41,7 +41,7 @@ export default function AdminAccountsTab(){
return (
<>
<div
className="flex flex-row justify-between items-center w-full"
className="flex flex-row justify-between items-center w-full mb-8"
>
<div
className="flex flex-row items-center justify-start w-full"
@@ -53,7 +53,7 @@ export default function AdminAccountsTab(){
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
className="text-nowrap"
onClick={() => setDisplayCreateAccountModal(true)}
>
Create Account

View File

@@ -46,7 +46,7 @@ export default function ClassGroupsTab({
return (
<>
<div
className="flex flex-row justify-between items-center w-full"
className="flex flex-row justify-between items-center w-full mb-8"
>
<div
className="flex flex-row items-center justify-start w-full"
@@ -58,7 +58,7 @@ export default function ClassGroupsTab({
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
className="text-nowrap"
onClick={() => setDisplayCreateClassGroupModal(true)}
>
Create Class Group

View File

@@ -14,22 +14,25 @@ export default function PersonAdminButtons({
buttonProps: ButtonProps;
showUpdatePersonModal: () => void;
showDeletePersonModal: () => void;
showCreatePersonCharacterModal: () => void;
showCreatePersonCharacterModal?: () => void;
}){
return (
<div
className="flex flex-row gap-2"
>
<SuccessButton
{...buttonProps}
size="sm"
onClick={showCreatePersonCharacterModal}
>
<BsPlusLg
size={30}
strokeWidth={1}
/>
</SuccessButton>
{
showCreatePersonCharacterModal &&
<SuccessButton
{...buttonProps}
size="sm"
onClick={showCreatePersonCharacterModal}
>
<BsPlusLg
size={30}
strokeWidth={1}
/>
</SuccessButton>
}
<PrimaryButton
{...buttonProps}
onClick={showUpdatePersonModal}

View File

@@ -0,0 +1,122 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination";
import { useGetPersonCharactersCountByPersonIdSearch } from "@/hooks/PersonCharacterHooks";
import { Person } from "@/interface/Person";
import { RaidGroup } from "@/interface/RaidGroup";
import { useEffect, useState } from "react";
import { BsArrowLeft } from "react-icons/bs";
import { useNavigate } from "react-router";
import { useDebouncedCallback } from "use-debounce";
import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
import PersonCharacterLoader from "../personCharacter/PersonCharacterLoader";
export default function PersonDisplay({
raidGroup,
person
}:{
raidGroup: RaidGroup;
person: Person;
}){
const [ displayCreatePersonCharacterModal, setDisplayCreatePersonCharacterModal ] = useState(false);
const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1);
const [ searchTerm, setSearchTerm ] = useState("");
const [ sentSearchTerm, setSentSearchTerm ] = useState<string>();
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 (
<>
<div
className="flex flex-row items-center justify-center w-full mb-8"
>
<div
className="flex flex-row items-center justify-start w-full"
>
<PrimaryButton
shape="square"
onClick={() => navigate(`/raidGroup/${raidGroup.raidGroupId}`)}
>
<BsArrowLeft
size={22}
/>
</PrimaryButton>
</div>
{/* Add Character Button */}
<div
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="text-nowrap"
onClick={() => setDisplayCreatePersonCharacterModal(true)}
>
Create Character
</PrimaryButton>
<PersonCharacterModal
display={displayCreatePersonCharacterModal}
close={() => setDisplayCreatePersonCharacterModal(false)}
personId={person.personId ?? ""}
raidGroupId={raidGroup.raidGroupId ?? ""}
gameId={raidGroup.gameId}
/>
</div>
{/* Character Search Box */}
<div
className="flex flex-row items-center justify-end w-full"
>
<div>
<TextInput
id={`characterSearchBox${modalId}`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
{/* Character List */}
<PersonCharacterLoader
raidGroup={raidGroup}
person={person}
page={page}
pageSize={pageSize}
searchTerm={sentSearchTerm}
/>
{/* Pagination */}
<div
className="my-12"
>
<Pagination
currentPage={page}
totalPages={totalPages}
onChange={setPage}
/>
</div>
</>
);
}

View File

@@ -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 (
<h1
className="flex flex-col items-center justify-center"
>
<div
className="flex flex-row items-center justify-center text-4xl"
>
{person.personName}
</div>
<div>
<PersonAdminButtons
buttonProps={buttonProps}
showUpdatePersonModal={() => setDisplayEditPersonModal(true)}
showDeletePersonModal={() => setDisplayDeletePersonModal(true)}
/>
</div>
<PersonModal
display={displayEditPersonModal}
close={() => setDisplayEditPersonModal(false)}
person={person}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
<DeletePersonModal
display={displayDeletePersonModal}
close={() => setDisplayDeletePersonModal(false)}
person={person}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
</h1>
);
}

View File

@@ -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) => [
<div>
<Link
to={`/raidGroup/${raidGroup.raidGroupId}/person/${person.personId}`}
>
{person.personName}
</div>,
</Link>,
<div>
{person.discordId}
</div>,
<div>
<Link
to={`/raidGroup/${raidGroup.raidGroupId}/person/${person.personId}`}
>
<PersonCharacterDisplay
personId={person.personId ?? ""}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
</div>,
</Link>,
<div
className="flex flex-row items-center justify-center gap-2 pl-16"
>

View File

@@ -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[] = [
<div>

View File

@@ -46,7 +46,7 @@ export default function PersonTab({
return (
<>
<div
className="flex flex-row items-center justify-center w-full"
className="flex flex-row items-center justify-center w-full mb-8"
>
<div
className="flex flex-row items-center justify-center w-full"
@@ -58,7 +58,7 @@ export default function PersonTab({
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
className="text-nowrap"
onClick={() => setDisplayCreatePersonModal(true)}
>
Create Person

View File

@@ -37,7 +37,7 @@ export default function DeletePersonModal({
addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
console.log(deletePersonMutate.error);
}
})
});
return (

View File

@@ -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 (
<div
className="flex flex-row gap-2"
>
<PrimaryButton
{...buttonProps}
onClick={showUpdatePersonCharacterModal}
>
<BsPencilFill
size={22}
/>
</PrimaryButton>
<DangerButton
{...buttonProps}
onClick={showDeletePersonCharacterModal}
>
<BsTrash3
size={22}
/>
</DangerButton>
</div>
);
}

View File

@@ -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<PersonCharacter>();
const [ displayUpdatePersonCharacterModal, setDisplayUpdatePersonCharacterModal ] = useState(false);
const [ displayDeletePersonCharacterModal, setDisplayDeletePersonCharacterModal ] = useState(false);
const buttonProps: ButtonProps = {
variant: "ghost",
size: "md",
shape: "square"
};
const headElements: React.ReactNode[] = [
<div
className="text-nowrap"
>
Character Name
</div>,
<div>
Class
</div>,
<div>
Rating
</div>,
<div>
Comments
</div>,
<div
className="pl-16"
>
Actions
</div>
];
const bodyElements: React.ReactNode[][] = personCharacters.map((personCharacter) => [
<div
className="text-nowrap"
>
{personCharacter.characterName}
</div>,
<div>
<GameClassCellDisplay
gameClassId={personCharacter.gameClassId}
/>
</div>,
<div>
{
personCharacter.characterRating && personCharacter.characterRating > 0 ?
personCharacter.characterRating :
<>&nbsp;</>
}
</div>,
<div>
{
personCharacter.characterComments || <>&nbsp;</>
}
</div>,
<div
className="flex flex-row flex-nowrap items-center justify-center gap-2 pl-16"
>
<div
className="py-4 border-l border-neutral-500"
>
&nbsp;
</div>
<PersonCharacterAdminButtons
buttonProps={buttonProps}
showUpdatePersonCharacterModal={() => {
setSelectedCharacter(personCharacter);
setDisplayUpdatePersonCharacterModal(true);
}}
showDeletePersonCharacterModal={() => {
setSelectedCharacter(personCharacter);
setDisplayDeletePersonCharacterModal(true);
}}
/>
</div>
]);
return (
<>
<Table
tableHeadElements={headElements}
tableBodyElements={bodyElements}
/>
<PersonCharacterModal
display={displayUpdatePersonCharacterModal}
close={() => {setDisplayUpdatePersonCharacterModal(false); setSelectedCharacter(undefined);}}
personCharacter={selectedCharacter}
personId={person.personId ?? ""}
gameId={raidGroup.gameId}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
<DeletePersonCharacterModal
display={displayDeletePersonCharacterModal}
close={() => {setDisplayDeletePersonCharacterModal(false); setSelectedCharacter(undefined);}}
personCharacter={selectedCharacter}
raidGroupId={raidGroup.raidGroupId ?? ""}
personId={person.personId ?? ""}
/>
</>
);
}

View File

@@ -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[] = [
<div>
Character Name
</div>,
<div>
Class
</div>,
<div>
Rating
</div>,
<div>
Comments
</div>,
<div
className="pl-16"
>
Actions
</div>
];
const bodyElements: React.ReactNode[][] = [
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton(),
PersonCharacterSkeleton()
];
return (
<Table
tableHeadElements={headElements}
tableBodyElements={bodyElements}
/>
);
}
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[] = [
<div
className={`h-6 w-96 mr-1 ${elementBg}`}
/>,
<div
className={`h-6 w-72 ml-2 ${elementBg}`}
/>,
<div
className={`h-6 w-32 ${elementBg}`}
/>,
<div
className={`h-6 w-[32rem] ${elementBg}`}
/>,
<div
className={`flex flex-row items-center justify-center gap-2 pl-16`}
>
<div className="py-4 border-l border-neutral-500">&nbsp;</div>
<PersonCharacterAdminButtons {...buttonsProps}/>
</div>
];
return elements;
}

View File

@@ -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"){
<PersonCharacterListSkeleton/>
}
else if(personCharactersQuery.status === "error"){
return <DangerMessage>Error: {personCharactersQuery.error.message}</DangerMessage>
}
else{
return (
<PersonCharacterList
personCharacters={personCharactersQuery.data ?? []}
raidGroup={raidGroup}
person={person}
/>
);
}
}

View File

@@ -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 (
<RaidBuilderModal
display={display}
close={close}
modalHeader={`Delete Character ${personCharacter?.characterName}`}
modalBody={`Are you sure you want to delete ${personCharacter?.characterName}`}
modalFooter={
<>
<DangerButton
onClick={deletePersonCharacter}
>
Delete
</DangerButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}

View File

@@ -45,7 +45,7 @@ export default function RaidLayoutTab({
return (
<>
<div
className="flex flex-row items-center justify-between w-full"
className="flex flex-row items-center justify-between w-full mb-8"
>
<div
className="flex flex-row items-center justify-start w-full"
@@ -57,7 +57,7 @@ export default function RaidLayoutTab({
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
className="text-nowrap"
onClick={() => setDisplayCreateRaidLayoutModal(true)}
>
Create Raid Layout