2026-04-11 22:02:57 +07:00
|
|
|
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
2026-04-11 17:04:14 +07:00
|
|
|
import * as React from 'react'
|
2026-04-11 22:02:57 +07:00
|
|
|
import { Fragment, useState } from 'react'
|
2025-04-05 06:07:26 +07:00
|
|
|
import {
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
EditOutlined,
|
|
|
|
|
DeleteOutlined,
|
|
|
|
|
PlusOutlined,
|
2026-04-11 17:04:14 +07:00
|
|
|
} from '@ant-design/icons'
|
|
|
|
|
import { Button, Input } from '@imphnen-frontend-service/ui/atoms'
|
|
|
|
|
import { DataTable } from '@imphnen-frontend-service/ui/organisms'
|
2025-04-05 06:07:26 +07:00
|
|
|
import {
|
|
|
|
|
ColumnDef,
|
|
|
|
|
getCoreRowModel,
|
|
|
|
|
getPaginationRowModel,
|
|
|
|
|
PaginationState,
|
|
|
|
|
useReactTable,
|
|
|
|
|
RowSelectionState,
|
2026-04-11 17:04:14 +07:00
|
|
|
} from '@tanstack/react-table'
|
2026-04-05 17:08:42 +07:00
|
|
|
import {
|
|
|
|
|
useGachaItemList,
|
|
|
|
|
useDeleteGachaItem,
|
|
|
|
|
TGachaItemDto,
|
2026-04-11 17:04:14 +07:00
|
|
|
} from '@imphnen-frontend-service/service'
|
2026-04-11 22:02:57 +07:00
|
|
|
import { toast } from 'sonner'
|
2025-04-05 06:07:26 +07:00
|
|
|
|
2026-04-11 17:04:14 +07:00
|
|
|
export const Route = createFileRoute('/_authenticated/gacha-roll')({
|
|
|
|
|
component: GachaRollPage,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function GachaRollPage() {
|
2026-04-11 22:02:57 +07:00
|
|
|
const navigate = useNavigate()
|
2026-04-11 17:04:14 +07:00
|
|
|
const [search, setSearch] = useState('')
|
2026-04-11 22:02:57 +07:00
|
|
|
const [deleteId, setDeleteId] = useState<string | null>(null)
|
2025-04-05 06:07:26 +07:00
|
|
|
|
|
|
|
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
|
|
|
pageIndex: 0,
|
|
|
|
|
pageSize: 9,
|
2026-04-11 17:04:14 +07:00
|
|
|
})
|
2025-04-05 06:07:26 +07:00
|
|
|
|
2026-04-11 17:04:14 +07:00
|
|
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
2025-04-05 06:07:26 +07:00
|
|
|
|
2026-04-05 17:08:42 +07:00
|
|
|
const { data: itemsData, isLoading } = useGachaItemList({
|
|
|
|
|
search,
|
|
|
|
|
page: pagination.pageIndex + 1,
|
|
|
|
|
per_page: pagination.pageSize,
|
2026-04-11 17:04:14 +07:00
|
|
|
})
|
|
|
|
|
const deleteItem = useDeleteGachaItem()
|
2026-04-05 17:08:42 +07:00
|
|
|
|
2026-04-11 17:04:14 +07:00
|
|
|
const items: TGachaItemDto[] = itemsData?.data ?? []
|
|
|
|
|
const totalItems = itemsData?.meta?.total ?? items.length
|
2026-04-05 17:08:42 +07:00
|
|
|
|
2026-04-11 22:02:57 +07:00
|
|
|
const handleDelete = async (id: string) => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteItem.mutateAsync(id)
|
|
|
|
|
toast.success('Item berhasil dihapus')
|
|
|
|
|
setDeleteId(null)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error)
|
|
|
|
|
toast.error('Item gagal dihapus')
|
2026-04-05 17:08:42 +07:00
|
|
|
}
|
2026-04-11 17:04:14 +07:00
|
|
|
}
|
2026-04-05 17:08:42 +07:00
|
|
|
|
|
|
|
|
const columns: ColumnDef<TGachaItemDto>[] = [
|
2025-04-05 06:07:26 +07:00
|
|
|
{
|
|
|
|
|
id: 'select',
|
|
|
|
|
header: ({ table }) => (
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
className="rounded"
|
|
|
|
|
checked={table.getIsAllRowsSelected()}
|
|
|
|
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
cell: ({ row }) => (
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
className="rounded"
|
|
|
|
|
checked={row.getIsSelected()}
|
|
|
|
|
onChange={row.getToggleSelectedHandler()}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
header: 'No',
|
|
|
|
|
accessorKey: 'id',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
header: 'Nama Item',
|
|
|
|
|
accessorKey: 'name',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
header: 'Action',
|
2026-04-05 17:08:42 +07:00
|
|
|
cell: ({ row }) => (
|
2025-04-05 06:07:26 +07:00
|
|
|
<div className="flex gap-[8px]">
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={(e) => {
|
2026-04-11 17:04:14 +07:00
|
|
|
e.stopPropagation()
|
2026-04-11 22:02:57 +07:00
|
|
|
navigate({ to: '/gacha-roll/$id', params: { id: row.original.id } })
|
2025-04-05 06:07:26 +07:00
|
|
|
}}
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<EditOutlined /> Update
|
|
|
|
|
</Button>
|
2026-04-11 22:02:57 +07:00
|
|
|
{deleteId === row.original.id ? (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="text-label2 text-neutral-500">Yakin?</span>
|
|
|
|
|
<Button
|
|
|
|
|
variant="danger"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
handleDelete(row.original.id)
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Ya
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
setDeleteId(null)
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Batal
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<Button
|
|
|
|
|
variant="danger"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
setDeleteId(row.original.id)
|
|
|
|
|
}}
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<DeleteOutlined /> Delete
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
2025-04-05 06:07:26 +07:00
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
},
|
2026-04-11 17:04:14 +07:00
|
|
|
]
|
2025-04-05 06:07:26 +07:00
|
|
|
|
|
|
|
|
const table = useReactTable({
|
2026-04-05 17:08:42 +07:00
|
|
|
data: items,
|
2025-04-05 06:07:26 +07:00
|
|
|
columns,
|
|
|
|
|
state: {
|
|
|
|
|
pagination,
|
|
|
|
|
rowSelection,
|
|
|
|
|
},
|
|
|
|
|
enableRowSelection: true,
|
|
|
|
|
onRowSelectionChange: setRowSelection,
|
|
|
|
|
getCoreRowModel: getCoreRowModel(),
|
|
|
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
|
|
|
onPaginationChange: setPagination,
|
2026-04-05 17:08:42 +07:00
|
|
|
pageCount: Math.ceil(totalItems / pagination.pageSize),
|
|
|
|
|
manualPagination: true,
|
2026-04-11 17:04:14 +07:00
|
|
|
})
|
2025-04-05 06:07:26 +07:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
|
|
|
|
|
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
|
|
|
|
|
<h1 className="text-p2 font-semibold">Gacha Roll</h1>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
|
|
|
|
|
<div className="flex justify-between items-center gap-8 mb-2">
|
|
|
|
|
<div className="relative w-full">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Cari berdasarkan nama item"
|
|
|
|
|
className="pl-12 w-full max-h-full"
|
2026-04-05 17:08:42 +07:00
|
|
|
value={search}
|
|
|
|
|
onChange={(e) => setSearch(e.target.value)}
|
2025-04-05 06:07:26 +07:00
|
|
|
/>
|
|
|
|
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
|
|
|
|
|
<SearchOutlined />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
size="md"
|
|
|
|
|
className="flex gap-3 text-nowrap"
|
2026-04-11 22:02:57 +07:00
|
|
|
onClick={() => navigate({ to: '/gacha-roll/create' })}
|
2025-04-05 06:07:26 +07:00
|
|
|
>
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
Tambah Item
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-05 17:08:42 +07:00
|
|
|
{isLoading ? (
|
|
|
|
|
<div className="text-center py-8 text-neutral-400">Loading...</div>
|
|
|
|
|
) : (
|
|
|
|
|
<DataTable data={items} columns={columns} table={table} />
|
|
|
|
|
)}
|
2025-04-05 06:07:26 +07:00
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
</Fragment>
|
2026-04-11 17:04:14 +07:00
|
|
|
)
|
|
|
|
|
}
|