generated from jotyy/Mantine-Admin
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from game-node-app/dev
Dev
- Loading branch information
Showing
29 changed files
with
1,505 additions
and
395 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from "react"; | ||
import { PageContainer } from "@/components/PageContainer/PageContainer"; | ||
import ExcludedGamesTable from "@/components/game/filter/ExcludedGamesTable"; | ||
import { Box, Text } from "@mantine/core"; | ||
|
||
const Page = () => { | ||
return ( | ||
<PageContainer title={"Manage game exclusions"}> | ||
<Box className={"w-10/12"}> | ||
<Text className={"text-sm text-dimmed"}> | ||
Excluded games won't show up in front-facing content, like | ||
the home page, explore screen, or the activities page. They | ||
can still be searched for, visited and be visible in user's | ||
collections. | ||
</Text> | ||
</Box> | ||
|
||
<ExcludedGamesTable /> | ||
</PageContainer> | ||
); | ||
}; | ||
|
||
export default Page; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React, { | ||
ComponentProps, | ||
PropsWithChildren, | ||
useEffect, | ||
useState, | ||
} from "react"; | ||
import { AspectRatio, Image, ImageProps } from "@mantine/core"; | ||
import Link from "next/link"; | ||
import { | ||
getSizedImageUrl, | ||
ImageSize, | ||
} from "@/components/game/util/getSizedImageUrl"; | ||
import { TGameOrSearchGame } from "@/components/game/util/types"; | ||
import { getCoverUrl } from "@/components/game/util/getCoverUrl"; | ||
import MainAppLink from "@/components/general/MainAppLink"; | ||
|
||
export interface IGameFigureProps | ||
extends PropsWithChildren<Omit<ComponentProps<typeof Link>, "href">> { | ||
game: TGameOrSearchGame | undefined; | ||
onClick?: (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void; | ||
imageProps?: ImageProps; | ||
href?: string; | ||
} | ||
|
||
/** | ||
* This component is the base building block for anything related to showing a game's metadata. | ||
* It only handles logic related to image loading (skeletons, etc.). | ||
* | ||
* @param metadata | ||
* @param href | ||
* @constructor | ||
*/ | ||
const GameFigureImage = ({ | ||
game, | ||
imageProps, | ||
href, | ||
onClick, | ||
children, | ||
...others | ||
}: IGameFigureProps) => { | ||
const coverUrl = getCoverUrl(game); | ||
const sizedCoverUrl = getSizedImageUrl(coverUrl, ImageSize.COVER_BIG); | ||
const defaultHref = `/game/${game?.id}`; | ||
return ( | ||
<MainAppLink | ||
href={href ?? defaultHref} | ||
className="w-full h-auto" | ||
onClick={onClick} | ||
{...others} | ||
> | ||
<AspectRatio ratio={264 / 354} pos="relative" h={"100%"} w={"auto"}> | ||
<Image | ||
radius={"sm"} | ||
src={sizedCoverUrl ?? "/img/game_placeholder.jpeg"} | ||
alt={"Game cover"} | ||
className="w-full h-auto max-h-full" | ||
{...imageProps} | ||
/> | ||
{children} | ||
</AspectRatio> | ||
</MainAppLink> | ||
); | ||
}; | ||
|
||
export default GameFigureImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
"use client"; | ||
|
||
import React, { useMemo, useState } from "react"; | ||
import { useExcludedGames } from "@/components/game/filter/hooks/useExcludedGames"; | ||
import { | ||
MantineReactTable, | ||
MRT_ColumnDef, | ||
MRT_PaginationState, | ||
} from "mantine-react-table"; | ||
import { useCustomTable } from "@/components/table/hooks/use-custom-table"; | ||
import { | ||
ChangeExclusionStatusDto, | ||
Game, | ||
GameExclusion, | ||
GameFilterService, | ||
} from "@/wrapper/server"; | ||
import { | ||
ActionIcon, | ||
Badge, | ||
Box, | ||
Button, | ||
Group, | ||
MantineColor, | ||
Menu, | ||
Modal, | ||
Paper, | ||
Tooltip, | ||
Text, | ||
} from "@mantine/core"; | ||
import { UserAvatarGroup } from "@/components/general/avatar/UserAvatarGroup"; | ||
import { useGames } from "@/components/game/hooks/useGames"; | ||
import GameFigureImage from "@/components/game/figure/GameFigureImage"; | ||
import { | ||
IconAdjustmentsPlus, | ||
IconCirclePlus, | ||
IconPlus, | ||
IconSquarePlus, | ||
} from "@tabler/icons-react"; | ||
import { useDisclosure } from "@mantine/hooks"; | ||
import AddGameExclusionForm from "@/components/game/filter/form/AddGameExclusionForm"; | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { notifications } from "@mantine/notifications"; | ||
import { getErrorMessage } from "@/util/getErrorMessage"; | ||
import { modals } from "@mantine/modals"; | ||
|
||
// necessary because useMutation only allows for one parameter in mutationFn | ||
interface ChangeExclusionMutationRequest extends ChangeExclusionStatusDto { | ||
gameId: number; | ||
} | ||
|
||
interface GameExclusionWithGameInfo extends GameExclusion { | ||
game: Game; | ||
} | ||
|
||
const columns: MRT_ColumnDef<GameExclusionWithGameInfo>[] = [ | ||
{ | ||
header: "Image", | ||
id: "figure", | ||
maxSize: 100, | ||
Cell: ({ row }) => { | ||
return ( | ||
<Box className={"max-w-20"}> | ||
<GameFigureImage game={row.original.game} /> | ||
</Box> | ||
); | ||
}, | ||
}, | ||
{ | ||
header: "Game Id", | ||
accessorKey: "targetGameId", | ||
enableSorting: false, | ||
}, | ||
{ | ||
header: "Name", | ||
accessorKey: "game.name", | ||
}, | ||
{ | ||
accessorFn: (row) => { | ||
return row.isActive ? "Active" : "Inactive"; | ||
}, | ||
header: "Status", | ||
filterVariant: "select", | ||
mantineFilterSelectProps: { | ||
data: [ | ||
{ label: "Active", value: "Active" }, | ||
{ label: "Inactive", value: "Inactive" }, | ||
], | ||
}, | ||
Cell: ({ row, renderedCellValue }) => { | ||
const item = row.original; | ||
const color: MantineColor = item.isActive ? "green" : "red"; | ||
return <Badge color={color}>{renderedCellValue}</Badge>; | ||
}, | ||
}, | ||
{ | ||
header: "Issued by", | ||
accessorKey: "issuerUserId", | ||
Cell: ({ row }) => { | ||
return <UserAvatarGroup userId={row.original.issuerUserId} />; | ||
}, | ||
}, | ||
{ | ||
header: "Created At", | ||
accessorFn: (row) => new Date(row.createdAt).toLocaleString("en-US"), | ||
sortingFn: (rowA, rowB, columnId) => { | ||
const createDateA = new Date(rowA.original.createdAt); | ||
const createDateB = new Date(rowB.original.createdAt); | ||
|
||
return createDateA.getTime() - createDateB.getTime(); | ||
}, | ||
id: "createdAt", | ||
}, | ||
]; | ||
|
||
const ExcludedGamesTable = () => { | ||
const [pagination, setPagination] = useState<MRT_PaginationState>({ | ||
pageIndex: 0, | ||
pageSize: 20, | ||
}); | ||
|
||
const [addExclusionModalOpened, addExclusionModalUtils] = useDisclosure(); | ||
|
||
const offset = pagination.pageIndex * pagination.pageSize; | ||
const limit = pagination.pageSize; | ||
|
||
const { data, isLoading, isError, isFetching, invalidate } = | ||
useExcludedGames(offset, limit); | ||
|
||
const gameIds = useMemo(() => { | ||
return data?.data.map((exclusion) => exclusion.targetGameId); | ||
}, [data]); | ||
|
||
const gamesQuery = useGames({ | ||
gameIds, | ||
relations: { | ||
cover: true, | ||
}, | ||
}); | ||
|
||
const items = useMemo(() => { | ||
if (data && gamesQuery.data) { | ||
return data.data.map((exclusion): GameExclusionWithGameInfo => { | ||
const relatedGame = gamesQuery.data.find( | ||
(game) => game.id === exclusion.targetGameId, | ||
)!; | ||
|
||
return { | ||
...exclusion, | ||
game: relatedGame, | ||
}; | ||
}); | ||
} | ||
}, [data, gamesQuery.data]); | ||
|
||
const changeStatusMutation = useMutation({ | ||
mutationFn: async (dto: ChangeExclusionMutationRequest) => { | ||
await GameFilterService.gameFilterControllerChangeStatus( | ||
dto.gameId, | ||
{ | ||
isActive: dto.isActive, | ||
}, | ||
); | ||
|
||
return dto.isActive; | ||
}, | ||
onSuccess: (isActive) => { | ||
notifications.show({ | ||
color: "green", | ||
message: `Sucessfully ${isActive ? "activated" : "deactivated"} filter.`, | ||
}); | ||
}, | ||
onError: (err) => { | ||
const msg = getErrorMessage(err); | ||
|
||
notifications.show({ | ||
color: "red", | ||
message: msg, | ||
}); | ||
}, | ||
onSettled: () => { | ||
invalidate(); | ||
}, | ||
}); | ||
|
||
const table = useCustomTable<GameExclusionWithGameInfo>({ | ||
columns: columns, | ||
data: items ?? [], | ||
state: { | ||
isLoading: isLoading, | ||
showAlertBanner: isError, | ||
showProgressBars: isFetching, | ||
pagination, | ||
}, | ||
manualPagination: true, | ||
onPaginationChange: setPagination, | ||
rowCount: data?.pagination.totalItems ?? 0, | ||
renderTopToolbarCustomActions: (table) => { | ||
return ( | ||
<Group className={"w-full h-full items-center justify-end"}> | ||
<Tooltip label={"Add exclusion"}> | ||
<ActionIcon | ||
variant={"subtle"} | ||
color={"gray"} | ||
size={"lg"} | ||
onClick={addExclusionModalUtils.open} | ||
> | ||
<IconSquarePlus /> | ||
</ActionIcon> | ||
</Tooltip> | ||
</Group> | ||
); | ||
}, | ||
enableRowActions: true, | ||
renderRowActionMenuItems: (tableItem) => { | ||
const item = tableItem.row.original; | ||
return ( | ||
<> | ||
<Menu.Item | ||
onClick={() => { | ||
const dto: ChangeExclusionMutationRequest = { | ||
gameId: item.targetGameId, | ||
isActive: !item.isActive, | ||
}; | ||
changeStatusMutation.mutate(dto); | ||
}} | ||
> | ||
{item.isActive ? "Deactivate" : "Activate"} | ||
</Menu.Item> | ||
</> | ||
); | ||
}, | ||
}); | ||
|
||
return ( | ||
<Paper withBorder radius="md" p="md" mt="lg"> | ||
<Modal | ||
opened={addExclusionModalOpened} | ||
onClose={addExclusionModalUtils.close} | ||
title={"Add game exclusion"} | ||
size={"lg"} | ||
> | ||
<AddGameExclusionForm onClose={addExclusionModalUtils.close} /> | ||
</Modal> | ||
<MantineReactTable table={table} /> | ||
</Paper> | ||
); | ||
}; | ||
|
||
export default ExcludedGamesTable; |
Oops, something went wrong.