Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#525 List search: added SearchBar, added new filter to inventory #526

Merged
merged 10 commits into from
Nov 29, 2024
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"typeface-francois-one": "0.0.71",
"typeface-public-sans": "^1.1.4",
"url": "^0.11.0",
"usehooks-ts": "^3.1.0",
"uuid": "^3.4.0",
"zlib": "npm:browserify-zlib@^0.2.0"
},
Expand Down
27 changes: 25 additions & 2 deletions src/components/Cellar/CellarInventoryTabPanel.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/** @typedef {import("../../index").farmhand.keg} keg */
import React, { useContext } from 'react'
import React, { useContext, useState } from 'react'
import { number } from 'prop-types'
import Divider from '@mui/material/Divider/index.js'
import Card from '@mui/material/Card/index.js'
import CardContent from '@mui/material/CardContent/index.js'
import ReactMarkdown from 'react-markdown'

import SearchBar from '../SearchBar/index.js'
import FarmhandContext from '../Farmhand/Farmhand.context.js'
import {
KEG_INTEREST_RATE,
Expand All @@ -15,6 +16,8 @@ import {
} from '../../constants.js'

import { integerString } from '../../utils/index.js'
import { itemsMap } from '../../data/maps.js'
import { FERMENTED_CROP_NAME } from '../../templates.js'

import { TabPanel } from './TabPanel/index.js'
import { Keg } from './Keg.js'
Expand All @@ -25,6 +28,8 @@ import { Keg } from './Keg.js'
* @param {number} props.currentTab
*/
export const CellarInventoryTabPanel = ({ index, currentTab }) => {
const [searchQuery, setSearchQuery] = useState('')

/**
* @type {{
* gameState: {
Expand All @@ -37,14 +42,32 @@ export const CellarInventoryTabPanel = ({ index, currentTab }) => {
gameState: { cellarInventory, purchasedCellar },
} = useContext(FarmhandContext)

const searchTerms = searchQuery
.toLowerCase()
.split(' ')
.filter(term => term.length > 0)

const filteredKegs = cellarInventory.filter(keg => {
const item = itemsMap[keg.itemId]
const itemName = item.name.toLowerCase()
const fermentationRecipeName = `${FERMENTED_CROP_NAME}${itemName}`

return searchTerms.every(
term => fermentationRecipeName.includes(term) || itemName.includes(term)
)
})

return (
<TabPanel value={currentTab} index={index}>
<h3>
Capacity: {integerString(cellarInventory.length)} /{' '}
{integerString(PURCHASEABLE_CELLARS.get(purchasedCellar).space)}
</h3>
{cellarInventory.length > 0 && (
<SearchBar placeholder="Search kegs..." onSearch={setSearchQuery} />
)}
<ul className="card-list">
{cellarInventory.map(keg => (
{filteredKegs.map(keg => (
<li key={keg.id}>
<Keg keg={keg} />
</li>
Expand Down
223 changes: 142 additions & 81 deletions src/components/CowPenContextMenu/CowPenContextMenu.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { array, func, number, object, string } from 'prop-types'
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward.js'
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward.js'
Expand All @@ -23,9 +23,9 @@ import { PURCHASEABLE_COW_PENS } from '../../constants.js'
import cowShopInventory from '../../data/shop-inventory-cow.js'

import CowCard from '../CowCard/index.js'
import SearchBar from '../SearchBar/index.js'

import { TabPanel, a11yProps } from './TabPanel/index.js'

import './CowPenContextMenu.sass'

const { AGE, COLOR, GENDER, HAPPINESS, VALUE, WEIGHT } = enumify([
Expand Down Expand Up @@ -84,6 +84,23 @@ export const CowPenContextMenu = ({
const [sortType, setSortType] = useState(AGE)
const [isAscending, setIsAscending] = useState(false)
const [currentTab, setCurrentTab] = useState(0)
const [searchQuery, setSearchQuery] = useState('')

useEffect(() => {
setSearchQuery('')
}, [currentTab])

const filteredCowInventory = searchQuery
? cowInventory.filter(cow =>
cow.name.toLowerCase().includes(searchQuery.toLowerCase())
)
: cowInventory

const filteredShopInventory = searchQuery
? cowShopInventory.filter(item =>
item.name.toLowerCase().includes(searchQuery.toLowerCase())
)
: cowShopInventory

return (
<div className="CowPenContextMenu">
Expand All @@ -108,95 +125,139 @@ export const CowPenContextMenu = ({
Capacity: {cowInventory.length} /{' '}
{PURCHASEABLE_COW_PENS.get(purchasedCowPen).cows}
</h3>
{cowInventory.length > 1 && (
<div {...{ className: 'sort-wrapper' }}>
<Fab
{...{
'aria-label': 'Toggle sorting order',
onClick: () => setIsAscending(!isAscending),
color: 'primary',
}}
>
{isAscending ? <ArrowUpwardIcon /> : <ArrowDownwardIcon />}
</Fab>
<Select
variant="standard"
{...{
className: 'sort-select',
displayEmpty: true,
value: sortType,
onChange: ({ target: { value } }) => setSortType(value),
}}
>
<MenuItem {...{ value: VALUE }}>Sort by Value</MenuItem>
<MenuItem {...{ value: AGE }}>Sort by Age</MenuItem>
<MenuItem {...{ value: HAPPINESS }}>Sort by Happiness</MenuItem>
<MenuItem {...{ value: WEIGHT }}>Sort by Weight</MenuItem>
<MenuItem {...{ value: GENDER }}>Sort by Gender</MenuItem>
<MenuItem {...{ value: COLOR }}>Sort by Color</MenuItem>
</Select>
</div>

{cowInventory.length > 0 && (
<SearchBar
placeholder="Search cows by name..."
onSearch={setSearchQuery}
/>
)}

<ul className="card-list purchased-cows">
{sortCows(cowInventory, sortType, isAscending).map(cow =>
isCowInBreedingPen(cow, cowBreedingPen) ? null : (
<li
{...{
key: cow.id,
onFocus: () => handleCowSelect(cow),
onClick: () => handleCowSelect(cow),
}}
>
<CowCard
{filteredCowInventory.length > 0 && (
<>
{filteredCowInventory.length > 1 && (
<div {...{ className: 'sort-wrapper' }}>
<Fab
{...{
cow,
handleCowAutomaticHugChange,
handleCowBreedChange,
handleCowHugClick,
handleCowNameInputChange,
handleCowOfferClick,
handleCowSellClick,
handleCowWithdrawClick,
isCowPurchased: true,
isSelected: cow.id === selectedCowId,
'aria-label': 'Toggle sorting order',
onClick: () => setIsAscending(!isAscending),
color: 'primary',
}}
/>
</li>
)
)}
</ul>
</TabPanel>
<TabPanel value={currentTab} index={1}>
<h3>Capacity: {numberOfCowsBreeding(cowBreedingPen)} / 2</h3>
<ul className="card-list purchased-cows breeding-cows">
{nullArray(numberOfCowsBreeding(cowBreedingPen)).map((_null, i) => {
const cowId = cowBreedingPen[`cowId${i + 1}`]
const cow = findCowById(cowInventory, cowId)
return (
<li {...{ key: cowId }}>
<CowCard
>
{isAscending ? <ArrowUpwardIcon /> : <ArrowDownwardIcon />}
</Fab>
<Select
variant="standard"
{...{
cow,
handleCowAutomaticHugChange,
handleCowBreedChange,
handleCowHugClick,
handleCowNameInputChange,
handleCowOfferClick,
handleCowSellClick,
handleCowWithdrawClick,
isCowPurchased: true,
isSelected: cow.id === selectedCowId,
className: 'sort-select',
displayEmpty: true,
value: sortType,
onChange: ({ target: { value } }) => setSortType(value),
}}
>
<MenuItem {...{ value: VALUE }}>Sort by Value</MenuItem>
<MenuItem {...{ value: AGE }}>Sort by Age</MenuItem>
<MenuItem {...{ value: HAPPINESS }}>
Sort by Happiness
</MenuItem>
<MenuItem {...{ value: WEIGHT }}>Sort by Weight</MenuItem>
<MenuItem {...{ value: GENDER }}>Sort by Gender</MenuItem>
<MenuItem {...{ value: COLOR }}>Sort by Color</MenuItem>
</Select>
</div>
)}

<ul className="card-list purchased-cows">
{sortCows(filteredCowInventory, sortType, isAscending).map(cow =>
isCowInBreedingPen(cow, cowBreedingPen) ? null : (
<li
{...{
key: cow.id,
onFocus: () => handleCowSelect(cow),
onClick: () => handleCowSelect(cow),
}}
>
<CowCard
{...{
cow,
handleCowAutomaticHugChange,
handleCowBreedChange,
handleCowHugClick,
handleCowNameInputChange,
handleCowOfferClick,
handleCowSellClick,
handleCowWithdrawClick,
isCowPurchased: true,
isSelected: cow.id === selectedCowId,
}}
/>
</li>
)
)}
</ul>
</>
)}
</TabPanel>
<TabPanel value={currentTab} index={1}>
{(() => {
const filteredCows = nullArray(numberOfCowsBreeding(cowBreedingPen))
.map((_null, i) => {
const cowId = cowBreedingPen[`cowId${i + 1}`]
const cow = findCowById(cowInventory, cowId)

if (
!cow ||
!cow.name.toLowerCase().includes(searchQuery.toLowerCase())
) {
return null
}

return cow
})
.filter(Boolean)

return (
<>
<h3>Capacity: {numberOfCowsBreeding(cowBreedingPen)} / 2</h3>
{cowInventory.length > 0 && (
<SearchBar
placeholder="Search cows by name..."
onSearch={setSearchQuery}
/>
</li>
)
})}
</ul>
)}
<ul className="card-list purchased-cows breeding-cows">
{filteredCows.map(cow => (
<li key={cow.id}>
<CowCard
{...{
cow,
handleCowAutomaticHugChange,
handleCowBreedChange,
handleCowHugClick,
handleCowNameInputChange,
handleCowOfferClick,
handleCowSellClick,
handleCowWithdrawClick,
isCowPurchased: true,
isSelected: cow.id === selectedCowId,
}}
/>
</li>
))}
</ul>
</>
)
})()}
</TabPanel>
<TabPanel value={currentTab} index={2}>
{cowShopInventory.length > 0 && (
<SearchBar
placeholder="Search supplies..."
onSearch={setSearchQuery}
/>
)}
<ul className="card-list">
{cowShopInventory.map(item => (
{filteredShopInventory.map(item => (
<li key={item.id}>
<Item
{...{
Expand Down
Loading
Loading