Class Groups tab working
This commit is contained in:
38
src/ui/classGroup/ClassGroupButtons.tsx
Normal file
38
src/ui/classGroup/ClassGroupButtons.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 ClassGroupButtons({
|
||||
buttonProps,
|
||||
showClassGroupModal,
|
||||
showDeleteClassGroupModal
|
||||
}:{
|
||||
buttonProps: ButtonProps;
|
||||
showClassGroupModal: () => void;
|
||||
showDeleteClassGroupModal: () => void;
|
||||
}){
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row gap-2"
|
||||
>
|
||||
<PrimaryButton
|
||||
{...buttonProps}
|
||||
onClick={showClassGroupModal}
|
||||
>
|
||||
<BsPencilFill
|
||||
size={22}
|
||||
/>
|
||||
</PrimaryButton>
|
||||
<DangerButton
|
||||
{...buttonProps}
|
||||
onClick={showDeleteClassGroupModal}
|
||||
>
|
||||
<BsTrash3
|
||||
size={22}
|
||||
/>
|
||||
</DangerButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
102
src/ui/classGroup/ClassGroupList.tsx
Normal file
102
src/ui/classGroup/ClassGroupList.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { ButtonProps } from "@/components/button/Button";
|
||||
import GameClassByClassGroupDisplay from "@/components/gameClass/GameClassByClassGroupDisplay";
|
||||
import Table from "@/components/table/Table";
|
||||
import { useGetGameClassesByClassGroup } from "@/hooks/GameClassHooks";
|
||||
import { ClassGroup } from "@/interface/ClassGroup";
|
||||
import { RaidGroup } from "@/interface/RaidGroup";
|
||||
import { useState } from "react";
|
||||
import ClassGroupButtons from "./ClassGroupButtons";
|
||||
import ClassGroupModal from "./modal/ClassGroupModal";
|
||||
import DeleteClassGroupModal from "./modal/DeleteClassGroupModal";
|
||||
|
||||
|
||||
export default function ClassGroupList({
|
||||
classGroups,
|
||||
raidGroup
|
||||
}:{
|
||||
classGroups: ClassGroup[];
|
||||
raidGroup: RaidGroup;
|
||||
}){
|
||||
const [ selectedClassGroup, setSelectedClassGroup ] = useState<ClassGroup>();
|
||||
const [ displayClassGroupModal, setDisplayClassGroupModal ] = useState(false);
|
||||
const [ displayDeleteClassGroupModal, setDisplayDeleteClassGroupModal ] = useState(false);
|
||||
const gameClassesQuery = useGetGameClassesByClassGroup(selectedClassGroup?.classGroupId ?? "");
|
||||
|
||||
|
||||
const buttonProps: ButtonProps = {
|
||||
variant: "ghost",
|
||||
size: "md",
|
||||
shape: "square"
|
||||
};
|
||||
|
||||
|
||||
const headElements: React.ReactNode[] = [
|
||||
<div>
|
||||
Name
|
||||
</div>,
|
||||
<div>
|
||||
Classes
|
||||
</div>,
|
||||
<div
|
||||
className="pl-16"
|
||||
>
|
||||
Actions
|
||||
</div>
|
||||
];
|
||||
|
||||
const bodyElements: React.ReactNode[][] = classGroups.map((classGroup) => [
|
||||
<div
|
||||
className="text-nowrap"
|
||||
>
|
||||
{classGroup.classGroupName}
|
||||
</div>,
|
||||
<div>
|
||||
<GameClassByClassGroupDisplay
|
||||
classGroupId={classGroup.classGroupId ?? ""}
|
||||
/>
|
||||
</div>,
|
||||
<div
|
||||
className="flex flex-row items-center justify-center gap-2 pl-16"
|
||||
>
|
||||
<div
|
||||
className="py-4 border-l border-neutral-500"
|
||||
>
|
||||
|
||||
</div>
|
||||
<ClassGroupButtons
|
||||
buttonProps={buttonProps}
|
||||
showClassGroupModal={() => {
|
||||
setSelectedClassGroup(classGroup);
|
||||
setDisplayClassGroupModal(true);
|
||||
}}
|
||||
showDeleteClassGroupModal={() => {
|
||||
setSelectedClassGroup(classGroup);
|
||||
setDisplayDeleteClassGroupModal(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
tableHeadElements={headElements}
|
||||
tableBodyElements={bodyElements}
|
||||
/>
|
||||
<ClassGroupModal
|
||||
display={displayClassGroupModal}
|
||||
close={() => { setDisplayClassGroupModal(false); setSelectedClassGroup(undefined); }}
|
||||
classGroup={selectedClassGroup}
|
||||
raidGroup={raidGroup}
|
||||
selectedGameClasses={gameClassesQuery.data ?? []}
|
||||
/>
|
||||
<DeleteClassGroupModal
|
||||
display={displayDeleteClassGroupModal}
|
||||
close={() => { setDisplayDeleteClassGroupModal(false); setSelectedClassGroup(undefined); }}
|
||||
raidGroupId={selectedClassGroup?.raidGroupId ?? ""}
|
||||
classGroup={selectedClassGroup}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
72
src/ui/classGroup/ClassGroupListSkeleton.tsx
Normal file
72
src/ui/classGroup/ClassGroupListSkeleton.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button";
|
||||
import Table from "@/components/table/Table";
|
||||
import { elementBg } from "@/util/SkeletonUtil";
|
||||
import ClassGroupButtons from "./ClassGroupButtons";
|
||||
|
||||
export default function ClassGroupListSkeleton(){
|
||||
const headerElements: React.ReactElement[] = [
|
||||
<div>
|
||||
Name
|
||||
</div>,
|
||||
<div>
|
||||
Classes
|
||||
</div>,
|
||||
<div
|
||||
className="pl-16"
|
||||
>
|
||||
Actions
|
||||
</div>
|
||||
];
|
||||
|
||||
const bodyElements: React.ReactNode[][] = [
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton(),
|
||||
ClassGroupSkeleton()
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<Table
|
||||
tableHeadElements={headerElements}
|
||||
tableBodyElements={bodyElements}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function ClassGroupSkeleton(): React.ReactNode[]{
|
||||
const buttonsProps = {
|
||||
buttonProps: {
|
||||
variant: "ghost" as ButtonVariant,
|
||||
size: "md" as ButtonSizeType,
|
||||
shape: "square" as ButtonShape,
|
||||
disabled: true
|
||||
},
|
||||
showClassGroupModal: () => {},
|
||||
showDeleteClassGroupModal: () => {}
|
||||
}
|
||||
|
||||
const elements: React.ReactNode[] = [
|
||||
<div
|
||||
className={`h-6 w-72 mr-2 ${elementBg}`}
|
||||
/>,
|
||||
<div
|
||||
className={`h-6 w-[64rem] ${elementBg}`}
|
||||
/>,
|
||||
<div
|
||||
className={`flex flex-row items-center justify-center gap-2 pl-16`}
|
||||
>
|
||||
<div className="py-4 border-l border-neutral-500"> </div>
|
||||
<ClassGroupButtons {...buttonsProps}/>
|
||||
</div>
|
||||
];
|
||||
|
||||
return elements;
|
||||
}
|
||||
42
src/ui/classGroup/ClassGroupsLoader.tsx
Normal file
42
src/ui/classGroup/ClassGroupsLoader.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import DangerMessage from "@/components/message/DangerMessage";
|
||||
import { useGetClassGroups } from "@/hooks/ClassGroupHooks";
|
||||
import { useGetGameClasses } from "@/hooks/GameClassHooks";
|
||||
import { RaidGroup } from "@/interface/RaidGroup";
|
||||
import ClassGroupList from "./ClassGroupList";
|
||||
|
||||
|
||||
export default function ClassGroupsLoader({
|
||||
page,
|
||||
pageSize,
|
||||
searchTerm,
|
||||
raidGroup,
|
||||
gameId
|
||||
}:{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
searchTerm?: string;
|
||||
raidGroup: RaidGroup;
|
||||
gameId: string;
|
||||
}){
|
||||
const classGroupsQuery = useGetClassGroups(raidGroup?.raidGroupId ?? "", page - 1, pageSize, searchTerm);
|
||||
const gameClassesQuery = useGetGameClasses(gameId, 0, 100);
|
||||
|
||||
|
||||
if((classGroupsQuery.status === "pending") || (gameClassesQuery.status === "pending")){
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
else if(classGroupsQuery.status === "error"){
|
||||
return <DangerMessage>Error: {classGroupsQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else if(gameClassesQuery.status === "error"){
|
||||
return <DangerMessage>Error: {gameClassesQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<ClassGroupList
|
||||
classGroups={classGroupsQuery.data ?? []}
|
||||
raidGroup={raidGroup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
108
src/ui/classGroup/ClassGroupsTab.tsx
Normal file
108
src/ui/classGroup/ClassGroupsTab.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import TextInput from "@/components/input/TextInput";
|
||||
import Pagination from "@/components/pagination/Pagination";
|
||||
import { useGetClassGroupsCount } from "@/hooks/ClassGroupHooks";
|
||||
import { RaidGroup } from "@/interface/RaidGroup";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import ClassGroupsLoader from "./ClassGroupsLoader";
|
||||
import ClassGroupModal from "./modal/ClassGroupModal";
|
||||
|
||||
|
||||
export default function ClassGroupsTab({
|
||||
raidGroup
|
||||
}:{
|
||||
raidGroup: RaidGroup;
|
||||
}){
|
||||
const [ displayCreateClassGroupModal, setDisplayCreateClassGroupModal ] = useState(false);
|
||||
const [ page, setPage ] = useState(1);
|
||||
const [ totalPages, setTotalPages ] = useState(1);
|
||||
const [ searchTerm, setSearchTerm ] = useState("");
|
||||
const [ sentSearchTerm, setSentSearchTerm ] = useState("");
|
||||
const pageSize = 10;
|
||||
const modalId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
const classGroupsCountQuery = useGetClassGroupsCount(raidGroup.raidGroupId!, sentSearchTerm);
|
||||
|
||||
|
||||
const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => {
|
||||
setSentSearchTerm(newSearchTerm);
|
||||
}, 1000);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
updateSearchTerm(searchTerm ?? "");
|
||||
}, [ searchTerm, updateSearchTerm ]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(classGroupsCountQuery.status === "success"){
|
||||
setTotalPages(Math.ceil(classGroupsCountQuery.data / pageSize));
|
||||
}
|
||||
}, [ classGroupsCountQuery ]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex flex-row justify-between items-center w-full"
|
||||
>
|
||||
<div
|
||||
className="flex flex-row items-center justify-start w-full"
|
||||
>
|
||||
|
||||
</div>
|
||||
{/* Add Class Group Button */}
|
||||
<div
|
||||
className="flex flex-row items-center justify-center w-full"
|
||||
>
|
||||
<PrimaryButton
|
||||
className="mb-8 text-nowrap"
|
||||
onClick={() => setDisplayCreateClassGroupModal(true)}
|
||||
>
|
||||
Create Class Group
|
||||
</PrimaryButton>
|
||||
<ClassGroupModal
|
||||
display={displayCreateClassGroupModal}
|
||||
close={() => setDisplayCreateClassGroupModal(false)}
|
||||
classGroup={undefined}
|
||||
selectedGameClasses={[]}
|
||||
raidGroup={raidGroup}
|
||||
/>
|
||||
</div>
|
||||
{/* Class Group Search Box */}
|
||||
<div
|
||||
className="flex flex-row items-center justify-end w-full"
|
||||
>
|
||||
<div>
|
||||
<TextInput
|
||||
id={`classGroupSearchBox${modalId}`}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Class Group List */}
|
||||
<ClassGroupsLoader
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
searchTerm={sentSearchTerm}
|
||||
raidGroup={raidGroup}
|
||||
gameId={raidGroup.gameId}
|
||||
/>
|
||||
{/* Pagination */}
|
||||
<div
|
||||
className="my-12"
|
||||
>
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={totalPages}
|
||||
onChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
121
src/ui/classGroup/modal/ClassGroupModal.tsx
Normal file
121
src/ui/classGroup/modal/ClassGroupModal.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import { GameClassesSelector } from "@/components/gameClass/GameClassesSelector";
|
||||
import TextInput from "@/components/input/TextInput";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useCreateClassGroup, useUpdateClassGroup } from "@/hooks/ClassGroupHooks";
|
||||
import { ClassGroup } from "@/interface/ClassGroup";
|
||||
import { GameClass } from "@/interface/GameClass";
|
||||
import { RaidGroup } from "@/interface/RaidGroup";
|
||||
import { useTimedModal } from "@/providers/TimedModalProvider";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export default function ClassGroupModal({
|
||||
display,
|
||||
close,
|
||||
classGroup,
|
||||
raidGroup,
|
||||
selectedGameClasses
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
classGroup: ClassGroup | undefined;
|
||||
raidGroup: RaidGroup;
|
||||
selectedGameClasses: GameClass[];
|
||||
}){
|
||||
const [ classGroupName, setClassGroupName ] = useState(classGroup?.classGroupName ?? "");
|
||||
const [ selectedGameClassIds, setSelectedGameClassIds ] = useState<string[]>(selectedGameClasses.map(gc => gc.gameClassId ?? ""));
|
||||
const modalId = crypto.randomUUID().replace("-", "");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setClassGroupName(classGroup?.classGroupName ?? "");
|
||||
setSelectedGameClassIds(selectedGameClasses.map(gc => gc.gameClassId ?? ""));
|
||||
}, [classGroup, selectedGameClasses]);
|
||||
|
||||
|
||||
const createClassGroupMutate = useCreateClassGroup(raidGroup.raidGroupId ?? "");
|
||||
const updateClassGroupMutate = useUpdateClassGroup(raidGroup.raidGroupId ?? "");
|
||||
const { addSuccessMessage, addErrorMessage } = useTimedModal();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(createClassGroupMutate.status === "success"){
|
||||
createClassGroupMutate.reset();
|
||||
addSuccessMessage("Class Group Created");
|
||||
close();
|
||||
}
|
||||
else if(createClassGroupMutate.status === "error"){
|
||||
createClassGroupMutate.reset();
|
||||
addErrorMessage(`Error creating class group ${classGroupName}: ${createClassGroupMutate.error.message}`);
|
||||
console.log(createClassGroupMutate.error);
|
||||
}
|
||||
else if(updateClassGroupMutate.status === "success"){
|
||||
updateClassGroupMutate.reset();
|
||||
addSuccessMessage("Class Group Updated");
|
||||
close();
|
||||
}
|
||||
else if(updateClassGroupMutate.status === "error"){
|
||||
updateClassGroupMutate.reset();
|
||||
addErrorMessage(`Error updating class group ${classGroupName}: ${updateClassGroupMutate.error.message}`);
|
||||
console.log(updateClassGroupMutate.error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const createClassGroup = () => {
|
||||
createClassGroupMutate.mutate({ classGroupName, gameClassIds: selectedGameClassIds });
|
||||
}
|
||||
|
||||
const updateClassGroup = () => {
|
||||
updateClassGroupMutate.mutate({
|
||||
classGroup: {
|
||||
classGroupId: classGroup?.classGroupId,
|
||||
raidGroupId: raidGroup.raidGroupId ?? "",
|
||||
classGroupName
|
||||
},
|
||||
gameClassIds: selectedGameClassIds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={classGroup ? "Update Class Group" : "Create Class Group"}
|
||||
modalBody={
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-4"
|
||||
>
|
||||
<TextInput
|
||||
id={`classGroupName${modalId}`}
|
||||
placeholder="Class Group Name"
|
||||
value={classGroupName}
|
||||
onChange={(e) => setClassGroupName(e.target.value)}
|
||||
/>
|
||||
<GameClassesSelector
|
||||
gameId={raidGroup.gameId}
|
||||
gameClassIds={selectedGameClasses.map(gc => gc.gameClassId ?? "")}
|
||||
onChange={(gameClassIds) => setSelectedGameClassIds(gameClassIds)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
modalFooter={
|
||||
<>
|
||||
<PrimaryButton
|
||||
onClick={classGroup ? updateClassGroup : createClassGroup}
|
||||
>
|
||||
{classGroup ? "Update" : "Create"}
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
66
src/ui/classGroup/modal/DeleteClassGroupModal.tsx
Normal file
66
src/ui/classGroup/modal/DeleteClassGroupModal.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import DangerButton from "@/components/button/DangerButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useDeleteClassGroup } from "@/hooks/ClassGroupHooks";
|
||||
import { ClassGroup } from "@/interface/ClassGroup";
|
||||
import { useTimedModal } from "@/providers/TimedModalProvider";
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
||||
export default function DeleteClassGroupModal({
|
||||
display,
|
||||
close,
|
||||
classGroup,
|
||||
raidGroupId
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
classGroup?: ClassGroup;
|
||||
raidGroupId: string;
|
||||
}){
|
||||
const deleteClassGroupMutate = useDeleteClassGroup(raidGroupId, classGroup?.classGroupId ?? "");
|
||||
const { addSuccessMessage, addErrorMessage } = useTimedModal();
|
||||
|
||||
|
||||
const deleteAccount = () => {
|
||||
deleteClassGroupMutate.mutate();
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(deleteClassGroupMutate.status === "success"){
|
||||
deleteClassGroupMutate.reset();
|
||||
addSuccessMessage("Class Group Deleted");
|
||||
close();
|
||||
}
|
||||
else if(deleteClassGroupMutate.status === "error"){
|
||||
deleteClassGroupMutate.reset();
|
||||
addErrorMessage(`Error deleting class group: ${deleteClassGroupMutate.error.message}`);
|
||||
console.log(deleteClassGroupMutate.error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader="Delete Class Group"
|
||||
modalBody={`Are you sure you want to delete ${classGroup?.classGroupName}`}
|
||||
modalFooter={
|
||||
<>
|
||||
<DangerButton
|
||||
onClick={deleteAccount}
|
||||
>
|
||||
Delete
|
||||
</DangerButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user