People tab working

This commit is contained in:
2025-03-08 13:26:39 -05:00
parent 0dfb971bc2
commit b763a1c7bd
22 changed files with 1050 additions and 22 deletions

View File

@@ -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"
>
<PrimaryButton
className="mb-8 tex-tnowrap"
className="mb-8 text-nowrap"
onClick={() => setDisplayCreateAccountModal(true)}
>
Create Account

View File

@@ -31,10 +31,8 @@ export default function RaidGroupHeader({
{
raidGroup.raidGroupIcon &&
<img
className="m-auto mr-4"
src={`${import.meta.env.VITE_ICON_URL}/raidGroupIcons/${raidGroup.raidGroupIcon}`}
height={72}
width={72}
className="m-auto mr-4 max-h-18 max-w-18"
src={`${import.meta.env.VITE_ICON_URL}/raidGroup/${raidGroup.raidGroupIcon}`}
/>
}
{raidGroup.raidGroupName}

View File

@@ -31,10 +31,8 @@ export default function GameHeader({
{
game.gameIcon &&
<img
className="m-auto mr-4"
src={`${import.meta.env.VITE_ICON_URL}/gameIcons/${game.gameIcon}`}
height={72}
width={72}
className="m-auto mr-4 max-h-18 max-w-18"
src={`${import.meta.env.VITE_ICON_URL}/game/${game.gameIcon}`}
/>
}
{game.gameName}

View File

@@ -47,10 +47,8 @@ export default function GamesList({
className="absolute -my-4"
>
<img
className="m-auto"
src={`${import.meta.env.VITE_ICON_URL}/gameIcons/${game.gameIcon}`}
height={56}
width={56}
className="m-auto max-h-14 max-w-14"
src={`${import.meta.env.VITE_ICON_URL}/game/${game.gameIcon}`}
/>
</div>
}

View File

@@ -43,13 +43,11 @@ export default function GameClassList({
{
gameClass.gameClassIcon &&
<div
className="absolute -my-4"
className="absolute -my-4 max-h-14 max-w-14"
>
<img
className="m-auto"
src={`${import.meta.env.VITE_ICON_URL}/gameClassIcons/${gameClass.gameClassIcon}`}
height={56}
width={56}
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClass.gameClassIcon}`}
/>
</div>
}

View File

@@ -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 (
<div
className="flex flex-row gap-2"
>
<SuccessButton
{...buttonProps}
size="sm"
onClick={showCreatePersonCharacterModal}
>
<BsPlusLg
size={30}
strokeWidth={1}
/>
</SuccessButton>
<PrimaryButton
{...buttonProps}
onClick={showUpdatePersonModal}
>
<BsPencilFill
size={22}
/>
</PrimaryButton>
<DangerButton
{...buttonProps}
onClick={showDeletePersonModal}
>
<BsTrash3
size={22}
/>
</DangerButton>
</div>
);
}

View File

@@ -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<Person>();
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[] = [
<div
className="text-nowrap"
>
Person Name
</div>,
<div
className="text-nowrap"
>
Discord ID
</div>,
<div>
Characters
</div>,
<div
className="pl-16"
>
Actions
</div>
];
const bodyElements: React.ReactNode[][] = people.map((person) => [
<div>
{person.personName}
</div>,
<div>
{person.discordId}
</div>,
<div>
<PersonCharacterDisplay
personId={person.personId ?? ""}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
</div>,
<div
className="flex flex-row items-center justify-center gap-2 pl-16"
>
<div
className="py-4 border-l border-neutral-500"
>
&nbsp;
</div>
<PersonAdminButtons
buttonProps={buttonProps}
showUpdatePersonModal={() => {
setSelectedPerson(person);
setDisplayUpdatePersonModal(true);
}}
showDeletePersonModal={() => {
setSelectedPerson(person);
setDisplayDeletePersonModal(true);
}}
showCreatePersonCharacterModal={() => {
setSelectedPerson(person);
setDisplayCreatePersonCharacterModal(true);
}}
/>
</div>
]);
return (
<>
<Table
tableHeadElements={headElements}
tableBodyElements={bodyElements}
/>
<PersonModal
display={displayUpdatePersonModal}
close={() => {setDisplayUpdatePersonModal(false); setSelectedPerson(undefined);}}
person={selectedPerson}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
<DeletePersonModal
display={displayDeletePersonModal}
close={() => {setDisplayDeletePersonModal(false); setSelectedPerson(undefined);}}
person={selectedPerson}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
<PersonCharacterModal
display={displayCreatePersonCharacterModal}
close={() => {setDisplayCreatePersonCharacterModal(false); setSelectedPerson(undefined);}}
personId={selectedPerson?.personId ?? ""}
personCharacter={undefined}
gameId={raidGroup.gameId}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function PersonListSkeleton(){
return (
<div>
Person List Skeleton
</div>
);
}

View File

@@ -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 <PersonListSkeleton/>
}
else if(peopleQuery.status === "error"){
return <DangerMessage>Error: {peopleQuery.error.message}</DangerMessage>
}
else{
return (
<PersonList
people={peopleQuery.data ?? []}
raidGroup={raidGroup}
/>
);
}
}

106
src/ui/person/PersonTab.tsx Normal file
View File

@@ -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<string>();
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 (
<>
<div
className="flex flex-row items-center justify-center w-full"
>
<div
className="flex flex-row items-center justify-center w-full"
>
&nbsp;
</div>
{/* Add Person Button */}
<div
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
onClick={() => setDisplayCreatePersonModal(true)}
>
Create Person
</PrimaryButton>
<PersonModal
display={displayCreatePersonModal}
close={() => setDisplayCreatePersonModal(false)}
person={undefined}
raidGroupId={raidGroup.raidGroupId ?? ""}
/>
</div>
{/* Person Search Box */}
<div
className="flex flex-row items-center justify-end w-full"
>
<div>
<TextInput
id={`personSearchBox${modalId}`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
{ /* Person List */}
<PersonLoader
raidGroup={raidGroup}
page={page}
pageSize={pageSize}
searchTerm={sentSearchTerm}
/>
{/* Pagination */}
<div
className="my-12"
>
<Pagination
currentPage={page}
totalPages={totalPages}
onChange={setPage}
/>
</div>
</>
);
}

View File

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

View File

@@ -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<string>(person?.personName ?? "");
const [ discordId, setDiscordId ] = useState<string>(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 (
<RaidBuilderModal
display={display}
close={close}
modalHeader={person ? "Update Person" : "Create Person"}
modalBody={
<div
className="flex flex-col items-center justify-center gap-4"
>
<TextInput
id={`personModalName${modalId}`}
placeholder="Person Name"
value={personName}
onChange={(e) => setPersonName(e.target.value)}
/>
<TextInput
id={`personModalDiscordId${modalId}`}
placeholder="Discord ID"
value={discordId}
onChange={(e) => setDiscordId(e.target.value)}
/>
</div>
}
modalFooter={
<>
<PrimaryButton
onClick={person ? updatePerson : createPerson}
>
{person ? "Update" : "Create"}
</PrimaryButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}

View File

@@ -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<string>(personCharacter?.characterName ?? "");
const [ gameClassId, setGameClassId ] = useState<string | undefined>(personCharacter?.gameClassId);
const [ characterRating, setCharacterRating ] = useState<number>();
const [ characterComments, setCharacterComments ] = useState<string>();
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 (
<RaidBuilderModal
display={display}
close={close}
modalHeader={personCharacter ? "Update Character" : "Create Character"}
modalBody={
<div
className="flex flex-col items-center justify-center gap-4"
>
<TextInput
id={`personCharacterModalCharacterName${modalId}`}
placeholder="Character Name"
value={characterName}
onChange={(e) => setCharacterName(e.target.value)}
/>
<GameClassSelector
gameId={gameId}
gameClassId={personCharacter?.gameClassId}
onChange={setGameClassId}
/>
<RatingSelector
rating={characterRating}
onChange={setCharacterRating}
/>
<TextInput
id={`personCharacterModalCharacterComments${modalId}`}
placeholder="Character Comments"
value={characterComments}
onChange={(e) => setCharacterComments(e.target.value)}
/>
</div>
}
modalFooter={
<>
<PrimaryButton
onClick={personCharacter ? updatePersonCharacter : createPersonCharacter}
>
{personCharacter ? "Update" : "Create"}
</PrimaryButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}

View File

@@ -47,10 +47,8 @@ export default function RaidGroupsList({
className="absolute -my-4"
>
<img
className="m-auto"
src={`${import.meta.env.VITE_ICON_URL}/raidGroupIcons/${raidGroup.raidGroupIcon}`}
height={56}
width={56}
className="m-auto max-h-14 max-w-14"
src={`${import.meta.env.VITE_ICON_URL}/raidGroup/${raidGroup.raidGroupIcon}`}
/>
</div>
}