import { AddLinkEventTracker, GetRoomEngine, GetSessionDataManager, ILinkEventTracker, IRareValue, IWheelAdminPrize, IWheelAdminPrizeEdit, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useState } from 'react'; import { LocalizeFormattedNumber, LocalizeText } from '../../api'; import { Column, Flex, LayoutCurrencyIcon, LayoutImage, Text } from '../../common'; import { useFortuneWheel, useHasPermission, useRareValues } from '../../hooks'; import { NitroCard, NitroInput } from '../../layout'; interface RareValueRow { spriteId: number; name: string; iconUrl: string; value: IRareValue; } interface EditRow { id: number; category: string; num: number; weight: number; label: string; } const CATEGORIES: { key: string; label: string }[] = [ { key: 'item', label: 'Raro (ID)' }, { key: 'diamanti', label: 'Diamanti' }, { key: 'duckets', label: 'Duckets' }, { key: 'crediti', label: 'Crediti' }, { key: 'giri', label: 'Giri extra' }, { key: 'nulla', label: 'Nulla' } ]; const prizeToCategory = (prize: IWheelAdminPrize): string => { switch(prize.type) { case 'item': return 'item'; case 'points': return (prize.pointsType === 5) ? 'diamanti' : 'duckets'; case 'credits': return 'crediti'; case 'spin': return 'giri'; default: return 'nulla'; } }; const prizeToNum = (prize: IWheelAdminPrize): number => (prize.type === 'item') ? (parseInt(prize.value) || 0) : prize.amount; const rowToEdit = (row: EditRow): IWheelAdminPrizeEdit => { const base = { id: row.id, weight: row.weight, label: row.label }; switch(row.category) { case 'item': return { ...base, type: 'item', value: String(row.num), amount: 1, pointsType: 0 }; case 'diamanti': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 5 }; case 'duckets': return { ...base, type: 'points', value: '', amount: row.num, pointsType: 0 }; case 'crediti': return { ...base, type: 'credits', value: '', amount: row.num, pointsType: 0 }; case 'giri': return { ...base, type: 'spin', value: '', amount: row.num, pointsType: 0 }; default: return { ...base, type: 'nothing', value: '', amount: 0, pointsType: 0 }; } }; export const RareValuesView: FC<{}> = () => { const [ isVisible, setIsVisible ] = useState(false); const [ tab, setTab ] = useState<'values' | 'editor'>('values'); const [ searchValue, setSearchValue ] = useState(''); const { values = null, loaded = false } = useRareValues(); const { adminPrizes = [], loadAdminPrizes = null, saveAdminPrizes = null } = useFortuneWheel(); const canEdit = useHasPermission('acc_supporttool'); const [ editRows, setEditRows ] = useState([]); useEffect(() => { const linkTracker: ILinkEventTracker = { linkReceived: (url: string) => { const parts = url.split('/'); if(parts.length < 2) return; switch(parts[1]) { case 'show': setIsVisible(true); return; case 'hide': setIsVisible(false); return; case 'toggle': setIsVisible(prev => !prev); return; } }, eventUrlPrefix: 'rare-values/', }; AddLinkEventTracker(linkTracker); return () => RemoveLinkEventTracker(linkTracker); }, []); useEffect(() => { if(isVisible && (tab === 'editor') && canEdit && loadAdminPrizes) loadAdminPrizes(); }, [ isVisible, tab, canEdit, loadAdminPrizes ]); useEffect(() => { setEditRows(adminPrizes.map(prize => ({ id: prize.id, category: prizeToCategory(prize), num: prizeToNum(prize), weight: prize.weight, label: prize.label }))); }, [ adminPrizes ]); const rows = useMemo(() => { if(!values) return []; const list: RareValueRow[] = []; values.forEach((value, spriteId) => { if(value.points <= 0) return; const floorData = GetSessionDataManager().getFloorItemData(spriteId); const wallData = floorData ? null : GetSessionDataManager().getWallItemData(spriteId); const data = (floorData ?? wallData); if(!data) return; const iconUrl = (floorData ? GetRoomEngine().getFurnitureFloorIconUrl(spriteId) : GetRoomEngine().getFurnitureWallIconUrl(spriteId)); list.push({ spriteId, name: (data.name || data.className || `#${ spriteId }`), iconUrl, value }); }); list.sort((a, b) => (b.value.points - a.value.points)); return list; }, [ values ]); const filtered = useMemo(() => { const query = searchValue.trim().toLocaleLowerCase(); if(!query) return rows; return rows.filter(row => row.name.toLocaleLowerCase().includes(query)); }, [ rows, searchValue ]); if(!isVisible) return null; const updateRow = (id: number, patch: Partial) => setEditRows(prev => prev.map(row => (row.id === id) ? { ...row, ...patch } : row)); return ( setIsVisible(false) } /> { canEdit && setTab('values') }> { LocalizeText('rarevalues.title') } setTab('editor') }> { LocalizeText('rarevalues.editor.tab') } } { (tab === 'values' || !canEdit) && setSearchValue(event.target.value) } /> { !loaded && { LocalizeText('rarevalues.loading') } } { (loaded && !filtered.length) && { LocalizeText('rarevalues.empty') } } { filtered.map(row => ( { row.name } { LocalizeFormattedNumber(row.value.points) } )) } } { (tab === 'editor' && canEdit) && { LocalizeText('rarevalues.editor.type') } { LocalizeText('rarevalues.editor.value') } { LocalizeText('rarevalues.editor.weight') } { LocalizeText('rarevalues.editor.label') } { editRows.map(row => ( updateRow(row.id, { num: parseInt(event.target.value) || 0 }) } className="w-16 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34] disabled:opacity-40" /> updateRow(row.id, { weight: parseInt(event.target.value) || 0 }) } className="w-12 rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> updateRow(row.id, { label: event.target.value }) } className="min-w-0 grow rounded border border-black/20 bg-white px-1 py-0.5 text-sm text-[#1f2d34]" /> )) } } ); };