show transcripts in a modal instead of a pop-up

This commit is contained in:
2025-07-05 23:39:14 +02:00
parent f8f5df6919
commit 7b86fb9d82

View File

@@ -2,6 +2,12 @@
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { import {
Table, Table,
TableBody, TableBody,
@@ -41,24 +47,8 @@ function getTranscript(gameNumber: number) {
`https://api.neatqueue.com/api/transcript/1226193436521267223/${gameNumber}` `https://api.neatqueue.com/api/transcript/1226193436521267223/${gameNumber}`
).then((res) => res.json()) ).then((res) => res.json())
} }
function openTranscript(gameNumber: number): void { // This function is now moved inside the GamesTable component
getTranscript(gameNumber) const useColumns = (openTranscriptFn?: (gameNumber: number) => void) => {
.then((html: string) => {
const newWindow = window.open('', '_blank')
if (newWindow) {
newWindow.document.write(html)
newWindow.document.close()
} else {
console.error(
'Failed to open new window - popup blocker may be enabled'
)
}
})
.catch((err) => {
console.error('Failed to load transcript:', err)
})
}
const useColumns = () => {
const format = useFormatter() const format = useFormatter()
const timeZone = useTimeZone() const timeZone = useTimeZone()
const session = useSession() const session = useSession()
@@ -186,7 +176,9 @@ const useColumns = () => {
cell: (info) => ( cell: (info) => (
<Button <Button
size={'sm'} size={'sm'}
onClick={() => openTranscript(info.getValue())} onClick={() =>
openTranscriptFn ? openTranscriptFn(info.getValue()) : null
}
type={'button'} type={'button'}
variant={'ghost'} variant={'ghost'}
> >
@@ -204,7 +196,28 @@ const useColumns = () => {
export function GamesTable({ games }: { games: SelectGames[] }) { export function GamesTable({ games }: { games: SelectGames[] }) {
const [sorting, setSorting] = useState<SortingState>([]) const [sorting, setSorting] = useState<SortingState>([])
const columns = useColumns() const [isDialogOpen, setIsDialogOpen] = useState(false)
const [transcriptContent, setTranscriptContent] = useState<string>('')
const [transcriptGameNumber, setTranscriptGameNumber] = useState<
number | null
>(null)
// New openTranscript function that sets state instead of opening a new window
const openTranscript = (gameNumber: number): void => {
setTranscriptGameNumber(gameNumber)
getTranscript(gameNumber)
.then((html: string) => {
setTranscriptContent(html)
setIsDialogOpen(true)
})
.catch((err) => {
console.error('Failed to load transcript:', err)
})
}
// Pass the openTranscript function to useColumns
const columns = useColumns(openTranscript)
const table = useReactTable({ const table = useReactTable({
data: games, data: games,
columns, columns,
@@ -218,68 +231,92 @@ export function GamesTable({ games }: { games: SelectGames[] }) {
}) })
return ( return (
<div className='rounded-md border'> <>
<Table> <div className='rounded-md border'>
<TableHeader> <Table>
{table.getHeaderGroups().map((headerGroup) => ( <TableHeader>
<TableRow key={headerGroup.id}> {table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => { <TableRow key={headerGroup.id}>
const sortDirection = header.column.getIsSorted() {headerGroup.headers.map((header) => {
return ( const sortDirection = header.column.getIsSorted()
<TableHead key={header.id} className={'px-0'}> return (
<span <TableHead key={header.id} className={'px-0'}>
className={cn( <span
'flex w-full items-center',
(header.column.columnDef.meta as any)?.className
)}
>
<Button
className={cn( className={cn(
header.column.getCanSort() && 'flex w-full items-center',
'cursor-pointer select-none', (header.column.columnDef.meta as any)?.className
(
header.column.columnDef.meta as any
)?.className?.includes('justify-end') &&
'flex-row-reverse'
)} )}
size={'table'}
variant='ghost'
onClick={header.column.getToggleSortingHandler()}
> >
{flexRender( <Button
header.column.columnDef.header, className={cn(
header.getContext() header.column.getCanSort() &&
)} 'cursor-pointer select-none',
{sortDirection ? ( (
<ArrowUp header.column.columnDef.meta as any
className={cn( )?.className?.includes('justify-end') &&
'transition-transform', 'flex-row-reverse'
sortDirection === 'asc' ? 'rotate-180' : '' )}
)} size={'table'}
/> variant='ghost'
) : ( onClick={header.column.getToggleSortingHandler()}
<div className={'h-4 w-4'} /> >
)} {flexRender(
</Button> header.column.columnDef.header,
</span> header.getContext()
</TableHead> )}
) {sortDirection ? (
})} <ArrowUp
</TableRow> className={cn(
))} 'transition-transform',
</TableHeader> sortDirection === 'asc' ? 'rotate-180' : ''
<TableBody> )}
{table.getRowModel().rows.map((row) => ( />
<TableRow key={row.id}> ) : (
{row.getVisibleCells().map((cell) => ( <div className={'h-4 w-4'} />
<TableCell key={cell.id}> )}
{flexRender(cell.column.columnDef.cell, cell.getContext())} </Button>
</TableCell> </span>
))} </TableHead>
</TableRow> )
))} })}
</TableBody> </TableRow>
</Table> ))}
</div> </TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* Transcript Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className='max-h-[80vh] w-full overflow-y-auto sm:max-w-[calc(100%-2rem)] '>
<DialogHeader>
<DialogTitle>
{transcriptGameNumber
? `Game Transcript #${transcriptGameNumber}`
: 'Game Transcript'}
</DialogTitle>
</DialogHeader>
{/* Use iframe to isolate the transcript content and prevent style leakage */}
<div className='!h-[60vh] mt-4 w-full'>
<iframe
srcDoc={transcriptContent}
title={`Game Transcript #${transcriptGameNumber || ''}`}
className='h-full w-full border-0'
sandbox='allow-same-origin'
/>
</div>
</DialogContent>
</Dialog>
</>
) )
} }