diff --git a/app/items/AddonGrid.tsx b/app/items/AddonGrid.tsx new file mode 100644 index 0000000..b312693 --- /dev/null +++ b/app/items/AddonGrid.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import shared from '../../styles/shared.module.css'; +import styles from '../../styles/Items.module.css'; +import QuantityCard from '../../components/QuantityCard'; +import { Addon, getAddonIconUrl, randInRange } from './types'; + +type Props = { + addons: Addon[]; + quantities: Record; + randMin: number; + randMax: number; + onSetQty: (id: string, qty: number) => void; +}; + +export default function AddonGrid({ addons, quantities, randMin, randMax, onSetQty }: Props) { + const [search, setSearch] = useState(''); + const [roleFilter, setRoleFilter] = useState<'all' | 'killer' | 'survivor'>('all'); + + const filtered = useMemo(() => { + return addons.filter(addon => { + if (roleFilter === 'killer' && addon.role !== 1) return false; + if (roleFilter === 'survivor' && addon.role !== 2) return false; + if (search.trim() && !addon.name.toLowerCase().includes(search.toLowerCase())) return false; + return true; + }); + }, [addons, search, roleFilter]); + + const handleGive100Visible = () => filtered.forEach(a => onSetQty(a.id, 100)); + const handleRandVisible = () => filtered.forEach(a => onSetQty(a.id, randInRange(randMin, randMax))); + const handleLockVisible = () => filtered.forEach(a => onSetQty(a.id, 0)); + + const activeCount = Object.values(quantities).filter(q => q > 0).length; + + return ( + <> +
+ setSearch(e.target.value)} + /> + +
+ + + +
+ + + {filtered.length} shown · {activeCount} active out of {addons.length} total + + + + +
+ + {filtered.length === 0 ? ( +
No addons match
+ ) : ( +
+ {filtered.map(addon => ( + + ))} +
+ )} + + ); +} diff --git a/app/items/page.tsx b/app/items/page.tsx index 70cdf07..375f549 100644 --- a/app/items/page.tsx +++ b/app/items/page.tsx @@ -6,12 +6,13 @@ import { useInventoryStore } from '@/store/useInventoryStore'; import shared from '../../styles/shared.module.css'; import styles from '../../styles/Items.module.css'; -import { Item, Offering } from './types'; -import { fetchItems, fetchOfferings } from '../../lib/db'; +import { Item, Offering, Addon } from './types'; +import { fetchItems, fetchOfferings, fetchAddons } from '../../lib/db'; import ItemGrid from './ItemGrid'; import OfferingGrid from './OfferingGrid'; +import AddonGrid from './AddonGrid'; -type Tab = 'items' | 'offerings'; +type Tab = 'items' | 'offerings' | 'addons'; export default function ItemsPage() { const store = useInventoryStore(); @@ -19,25 +20,29 @@ export default function ItemsPage() { const [tab, setTab] = useState('items'); const [items, setItems] = useState([]); const [offerings, setOfferings] = useState([]); + const [addons, setAddons] = useState([]); const [randMin, setRandMin] = useState(50); const [randMax, setRandMax] = useState(200); useEffect(() => { - Promise.all([fetchItems(), fetchOfferings()]) - .then(([i, o]) => { + Promise.all([fetchItems(), fetchOfferings(), fetchAddons()]) + .then(([i, o, a]) => { setItems(i); setOfferings(o); + setAddons(a); }); }, []); const handleClearAll = () => { store.clearCategory('items'); store.clearCategory('offerings'); + store.clearCategory('addons'); }; const activeItems = Object.values(store.items).filter(q => q > 0).length; const activeOfferings = Object.values(store.offerings).filter(q => q > 0).length; + const activeAddons = Object.values(store.addons).filter(q => q > 0).length; return (
@@ -45,7 +50,7 @@ export default function ItemsPage() {

Items & Offerings

- {activeItems} active items · {activeOfferings} active offerings + {activeItems} items · {activeOfferings} offerings · {activeAddons} addons

@@ -74,7 +79,7 @@ export default function ItemsPage() {
- {(['items', 'offerings'] as Tab[]).map(t => ( + {(['items', 'offerings', 'addons'] as Tab[]).map(t => (
); } \ No newline at end of file diff --git a/app/items/types.ts b/app/items/types.ts index 8902fcd..2bca35c 100644 --- a/app/items/types.ts +++ b/app/items/types.ts @@ -13,6 +13,13 @@ export type Offering = { role: number; }; +export type Addon = { + id: string; + name: string; + iconFilePath: string; + role: number; +}; + export type ItemType = 'all' | 'toolbox' | 'flashlight' | 'medkit' | 'key' | 'map' | 'other'; export type OfferingRole = 'all' | 'shared' | 'killer' | 'survivor'; @@ -39,3 +46,14 @@ export const getOfferingIconUrl = (iconFilePath: string) => { const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; return `${DB_BASE_URL}/icons/offering-icons/${file}.png`; }; + +export const getAddonIconUrl = (iconFilePath: string) => { + const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; + return `${DB_BASE_URL}/icons/addon-icons/${file}.png`; +}; + +export const randInRange = (min: number, max: number) => { + const lo = Math.min(min, max); + const hi = Math.max(min, max); + return Math.floor(Math.random() * (hi - lo + 1)) + lo; +}; diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index f21ef58..2dc638b 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -11,7 +11,7 @@ export default function Sidebar() { Dashboard Characters Customizations - Items + Items, offerings & addons DLCs