From 7252c05569d534edc05fde7a58d47a523aeb72b2 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 18 Sep 2025 01:07:13 +0700 Subject: [PATCH] Game Prize --- .../apps/marketing/games/game-prizes/page.tsx | 7 + .../layout/vertical/VerticalMenu.tsx | 3 + src/data/dictionaries/en.json | 3 +- src/data/dictionaries/id.json | 3 +- src/services/mutations/gamePrize.ts | 52 ++ src/services/queries/gamePrize.ts | 46 ++ src/types/services/gamePrize.ts | 35 ++ .../game-prizes/AddEditGamePrizeDrawer.tsx | 430 ++++++++++++++++ .../game-prizes/DeleteGamePrizeDialog.tsx | 104 ++++ .../games/game-prizes/GamePrizeListTable.tsx | 483 ++++++++++++++++++ .../marketing/games/game-prizes/index.tsx | 17 + 11 files changed, 1181 insertions(+), 2 deletions(-) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/marketing/games/game-prizes/page.tsx create mode 100644 src/services/mutations/gamePrize.ts create mode 100644 src/services/queries/gamePrize.ts create mode 100644 src/types/services/gamePrize.ts create mode 100644 src/views/apps/marketing/games/game-prizes/AddEditGamePrizeDrawer.tsx create mode 100644 src/views/apps/marketing/games/game-prizes/DeleteGamePrizeDialog.tsx create mode 100644 src/views/apps/marketing/games/game-prizes/GamePrizeListTable.tsx create mode 100644 src/views/apps/marketing/games/game-prizes/index.tsx diff --git a/src/app/[lang]/(dashboard)/(private)/apps/marketing/games/game-prizes/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/marketing/games/game-prizes/page.tsx new file mode 100644 index 0000000..2432bf9 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/marketing/games/game-prizes/page.tsx @@ -0,0 +1,7 @@ +import GamePrizeList from '@/views/apps/marketing/games/game-prizes' + +const GamePrizePage = () => { + return +} + +export default GamePrizePage diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 3434cb1..476a452 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -163,6 +163,9 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].list} + + {dictionary['navigation'].game_prizes} + {dictionary['navigation'].campaign} diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index 1545ad5..2a15cd5 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -136,6 +136,7 @@ "customer_analytics": "Customer Analytics", "voucher": "Voucher", "tiers_text": "Tiers", - "games": "Games" + "games": "Games", + "game_prizes": "Game Prizes" } } diff --git a/src/data/dictionaries/id.json b/src/data/dictionaries/id.json index 460e162..f23fb6b 100644 --- a/src/data/dictionaries/id.json +++ b/src/data/dictionaries/id.json @@ -136,6 +136,7 @@ "customer_analytics": "Analisis Pelanggan", "voucher": "Vocher", "tiers_text": "Tiers", - "games": "Permaninan" + "games": "Permainan", + "game_prizes": "Hadiah Permainan" } } diff --git a/src/services/mutations/gamePrize.ts b/src/services/mutations/gamePrize.ts new file mode 100644 index 0000000..2aac02a --- /dev/null +++ b/src/services/mutations/gamePrize.ts @@ -0,0 +1,52 @@ +import { GamePrizeRequest } from '@/types/services/gamePrize' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { toast } from 'react-toastify' +import { api } from '../api' + +export const useGamePrizesMutation = () => { + const queryClient = useQueryClient() + + const createGamePrize = useMutation({ + mutationFn: async (newGamePrize: GamePrizeRequest) => { + const response = await api.post('/marketing/game-prizes', newGamePrize) + return response.data + }, + onSuccess: () => { + toast.success('GamePrize created successfully!') + queryClient.invalidateQueries({ queryKey: ['gamePrizes'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + + const updateGamePrize = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: GamePrizeRequest }) => { + const response = await api.put(`/marketing/game-prizes/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('GamePrize updated successfully!') + queryClient.invalidateQueries({ queryKey: ['gamePrizes'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') + } + }) + + const deleteGamePrize = useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/marketing/game-prizes/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('GamePrize deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['gamePrizes'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') + } + }) + + return { createGamePrize, updateGamePrize, deleteGamePrize } +} diff --git a/src/services/queries/gamePrize.ts b/src/services/queries/gamePrize.ts new file mode 100644 index 0000000..5712fcc --- /dev/null +++ b/src/services/queries/gamePrize.ts @@ -0,0 +1,46 @@ +import { useQuery } from '@tanstack/react-query' +import { api } from '../api' +import { GamePrize, GamePrizes } from '@/types/services/gamePrize' + +interface GamePrizeQueryParams { + page?: number + limit?: number + search?: string +} + +export function useGamePrizes(params: GamePrizeQueryParams = {}) { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['gamePrizes', { page, limit, search, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('page', page.toString()) + queryParams.append('limit', limit.toString()) + + if (search) { + queryParams.append('search', search) + } + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/marketing/game-prizes?${queryParams.toString()}`) + return res.data.data + } + }) +} + +export function useGamePrizeById(id: string) { + return useQuery({ + queryKey: ['gamePrizes', id], + queryFn: async () => { + const res = await api.get(`/marketing/game-prizes/${id}`) + return res.data.data + } + }) +} diff --git a/src/types/services/gamePrize.ts b/src/types/services/gamePrize.ts new file mode 100644 index 0000000..21259f3 --- /dev/null +++ b/src/types/services/gamePrize.ts @@ -0,0 +1,35 @@ +import { Game } from './game' + +export interface GamePrize { + id: string // uuid + game_id: string // uuid + name: string + weight: number + stock: number + max_stock?: number + threshold?: number // int64 → number + fallback_prize_id?: string // uuid + metadata: Record + game?: Game // relasi ke GameResponse + fallback_prize?: GamePrize // self reference + created_at: string // ISO datetime + updated_at: string // ISO datetime +} + +export interface GamePrizes { + data: GamePrize[] + total_count: number + page: number + limit: number + total_pages: number +} + +export interface GamePrizeRequest { + game_id: string // uuid + name: string + weight: number // min 1 + stock: number // min 0 + max_stock?: number // optional + threshold?: number // optional (int64 → number in TS) + fallback_prize_id?: string // optional uuid +} diff --git a/src/views/apps/marketing/games/game-prizes/AddEditGamePrizeDrawer.tsx b/src/views/apps/marketing/games/game-prizes/AddEditGamePrizeDrawer.tsx new file mode 100644 index 0000000..7bbba2a --- /dev/null +++ b/src/views/apps/marketing/games/game-prizes/AddEditGamePrizeDrawer.tsx @@ -0,0 +1,430 @@ +// React Imports +import { useState, useEffect } from 'react' + +// MUI Imports +import Button from '@mui/material/Button' +import Drawer from '@mui/material/Drawer' +import IconButton from '@mui/material/IconButton' +import Typography from '@mui/material/Typography' +import Box from '@mui/material/Box' +import Switch from '@mui/material/Switch' +import FormControlLabel from '@mui/material/FormControlLabel' +import InputAdornment from '@mui/material/InputAdornment' + +// Third-party Imports +import { useForm, Controller } from 'react-hook-form' + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' +import CustomAutocomplete from '@core/components/mui/Autocomplete' + +// Services +import { useGamePrizesMutation } from '@/services/mutations/gamePrize' +import { useGames } from '@/services/queries/game' +import { GamePrize, GamePrizeRequest } from '@/types/services/gamePrize' +import { useGamePrizes } from '@/services/queries/gamePrize' + +type Props = { + open: boolean + handleClose: () => void + data?: GamePrize // GamePrize data for edit (if exists) +} + +type FormValidateType = { + game_id: string + name: string + weight: number + stock: number + max_stock?: number + threshold?: number + fallback_prize_id?: string +} + +// Initial form data +const initialData: FormValidateType = { + game_id: '', + name: '', + weight: 1, + stock: 0, + max_stock: 0, + threshold: 0, + fallback_prize_id: undefined +} + +const AddEditGamePrizeDrawer = (props: Props) => { + // Props + const { open, handleClose, data } = props + + // States + const [isSubmitting, setIsSubmitting] = useState(false) + + // Queries + const { data: gamesData } = useGames({ page: 1, limit: 100, search: '' }) + const { data: gamePrizesData } = useGamePrizes({ page: 1, limit: 100, search: '' }) + + // Mutations + const { createGamePrize, updateGamePrize } = useGamePrizesMutation() + + // Determine if this is edit mode + const isEditMode = Boolean(data?.id) + + // Get available games and fallback prizes + const games = gamesData?.data ?? [] + const availablePrizes = gamePrizesData?.data?.filter(prize => prize.id !== data?.id) ?? [] + + // Hooks + const { + control, + reset: resetForm, + handleSubmit, + watch, + formState: { errors } + } = useForm({ + defaultValues: initialData + }) + + const watchedMaxStock = watch('max_stock') + + // Effect to populate form when editing + useEffect(() => { + if (isEditMode && data) { + // Populate form with existing data + const formData: FormValidateType = { + game_id: data.game_id || '', + name: data.name || '', + weight: data.weight || 1, + stock: data.stock || 0, + max_stock: data.max_stock, + threshold: data.threshold, + fallback_prize_id: data.fallback_prize_id + } + + resetForm(formData) + } else { + // Reset to initial data for add mode + resetForm(initialData) + } + }, [data, isEditMode, resetForm]) + + const handleFormSubmit = async (formData: FormValidateType) => { + try { + setIsSubmitting(true) + + // Create GamePrizeRequest object + const gamePrizeRequest: GamePrizeRequest = { + game_id: formData.game_id, + name: formData.name, + weight: typeof formData.weight === 'string' ? parseFloat(formData.weight) : formData.weight || 1, + stock: typeof formData.stock === 'string' ? parseInt(formData.stock, 10) : formData.stock || 0, + max_stock: formData.max_stock + ? typeof formData.max_stock === 'string' + ? parseInt(formData.max_stock, 10) + : formData.max_stock + : undefined, + threshold: formData.threshold + ? typeof formData.threshold === 'string' + ? parseInt(formData.threshold, 10) + : formData.threshold + : undefined, + fallback_prize_id: formData.fallback_prize_id || undefined + } + + if (isEditMode && data?.id) { + // Update existing game prize + updateGamePrize.mutate( + { id: data.id, payload: gamePrizeRequest }, + { + onSuccess: () => { + handleReset() + handleClose() + } + } + ) + } else { + // Create new game prize + createGamePrize.mutate(gamePrizeRequest, { + onSuccess: () => { + handleReset() + handleClose() + } + }) + } + } catch (error) { + console.error('Error submitting game prize:', error) + // Handle error (show toast, etc.) + } finally { + setIsSubmitting(false) + } + } + + const handleReset = () => { + handleClose() + resetForm(initialData) + } + + return ( + + {/* Sticky Header */} + +
+ {isEditMode ? 'Edit Hadiah Game' : 'Tambah Hadiah Game Baru'} + + + +
+
+ + {/* Scrollable Content */} + +
+
+ {/* Game Selection */} +
+ + Pilih Game * + + ( + `${option.name} (${option.type})`} + value={games.find(game => game.id === value) || null} + onChange={(_, newValue) => onChange(newValue?.id || '')} + disabled={isEditMode} + renderInput={params => ( + + )} + isOptionEqualToValue={(option, value) => option.id === value?.id} + noOptionsText='Tidak ada game tersedia' + /> + )} + /> +
+ + {/* Nama Hadiah */} +
+ + Nama Hadiah * + + ( + + )} + /> +
+ + {/* Bobot */} +
+ + Bobot * + + ( + % + }} + inputProps={{ min: 1, max: 100 }} + /> + )} + /> +
+ + {/* Stock */} +
+ + Stok * + + { + if (watchedMaxStock && value > watchedMaxStock) { + return 'Stok tidak boleh melebihi maksimal stok' + } + return true + } + }} + render={({ field }) => ( + + )} + /> +
+ + {/* Max Stock */} +
+ + Maksimal Stok + + ( + + )} + /> +
+ + {/* Threshold */} +
+ + Threshold + + ( + + )} + /> +
+ + {/* Fallback Prize */} +
+ + Hadiah Cadangan + + ( + `${option.name} (Stok: ${option.stock})`} + value={availablePrizes.find(prize => prize.id === value) || null} + onChange={(_, newValue) => onChange(newValue?.id || '')} + renderInput={params => ( + + )} + isOptionEqualToValue={(option, value) => option.id === value?.id} + noOptionsText='Tidak ada hadiah tersedia' + clearText='Hapus' + /> + )} + /> +
+
+
+
+ + {/* Sticky Footer */} + +
+ + +
+
+
+ ) +} + +export default AddEditGamePrizeDrawer diff --git a/src/views/apps/marketing/games/game-prizes/DeleteGamePrizeDialog.tsx b/src/views/apps/marketing/games/game-prizes/DeleteGamePrizeDialog.tsx new file mode 100644 index 0000000..f0ba08e --- /dev/null +++ b/src/views/apps/marketing/games/game-prizes/DeleteGamePrizeDialog.tsx @@ -0,0 +1,104 @@ +// React Imports +import { useState } from 'react' + +// MUI Imports +import Dialog from '@mui/material/Dialog' +import DialogTitle from '@mui/material/DialogTitle' +import DialogContent from '@mui/material/DialogContent' +import DialogActions from '@mui/material/DialogActions' +import DialogContentText from '@mui/material/DialogContentText' +import Button from '@mui/material/Button' +import Typography from '@mui/material/Typography' +import Box from '@mui/material/Box' +import Alert from '@mui/material/Alert' +import { GamePrize } from '@/types/services/gamePrize' +import { useGamePrizes } from '@/services/queries/gamePrize' + +// Types + +type Props = { + open: boolean + onClose: () => void + onConfirm: () => void + gamePrize: GamePrize | null + isDeleting?: boolean +} + +const DeleteGamePrizeDialog = ({ open, onClose, onConfirm, gamePrize, isDeleting = false }: Props) => { + if (!gamePrize) return null + + return ( + + + + + Hapus Game + + + + + + Apakah Anda yakin ingin menghapus game berikut? + + + + + {gamePrize.name} + + + Dibuat:{' '} + {new Date(gamePrize.created_at).toLocaleDateString('id-ID', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + + + + + + Peringatan: Tindakan ini tidak dapat dibatalkan. Semua data yang terkait dengan game ini + akan dihapus secara permanen. + + + + + Pastikan tidak ada pengguna yang masih menggunakan game ini sebelum menghapus. + + + + + + + + + ) +} + +export default DeleteGamePrizeDialog diff --git a/src/views/apps/marketing/games/game-prizes/GamePrizeListTable.tsx b/src/views/apps/marketing/games/game-prizes/GamePrizeListTable.tsx new file mode 100644 index 0000000..2e4bfbe --- /dev/null +++ b/src/views/apps/marketing/games/game-prizes/GamePrizeListTable.tsx @@ -0,0 +1,483 @@ +'use client' + +// React Imports +import { useEffect, useState, useMemo, useCallback } from 'react' + +// Next Imports +import Link from 'next/link' +import { useParams } from 'next/navigation' + +// MUI Imports +import Card from '@mui/material/Card' +import CardHeader from '@mui/material/CardHeader' +import Button from '@mui/material/Button' +import Typography from '@mui/material/Typography' +import Chip from '@mui/material/Chip' +import Checkbox from '@mui/material/Checkbox' +import IconButton from '@mui/material/IconButton' +import { styled } from '@mui/material/styles' +import TablePagination from '@mui/material/TablePagination' +import type { TextFieldProps } from '@mui/material/TextField' +import MenuItem from '@mui/material/MenuItem' + +// Third-party Imports +import classnames from 'classnames' +import { rankItem } from '@tanstack/match-sorter-utils' +import { + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, + getFilteredRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFacetedMinMaxValues, + getPaginationRowModel, + getSortedRowModel +} from '@tanstack/react-table' +import type { ColumnDef, FilterFn } from '@tanstack/react-table' +import type { RankingInfo } from '@tanstack/match-sorter-utils' + +// Type Imports +import type { ThemeColor } from '@core/types' +import type { Locale } from '@configs/i18n' + +// Component Imports +import OptionMenu from '@core/components/option-menu' +import TablePaginationComponent from '@components/TablePaginationComponent' +import CustomTextField from '@core/components/mui/TextField' +import CustomAvatar from '@core/components/mui/Avatar' + +// Util Imports +import { getInitials } from '@/utils/getInitials' +import { getLocalizedUrl } from '@/utils/i18n' +import { formatCurrency } from '@/utils/transform' + +// Style Imports +import tableStyles from '@core/styles/table.module.css' +import Loading from '@/components/layout/shared/Loading' +import AddEditGamePrizeDrawer from './AddEditGamePrizeDrawer' +import DeleteGamePrizeDialog from './DeleteGamePrizeDialog' +import { useGamePrizes } from '@/services/queries/gamePrize' +import { useGamePrizesMutation } from '@/services/mutations/gamePrize' +import { GamePrize } from '@/types/services/gamePrize' + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type GamePrizeWithAction = GamePrize & { + action?: string +} + +// Styled Components +const Icon = styled('i')({}) + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ + itemRank + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const DebouncedInput = ({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit) => { + // States + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value) + }, debounce) + + return () => clearTimeout(timeout) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return setValue(e.target.value)} /> +} + +// Column Definitions +const columnHelper = createColumnHelper() + +const GamePrizeListTable = () => { + // States + const [addGamePrizeOpen, setAddGamePrizeOpen] = useState(false) + const [editGamePrizeData, setEditGamePrizeData] = useState(undefined) + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [gamePrizeToDelete, setGamePrizeToDelete] = useState(null) + const [rowSelection, setRowSelection] = useState({}) + const [globalFilter, setGlobalFilter] = useState('') + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [search, setSearch] = useState('') + + // Replace with actual hook for game prizes + const { data, isLoading, error, isFetching } = useGamePrizes({ + page: currentPage, + limit: pageSize, + search + }) + + const { deleteGamePrize } = useGamePrizesMutation() + + const gamePrizes = data?.data ?? [] + const totalCount = data?.total_count ?? 0 + + // Hooks + const { lang: locale } = useParams() + + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setCurrentPage(newPage) + }, []) + + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10) + setPageSize(newPageSize) + setCurrentPage(1) // Reset to first page + }, []) + + const handleEditGamePrize = (gamePrize: GamePrize) => { + setEditGamePrizeData(gamePrize) + setAddGamePrizeOpen(true) + } + + const handleDeleteGamePrize = (gamePrize: GamePrize) => { + setGamePrizeToDelete(gamePrize) + setDeleteDialogOpen(true) + } + + const handleConfirmDelete = () => { + if (gamePrizeToDelete) { + deleteGamePrize.mutate(gamePrizeToDelete.id, { + onSuccess: () => { + console.log('Game Prize deleted successfully') + setDeleteDialogOpen(false) + setGamePrizeToDelete(null) + }, + onError: error => { + console.error('Error deleting game prize:', error) + } + }) + } + } + + const handleCloseDeleteDialog = () => { + if (deleteGamePrize.isPending) return + setDeleteDialogOpen(false) + setGamePrizeToDelete(null) + } + + const handleCloseGamePrizeDrawer = () => { + setAddGamePrizeOpen(false) + setEditGamePrizeData(undefined) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('name', { + header: 'Nama Hadiah', + cell: ({ row }) => ( +
+ + {getInitials(row.original.name)} + +
+ + + {row.original.name} + + + + {row.original.game?.name || 'Game tidak tersedia'} + +
+
+ ) + }), + columnHelper.accessor('game', { + header: 'Game', + cell: ({ row }) => ( +
+ {row.original.game?.name || 'N/A'} + + {row.original.game?.type || ''} + +
+ ) + }), + columnHelper.accessor('weight', { + header: 'Bobot', + cell: ({ row }) => {row.original.weight}% + }), + columnHelper.accessor('stock', { + header: 'Stok', + cell: ({ row }) => ( +
+ + {row.original.stock} + {row.original.max_stock && ` / ${row.original.max_stock}`} + +
+ ) + }), + columnHelper.accessor('threshold', { + header: 'Threshold', + cell: ({ row }) => {row.original.threshold || 'Tidak Ada'} + }), + columnHelper.accessor('fallback_prize', { + header: 'Hadiah Cadangan', + cell: ({ row }) => ( + {row.original.fallback_prize?.name || 'Tidak Ada'} + ) + }), + columnHelper.accessor('created_at', { + header: 'Tanggal Dibuat', + cell: ({ row }) => ( + + {new Date(row.original.created_at).toLocaleDateString('id-ID', { + year: 'numeric', + month: 'short', + day: 'numeric' + })} + + ) + }), + { + id: 'actions', + header: 'Aksi', + cell: ({ row }) => ( +
+ handleEditGamePrize(row.original) + } + }, + { + text: 'Hapus', + icon: 'tabler-trash text-[22px]', + menuItemProps: { + className: 'flex items-center gap-2 text-textSecondary', + onClick: () => handleDeleteGamePrize(row.original) + } + } + ]} + /> +
+ ), + enableSorting: false + } + ], + [locale, handleEditGamePrize, handleDeleteGamePrize] + ) + + const table = useReactTable({ + data: gamePrizes as GamePrize[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + globalFilter, + pagination: { + pageIndex: currentPage, + pageSize + } + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) + }) + + return ( + <> + +
+ table.setPageSize(Number(e.target.value))} + className='max-sm:is-full sm:is-[70px]' + > + 10 + 25 + 50 + +
+ setSearch(value as string)} + placeholder='Cari Hadiah Game' + className='max-sm:is-full' + /> + + +
+
+
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {table.getFilteredRowModel().rows.length === 0 ? ( + + + + + + ) : ( + + {table + .getRowModel() + .rows.slice(0, table.getState().pagination.pageSize) + .map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ Tidak ada data tersedia +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )} +
+ ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + /> +
+ + {/* Add/Edit Game Prize Drawer */} + + + {/* Delete Game Prize Dialog */} + + + ) +} + +export default GamePrizeListTable diff --git a/src/views/apps/marketing/games/game-prizes/index.tsx b/src/views/apps/marketing/games/game-prizes/index.tsx new file mode 100644 index 0000000..7eb0440 --- /dev/null +++ b/src/views/apps/marketing/games/game-prizes/index.tsx @@ -0,0 +1,17 @@ +// MUI Imports +import Grid from '@mui/material/Grid2' +import GamePrizeListTable from './GamePrizeListTable' + +// Type Imports + +const GamePrizeList = () => { + return ( + + + + + + ) +} + +export default GamePrizeList