Raid Layout page working

This commit is contained in:
2025-03-09 12:15:29 -04:00
parent c2f13d9900
commit 8b8538a968
15 changed files with 973 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import SecondaryButton from "@/components/button/SecondaryButton";
import DangerMessage from "@/components/message/DangerMessage";
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
import { useGetClassGroups } from "@/hooks/ClassGroupHooks";
import { ClassGroup } from "@/interface/ClassGroup";
import { useEffect, useState } from "react";
export default function SelectClassGroupModal({
display,
close,
selectedClassGroup,
onChange,
raidGroupId
}:{
display: boolean;
close: () => void;
selectedClassGroup?: ClassGroup | null;
onChange: (classGroup?: ClassGroup | null) => void;
raidGroupId: string;
}){
const [ currentClassGroup, setCurrentClassGroup ] = useState(selectedClassGroup);
const selectorId = crypto.randomUUID().replaceAll("-", "");
const classGroupsQuery = useGetClassGroups(raidGroupId, 0, 100);
useEffect(() => {
setCurrentClassGroup(selectedClassGroup);
}, [ selectedClassGroup ]);
if(classGroupsQuery.status === "pending"){
return <div>Loading...</div>
}
else if(classGroupsQuery.status === "error"){
return <DangerMessage>Error: {classGroupsQuery.error.message}</DangerMessage>
}
else{
return (
<RaidBuilderModal
display={display}
close={close}
modalHeader="Select Class Group"
modalBody={
<div
className="grid grid-cols-3 gap-4 w-[36rem]"
style={{flex: "0 0 33.333333333%"}}
>
{
classGroupsQuery.data?.map((classGroup) => (
<div
key={classGroup.classGroupId}
className="flex flex-row items-center justify-center"
>
<input
id={`classGroup${classGroup.classGroupId}${selectorId}`}
className="cursor-pointer"
type="radio"
name="classGroupId"
value={classGroup.classGroupId}
checked={classGroup.classGroupId === currentClassGroup?.classGroupId}
onClick={() => setCurrentClassGroup(currentClassGroup === classGroup ? undefined : classGroup)}
onChange={() => {}}
/>
<label
className="flex flex-row flex-nowrap justify-center items-center text-nowrap cursor-pointer ml-2"
htmlFor={`classGroup${classGroup.classGroupId}${selectorId}`}
>
{classGroup.classGroupName}
</label>
</div>
))
}
</div>
}
modalFooter={
<>
<PrimaryButton
onClick={() => { onChange(currentClassGroup); close(); }}
>
Select
</PrimaryButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}
}

View File

@@ -0,0 +1,37 @@
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 RaidLayoutAdminButtons({
buttonProps,
showRaidLayoutModal,
showDeleteRaidLayoutModal
}:{
buttonProps: ButtonProps;
showRaidLayoutModal: () => void;
showDeleteRaidLayoutModal: () => void;
}){
return (
<div
className="flex flex-row gap-2"
>
<PrimaryButton
{...buttonProps}
onClick={showRaidLayoutModal}
>
<BsPencilFill
size={22}
/>
</PrimaryButton>
<DangerButton
{...buttonProps}
onClick={showDeleteRaidLayoutModal}
>
<BsTrash3
size={22}
/>
</DangerButton>
</div>
);
}

View File

@@ -0,0 +1,109 @@
import { ButtonProps } from "@/components/button/Button";
import ClassGroupsByRaidLayoutDisplay from "@/components/classGroup/ClassGroupsByRaidLayoutDisplay";
import Table from "@/components/table/Table";
import { useGetClassGroupsByRaidLayout } from "@/hooks/ClassGroupHooks";
import { ClassGroup } from "@/interface/ClassGroup";
import { RaidGroup } from "@/interface/RaidGroup";
import { RaidLayout } from "@/interface/RaidLayout";
import { useEffect, useState } from "react";
import DeleteRaidLayoutModal from "./modal/DeleteRaidLayoutModal";
import RaidLayoutModal from "./modal/RaidLayoutModal";
import RaidLayoutAdminButtons from "./RaidLayoutAdminButtons";
export default function RaidLayoutList({
raidLayouts,
raidGroup
}:{
raidLayouts: RaidLayout[];
raidGroup: RaidGroup;
}){
const [ selectedRaidLayout, setSelectedRaidLayout ] = useState<RaidLayout>();
const [ displayEditRaidLayoutModal, showEditRaidLayoutModal ] = useState(false);
const [ displayDeleteRaidLayoutModal, showDeleteRaidLayoutModal ] = useState(false);
const [ selectedLayoutClassGroups, setSelectedLayoutClassGroups ] = useState<(ClassGroup | null)[]>([]);
const classGroupsQuery = useGetClassGroupsByRaidLayout(raidGroup.raidGroupId ?? "", selectedRaidLayout?.raidLayoutId);
useEffect(() => {
setSelectedLayoutClassGroups(classGroupsQuery.data ?? []);
}, [ classGroupsQuery.data ]);
const buttonProps: ButtonProps = {
variant: "ghost",
size: "md",
shape: "square"
};
const headElements: React.ReactNode[] = [
<div
className="text-nowrap"
>
Raid Layout Name
</div>,
<div
className="text-nowrap"
>
Raid Size
</div>,
<div
className="text-nowrap"
>
Class Groups
</div>,
<div
className="pl-16"
>
Actions
</div>
];
const bodyElements: React.ReactNode[][] = raidLayouts.map((raidLayout) => [
<div>
{raidLayout.raidLayoutName}
</div>,
<div>
{raidLayout.raidSize}
</div>,
<div>
<ClassGroupsByRaidLayoutDisplay raidGroupId={raidGroup.raidGroupId ?? ""} raidLayoutId={raidLayout.raidLayoutId ?? ""}/>
</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>
<RaidLayoutAdminButtons
buttonProps={buttonProps}
showRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showEditRaidLayoutModal(true); }}
showDeleteRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showDeleteRaidLayoutModal(true); }}
/>
</div>
]);
return (
<>
<Table
tableHeadElements={headElements}
tableBodyElements={bodyElements}
/>
<RaidLayoutModal
display={displayEditRaidLayoutModal}
close={() => { showEditRaidLayoutModal(false); setSelectedRaidLayout(undefined); }}
raidLayout={selectedRaidLayout}
raidGroup={raidGroup}
selectedClassGroups={selectedLayoutClassGroups}
/>
<DeleteRaidLayoutModal
display={displayDeleteRaidLayoutModal}
close={() => { showDeleteRaidLayoutModal(false); setSelectedRaidLayout(undefined); }}
raidLayout={selectedRaidLayout}
/>
</>
);
}

View File

@@ -0,0 +1,35 @@
import DangerMessage from "@/components/message/DangerMessage";
import { useGetRaidLayoutsByRaidGroup } from "@/hooks/RaidLayoutHooks";
import { RaidGroup } from "@/interface/RaidGroup";
import RaidLayoutList from "./RaidLayoutList";
export default function RaidLayoutLoader({
page,
pageSize,
searchTerm,
raidGroup
}:{
page: number;
pageSize: number;
searchTerm?: string;
raidGroup: RaidGroup;
}){
const raidLayoutsQuery = useGetRaidLayoutsByRaidGroup(raidGroup.raidGroupId ?? "", page - 1, pageSize, searchTerm);
if(raidLayoutsQuery.status === "pending"){
return <div>Loading...</div>
}
else if(raidLayoutsQuery.status === "error"){
return <DangerMessage>Error: {raidLayoutsQuery.error.message}</DangerMessage>
}
else{
return (
<RaidLayoutList
raidLayouts={raidLayoutsQuery.data ?? []}
raidGroup={raidGroup}
/>
);
}
}

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 { useGetRaidLayoutsByRaidGroupCount } from "@/hooks/RaidLayoutHooks";
import { RaidGroup } from "@/interface/RaidGroup";
import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import RaidLayoutModal from "./modal/RaidLayoutModal";
import RaidLayoutLoader from "./RaidLayoutLoader";
export default function RaidLayoutTab({
raidGroup
}:{
raidGroup: RaidGroup;
}){
const [ displayCreateRaidLayoutModal, setDisplayCreateRaidLayoutModal ] = 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 raidLayoutCountQuery = useGetRaidLayoutsByRaidGroupCount(raidGroup.raidGroupId!, sentSearchTerm);
const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => {
setSentSearchTerm(newSearchTerm);
}, 1000);
useEffect(() => {
updateSearchTerm(searchTerm);
}, [ searchTerm, updateSearchTerm ]);
useEffect(() => {
if(raidLayoutCountQuery.status === "success"){
setTotalPages(Math.ceil(raidLayoutCountQuery.data / pageSize));
}
}, [ raidLayoutCountQuery ]);
return (
<>
<div
className="flex flex-row items-center justify-between w-full"
>
<div
className="flex flex-row items-center justify-start w-full"
>
&nbsp;
</div>
{/* Add Raid Layout Button */}
<div
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8 text-nowrap"
onClick={() => setDisplayCreateRaidLayoutModal(true)}
>
Create Raid Layout
</PrimaryButton>
<RaidLayoutModal
display={displayCreateRaidLayoutModal}
close={() => setDisplayCreateRaidLayoutModal(false)}
raidLayout={undefined}
raidGroup={raidGroup}
selectedClassGroups={[]}
/>
</div>
{/* Raid Layout Search Box */}
<div
className="flex flex-row items-center justify-end w-full"
>
<div>
<TextInput
id={`raidLayoutSearchBox${modalId}`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
{/* Raid Layout List */}
<RaidLayoutLoader
page={page}
pageSize={pageSize}
searchTerm={sentSearchTerm}
raidGroup={raidGroup}
/>
{/* Pagination */}
<div
className="my-12"
>
<Pagination
currentPage={page}
totalPages={totalPages}
onChange={setPage}
/>
</div>
</>
);
}

View File

@@ -0,0 +1,64 @@
import DangerButton from "@/components/button/DangerButton";
import SecondaryButton from "@/components/button/SecondaryButton";
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
import { useDeleteRaidLayout } from "@/hooks/RaidLayoutHooks";
import { RaidLayout } from "@/interface/RaidLayout";
import { useTimedModal } from "@/providers/TimedModalProvider";
import { useEffect } from "react";
export default function DeleteRaidLayoutModal({
display,
close,
raidLayout
}:{
display: boolean;
close: () => void;
raidLayout?: RaidLayout;
}){
const deleteRaidLayoutMutate = useDeleteRaidLayout(raidLayout?.raidGroupId ?? "", raidLayout?.raidLayoutId ?? "");
const { addSuccessMessage, addErrorMessage } = useTimedModal();
const deleteRaidLayout = () => {
deleteRaidLayoutMutate.mutate();
}
useEffect(() => {
if(deleteRaidLayoutMutate.status === "success"){
deleteRaidLayoutMutate.reset();
addSuccessMessage("Raid Layout Deleted");
close();
}
else if(deleteRaidLayoutMutate.status === "error"){
deleteRaidLayoutMutate.reset();
addErrorMessage(`Error deleting raid layout ${raidLayout?.raidLayoutName}: ${deleteRaidLayoutMutate.error.message}`);
console.log(deleteRaidLayoutMutate.error);
}
});
return (
<RaidBuilderModal
display={display}
close={close}
modalHeader={"Delete Raid Layout"}
modalBody={`Are you sure you want to delete ${raidLayout?.raidLayoutName}`}
modalFooter={
<>
<DangerButton
onClick={deleteRaidLayout}
>
Delete
</DangerButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}

View File

@@ -0,0 +1,164 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import SecondaryButton from "@/components/button/SecondaryButton";
import ClassGroupSelector from "@/components/classGroup/ClassGroupSelector";
import NumberInput from "@/components/input/NumberInput";
import TextInput from "@/components/input/TextInput";
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
import { useCreateRaidLayout, useUpdateRaidLayout } from "@/hooks/RaidLayoutHooks";
import { ClassGroup } from "@/interface/ClassGroup";
import { RaidGroup } from "@/interface/RaidGroup";
import { RaidLayout, RaidLayoutClassGroupXref } from "@/interface/RaidLayout";
import { useTimedModal } from "@/providers/TimedModalProvider";
import { useEffect, useState } from "react";
export default function RaidLayoutModal({
display,
close,
raidLayout,
raidGroup,
selectedClassGroups
}:{
display: boolean;
close: () => void;
raidLayout?: RaidLayout;
raidGroup: RaidGroup;
selectedClassGroups: (ClassGroup | null)[];
}){
const [ raidLayoutName, setRaidLayoutName ] = useState(raidLayout?.raidLayoutName ?? "");
const [ raidLayoutSize, setRaidLayoutSize ] = useState(raidLayout?.raidSize ?? 0);
const [ classGroups, setClassGroups ] = useState(selectedClassGroups ?? []);
const modalId = crypto.randomUUID().replaceAll("-", "");
useEffect(() => {
setRaidLayoutName(raidLayout?.raidLayoutName ?? "");
setRaidLayoutSize(raidLayout?.raidSize ?? 0);
setClassGroups(selectedClassGroups);
}, [ raidLayout, selectedClassGroups ]);
const updateRaidLayoutSize = (newLayoutSize: number) => {
console.log("newLayoutSize = " + newLayoutSize + ", " + classGroups.length);
setRaidLayoutSize(newLayoutSize);
if(newLayoutSize < classGroups.length){
console.log("Removing");
setClassGroups(classGroups.slice(0, newLayoutSize));
}
else if(newLayoutSize > classGroups.length){
console.log("Adding");
setClassGroups([
...classGroups,
null
]);
}
}
console.log("classGroups");
console.log(classGroups);
const createRaidLayoutMutate = useCreateRaidLayout(raidGroup.raidGroupId ?? "");
const updateRaidLayoutMutate = useUpdateRaidLayout(raidGroup.raidGroupId ?? "");
const { addSuccessMessage, addErrorMessage } = useTimedModal();
useEffect(() => {
if(createRaidLayoutMutate.status === "success"){
createRaidLayoutMutate.reset();
addSuccessMessage("Raid Layout Created");
close();
}
else if(createRaidLayoutMutate.status === "error"){
createRaidLayoutMutate.reset();
addErrorMessage(`Error creating raid layout ${raidLayoutName}: ${createRaidLayoutMutate.error.message}`);
console.log(createRaidLayoutMutate.error);
}
else if(updateRaidLayoutMutate.status === "success"){
updateRaidLayoutMutate.reset();
addSuccessMessage("Raid Layout Updated");
close();
}
else if(updateRaidLayoutMutate.status === "error"){
updateRaidLayoutMutate.reset();
addErrorMessage(`Error updating raid layout ${raidLayoutName}: ${updateRaidLayoutMutate.error.message}`);
console.log(updateRaidLayoutMutate.error);
}
});
const createRaidLayout = () => {
createRaidLayoutMutate.mutate({
raidLayout: {
raidLayoutName,
raidSize: raidLayoutSize,
raidGroupId: raidGroup.raidGroupId ?? ""
},
raidLayoutClassGroupXrefs: classGroups.map((cg, index) => ({
raidLayoutId: raidLayout?.raidLayoutId ?? "",
classGroupId: cg?.classGroupId,
layoutLocation: index
} as RaidLayoutClassGroupXref))
});
}
const updateRaidLayout = () => {
updateRaidLayoutMutate.mutate({
raidLayout: {
raidLayoutId: raidLayout?.raidLayoutId,
raidLayoutName,
raidSize: raidLayoutSize,
raidGroupId: raidGroup.raidGroupId ?? ""
},
raidLayoutClassGroupXrefs: classGroups.map((cg, index) => ({
raidLayoutId: raidLayout?.raidLayoutId ?? "",
classGroupId: cg?.classGroupId,
layoutLocation: index
} as RaidLayoutClassGroupXref))
});
}
return (
<RaidBuilderModal
display={display}
close={close}
modalHeader={raidLayout ? "Update Raid Layout" : "Create Raid Layout"}
modalBody={
<div
className="flex flex-col items-center justify-center gap-4"
>
<TextInput
id={`raidLayoutName${modalId}`}
placeholder="Raid Layout Name"
value={raidLayoutName}
onChange={(e) => setRaidLayoutName(e.target.value)}
/>
<NumberInput
id="raidLayoutSizeInput"
min={1}
max={100}
value={raidLayoutSize}
onChange={updateRaidLayoutSize}
/>
<ClassGroupSelector
raidGroupId={raidGroup.raidGroupId ?? ""}
selectedClassGroups={classGroups}
onChange={setClassGroups}
/>
</div>
}
modalFooter={
<>
<PrimaryButton
onClick={raidLayout ? updateRaidLayout : createRaidLayout}
>
{raidLayout ? "Update" : "Create"}
</PrimaryButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</>
}
/>
);
}