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

319 lines
12 KiB
TypeScript

'use client'
import {
Column,
Table as ReactTable,
useReactTable,
ColumnFiltersState,
getCoreRowModel,
getFilteredRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getPaginationRowModel,
sortingFns,
getSortedRowModel,
FilterFn,
SortingFn,
ColumnDef,
flexRender,
FilterFns,
ColumnOrderState,
createColumnHelper,
} from '@tanstack/react-table'
import Image from 'next/image';
import { useState } from "react";
import { IStream } from "@/lib/streams";
import { LocalizedDate } from './localized-date';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleLeft, faAngleRight, faAnglesLeft, faAnglesRight, faChevronCircleRight, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import Link from 'next/link';
function Filter({
column,
table,
}: {
column: Column<any, any>
table: ReactTable<any>
}) {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id)
const columnFilterValue = column.getFilterValue()
if (typeof firstValue === 'number') {
return (
<div className="flex space-x-2">
<input
type="number"
value={(columnFilterValue as [number, number])?.[0] ?? ''}
onChange={e =>
column.setFilterValue((old: [number, number]) => [
e.target.value,
old?.[1],
])
}
placeholder={`Min`}
className="w-24 border shadow rounded"
/>
<input
type="number"
value={(columnFilterValue as [number, number])?.[1] ?? ''}
onChange={e =>
column.setFilterValue((old: [number, number]) => [
old?.[0],
e.target.value,
])
}
placeholder={`Max`}
className="w-24 border shadow rounded"
/>
</div>
)
}
if (typeof firstValue === 'boolean') {
return (
<>
<div className='select'>
<select
onChange={(evt) => {
if (evt.target.value === "any")
column?.setFilterValue(null);
if (evt.target.value === "yes")
column?.setFilterValue(true);
if (evt.target.value === "no")
column?.setFilterValue(false);
}}
>
<option>any</option>
<option>yes</option>
<option>no</option>
</select>
</div>
</>
)
}
return (
<input
type="text"
value={(columnFilterValue ?? '') as string}
onChange={e => column.setFilterValue(e.target.value)}
placeholder={`Search...`}
className="input"
/>
)
}
const archiveStatusClassName = (archiveStatus: string): string => {
if (archiveStatus === 'missing') return 'is-danger';
if (archiveStatus === 'issue') return 'is-warning';
if (archiveStatus === 'good') return 'is-success';
return 'is-info';
};
export default function StreamsTable({ streams }: { streams: IStream[] }) {
const columnHelper = createColumnHelper<IStream>()
const columns = [
columnHelper.accessor('attributes.cuid', {
cell: info => <Link href={`/streams/${info.getValue()}`}>{info.getValue()}</Link>,
header: () => <span>ID</span>
}),
columnHelper.accessor('attributes.vtuber.data.attributes.image', {
cell: info => <figure className='image is-24x24'><Image className="is-rounded" width={24} height={24} alt="" src={info.getValue()}></Image></figure>,
header: () => <span></span>,
enableColumnFilter: false
}),
columnHelper.accessor('attributes.vtuber.data.attributes.displayName', {
cell: info => info.getValue(),
header: () => <span>VTuber</span>
}),
columnHelper.accessor('attributes.date', {
cell: info => <LocalizedDate date={new Date(info.getValue())}/>,
header: () => <span>Date</span>
}),
columnHelper.accessor('attributes.isChaturbateStream', {
id: 'isChaturbateStream',
cell: info => info.getValue() === true ? 'yes' : 'no',
header: () => <span>Chaturbate</span>
}),
columnHelper.accessor('attributes.isFanslyStream', {
id: 'isFanslyStream',
cell: info => info.getValue() === true ? 'yes' : 'no',
header: () => <span>Fansly</span>
}),
columnHelper.accessor('attributes.archiveStatus', {
cell: info => <div className={`tag ${archiveStatusClassName(info.getValue())}`} >{info.getValue()}</div>,
header: () => <span>Status</span>
})
]
const [columnVisibility, setColumnVisibility] = useState({})
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [data, setData] = useState(() => streams)
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
state: {
columnVisibility,
columnOrder,
columnFilters
},
onColumnOrderChange: setColumnOrder,
onColumnFiltersChange: setColumnFilters,
})
return (
<>
<div className="p-2">
<div className="h-2" />
<table className='table'>
<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()
)}
{header.column.getCanFilter() ? (
<div>
<Filter column={header.column} table={table} />
</div>
) : null}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => {
return (
<tr key={row.id}>
{row.getVisibleCells().map(cell => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
<div className="columns is-multiline is-mobile" />
<div className="column is-12">
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<FontAwesomeIcon className='fa-solid fa-angles-left' icon={faAnglesLeft}></FontAwesomeIcon>
</button>
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<FontAwesomeIcon className='fa-solid fa-angle-left' icon={faAngleLeft}></FontAwesomeIcon>
</button>
<button
className="button icon is-rounded is-medium p-1 m-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<FontAwesomeIcon className="fa-solid fa-angle-right" icon={faAngleRight}></FontAwesomeIcon>
</button>
<button
className="button icon is-medium is-rounded p-1 m-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<FontAwesomeIcon className='fa-solid fa-angles-right' icon={faAnglesRight}></FontAwesomeIcon>
</button>
</div>
<div className='column is-2'>
<div className=''>
<div className=''>
<span className='mr-1'>Page</span>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</div>
</div>
<div className=''>
<label className='label'>
Go to page:
</label>
<div className="control is-expanded">
<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 p-1"
/>
</div>
</div>
</div>
<div className='column is-2'>
<div className="m-1">
<span className='mr-1'>Page</span>
</div>
<div className='select'>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</div>
</div>
</>
)
}