1
0
forked from a/lifeto-shop
lifeto-shop/src/components/inventory/index.tsx

189 lines
6.6 KiB
TypeScript
Raw Normal View History

2025-06-20 05:41:10 +00:00
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
2025-06-23 06:33:03 +00:00
import { useEffect } from 'react'
2025-06-20 05:41:10 +00:00
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
import {
clearItemSelectionActionAtom,
2025-06-23 06:33:03 +00:00
currentCharacterInventoryAtom,
2025-06-20 05:41:10 +00:00
filteredCharacterItemsAtom,
inventoryPageRangeAtom,
itemSelectionSelectAllFilterActionAtom,
itemSelectionSelectAllPageActionAtom,
2025-06-23 06:33:03 +00:00
moveSelectedItemsAtom,
openMoveConfirmationAtom,
2025-06-20 05:41:10 +00:00
paginateInventoryActionAtom,
preferenceInventorySearch,
selectedCharacterAtom,
} from '@/state/atoms'
2025-06-23 06:33:03 +00:00
import { MoveConfirmationPopup } from './MoveConfirmationPopup'
2025-06-20 05:41:10 +00:00
import { InventoryTargetSelector } from './movetarget'
import { InventoryTable } from './table'
2025-06-23 06:33:03 +00:00
import { InventoryFilters } from './InventoryFilters'
2025-05-25 05:17:41 +00:00
2025-06-23 06:33:03 +00:00
const InventoryRangeDisplay = () => {
2025-05-25 05:17:41 +00:00
const inventoryRange = useAtomValue(inventoryPageRangeAtom)
const items = useAtomValue(filteredCharacterItemsAtom)
2025-06-23 06:33:03 +00:00
2025-06-20 05:41:10 +00:00
return (
2025-06-23 06:33:03 +00:00
<div className="flex items-center px-2 bg-yellow-100 border border-black-1 whitespace-pre select-none">
{inventoryRange.start}..{inventoryRange.end}/{items.length}
2025-05-25 05:17:41 +00:00
</div>
2025-06-20 05:41:10 +00:00
)
2025-05-25 05:17:41 +00:00
}
2025-06-23 06:33:03 +00:00
2025-05-25 05:17:41 +00:00
export const Inventory = () => {
const selectedCharacter = useAtomValue(selectedCharacterAtom)
const clearItemSelection = useSetAtom(clearItemSelectionActionAtom)
2025-06-23 06:33:03 +00:00
const { refetch: refetchInventory } = useAtomValue(currentCharacterInventoryAtom)
2025-05-25 05:17:41 +00:00
const addPageItemSelection = useSetAtom(itemSelectionSelectAllPageActionAtom)
const addFilterItemSelection = useSetAtom(itemSelectionSelectAllFilterActionAtom)
const [search, setSearch] = useAtom(preferenceInventorySearch)
const paginateInventory = useSetAtom(paginateInventoryActionAtom)
2025-06-23 06:33:03 +00:00
const openMoveConfirmation = useSetAtom(openMoveConfirmationAtom)
const moveSelectedItems = useSetAtom(moveSelectedItemsAtom)
// Add keyboard navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Don't paginate if user is typing in an input
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
return
}
if (e.key === 'ArrowLeft') {
e.preventDefault()
paginateInventory(-1)
} else if (e.key === 'ArrowRight') {
e.preventDefault()
paginateInventory(1)
}
}
window.addEventListener('keydown', handleKeyDown)
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
}, [paginateInventory])
2025-05-25 05:17:41 +00:00
2025-06-20 05:41:10 +00:00
if (!selectedCharacter) {
return <div>select a character</div>
2025-05-25 05:17:41 +00:00
}
2025-06-20 05:41:10 +00:00
return (
<div className={`flex flex-col h-full w-full`}>
<div className="flex flex-col py-2 flex-0 justify-between h-full">
2025-06-23 06:33:03 +00:00
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-0 items-center justify-between">
<div className="flex flex-row gap-0 items-stretch">
<button
type="button"
className="hover:cursor-pointer border border-black-1 bg-blue-200 hover:bg-blue-300 px-2 py-1"
onClick={() => {
if (selectedCharacter) {
refetchInventory()
}
}}
title="Refresh inventory"
>
</button>
<input
type="text"
value={search}
className="border border-black-1 px-2 py-1"
placeholder="search..."
onChange={e => {
setSearch(e.target.value)
}}
/>
<button
type="button"
className="hover:cursor-pointer border border-black-1 bg-green-200 hover:bg-green-300 px-2 flex items-center justify-center"
onClick={() => {
paginateInventory(-1)
}}
aria-label="Previous page"
title="Previous page (← arrow key)"
>
<FaArrowLeft />
</button>
<button
type="button"
className="hover:cursor-pointer border border-black-1 bg-green-200 hover:bg-green-300 px-2 flex items-center justify-center"
onClick={() => {
paginateInventory(1)
}}
aria-label="Next page"
title="Next page (→ arrow key)"
>
<FaArrowRight />
</button>
<InventoryRangeDisplay />
</div>
<div className="flex flex-row gap-0">
<InventoryTargetSelector />
<button
type="button"
onClick={async e => {
if (e.shiftKey) {
// Shift+click: skip confirmation
const result = await moveSelectedItems()
if (result.successCount > 0) {
clearItemSelection()
}
} else {
// Normal click: show confirmation
openMoveConfirmation()
}
}}
className="hover:cursor-pointer whitespace-preborder border-black-1 bg-orange-200 hover:bg-orange-300 px-2 py-1"
title="Click to move with confirmation, Shift+Click to move immediately"
>
Move Selected
</button>
</div>
</div>
<div className="flex flex-row gap-0">
2025-06-20 06:18:37 +00:00
<button
type="button"
2025-06-23 06:33:03 +00:00
className="whitespace-pre bg-purple-200 px-2 py-1 hover:cursor-pointer hover:bg-purple-300 border border-black-1"
2025-06-20 05:41:10 +00:00
onClick={() => {
2025-06-23 06:33:03 +00:00
addFilterItemSelection()
2025-06-20 05:41:10 +00:00
}}
>
select filtered
2025-06-20 06:18:37 +00:00
</button>
<button
type="button"
2025-06-23 06:33:03 +00:00
className="whitespace-pre bg-cyan-200 px-2 py-1 hover:cursor-pointer hover:bg-cyan-300 border border-black-1"
2025-06-20 05:41:10 +00:00
onClick={() => {
2025-06-23 06:33:03 +00:00
addPageItemSelection()
2025-06-20 05:41:10 +00:00
}}
>
select page
2025-06-20 06:18:37 +00:00
</button>
<button
type="button"
2025-06-23 06:33:03 +00:00
className="whitespace-pre bg-red-200 px-2 py-1 hover:cursor-pointer hover:bg-red-300 border border-black-1"
2025-06-20 05:41:10 +00:00
onClick={() => {
clearItemSelection()
}}
>
2025-06-23 06:33:03 +00:00
clear
2025-06-20 06:18:37 +00:00
</button>
2025-06-20 05:41:10 +00:00
</div>
2025-05-25 05:17:41 +00:00
</div>
</div>
2025-06-23 06:33:03 +00:00
<div className="flex flex-row justify-between items-center">
<InventoryFilters />
<InventoryRangeDisplay />
</div>
2025-06-20 05:41:10 +00:00
<div className="flex flex-col flex-1 h-full border border-black-2">
<InventoryTable />
</div>
2025-06-23 06:33:03 +00:00
<MoveConfirmationPopup />
2025-05-25 05:17:41 +00:00
</div>
2025-06-20 05:41:10 +00:00
)
2025-05-25 05:17:41 +00:00
}