diff --git a/src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx b/src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx index 59fc941..6324349 100644 --- a/src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx +++ b/src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx @@ -20,7 +20,7 @@ export default function ClassGroupsByRaidLayoutDisplay({ else{ return (
{ classGroupsQuery.data.map((classGroup, index) => ( diff --git a/src/hooks/RaidInstanceHooks.ts b/src/hooks/RaidInstanceHooks.ts new file mode 100644 index 0000000..fd3d1fc --- /dev/null +++ b/src/hooks/RaidInstanceHooks.ts @@ -0,0 +1,140 @@ +import { RaidInstance } from "@/interface/RaidInstance"; +import { api } from "@/util/AxiosUtil"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + + +export function useGetRaidInstance(raidInstanceId: string, raidGroupId: string){ + return useQuery({ + queryKey: ["raidInstances", raidInstanceId, raidGroupId], + queryFn: async () => { + const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}`); + + if(response.status !== 200){ + throw new Error("Failed to get raid instance"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data as RaidInstance; + } + }); +} + +export function useGetRaidInstancesByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){ + return useQuery({ + queryKey: ["raidInstances", raidGroupId, {page, pageSize, searchTerm}], + queryFn: async () => { + const params = new URLSearchParams(); + params.append("page", page.toString()); + params.append("pageSize", pageSize.toString()); + if(searchTerm){ + params.append("searchTerm", searchTerm); + } + + const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance?${params}`); + + if(response.status !== 200){ + throw new Error("Failed to get accounts"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data as RaidInstance[]; + } + }); +} + + +export function useGetRaidInstancesByRaidGroupCount(raidGroupId: string, searchTerm?: string){ + return useQuery({ + queryKey: ["raidInstances", raidGroupId, "count", {searchTerm}], + queryFn: async () => { + const params = new URLSearchParams(); + if(searchTerm){ + params.append("searchTerm", searchTerm); + } + + const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance/count?${params}`); + + if(response.status !== 200){ + throw new Error("Failed to get accounts"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data.count as number; + } + }); +} + + +export function useCreateRaidInstance(raidGroupId: string){ + const queryClient = useQueryClient(); + + + return useMutation({ + mutationKey: ["createRaidInstance", raidGroupId], + mutationFn: async (raidInstance: RaidInstance) => { + const response = await api.post(`/raidGroup/${raidGroupId}/raidInstance`, raidInstance); + + if(response.status !== 200){ + throw new Error("Failed to create raid instance"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidInstances"] }); + } + }); +} + +export function useUpdateRaidInstance(raidGroupId: string){ + const queryClient = useQueryClient(); + + + return useMutation({ + mutationKey: ["updateRaidInstance", raidGroupId], + mutationFn: async (raidInstance: RaidInstance) => { + console.log("raidInstance"); + console.log(raidInstance); + const response = await api.put(`/raidGroup/${raidGroupId}/raidInstance/${raidInstance.raidInstanceId}`, raidInstance); + + if(response.status !== 200){ + throw new Error("Failed to update raid instance"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidInstances"] }); + } + }); +} + +export function useDeleteRaidInstance(raidGroupId: string, raidInstanceId: string){ + const queryClient = useQueryClient(); + + + return useMutation({ + mutationKey: ["deleteRaidInstance", raidGroupId, raidInstanceId], + mutationFn: async () => { + const response = await api.delete(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}`); + + if(response.status !== 200){ + throw new Error("Failed to delete raid instance"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidInstances"] }); + } + }); +} diff --git a/src/interface/RaidInstance.ts b/src/interface/RaidInstance.ts new file mode 100644 index 0000000..bce396e --- /dev/null +++ b/src/interface/RaidInstance.ts @@ -0,0 +1,10 @@ +export interface RaidInstance { + raidInstanceId?: string; + raidGroupId: string; + raidLayoutId?: string; + raidInstanceName?: string; + raidSize?: number; + numberRuns: number; + raidStartDate: Date; + raidEndDate: Date; +} diff --git a/src/pages/protected/RaidGroupPage.tsx b/src/pages/protected/RaidGroupPage.tsx index da9e5f3..8631f68 100644 --- a/src/pages/protected/RaidGroupPage.tsx +++ b/src/pages/protected/RaidGroupPage.tsx @@ -7,7 +7,7 @@ import RaidGroupHeader from "@/ui/calendar/RaidGroupHeader"; import ClassGroupsTab from "@/ui/classGroup/ClassGroupsTab"; import PersonTab from "@/ui/person/PersonTab"; import RaidGroupRequestTab from "@/ui/raidGroupRequest/RaidGroupRequestTab"; -import RaidInstancesTab from "@/ui/raidInstances/RaidInstancesTab"; +import RaidInstanceTab from "@/ui/raidInstances/RaidInstanceTab"; import RaidLayoutTab from "@/ui/raidLayout/RaidLayoutTab"; import { useEffect, useState } from "react"; import { Navigate, useParams } from "react-router"; @@ -61,7 +61,7 @@ export default function RaidGroupPage(){ }, { tabHeader: "Raid Instances", - tabContent: + tabContent: }, { tabHeader: "Users", diff --git a/src/ui/game/GamesListSkeleton.tsx b/src/ui/game/GamesListSkeleton.tsx index e754353..0fbca22 100644 --- a/src/ui/game/GamesListSkeleton.tsx +++ b/src/ui/game/GamesListSkeleton.tsx @@ -4,6 +4,7 @@ import { elementBg } from "@/util/SkeletonUtil"; import React from "react"; import GameAdminButtons from "./GameAdminButtons"; + export default function GamesListSkeleton(){ const headElements: React.ReactNode[] = [
diff --git a/src/ui/raidInstances/RaidInstanceAdminButtons.tsx b/src/ui/raidInstances/RaidInstanceAdminButtons.tsx new file mode 100644 index 0000000..c082203 --- /dev/null +++ b/src/ui/raidInstances/RaidInstanceAdminButtons.tsx @@ -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 RaidInstanceAdminButtons({ + buttonProps, + showRaidInstanceModal, + showDeleteRaidInstanceModal +}:{ + buttonProps: ButtonProps; + showRaidInstanceModal: () => void; + showDeleteRaidInstanceModal: () => void; +}){ + return ( +
+ + + + + + +
+ ); +} diff --git a/src/ui/raidInstances/RaidInstanceList.tsx b/src/ui/raidInstances/RaidInstanceList.tsx new file mode 100644 index 0000000..30faaca --- /dev/null +++ b/src/ui/raidInstances/RaidInstanceList.tsx @@ -0,0 +1,124 @@ +import { ButtonProps } from "@/components/button/Button"; +import Table from "@/components/table/Table"; +import { RaidGroup } from "@/interface/RaidGroup"; +import { RaidInstance } from "@/interface/RaidInstance"; +import moment from "moment"; +import { useState } from "react"; +import { Link } from "react-router"; +import DeleteRaidInstanceModal from "./modals/DeleteRaidInstanceModal"; +import RaidInstanceModal from "./modals/RaidInstanceModal"; +import RaidInstanceAdminButtons from "./RaidInstanceAdminButtons"; + + +export default function RaidInstanceList({ + raidInstances, + raidGroup +}:{ + raidInstances: RaidInstance[]; + raidGroup: RaidGroup; +}){ + const [ selectedRaidInstance, setSelectedRaidInstance ] = useState(); + const [ displayEditRaidInstanceModal, setDisplayEditRaidInstanceModal ] = useState(false); + const [ displayDeleteRaidInstanceModal, setDisplayDeleteRaidInstanceModal ] = useState(false); + + + const buttonProps: ButtonProps = { + variant: "ghost", + size: "md", + shape: "square" + }; + + + const headElements: React.ReactNode[] = [ +
+ Raid Instance +
, +
+ Start Date +
, +
+ End Date +
, +
+ Size +
, +
+ Runs +
, +
+ Actions +
+ ]; + + const bodyElements: React.ReactNode[][] = raidInstances.map((raidInstance) => [ + + {raidInstance.raidInstanceName} + , +
+ {moment(raidInstance.raidStartDate).format("MM-DD-YYYY HH:mm")} +
, +
+ {moment(raidInstance.raidEndDate).format("MM-DD-YYYY HH:mm")} +
, +
+ {raidInstance.raidSize} +
, +
+ {raidInstance.numberRuns} +
, +
+
+   +
+ { setSelectedRaidInstance(raidInstance); setDisplayEditRaidInstanceModal(true); }} + showDeleteRaidInstanceModal={() => { setSelectedRaidInstance(raidInstance); setDisplayDeleteRaidInstanceModal(true); }} + /> +
+ ]); + + + return ( + <> + + { setDisplayEditRaidInstanceModal(false); setSelectedRaidInstance(undefined); }} + raidInstance={selectedRaidInstance} + raidGroup={raidGroup} + /> + { setDisplayDeleteRaidInstanceModal(false); setSelectedRaidInstance(undefined); }} + raidInstance={selectedRaidInstance} + /> + + ); +} diff --git a/src/ui/raidInstances/RaidInstanceListSkeleton.tsx b/src/ui/raidInstances/RaidInstanceListSkeleton.tsx new file mode 100644 index 0000000..5a842a7 --- /dev/null +++ b/src/ui/raidInstances/RaidInstanceListSkeleton.tsx @@ -0,0 +1,95 @@ +import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; +import Table from "@/components/table/Table"; +import { elementBg } from "@/util/SkeletonUtil"; +import RaidInstanceAdminButtons from "./RaidInstanceAdminButtons"; + + +export default function RaidInstanceListSkeleton(){ + const headElements: React.ReactNode[] = [ +
+ Raid Instance +
, +
+ Start Date +
, +
+ End Date +
, +
+ Size +
, +
+ Runs +
, +
+ Actions +
+ ]; + + const bodyElements: React.ReactNode[][] = [ + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton(), + RaidInstanceSkeleton() + ]; + + + return ( +
+ ); +} + + +function RaidInstanceSkeleton(){ + const buttonsProps = { + buttonProps: { + variant: "ghost" as ButtonVariant, + size: "md" as ButtonSizeType, + shape: "square" as ButtonShape, + disabled: true + }, + showRaidInstanceModal: () => {}, + showDeleteRaidInstanceModal: () => {} + }; + + const elements: React.ReactNode[] = [ +
, +
, +
, +
, +
, +
+
 
+ +
+ ]; + + return elements; +} diff --git a/src/ui/raidInstances/RaidInstanceLoader.tsx b/src/ui/raidInstances/RaidInstanceLoader.tsx new file mode 100644 index 0000000..d528b0b --- /dev/null +++ b/src/ui/raidInstances/RaidInstanceLoader.tsx @@ -0,0 +1,36 @@ +import DangerMessage from "@/components/message/DangerMessage"; +import { useGetRaidInstancesByRaidGroup } from "@/hooks/RaidInstanceHooks"; +import { RaidGroup } from "@/interface/RaidGroup"; +import RaidInstanceList from "./RaidInstanceList"; +import RaidInstanceListSkeleton from "./RaidInstanceListSkeleton"; + + +export default function RaidInstanceLoader({ + raidGroup, + page, + pageSize, + searchTerm +}:{ + raidGroup: RaidGroup; + page: number; + pageSize: number; + searchTerm?: string; +}){ + const raidInstancesQuery = useGetRaidInstancesByRaidGroup(raidGroup.raidGroupId ?? "", page - 1, pageSize, searchTerm); + + + if(raidInstancesQuery.status === "pending"){ + return + } + else if(raidInstancesQuery.status === "error"){ + return Error: {raidInstancesQuery.error.message} + } + else{ + return ( + + ); + } +} diff --git a/src/ui/raidInstances/RaidInstanceTab.tsx b/src/ui/raidInstances/RaidInstanceTab.tsx new file mode 100644 index 0000000..672f4a5 --- /dev/null +++ b/src/ui/raidInstances/RaidInstanceTab.tsx @@ -0,0 +1,105 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import TextInput from "@/components/input/TextInput"; +import Pagination from "@/components/pagination/Pagination"; +import { useGetRaidInstancesByRaidGroupCount } from "@/hooks/RaidInstanceHooks"; +import { RaidGroup } from "@/interface/RaidGroup"; +import { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import RaidInstanceLoader from "./RaidInstanceLoader"; +import RaidInstanceModal from "./modals/RaidInstanceModal"; + + +export default function RaidInstanceTab({ + raidGroup +}:{ + raidGroup: RaidGroup; +}){ + const [ displayCreateRaidInstanceModal, setDisplayCreateRaidInstanceModal ] = 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 updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => { + setSentSearchTerm(newSearchTerm); + }, 1000); + + + useEffect(() => { + updateSearchTerm(searchTerm); + }, [ searchTerm, updateSearchTerm ]); + + + const raidInstanceCountQuery = useGetRaidInstancesByRaidGroupCount(raidGroup.raidGroupId!, sentSearchTerm); + + useEffect(() => { + if(raidInstanceCountQuery.status === "success"){ + setTotalPages(Math.ceil(raidInstanceCountQuery.data / pageSize)); + } + }, [ raidInstanceCountQuery ]); + + + return ( + <> +
+
+   +
+ {/* Add Raid Instance Button */} +
+ setDisplayCreateRaidInstanceModal(true)} + > + Create Raid Instance + + setDisplayCreateRaidInstanceModal(false)} + raidInstance={undefined} + raidGroup={raidGroup} + /> +
+ {/* Raid Instance Search Box */} +
+
+ setSearchTerm(e.target.value)} + placeholder="Search" + /> +
+
+
+ {/* Raid Layout List */} + + {/* Pagination */} +
+ +
+ + ); +} diff --git a/src/ui/raidInstances/RaidInstancesTab.tsx b/src/ui/raidInstances/RaidInstancesTab.tsx deleted file mode 100644 index 4960708..0000000 --- a/src/ui/raidInstances/RaidInstancesTab.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function RaidInstancesTab(){ - return ( -
- Raid Instances tab -
- ); -} diff --git a/src/ui/raidInstances/modals/DeleteRaidInstanceModal.tsx b/src/ui/raidInstances/modals/DeleteRaidInstanceModal.tsx new file mode 100644 index 0000000..412cf24 --- /dev/null +++ b/src/ui/raidInstances/modals/DeleteRaidInstanceModal.tsx @@ -0,0 +1,64 @@ +import DangerButton from "@/components/button/DangerButton"; +import SecondaryButton from "@/components/button/SecondaryButton"; +import RaidBuilderModal from "@/components/modal/RaidBuilderModal"; +import { useDeleteRaidInstance } from "@/hooks/RaidInstanceHooks"; +import { RaidInstance } from "@/interface/RaidInstance"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect } from "react"; + + +export default function DeleteRaidInstanceModal({ + display, + close, + raidInstance +}:{ + display: boolean; + close: () => void; + raidInstance?: RaidInstance; +}){ + const deleteRaidInstanceMutate = useDeleteRaidInstance(raidInstance?.raidGroupId ?? "", raidInstance?.raidInstanceId ?? ""); + const { addSuccessMessage, addErrorMessage } = useTimedModal(); + + + const deleteRaidInstance = () => { + deleteRaidInstanceMutate.mutate(); + } + + + useEffect(() => { + if(deleteRaidInstanceMutate.status === "success"){ + deleteRaidInstanceMutate.reset(); + addSuccessMessage("Raid Instance Deleted"); + close(); + } + else if(deleteRaidInstanceMutate.status === "error"){ + deleteRaidInstanceMutate.reset(); + addErrorMessage(`Error deleting raid instance ${raidInstance?.raidInstanceName}: ${deleteRaidInstanceMutate.error.message}`); + console.log(deleteRaidInstanceMutate.error); + } + }); + + + return ( + + + Delete + + + Cancel + + + } + /> + ); +} diff --git a/src/ui/raidInstances/modals/RaidInstanceModal.tsx b/src/ui/raidInstances/modals/RaidInstanceModal.tsx new file mode 100644 index 0000000..0d660f6 --- /dev/null +++ b/src/ui/raidInstances/modals/RaidInstanceModal.tsx @@ -0,0 +1,156 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import SecondaryButton from "@/components/button/SecondaryButton"; +import DateInput from "@/components/input/DateInput"; +import TextInput from "@/components/input/TextInput"; +import RaidBuilderModal from "@/components/modal/RaidBuilderModal"; +import { useCreateRaidInstance, useUpdateRaidInstance } from "@/hooks/RaidInstanceHooks"; +import { RaidGroup } from "@/interface/RaidGroup"; +import { RaidInstance } from "@/interface/RaidInstance"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import moment from "moment"; +import { useEffect, useState } from "react"; + + +export default function RaidInstanceModal({ + display, + close, + raidInstance, + raidGroup +}:{ + display: boolean; + close: () => void; + raidInstance?: RaidInstance; + raidGroup: RaidGroup; +}){ + const [raidInstanceName, setRaidInstanceName] = useState(""); + const [raidStartDate, setRaidStartDate] = useState(new Date()); + const [raidEndDate, setRaidEndDate] = useState(new Date()); + const [raidSize, setRaidSize] = useState(0); + const [numberRuns, setNumberRuns] = useState(0); + const modalId = crypto.randomUUID().replaceAll("-", ""); + + const createRaidInstanceMutate = useCreateRaidInstance(raidGroup.raidGroupId ?? ""); + const updateRaidInstanceMutate = useUpdateRaidInstance(raidGroup.raidGroupId ?? ""); + const { addSuccessMessage, addErrorMessage } = useTimedModal(); + + useEffect(() => { + if(raidInstance){ + setRaidInstanceName(raidInstance.raidInstanceName ?? ""); + setRaidStartDate(raidInstance.raidStartDate); + setRaidEndDate(raidInstance.raidEndDate); + setRaidSize(raidInstance.raidSize ?? 0); + setNumberRuns(raidInstance.numberRuns); + } + else{ + const currentDate = new Date(); + const futureDate = new Date(); + futureDate.setHours(futureDate.getHours() + 1); + setRaidInstanceName(""); + setRaidStartDate(currentDate); + setRaidEndDate(futureDate); + setRaidSize(0); + setNumberRuns(0); + } + }, [ raidInstance ]); + + useEffect(() => { + if(createRaidInstanceMutate.status === "success"){ + createRaidInstanceMutate.reset(); + addSuccessMessage("Raid Instance Created"); + close(); + } + else if(createRaidInstanceMutate.status === "error"){ + createRaidInstanceMutate.reset(); + addErrorMessage(`Error creating raid instance ${raidInstanceName}: ${createRaidInstanceMutate.error.message}`); + console.log(createRaidInstanceMutate.error); + } + else if(updateRaidInstanceMutate.status === "success"){ + updateRaidInstanceMutate.reset(); + addSuccessMessage("Raid Instance Updated"); + close(); + } + else if(updateRaidInstanceMutate.status === "error"){ + updateRaidInstanceMutate.reset(); + addErrorMessage(`Error updating raid instance ${raidInstanceName}: ${updateRaidInstanceMutate.error.message}`); + console.log(updateRaidInstanceMutate.error); + } + }); + + const createRaidInstance = () => { + createRaidInstanceMutate.mutate({ + raidInstanceName, + raidStartDate, + raidEndDate, + raidSize, + numberRuns, + raidGroupId: raidGroup.raidGroupId ?? "" + }); + } + + const updateRaidInstance = () => { + updateRaidInstanceMutate.mutate({ + raidInstanceId: raidInstance?.raidInstanceId, + raidInstanceName, + raidStartDate, + raidEndDate, + raidSize, + numberRuns, + raidGroupId: raidGroup.raidGroupId ?? "" + }); + } + + + return ( + + setRaidInstanceName(e.target.value)} + /> +
+ {/* + + */} + setRaidStartDate(moment(e.target.value).toDate())} + /> + setRaidEndDate(moment(e.target.value).toDate())} + /> +
+ } + modalFooter={ + <> + + {raidInstance ? "Update" : "Create"} + + + Cancel + + + } + /> + ); +} diff --git a/src/ui/raidLayout/RaidLayoutListSkeleton.tsx b/src/ui/raidLayout/RaidLayoutListSkeleton.tsx new file mode 100644 index 0000000..429f4d1 --- /dev/null +++ b/src/ui/raidLayout/RaidLayoutListSkeleton.tsx @@ -0,0 +1,85 @@ +import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; +import Table from "@/components/table/Table"; +import { elementBg } from "@/util/SkeletonUtil"; +import RaidLayoutAdminButtons from "./RaidLayoutAdminButtons"; + + +export default function RaidLayoutListSkeleton(){ + const headElements: React.ReactNode[] = [ +
+ Raid Layout Name +
, +
+ Raid Size +
, +
+ Class Groups +
, +
+ Actions +
+ ]; + + const bodyElements: React.ReactNode[][] = [ + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton(), + RaidLayoutSkeleton() + ]; + + + return ( +
+ ); +} + + +function RaidLayoutSkeleton(){ + const buttonsProps = { + buttonProps: { + variant: "ghost" as ButtonVariant, + size: "md" as ButtonSizeType, + shape: "square" as ButtonShape, + disabled: true + }, + showRaidLayoutModal: () => {}, + showDeleteRaidLayoutModal: () => {} + }; + + const elements: React.ReactNode[] = [ +
, +
, +
, +
+
 
+ +
+ ]; + + return elements; +} diff --git a/src/ui/raidLayout/modal/RaidLayoutModal.tsx b/src/ui/raidLayout/modal/RaidLayoutModal.tsx index d5b74a6..a0dbf51 100644 --- a/src/ui/raidLayout/modal/RaidLayoutModal.tsx +++ b/src/ui/raidLayout/modal/RaidLayoutModal.tsx @@ -45,7 +45,7 @@ export default function RaidLayoutModal({ else if(newLayoutSize > classGroups.length){ setClassGroups([ ...classGroups, - null + ...Array(newLayoutSize - classGroups.length).fill(null) ]); } } @@ -128,7 +128,7 @@ export default function RaidLayoutModal({ />