'use client'; import { useState, useEffect, useMemo } from 'react'; import { useInventoryStore } from '@/store/useInventoryStore'; import shared from '../../styles/shared.module.css'; import styles from '../../styles/Customizations.module.css'; import { getFileName, cleanFolderName, isKiller } from '../../lib/utils'; import { fetchCharacters, fetchCustomizations } from '../../lib/db'; import { Character, CustomizationItem, RoleFilter, Tab, TAB_CATEGORIES, CATEGORY_ORDER } from './types'; import CharacterPicker from './CharacterPicker'; import CharacterCosmetics from './CharacterCosmetics'; import FlatCategory from './FlatCategory'; /* constants */ const PAGE_SIZE = 60; export default function CustomizationsPage() { const store = useInventoryStore(); const [allItems, setAllItems] = useState([]); const [characters, setCharacters] = useState([]); const [tab, setTab] = useState('cosmetics'); const [selectedChar, setSelectedChar] = useState(null); const [charSearch, setCharSearch] = useState(''); const [charRole, setCharRole] = useState('all'); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); useEffect(() => { Promise.all([fetchCustomizations(), fetchCharacters()]).then( ([items, chars]) => { setAllItems(items); setCharacters(chars); } ); }, []); useEffect(() => { setPage(1); }, [tab, search]); /* derived data */ const characterMap = useMemo(() => { const m = new Map(); characters.forEach((c) => m.set(c.idx, c.name)); return m; }, [characters]); const unlockedSet = useMemo( () => new Set(store.unlockedCustomizations), [store.unlockedCustomizations] ); const itemsByCharacter = useMemo(() => { const map = new Map(); allItems.forEach((item) => { if ( item.category >= 1 && item.category <= 7 && item.associatedCharacter > -1 ) { if (!map.has(item.associatedCharacter)) map.set(item.associatedCharacter, []); map.get(item.associatedCharacter)!.push(item); } }); return map; }, [allItems]); const charFullyUnlocked = useMemo(() => { const result = new Map(); characters.forEach((char) => { const items = itemsByCharacter.get(char.idx) ?? []; result.set( char.idx, items.length > 0 && items.every((i) => unlockedSet.has(i.id)) ); }); return result; }, [characters, itemsByCharacter, unlockedSet]); const filteredChars = useMemo(() => { return characters.filter((c) => { if (charRole === 'survivors' && isKiller(c.idx)) return false; if (charRole === 'killers' && !isKiller(c.idx)) return false; if ( charSearch.trim() && !c.name.toLowerCase().includes(charSearch.toLowerCase()) ) return false; return true; }); }, [characters, charRole, charSearch]); const flatItems = useMemo(() => { if (tab === 'cosmetics') return []; const cat = TAB_CATEGORIES[tab]; if (cat === undefined) return []; return allItems.filter((item) => { if (item.category !== cat) return false; if ( search.trim() && !item.name.toLowerCase().includes(search.toLowerCase()) ) return false; return true; }); }, [allItems, tab, search]); const totalPages = Math.max(1, Math.ceil(flatItems.length / PAGE_SIZE)); const pagedItems = useMemo( () => flatItems.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [flatItems, page] ); const charCosmetics = useMemo(() => { if (selectedChar === null) return {}; const items = itemsByCharacter.get(selectedChar) ?? []; const groups: Record = {}; CATEGORY_ORDER.forEach((cat) => { const group = items.filter((i) => i.category === cat); if (group.length > 0) groups[cat] = group; }); return groups; }, [itemsByCharacter, selectedChar]); /* lock/unlock helpers */ const mergeUnlock = (ids: string[]) => { const merged = Array.from( new Set([...store.unlockedCustomizations, ...ids]) ); store.unlockAllInCategory('customizations', merged); }; const removeLock = (ids: string[]) => { const toRemove = new Set(ids); const remaining = store.unlockedCustomizations.filter( (id) => !toRemove.has(id) ); store.unlockAllInCategory('customizations', remaining); }; /* handlers */ const handleToggle = (id: string) => store.toggleItem(id, 'customizations'); const handleUnlockShownCosmetics = () => { const charSet = new Set(filteredChars.map((c) => c.idx)); mergeUnlock( allItems .filter( (i) => charSet.has(i.associatedCharacter) && i.category >= 1 && i.category <= 7 ) .map((i) => i.id) ); }; const handleLockShownCosmetics = () => { const charSet = new Set(filteredChars.map((c) => c.idx)); removeLock( allItems .filter( (i) => charSet.has(i.associatedCharacter) && i.category >= 1 && i.category <= 7 ) .map((i) => i.id) ); }; const handleUnlockCharCosmetics = () => mergeUnlock( Object.values(charCosmetics) .flat() .map((i) => i.id) ); const handleLockCharCosmetics = () => removeLock( Object.values(charCosmetics) .flat() .map((i) => i.id) ); const handleUnlockAll = () => mergeUnlock(flatItems.map((i) => i.id)); const handleLockAll = () => removeLock(flatItems.map((i) => i.id)); const handleUnlockPage = () => mergeUnlock(pagedItems.map((i) => i.id)); const handleLockPage = () => removeLock(pagedItems.map((i) => i.id)); return (

Customizations

{store.unlockedCustomizations.length} of {allItems.length || '-'}{' '} unlocked

{( ['cosmetics', 'charms', 'badges', 'banners'] as Tab[] ).map((t) => ( ))}
{tab === 'cosmetics' && selectedChar === null && ( )} {tab === 'cosmetics' && selectedChar !== null && ( setSelectedChar(null)} onUnlockAll={handleUnlockCharCosmetics} onLockAll={handleLockCharCosmetics} onToggle={handleToggle} /> )} {tab !== 'cosmetics' && ( )}
); }