240 lines
7.7 KiB
TypeScript
240 lines
7.7 KiB
TypeScript
import { createColumnHelper } from '@tanstack/react-table'
|
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
|
import { useMemo } from 'react'
|
|
import {
|
|
currentItemSelectionAtom,
|
|
itemSelectionSetActionAtom,
|
|
mouseDragSelectionStateAtom,
|
|
} from '@/state/atoms'
|
|
import { StatsColumns } from '../columns'
|
|
import { ItemWithSelection } from './defs'
|
|
|
|
const ch = createColumnHelper<ItemWithSelection>()
|
|
|
|
const columns = {
|
|
icon: ch.display({
|
|
id: 'icon',
|
|
header: function Component(_col) {
|
|
return <div className="flex flex-row justify-center"></div>
|
|
},
|
|
cell: function Component({ row }) {
|
|
const setItemSelection = useSetAtom(itemSelectionSetActionAtom)
|
|
const c = useAtomValue(currentItemSelectionAtom)
|
|
const [dragState, setDragState] = useAtom(mouseDragSelectionStateAtom)
|
|
const selected = useMemo(() => {
|
|
return c[0].has(row.original.item.id)
|
|
}, [c])
|
|
|
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
e.preventDefault()
|
|
const newSelected = !selected
|
|
setItemSelection({
|
|
[row.original.item.id]: newSelected ? row.original.item.item_count : undefined,
|
|
})
|
|
setDragState({
|
|
isDragging: true,
|
|
lastAction: newSelected ? 'select' : 'deselect',
|
|
lastItemId: row.original.item.id,
|
|
})
|
|
}
|
|
|
|
const handleMouseEnter = () => {
|
|
if (dragState.isDragging && dragState.lastItemId !== row.original.item.id) {
|
|
if (dragState.lastAction === 'select' && !selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: row.original.item.item_count,
|
|
})
|
|
} else if (dragState.lastAction === 'deselect' && selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: undefined,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={`no-select flex flex-row ${row.original.status?.selected ? 'animate-pulse' : ''}`}
|
|
onMouseDown={handleMouseDown}
|
|
onMouseEnter={handleMouseEnter}
|
|
>
|
|
<div className="flex flex-row w-6 h-6 justify-center">
|
|
<img
|
|
src={row.original.item.item_image || ''}
|
|
alt="icon"
|
|
className="select-none object-contain pointer-events-none"
|
|
draggable={false}
|
|
/>
|
|
</div>
|
|
</button>
|
|
)
|
|
},
|
|
}),
|
|
count: ch.display({
|
|
id: 'count',
|
|
header: function Component(_col) {
|
|
return <div className="flex flex-row justify-center">#</div>
|
|
},
|
|
cell: function Component({ row }) {
|
|
const c = useAtomValue(currentItemSelectionAtom)
|
|
const setItemSelection = useSetAtom(itemSelectionSetActionAtom)
|
|
const dragState = useAtomValue(mouseDragSelectionStateAtom)
|
|
const currentValue = useMemo(() => {
|
|
const got = c[0].get(row.original.item.id)
|
|
if (got !== undefined) {
|
|
return got.toString()
|
|
}
|
|
return ''
|
|
}, [c])
|
|
const itemCount = row.original.item.item_count
|
|
const selected = useMemo(() => {
|
|
return c[0].has(row.original.item.id)
|
|
}, [c])
|
|
|
|
const handleMouseEnter = () => {
|
|
if (dragState.isDragging && dragState.lastItemId !== row.original.item.id) {
|
|
if (dragState.lastAction === 'select' && !selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: row.original.item.item_count,
|
|
})
|
|
} else if (dragState.lastAction === 'deselect' && selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: undefined,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
// biome-ignore lint/a11y/useSemanticElements: Using div for layout with input child
|
|
// biome-ignore lint/a11y/noStaticElementInteractions: Mouse interaction needed for drag select
|
|
<div
|
|
className={`flex flex-row select-none ${row.original.status?.selected ? 'bg-gray-200' : ''}`}
|
|
onMouseEnter={handleMouseEnter}
|
|
>
|
|
<input
|
|
className="w-10 text-center "
|
|
value={currentValue}
|
|
onChange={e => {
|
|
if (e.target.value === '') {
|
|
setItemSelection({ [row.original.item.id]: undefined })
|
|
return
|
|
}
|
|
if (e.target.value === '-') {
|
|
setItemSelection({
|
|
[row.original.item.id]: itemCount,
|
|
})
|
|
}
|
|
let parsedInt = parseInt(e.target.value)
|
|
if (Number.isNaN(parsedInt)) {
|
|
return
|
|
}
|
|
if (parsedInt > itemCount) {
|
|
parsedInt = itemCount
|
|
}
|
|
setItemSelection({
|
|
[row.original.item.id]: parsedInt,
|
|
})
|
|
}}
|
|
placeholder={itemCount.toString()}
|
|
/>
|
|
</div>
|
|
)
|
|
},
|
|
}),
|
|
name: ch.display({
|
|
id: 'name',
|
|
header: _col => {
|
|
return <div className="flex flex-row text-sm">name</div>
|
|
},
|
|
cell: function Component({ row }) {
|
|
const c = useAtomValue(currentItemSelectionAtom)
|
|
const setItemSelection = useSetAtom(itemSelectionSetActionAtom)
|
|
const [dragState, setDragState] = useAtom(mouseDragSelectionStateAtom)
|
|
const selected = useMemo(() => {
|
|
return c[0].has(row.original.item.id)
|
|
}, [c])
|
|
|
|
const handleMouseEnter = () => {
|
|
if (dragState.isDragging && dragState.lastItemId !== row.original.item.id) {
|
|
if (dragState.lastAction === 'select' && !selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: row.original.item.item_count,
|
|
})
|
|
} else if (dragState.lastAction === 'deselect' && selected) {
|
|
setItemSelection({
|
|
[row.original.item.id]: undefined,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
e.preventDefault()
|
|
const newSelected = !selected
|
|
setItemSelection({
|
|
[row.original.item.id]: newSelected ? row.original.item.item_count : undefined,
|
|
})
|
|
setDragState({
|
|
isDragging: true,
|
|
lastAction: newSelected ? 'select' : 'deselect',
|
|
lastItemId: row.original.item.id,
|
|
})
|
|
}
|
|
|
|
return (
|
|
// biome-ignore lint/a11y/useSemanticElements: Using div for text content
|
|
// biome-ignore lint/a11y/noStaticElementInteractions: Mouse interaction needed for drag select
|
|
<div
|
|
className="flex flex-row whitespace-pre cursor-pointer select-none hover:bg-gray-100"
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseDown={handleMouseDown}
|
|
>
|
|
<span>{row.original.item.item_name}</span>
|
|
</div>
|
|
)
|
|
},
|
|
}),
|
|
slots: ch.display({
|
|
id: 'slots',
|
|
header: _col => {
|
|
return <div className="flex flex-row text-sm">slots</div>
|
|
},
|
|
cell: function Component({ row }) {
|
|
return (
|
|
<div className="flex flex-row justify-center">
|
|
<span>{row.original.item.item_slots}</span>
|
|
</div>
|
|
)
|
|
},
|
|
}),
|
|
stats: ch.group({
|
|
id: 'stats',
|
|
header: _col => {
|
|
return <div className="flex flex-row text-sm">stats</div>
|
|
},
|
|
columns: [
|
|
...StatsColumns.map(c => {
|
|
return ch.display({
|
|
id: `stats.${c}`,
|
|
header: _col => {
|
|
return <div className="flex flex-row text-sm justify-center">{c}</div>
|
|
},
|
|
cell: function Component({ row }) {
|
|
const stats = row.original.item.stats
|
|
const stat = stats ? stats[c] : ''
|
|
return (
|
|
<div className={`flex flex-row justify-start ${stat ? 'border' : ''}`}>
|
|
<span>{stat}</span>
|
|
</div>
|
|
)
|
|
},
|
|
})
|
|
}),
|
|
],
|
|
}),
|
|
} as const
|
|
|
|
export const InventoryColumns = columns
|