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,6 +22,7 @@ export default function RatingSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<label>
|
||||||
<select
|
<select
|
||||||
onChange={(e) => setCurrentRating(parseInt(e.target.value))}
|
onChange={(e) => setCurrentRating(parseInt(e.target.value))}
|
||||||
value={currentRating}
|
value={currentRating}
|
||||||
@@ -38,6 +39,8 @@ export default function RatingSelector({
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
<span>Character Rating</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function TableBody({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
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,
|
"py-4 my-2": elementIndex < row.length - 1,
|
||||||
"rounded-l pl-2": elementIndex === 0,
|
"rounded-l pl-2": elementIndex === 0,
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ import { api } from "@/util/AxiosUtil";
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
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){
|
export function useGetGameClasses(gameId: string, page: number, pageSize: number, searchTerm?: string){
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["gameClasses", gameId, { page, pageSize, searchTerm }],
|
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){
|
export function useCreatePersonCharacter(raidGroupId: string, personId: string){
|
||||||
const queryClient = useQueryClient();
|
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(){
|
export default function PersonPage(){
|
||||||
//TODO:
|
const { raidGroupId, personId } = useParams();
|
||||||
|
const [ raidGroup, setRaidGroup ] = useState<RaidGroup>();
|
||||||
|
const [ person, setPerson ] = useState<Person>();
|
||||||
|
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div>
|
<div>Loading...</div>
|
||||||
Person Page
|
|
||||||
</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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row justify-between items-center w-full"
|
className="flex flex-row justify-between items-center w-full mb-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-start w-full"
|
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"
|
className="flex flex-row items-center justify-center w-full"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mb-8 text-nowrap"
|
className="text-nowrap"
|
||||||
onClick={() => setDisplayCreateAccountModal(true)}
|
onClick={() => setDisplayCreateAccountModal(true)}
|
||||||
>
|
>
|
||||||
Create Account
|
Create Account
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function ClassGroupsTab({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row justify-between items-center w-full"
|
className="flex flex-row justify-between items-center w-full mb-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-start w-full"
|
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"
|
className="flex flex-row items-center justify-center w-full"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mb-8 text-nowrap"
|
className="text-nowrap"
|
||||||
onClick={() => setDisplayCreateClassGroupModal(true)}
|
onClick={() => setDisplayCreateClassGroupModal(true)}
|
||||||
>
|
>
|
||||||
Create Class Group
|
Create Class Group
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ export default function PersonAdminButtons({
|
|||||||
buttonProps: ButtonProps;
|
buttonProps: ButtonProps;
|
||||||
showUpdatePersonModal: () => void;
|
showUpdatePersonModal: () => void;
|
||||||
showDeletePersonModal: () => void;
|
showDeletePersonModal: () => void;
|
||||||
showCreatePersonCharacterModal: () => void;
|
showCreatePersonCharacterModal?: () => void;
|
||||||
}){
|
}){
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-row gap-2"
|
className="flex flex-row gap-2"
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
showCreatePersonCharacterModal &&
|
||||||
<SuccessButton
|
<SuccessButton
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -30,6 +32,7 @@ export default function PersonAdminButtons({
|
|||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>
|
/>
|
||||||
</SuccessButton>
|
</SuccessButton>
|
||||||
|
}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
onClick={showUpdatePersonModal}
|
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 { Person } from "@/interface/Person";
|
||||||
import { RaidGroup } from "@/interface/RaidGroup";
|
import { RaidGroup } from "@/interface/RaidGroup";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router";
|
||||||
import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
|
import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
|
||||||
import DeletePersonModal from "./modals/DeletePersonModal";
|
import DeletePersonModal from "./modals/DeletePersonModal";
|
||||||
import PersonModal from "./modals/PersonModal";
|
import PersonModal from "./modals/PersonModal";
|
||||||
@@ -52,18 +53,22 @@ export default function PersonList({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const bodyElements: React.ReactNode[][] = people.map((person) => [
|
const bodyElements: React.ReactNode[][] = people.map((person) => [
|
||||||
<div>
|
<Link
|
||||||
|
to={`/raidGroup/${raidGroup.raidGroupId}/person/${person.personId}`}
|
||||||
|
>
|
||||||
{person.personName}
|
{person.personName}
|
||||||
</div>,
|
</Link>,
|
||||||
<div>
|
<div>
|
||||||
{person.discordId}
|
{person.discordId}
|
||||||
</div>,
|
</div>,
|
||||||
<div>
|
<Link
|
||||||
|
to={`/raidGroup/${raidGroup.raidGroupId}/person/${person.personId}`}
|
||||||
|
>
|
||||||
<PersonCharacterDisplay
|
<PersonCharacterDisplay
|
||||||
personId={person.personId ?? ""}
|
personId={person.personId ?? ""}
|
||||||
raidGroupId={raidGroup.raidGroupId ?? ""}
|
raidGroupId={raidGroup.raidGroupId ?? ""}
|
||||||
/>
|
/>
|
||||||
</div>,
|
</Link>,
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-center gap-2 pl-16"
|
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 React from "react";
|
||||||
import PersonAdminButtons from "./PersonAdminButtons";
|
import PersonAdminButtons from "./PersonAdminButtons";
|
||||||
|
|
||||||
|
|
||||||
export default function PersonListSkeleton(){
|
export default function PersonListSkeleton(){
|
||||||
const headElements: React.ReactNode[] = [
|
const headElements: React.ReactNode[] = [
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function PersonTab({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-center w-full"
|
className="flex flex-row items-center justify-center w-full mb-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-center w-full"
|
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"
|
className="flex flex-row items-center justify-center w-full"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mb-8 text-nowrap"
|
className="text-nowrap"
|
||||||
onClick={() => setDisplayCreatePersonModal(true)}
|
onClick={() => setDisplayCreatePersonModal(true)}
|
||||||
>
|
>
|
||||||
Create Person
|
Create Person
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function DeletePersonModal({
|
|||||||
addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
|
addErrorMessage(`Error deleting person ${person?.personName}: ${deletePersonMutate.error.message}`);
|
||||||
console.log(deletePersonMutate.error);
|
console.log(deletePersonMutate.error);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-between w-full"
|
className="flex flex-row items-center justify-between w-full mb-8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row items-center justify-start w-full"
|
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"
|
className="flex flex-row items-center justify-center w-full"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mb-8 text-nowrap"
|
className="text-nowrap"
|
||||||
onClick={() => setDisplayCreateRaidLayoutModal(true)}
|
onClick={() => setDisplayCreateRaidLayoutModal(true)}
|
||||||
>
|
>
|
||||||
Create Raid Layout
|
Create Raid Layout
|
||||||
|
|||||||
Reference in New Issue
Block a user