Person page working
This commit is contained in:
32
src/components/gameClass/GameClassCellDisplay.tsx
Normal file
32
src/components/gameClass/GameClassCellDisplay.tsx
Normal file
@@ -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 <div>Loading...</div>;
|
||||
}
|
||||
else if(gameClassQuery.status === "error"){
|
||||
return <DangerMessage>Error: {gameClassQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center justify-center"
|
||||
>
|
||||
<img
|
||||
className="max-h-14 max-w-14 mr-2 -mt-4"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClassQuery.data.gameClassIcon}`}
|
||||
/>
|
||||
{gameClassQuery.data.gameClassName}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,22 +22,25 @@ export default function RatingSelector({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
onChange={(e) => setCurrentRating(parseInt(e.target.value))}
|
||||
value={currentRating}
|
||||
>
|
||||
<option value={undefined}></option>
|
||||
{
|
||||
ratings.map((rating) => (
|
||||
<option
|
||||
key={rating}
|
||||
value={rating}
|
||||
>
|
||||
{rating}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<label>
|
||||
<select
|
||||
onChange={(e) => setCurrentRating(parseInt(e.target.value))}
|
||||
value={currentRating}
|
||||
>
|
||||
<option value={undefined}></option>
|
||||
{
|
||||
ratings.map((rating) => (
|
||||
<option
|
||||
key={rating}
|
||||
value={rating}
|
||||
>
|
||||
{rating}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<span>Character Rating</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function TableBody({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-neutral-200 dark:bg-neutral-700",
|
||||
"bg-neutral-200 dark:bg-neutral-700 h-14",
|
||||
{
|
||||
"py-4 my-2": elementIndex < row.length - 1,
|
||||
"rounded-l pl-2": elementIndex === 0,
|
||||
|
||||
@@ -3,6 +3,25 @@ import { api } from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetGameClass(gameClassId: string){
|
||||
return useQuery({
|
||||
queryKey: ["gameClasses", gameClassId],
|
||||
queryFn: async () => {
|
||||
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 }],
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<RaidGroup>();
|
||||
const [ person, setPerson ] = useState<Person>();
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
Person Page
|
||||
</div>
|
||||
);
|
||||
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 (
|
||||
<div>Loading...</div>
|
||||
);
|
||||
}
|
||||
else if(raidGroupQuery.status === "error"){
|
||||
return (
|
||||
<DangerMessage>Error: {raidGroupQuery.error.message}</DangerMessage>
|
||||
);
|
||||
}
|
||||
else if (personQuery.status === "error"){
|
||||
return (
|
||||
<DangerMessage>Error: {personQuery.error.message}</DangerMessage>
|
||||
);
|
||||
}
|
||||
else if(raidGroupQuery.status === "success" && raidGroupQuery.data === undefined){
|
||||
return (
|
||||
<DangerMessage>Raid Group not found</DangerMessage>
|
||||
);
|
||||
}
|
||||
else if(personQuery.status === "success" && personQuery.data === undefined){
|
||||
return (
|
||||
<DangerMessage>Person not found</DangerMessage>
|
||||
);
|
||||
}
|
||||
else if(raidGroup && person){
|
||||
return (
|
||||
<main
|
||||
className="flex flex-col items-center justify-center gap-y-8"
|
||||
>
|
||||
<PersonHeader
|
||||
raidGroup={raidGroup}
|
||||
person={person}
|
||||
/>
|
||||
<PersonDisplay
|
||||
raidGroup={raidGroup}
|
||||
person={person}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
122
src/ui/person/PersonDisplay.tsx
Normal file
122
src/ui/person/PersonDisplay.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
57
src/ui/person/PersonHeader.tsx
Normal file
57
src/ui/person/PersonHeader.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function DeletePersonModal({
|
||||
addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
|
||||
console.log(deletePersonMutate.error);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
|
||||
38
src/ui/personCharacter/PersonCharacterAdminButtons.tsx
Normal file
38
src/ui/personCharacter/PersonCharacterAdminButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
125
src/ui/personCharacter/PersonCharacterList.tsx
Normal file
125
src/ui/personCharacter/PersonCharacterList.tsx
Normal 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 :
|
||||
<> </>
|
||||
}
|
||||
</div>,
|
||||
<div>
|
||||
{
|
||||
personCharacter.characterComments || <> </>
|
||||
}
|
||||
</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"
|
||||
>
|
||||
|
||||
</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 ?? ""}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
84
src/ui/personCharacter/PersonCharacterListSkeleton.tsx
Normal file
84
src/ui/personCharacter/PersonCharacterListSkeleton.tsx
Normal 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"> </div>
|
||||
<PersonCharacterAdminButtons {...buttonsProps}/>
|
||||
</div>
|
||||
];
|
||||
|
||||
return elements;
|
||||
}
|
||||
40
src/ui/personCharacter/PersonCharacterLoader.tsx
Normal file
40
src/ui/personCharacter/PersonCharacterLoader.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
67
src/ui/personCharacter/modal/DeletePersonCharacterModal.tsx
Normal file
67
src/ui/personCharacter/modal/DeletePersonCharacterModal.tsx
Normal 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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user