fp/services/next/app/components/streams-table.tsx

288 lines
8.7 KiB
TypeScript

'use client'
import React from 'react'
import ReactDOM from 'react-dom/client'
import Link from 'next/link'
import { defaultImageBlur } from '../lib/constants'
import {
keepPreviousData,
QueryClient,
useQuery,
} from '@tanstack/react-query'
import { format } from 'date-fns'
import Image from "next/legacy/image"
import {
PaginationState,
useReactTable,
getCoreRowModel,
ColumnDef,
flexRender,
} from '@tanstack/react-table'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { fetchStreamData } from '@/app/lib/streams'
import { IStream } from '@futureporn/types'
const queryClient = new QueryClient()
function getStatusClass(value: string) {
switch (value) {
case 'issue':
return 'is-warning';
case 'missing':
return 'is-danger';
case 'good':
return 'is-success';
default:
return '';
}
}
export default function StreamsTable() {
const rerender = React.useReducer(() => ({}), {})[1]
// image & name
// title
// platform
// date & time
// archiveStatus
const columns = React.useMemo<ColumnDef<IStream>[]>(
() => [
{
header: 'VTuber',
accessorFn: d => ({
displayName: d.vtuber.display_name,
image: d.vtuber.image,
imageBlur: d.vtuber.image_blur
}),
cell: info => {
const { displayName, image, imageBlur } = info.getValue<{ displayName: string, image: string, imageBlur: string }>();
return (
<>
<div className="columns is-mobile">
<div className="column mr-0 is-flex-grow-0">
<figure className="image is-24x24">
<Image
className="is-rounded"
src={image}
alt={displayName}
placeholder="blur"
objectFit='cover'
blurDataURL={imageBlur || defaultImageBlur}
width={32}
height={32}
/>
</figure>
</div>
<div className="column ml-0">
<span>{displayName}</span>
</div>
</div>
</>
)
}
},
{
header: 'Date',
accessorFn: d => format(new Date(d.date), 'yyyy-MM-dd HH:mm'),
sortingFn: 'datetime',
sortDescFirst: true,
cell: info => (<Link href={`/archive/${info.row.original.uuid}`}>{info.getValue() as string}</Link>)
},
{
header: 'Platform',
accessorFn: d => [
(d.is_chaturbate_stream && 'CB'),
(d.is_fansly_stream && 'Fansly')
].filter(Boolean).join(', ') || '???'
},
{
header: 'Status',
accessorFn: d => {
if (!d.archive_status) return 'missing';
return d.archive_status
}
},
// {
// header: 'Name',
// footer: props => props.column.id,
// columns: [
// {
// accessorKey: 'firstName',
// cell: info => info.getValue(),
// footer: props => props.column.id,
// },
// {
// accessorFn: row => row.lastName,
// id: 'lastName',
// cell: info => info.getValue(),
// header: () => <span>Last Name</span>,
// footer: props => props.column.id,
// },
// ],
// },
],
[]
)
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 50,
})
const { data, error, isPending } = useQuery({
queryKey: ['streams', pagination.pageIndex, pagination.pageSize],
queryFn: () => fetchStreamData(pagination),
placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page,
staleTime: 1000,
}, queryClient)
const defaultData = React.useMemo(() => [], [])
const table = useReactTable({
data: data?.rows ?? defaultData,
columns,
// pageCount: dataQuery.data?.pageCount ?? -1, //you can now pass in `rowCount` instead of pageCount and `pageCount` will be calculated internally (new in v8.13.0)
rowCount: data?.rowCount, // new in v8.13.0 - alternatively, just pass in `pageCount` directly
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true, //we're doing manual "server-side" pagination
// getPaginationRowModel: getPaginationRowModel(), // If only doing manual pagination, you don't need this
debugTable: true,
})
return (
<div className="p-2">
<div className="h-2" />
{isPending && <FontAwesomeIcon className="mt-5 fa-spin-pulse" icon={faSpinner} ></FontAwesomeIcon>}
{!isPending && <>
<table className='table is-hoverable is-fullwidth'>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => {
return (
<tr key={row.id}>
{row.getVisibleCells().map(cell => {
return (
<td
className={getStatusClass(cell.getValue() as string)}
key={cell.id}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
<div className="columns is-mobile is-vcentered">
<div className='column is-half'>
<button
className="button border rounded mx-1"
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
className="button border rounded mx-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
className="button border rounded mx-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
className="button border rounded mx-1"
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage()}
>
{'>>'}
</button>
</div>
<div className='column is-half'>
<span>Page </span>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}{table.getPageCount().toLocaleString()}
</strong>
</div>
</div>
{/* second row with page number input and pages-per-screen select */}
<div className='columns is-mobile is-vcentered'>
<div className='column is-2 '>
<span className='is-text-centered'>Go to page:</span>
</div>
<div className='column is-3'>
<input
type="number"
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
className="input"
/>
</div>
<div className='column is-5'>
<div className="select">
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[20, 50, 100].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</div>
</div>
</>}
</div>
)
}