Game calendar working

This commit is contained in:
2025-03-06 19:49:03 -05:00
parent ef6da3ea64
commit 28462776ac
30 changed files with 1025 additions and 67 deletions

View File

@@ -15,7 +15,7 @@ export default function AdminAccountsTab(){
const [ searchTerm, setSearchTerm ] = useState<string>("");
const [ sentSearchTerm, setSentSearchTerm ] = useState<string>();
const pageSize = 10;
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
const accountsCountQuery = useGetAccountsCount(sentSearchTerm);

View File

@@ -23,7 +23,7 @@ export default function AccountModal({
const [ email, setEmail ] = useState<string>(account?.email ?? "");
const [ password, setPassword ] = useState<string>("");
const [ accountStatus, setAccountStatus ] = useState<AccountStatus>(account?.accountStatus ?? AccountStatus.ACTIVE);
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
useEffect(() => {

View File

@@ -22,7 +22,7 @@ export default function AccountPasswordRestModal({
const passwordResetMutate = useResetPassword(account?.accountId ?? "");
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
const resetPassword = () => {

View File

@@ -0,0 +1,80 @@
import { useCreateGameCalendarEvent, useCreateRaidGroupCalendarEvent, useDeleteGameCalendarEvent, useDeleteRaidGroupCalendarEvent, useUpdateGameCalendarEvent, useUpdateRaidGroupCalendarEvent } from "@/hooks/CalendarHooks";
import { CalendarEvent } from "@/interface/Calendar";
import { calendarEventToFullCalendarEvent } from "@/util/CalendarUtil";
import { EventClickArg } from "@fullcalendar/core/index.js";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction";
import FullCalendar from "@fullcalendar/react";
import moment from "moment";
import { useState } from "react";
import CalendarEventModal from "./modals/CalendarEventModal";
export default function CalendarDisplay({
calendarEvents,
raidGroupId,
gameId
}:{
calendarEvents: CalendarEvent[];
raidGroupId?: string;
gameId?: string;
}){
const [ displayCalendarEventModal, setDisplayCalendarEventModal ] = useState(false);
const [ alterCalendarEvent, setAlterCalendarEvent ] = useState<CalendarEvent>();
const createGameCalendarEventMutate = useCreateGameCalendarEvent(gameId ?? "");
const updateGameCalendarEventMutate = useUpdateGameCalendarEvent(gameId ?? "");
const deleteGameCalendarEventMutate = useDeleteGameCalendarEvent(gameId ?? "");
const createRaidGroupCalendarEventMutate = useCreateRaidGroupCalendarEvent(raidGroupId ?? "");
const updateRaidGroupCalendarEventMutate = useUpdateRaidGroupCalendarEvent(raidGroupId ?? "");
const deleteRaidGroupCalendarEventMutate = useDeleteRaidGroupCalendarEvent(raidGroupId ?? "");
const newEvents = calendarEventToFullCalendarEvent(calendarEvents);
const showAddCalendarEventModal = (dateClickArg: DateClickArg) => {
console.log("showAdd()");
console.log(dateClickArg.date);
setAlterCalendarEvent({
eventStartDate: dateClickArg.date,
eventEndDate: moment(dateClickArg.date).add(1, "hours").toDate()
} as CalendarEvent);
setDisplayCalendarEventModal(true);
}
const showEditCalendarEventModal = (eventClickArg: EventClickArg) => {
setAlterCalendarEvent(calendarEvents.find((calEvent) => calEvent.calendarEventId === eventClickArg.event.id));
setDisplayCalendarEventModal(true);
}
const hideCalendarEventModal = () => {
setAlterCalendarEvent(undefined);
setDisplayCalendarEventModal(false);
}
return (
<div
className="w-full"
>
<FullCalendar
plugins={[ dayGridPlugin, interactionPlugin ]}
initialView="dayGridMonth"
events={newEvents}
eventClassNames="cursor-pointer my-2"
eventDisplay="block"
eventClick={showEditCalendarEventModal}
dateClick={showAddCalendarEventModal}
/>
<CalendarEventModal
display={displayCalendarEventModal}
close={hideCalendarEventModal}
calendarEvent={alterCalendarEvent}
createCalendarEventMutate={gameId ? createGameCalendarEventMutate : createRaidGroupCalendarEventMutate}
updateCalendarEventMutate={gameId ? updateGameCalendarEventMutate : updateRaidGroupCalendarEventMutate}
deleteCalendarEventMutate={gameId ? deleteGameCalendarEventMutate : deleteRaidGroupCalendarEventMutate}
/>
</div>
);
}

View File

@@ -0,0 +1,18 @@
import GameCalendarLoader from "./GameCalendarLoader";
export default function GameCalendarDisplay({
gameId
}:{
gameId: string;
}){
return (
<div
className="flex flex-col items-center justify-center"
>
<GameCalendarLoader
gameId={gameId}
/>
</div>
);
}

View File

@@ -0,0 +1,34 @@
import DangerMessage from "@/components/message/DangerMessage";
import { useGetGameCalendar } from "@/hooks/CalendarHooks";
import CalendarDisplay from "./CalendarDisplay";
export default function GameCalendarLoader({
gameId
}:{
gameId: string;
}){
const gameCalendarQuery = useGetGameCalendar(gameId);
if(gameCalendarQuery.status === "pending"){
return (
<div>
Loading...
</div>
);
}
else if(gameCalendarQuery.status === "error"){
return (
<DangerMessage>Error {gameCalendarQuery.error.message}</DangerMessage>
);
}
else{
return (
<CalendarDisplay
gameId={gameId}
calendarEvents={gameCalendarQuery.data}
/>
);
}
}

View File

@@ -0,0 +1,193 @@
import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton";
import SecondaryButton from "@/components/button/SecondaryButton";
import DateInput from "@/components/input/DateInput";
import TextArea from "@/components/input/TextArea";
import TextInput from "@/components/input/TextInput";
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
import { CalendarEvent } from "@/interface/Calendar";
import { useTimedModal } from "@/providers/TimedModalProvider";
import { UseMutationResult } from "@tanstack/react-query";
import moment from "moment";
import { useEffect, useState } from "react";
import { BsTrash3 } from "react-icons/bs";
export default function CalendarEventModal({
display,
close,
calendarEvent,
createCalendarEventMutate,
updateCalendarEventMutate,
deleteCalendarEventMutate
}:{
display: boolean;
close: () => void;
calendarEvent?: CalendarEvent;
createCalendarEventMutate: UseMutationResult<void, Error, CalendarEvent, unknown>;
updateCalendarEventMutate: UseMutationResult<void, Error, CalendarEvent, unknown>;
deleteCalendarEventMutate: UseMutationResult<void, Error, CalendarEvent, unknown>;
}){
const [ eventName, setEventName ] = useState<string>(calendarEvent?.eventName ?? "");
const [ eventDescription, setEventDescription ] = useState<string>(calendarEvent?.eventDescription ?? "");
const [ eventStartDate, setEventStartDate ] = useState(calendarEvent?.eventStartDate ?? new Date());
const [ eventEndDate, setEventEndDate ] = useState(calendarEvent?.eventEndDate ?? new Date());
const { addSuccessMessage, addErrorMessage } = useTimedModal();
const modalId = crypto.randomUUID().replaceAll("-", "");
useEffect(() => {
if(createCalendarEventMutate.status === "success"){
createCalendarEventMutate.reset();
addSuccessMessage(`Calendar Event ${eventName} created successfully`);
close();
}
else if(updateCalendarEventMutate.status === "success"){
updateCalendarEventMutate.reset();
addSuccessMessage(`Calendar Event ${eventName} updated successfully`);
close();
}
else if(deleteCalendarEventMutate.status === "success"){
deleteCalendarEventMutate.reset();
addSuccessMessage(`Calendar Event ${eventName} deleted successfully`);
close();
}
else if(createCalendarEventMutate.status === "error"){
createCalendarEventMutate.reset();
addErrorMessage(`Error creating calendar event ${eventName}: ${createCalendarEventMutate.error.message}`);
console.log(createCalendarEventMutate.error);
}
else if(updateCalendarEventMutate.status === "error"){
updateCalendarEventMutate.reset();
addErrorMessage(`Error updating calendar event ${eventName}: ${updateCalendarEventMutate.error.message}`);
console.log(updateCalendarEventMutate.error);
}
else if(deleteCalendarEventMutate.status === "error"){
deleteCalendarEventMutate.reset();
addErrorMessage(`Error deleting calendar event ${eventName}: ${deleteCalendarEventMutate.error.message}`);
console.log(deleteCalendarEventMutate.error);
}
});
const createCalendarEvent = () => {
createCalendarEventMutate.mutate({eventName, eventDescription, eventStartDate, eventEndDate});
}
const updateCalendarEvent = () => {
updateCalendarEventMutate.mutate({calendarEventId: calendarEvent?.calendarEventId, eventName, eventDescription, eventStartDate, eventEndDate});
}
const deleteCalendarEvent = () => {
deleteCalendarEventMutate.mutate(calendarEvent as CalendarEvent);
}
useEffect(() => {
if(calendarEvent){
setEventName(calendarEvent?.eventName ?? "");
setEventDescription(calendarEvent?.eventDescription ?? "");
setEventStartDate(calendarEvent.eventStartDate);
setEventEndDate(calendarEvent.eventEndDate);
}
else{
setEventName("");
setEventDescription("");
setEventStartDate(new Date());
setEventEndDate(new Date());
}
}, [ calendarEvent ]);
return (
<RaidBuilderModal
display={display}
close={close}
modalHeader={calendarEvent?.calendarEventId ? "Update Event" : "Create Event"}
modalBody={
<div
className="flex flex-col items-center justify-center gap-4"
>
<div
className="px-4"
>
<TextInput
id={`calendarEventModalNameInput${modalId}`}
placeholder="Event Name"
onChange={(e) => setEventName(e.target.value)}
value={eventName}
/>
</div>
<TextArea
id={`calendarEventModalDescriptionInput${modalId}`}
placeholder="Event Description"
onChange={(e) => setEventDescription(e.target.value)}
value={eventDescription}
/>
<div
className="w-full"
>
<DateInput
id={`calendarEventModalStartDateInput${modalId}`}
placeholder="Start Date"
value={moment(eventStartDate).format("YYYY-MM-DDTHH:mm")}
onChange={(e) => setEventStartDate(moment(e.target.value).toDate())}
/>
<DateInput
id={`calendarEventModalEndDateInput${modalId}`}
placeholder="End Date"
value={moment(eventEndDate).format("YYYY-MM-DDTHH:mm")}
onChange={(e) => setEventEndDate(moment(e.target.value).toDate())}
/>
</div>
</div>
}
modalFooter={
<div
className="flex flex-row items-center justify-center gap-4 w-full"
>
<div
className="flex flex-row items-center justify-start w-full"
>
&nbsp;
</div>
<div
className="flex flex-row items-center justify-center w-full gap-4"
>
<PrimaryButton
onClick={calendarEvent?.calendarEventId ? updateCalendarEvent : createCalendarEvent}
>
{calendarEvent?.calendarEventId ? "Update" : "Create"}
</PrimaryButton>
<SecondaryButton
onClick={close}
>
Cancel
</SecondaryButton>
</div>
<div
className="flex flex-row items-center justify-end w-full"
>
{
calendarEvent?.calendarEventId &&
<DangerButton
variant="ghost"
shape="square"
onClick={deleteCalendarEvent}
>
<BsTrash3
size={22}
/>
</DangerButton>
}
{
!calendarEvent?.calendarEventId &&
<>&nbsp;</>
}
</div>
</div>
}
/>
);
}

View File

@@ -15,7 +15,7 @@ export default function AllGamesDisplay(){
const [ searchTerm, setSearchTerm ] = useState<string>("");
const [ sentSearchTerm, setSentSearchTerm ] = useState<string>();
const pageSize = 10;
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
const gamesCountQuery = useGetGamesCount(sentSearchTerm);

View File

@@ -0,0 +1,61 @@
import { ButtonProps } from "@/components/button/Button";
import { Game } from "@/interface/Game";
import { useState } from "react";
import GameAdminButtons from "./GameAdminButtons";
import DeleteGameModal from "./modals/DeleteGameModal";
import GameModal from "./modals/GameModal";
export default function GameHeader({
game
}:{
game: Game;
}){
const [ displayEditGameModal, setDisplayEditGameModal ] = useState(false);
const [ displayDeleteGameModal, setDisplayDeleteGameModal ] = 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"
>
{
game.gameIcon &&
<img
className="m-auto mr-4"
src={`${import.meta.env.VITE_ICON_URL}/gameIcons/${game.gameIcon}`}
height={72}
width={72}
/>
}
{game.gameName}
</div>
<div>
<GameAdminButtons
buttonProps={buttonProps}
showEditGameModal={() => setDisplayEditGameModal(true)}
showDeleteGameModal={() => setDisplayDeleteGameModal(true)}
/>
</div>
<GameModal
display={displayEditGameModal}
close={() => setDisplayEditGameModal(false)}
game={game}
/>
<DeleteGameModal
display={displayDeleteGameModal}
close={() => setDisplayDeleteGameModal(false)}
game={game}
/>
</h1>
);
}

View File

@@ -21,7 +21,7 @@ export default function GameModal({
const [ gameName, setGameName ] = useState(game?.gameName);
const [ gameIcon, setGameIcon ] = useState(game?.gameIcon);
const [ iconFile, setIconFile ] = useState<File | null>(null);
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
useEffect(() => {

View File

@@ -1,20 +1,17 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination";
import { useGetRaidGroupsCount } from "@/hooks/RaidGroupHooks";
import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import RaidGroupModal from "./modals/RaidGroupModal";
import RaidGroupCreateAndSearch from "./RaidGroupCreateAndSearch";
import RaidGroupsLoader from "./RaidGroupsLoader";
export default function AdminRaidGroupTab(){
const [ displayCreateRaidGroupModal, setDisplayRaidGroupModal ] = useState(false);
export default function AllRaidGroupsDisplay(){
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().replace("-", "");
const raidGroupsCountQuery = useGetRaidGroupsCount(sentSearchTerm);
@@ -37,44 +34,10 @@ export default function AdminRaidGroupTab(){
return (
<>
<div
className="flex flex-row justify-between items-center w-full"
>
<div
className="flex flex-row items-center justify-start w-full"
>
&nbsp;
</div>
{/* Add Raid Group Button */}
<div
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8"
onClick={() => setDisplayRaidGroupModal(true)}
>
Create Raid Group
</PrimaryButton>
<RaidGroupModal
display={displayCreateRaidGroupModal}
close={() => setDisplayRaidGroupModal(false)}
raidGroup={undefined}
/>
</div>
{/* Raid Group Search Box */}
<div
className="flex flex-row items-center justify-end w-full"
>
<div>
<TextInput
id={`raidGroupSearchBox${modalId}`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
<RaidGroupCreateAndSearch
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
{/* Raid Group List */}
<RaidGroupsLoader
page={page}

View File

@@ -0,0 +1,58 @@
import PrimaryButton from "@/components/button/PrimaryButton";
import TextInput from "@/components/input/TextInput";
import { useState } from "react";
import RaidGroupModal from "./modals/RaidGroupModal";
export default function RaidGroupCreateAndSearch({
searchTerm,
setSearchTerm
}:{
searchTerm: string;
setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
}){
const [ displayCreateRaidGroupModal, setDisplayRaidGroupModal ] = useState(false);
const modalId = crypto.randomUUID().replaceAll("-", "");
return (
<div
className="flex flex-row justify-between items-center w-full"
>
<div
className="flex flex-row items-center justify-start w-full"
>
&nbsp;
</div>
{/* Add Raid Group Button */}
<div
className="flex flex-row items-center justify-center w-full"
>
<PrimaryButton
className="mb-8"
onClick={() => setDisplayRaidGroupModal(true)}
>
Create Raid Group
</PrimaryButton>
<RaidGroupModal
display={displayCreateRaidGroupModal}
close={() => setDisplayRaidGroupModal(false)}
raidGroup={undefined}
/>
</div>
{/* Raid Group Search Box */}
<div
className="flex flex-row items-center justify-end w-full"
>
<div>
<TextInput
id={`raidGroupSearchBox${modalId}`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,66 @@
import Pagination from "@/components/pagination/Pagination";
import { useGetRaidGroupsByGameCount } from "@/hooks/RaidGroupHooks";
import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import RaidGroupCreateAndSearch from "./RaidGroupCreateAndSearch";
import RaidGroupsByGameLoader from "./RaidGroupsByGameLoader";
export default function RaidGroupsByGameDisplay({
gameId
}:{
gameId: string;
}){
const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1);
const [ searchTerm, setSearchTerm ] = useState("");
const [ sentSearchTerm, setSentSearchTerm ] = useState<string>();
const pageSize = 10;
const raidGroupsCountQuery = useGetRaidGroupsByGameCount(gameId, sentSearchTerm);
const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => {
setSentSearchTerm(newSearchTerm.length ? newSearchTerm : undefined);
}, 1000);
useEffect(() => {
updateSearchTerm(searchTerm);
}, [ searchTerm, updateSearchTerm ]);
useEffect(() => {
if(raidGroupsCountQuery.status === "success"){
setTotalPages(Math.ceil(raidGroupsCountQuery.data / pageSize));
}
}, [ raidGroupsCountQuery ]);
return (
<div
className="flex flex-col items-center justify-center"
>
<RaidGroupCreateAndSearch
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
{/* Raid Group List */}
<RaidGroupsByGameLoader
gameId={gameId ?? ""}
page={page}
pageSize={pageSize}
searchTerm={searchTerm}
/>
{/* Pagination */}
<div
className="my-12"
>
<Pagination
currentPage={page}
totalPages={totalPages}
onChange={setPage}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,34 @@
import DangerMessage from "@/components/message/DangerMessage";
import { useGetRaidGroupsByGame } from "@/hooks/RaidGroupHooks";
import RaidGroupsList from "./RaidGroupsList";
import RaidGroupsListSkeleton from "./RaidGroupsListSkeleton";
export default function RaidGroupsByGameLoader({
gameId,
page,
pageSize,
searchTerm
}:{
gameId: string;
page: number;
pageSize: number;
searchTerm?: string;
}){
const raidGroupsQuery = useGetRaidGroupsByGame(gameId, page - 1, pageSize, searchTerm);
if(raidGroupsQuery.status === "pending"){
return <RaidGroupsListSkeleton/>
}
else if(raidGroupsQuery.status === "error"){
return <DangerMessage>Error {raidGroupsQuery.error.message}</DangerMessage>
}
else{
return (
<RaidGroupsList
raidGroups={raidGroupsQuery.data ?? []}
/>
);
}
}

View File

@@ -25,7 +25,7 @@ export default function RaidGroupModal({
const [ raidGroupIcon, setRaidGroupIcon ] = useState(raidGroup?.raidGroupIcon);
const [ iconFile, setIconFile ] = useState<File | null>(null);
const [ game, setGame ] = useState<Game>();
const modalId = crypto.randomUUID().replace("-", "");
const modalId = crypto.randomUUID().replaceAll("-", "");
useEffect(() => {