1
0
forked from a/lifeto-shop
lifeto-shop/src/lib/table/tanstack.tsx
2025-06-20 01:18:37 -05:00

147 lines
4.4 KiB
TypeScript

import { createColumnHelper } from '@tanstack/react-table'
import { useAtomValue, useSetAtom } from 'jotai'
import { useMemo } from 'react'
import { currentItemSelectionAtom, itemSelectionSetActionAtom } 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 selected = useMemo(() => {
return c[0].has(row.original.item.id)
}, [c])
return (
<button
type="button"
className={`no-select flex flex-row ${row.original.status?.selected ? 'animate-pulse' : ''}`}
onClick={_e => {
setItemSelection({
[row.original.item.id]: selected ? undefined : row.original.item.item_count,
})
}}
>
<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 select-none"
/>
</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 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
return (
<div
className={`flex flex-row select-none ${row.original.status?.selected ? 'bg-gray-200' : ''}`}
>
<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 }) {
return (
<div className="flex flex-row whitespace-pre">
<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