131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useMemo } from 'react';
|
|
import { useInventoryStore } from '@/store/useInventoryStore';
|
|
import shared from '../../styles/shared.module.css';
|
|
import styles from '../../styles/Characters.module.css';
|
|
import { fetchPerks } from '../../lib/db';
|
|
import { getPerkIconUrl, Perk } from './types';
|
|
|
|
export default function PerksPage() {
|
|
const store = useInventoryStore();
|
|
const [perks, setPerks] = useState<Perk[]>([]);
|
|
const [search, setSearch] = useState('');
|
|
const [roleFilter, setRoleFilter] = useState<'all' | 'killer' | 'survivor'>(
|
|
'all'
|
|
);
|
|
|
|
useEffect(() => {
|
|
fetchPerks().then(setPerks);
|
|
}, []);
|
|
|
|
const filtered = useMemo(() => {
|
|
return perks.filter((p) => {
|
|
if (roleFilter === 'killer' && p.role !== 1) return false;
|
|
if (roleFilter === 'survivor' && p.role !== 2) return false;
|
|
if (search.trim() && !p.name.toLowerCase().includes(search.toLowerCase()))
|
|
return false;
|
|
return true;
|
|
});
|
|
}, [perks, search, roleFilter]);
|
|
|
|
const handleClearAll = () => store.clearCategory('perks');
|
|
const handleUnlockVisible = () =>
|
|
store.unlockAllInCategory(
|
|
'perks',
|
|
Array.from(
|
|
new Set([...store.unlockedPerks, ...filtered.map((p) => p.id)])
|
|
)
|
|
);
|
|
const handleLockVisible = () =>
|
|
store.unlockAllInCategory(
|
|
'perks',
|
|
store.unlockedPerks.filter((id) => !filtered.find((p) => p.id === id))
|
|
);
|
|
|
|
const activeCount = store.unlockedPerks.length;
|
|
|
|
return (
|
|
<div className={shared.container}>
|
|
<header className={shared.header}>
|
|
<div>
|
|
<h1 className={shared.title}>Perks</h1>
|
|
<p className={shared.subtitle}>
|
|
{activeCount} active out of {perks.length} total
|
|
</p>
|
|
</div>
|
|
<button className={shared.clearBtn} onClick={handleClearAll}>
|
|
Clear All
|
|
</button>
|
|
</header>
|
|
|
|
<div className={shared.toolbar}>
|
|
<input
|
|
className={shared.searchInput}
|
|
type='text'
|
|
placeholder='Search perks...'
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
|
|
<div className={shared.roleFilter}>
|
|
<button
|
|
className={`${shared.roleBtn} ${roleFilter === 'all' ? shared.roleBtnActive : ''}`}
|
|
onClick={() => setRoleFilter('all')}
|
|
>
|
|
All
|
|
</button>
|
|
<button
|
|
className={`${shared.roleBtn} ${roleFilter === 'survivor' ? shared.roleBtnActive : ''}`}
|
|
onClick={() => setRoleFilter('survivor')}
|
|
>
|
|
Survivor
|
|
</button>
|
|
<button
|
|
className={`${shared.roleBtn} ${roleFilter === 'killer' ? shared.roleBtnActive : ''}`}
|
|
onClick={() => setRoleFilter('killer')}
|
|
>
|
|
Killer
|
|
</button>
|
|
</div>
|
|
|
|
<span className={shared.spacer} />
|
|
<span className={shared.resultCount}>{filtered.length} shown</span>
|
|
<button className={shared.unlockAllBtn} onClick={handleUnlockVisible}>
|
|
Unlock visible
|
|
</button>
|
|
<button className={shared.lockAllBtn} onClick={handleLockVisible}>
|
|
Lock visible
|
|
</button>
|
|
</div>
|
|
|
|
<div className={styles.grid}>
|
|
{filtered.map((perk) => {
|
|
const unlocked = store.unlockedPerks.includes(perk.id);
|
|
const killer = perk.role === 1;
|
|
return (
|
|
<div
|
|
key={perk.id}
|
|
className={`${shared.card} ${unlocked ? shared.cardUnlocked : ''}`}
|
|
onClick={() => store.toggleItem(perk.id, 'perks')}
|
|
>
|
|
<img
|
|
className={shared.cardIcon}
|
|
src={getPerkIconUrl(perk.iconFilePath)}
|
|
alt={perk.name}
|
|
loading='lazy'
|
|
/>
|
|
<span className={shared.cardName}>{perk.name}</span>
|
|
<span
|
|
className={`${shared.rolePip} ${killer ? shared.rolePipKiller : ''}`}
|
|
>
|
|
{killer ? 'Killer' : 'Survivor'}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|