'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 { 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([ fetch('/data/customization_items.json').then(r => r.json()).catch(() => []), fetch('/data/characters.json').then(r => r.json()).catch(() => []), ]).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', 'portraits'] as Tab[]).map(t => ( ))}
{tab === 'cosmetics' && selectedChar === null && ( )} {tab === 'cosmetics' && selectedChar !== null && ( setSelectedChar(null)} onUnlockAll={handleUnlockCharCosmetics} onLockAll={handleLockCharCosmetics} onToggle={handleToggle} /> )} {tab !== 'cosmetics' && ( )}
) }