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

290 lines
8.9 KiB
TypeScript
Raw Normal View History

2024-02-02 23:50:24 +00:00
'use client'
2024-05-27 22:20:58 +00:00
import React from 'react'
import ReactDOM from 'react-dom/client'
import Link from 'next/link'
2024-02-02 23:50:24 +00:00
import {
2024-05-27 22:20:58 +00:00
keepPreviousData,
QueryClient,
useQuery,
} from '@tanstack/react-query'
2024-06-04 23:06:47 +00:00
import { format } from 'date-fns'
2024-06-13 02:54:44 +00:00
import Image from "next/legacy/image"
2024-05-27 22:20:58 +00:00
import {
PaginationState,
useReactTable,
getCoreRowModel,
ColumnDef,
flexRender,
} from '@tanstack/react-table'
2024-07-06 08:49:51 +00:00
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
2024-02-02 23:50:24 +00:00
2024-07-10 22:11:18 +00:00
import { fetchStreamData } from '@/app/lib/streams'
2024-07-15 16:07:04 +00:00
import { IStream } from '@futureporn/types'
2024-02-02 23:50:24 +00:00
2024-05-27 22:20:58 +00:00
const queryClient = new QueryClient()
2024-02-02 23:50:24 +00:00
2024-05-27 22:20:58 +00:00
function getStatusClass(value: string) {
switch (value) {
case 'issue':
return 'is-warning';
case 'missing':
return 'is-danger';
case 'good':
return 'is-success';
default:
return '';
}
}
2024-02-02 23:50:24 +00:00
2024-05-27 22:20:58 +00:00
export default function StreamsTable() {
const rerender = React.useReducer(() => ({}), {})[1]
2024-02-02 23:50:24 +00:00
2024-06-04 23:06:47 +00:00
// image & name
2024-05-27 22:20:58 +00:00
// title
// platform
2024-06-04 23:06:47 +00:00
// date & time
2024-05-27 22:20:58 +00:00
// archiveStatus
const columns = React.useMemo<ColumnDef<IStream>[]>(
() => [
{
2024-06-04 23:06:47 +00:00
header: 'VTuber',
accessorFn: d => ({
displayName: d.attributes.vtuber.data?.attributes?.displayName,
image: d.attributes.vtuber.data?.attributes.image,
imageBlur: d.attributes.vtuber.data?.attributes.imageBlur
}),
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"
2024-06-13 02:54:44 +00:00
objectFit='cover'
2024-06-04 23:06:47 +00:00
blurDataURL={imageBlur}
width={32}
height={32}
/>
</figure>
</div>
<div className="column ml-0">
<span>{displayName}</span>
</div>
</div>
</>
)
}
2024-02-02 23:50:24 +00:00
},
2024-05-27 22:20:58 +00:00
{
2024-06-13 05:17:44 +00:00
header: 'Date',
2024-06-04 23:06:47 +00:00
accessorFn: d => format(new Date(d.attributes.date2), 'yyyy-MM-dd HH:mm'),
2024-06-13 02:54:44 +00:00
// accessorFn: d => new Date(d.attributes.date2),
sortingFn: 'datetime',
sortDescFirst: true,
2024-06-04 23:06:47 +00:00
cell: info => (<Link href={`/archive/${info.row.original.attributes.cuid}`}>{info.getValue() as string}</Link>)
2024-05-27 22:20:58 +00:00
},
{
header: 'Platform',
accessorFn: d => [
(d.attributes.isChaturbateStream && 'CB'),
(d.attributes.isFanslyStream && 'Fansly')
2024-07-04 21:20:29 +00:00
].filter(Boolean).join(', ') || '???'
2024-05-27 22:20:58 +00:00
},
{
header: 'Status',
accessorFn: d => {
if (!d.attributes.archiveStatus) return 'missing';
return d.attributes.archiveStatus
}
},
// {
// 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,
})
2024-07-06 08:49:51 +00:00
const { data, error, isPending } = useQuery({
2024-05-27 22:20:58 +00:00
queryKey: ['streams', pagination.pageIndex, pagination.pageSize],
queryFn: () => fetchStreamData(pagination),
placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page,
2024-07-06 08:49:51 +00:00
staleTime: 1000,
2024-05-27 22:20:58 +00:00
}, queryClient)
const defaultData = React.useMemo(() => [], [])
const table = useReactTable({
2024-07-06 08:49:51 +00:00
data: data?.rows ?? defaultData,
2024-05-27 22:20:58 +00:00
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)
2024-07-06 08:49:51 +00:00
rowCount: data?.rowCount, // new in v8.13.0 - alternatively, just pass in `pageCount` directly
2024-05-27 22:20:58 +00:00
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" />
2024-07-06 08:49:51 +00:00
{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 => {
2024-05-27 22:20:58 +00:00
return (
2024-07-06 08:49:51 +00:00
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
2024-05-27 22:20:58 +00:00
)}
2024-07-06 08:49:51 +00:00
</th>
2024-05-27 22:20:58 +00:00
)
})}
</tr>
2024-07-06 08:49:51 +00:00
))}
</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>
2024-05-27 22:20:58 +00:00
<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>
2024-07-06 08:49:51 +00:00
</>}
2024-05-27 22:20:58 +00:00
</div>
)
2024-02-02 23:50:24 +00:00
}