@@ -5,22 +5,26 @@ import { useTranslations } from "../shared/hooks/useTranslations";
55import { useWindowControls , useSocket } from "../shared/hooks" ;
66import MonstersHeader from "./MonstersHeader" ;
77import SortDropdown from "./SortDropdown" ;
8+ import { TRACKED_MONSTER_IDS } from "../shared/constants" ;
89
910interface MonsterEntry {
1011 name ?: string | null ;
1112 hp ?: number | null ;
1213 max_hp ?: number | null ;
1314 monster_id ?: number | null ;
1415 last_seen ?: number | null ;
16+ position ?: { x : number ; y : number ; z : number } | null ;
17+ distance ?: number | null ;
1518}
1619
1720export default function MonstersApp ( ) : React . JSX . Element {
1821 const [ monsters , setMonsters ] = useState < Record < string , MonsterEntry > > ( { } ) ;
1922 const [ isLoading , setIsLoading ] = useState < boolean > ( true ) ;
2023 const [ error , setError ] = useState < string | null > ( null ) ;
21- const [ sortKey , setSortKey ] = useState < "id" | "name" | "hp" > ( "hp " ) ;
22- const [ sortDesc , setSortDesc ] = useState < boolean > ( true ) ;
24+ const [ sortKey , setSortKey ] = useState < "id" | "name" | "hp" | "distance" > ( "distance " ) ;
25+ const [ sortDesc , setSortDesc ] = useState < boolean > ( false ) ;
2326 const [ zhNames , setZhNames ] = useState < Record < number , string > > ( { } ) ;
27+ const [ bossOnlyMode , setBossOnlyMode ] = useState < boolean > ( false ) ;
2428
2529 const { scale, zoomIn, zoomOut, handleDragStart, handleClose, isDragging } = useWindowControls ( {
2630 baseWidth : 560 ,
@@ -149,7 +153,25 @@ export default function MonstersApp(): React.JSX.Element {
149153 ) : (
150154 < div className = "monsters-container" >
151155 < div className = "flex justify-between items-center mb-2 gap-2" >
152- < div className = "text-sm font-semibold" > Monsters ({ Object . keys ( monsters ) . length } )</ div >
156+ < div className = "flex items-center gap-2" >
157+ < div className = "text-sm font-semibold" >
158+ Monsters ({
159+ bossOnlyMode
160+ ? Object . values ( monsters ) . filter ( m => m . monster_id && TRACKED_MONSTER_IDS . has ( String ( m . monster_id ) ) ) . length
161+ : Object . keys ( monsters ) . length
162+ } )
163+ </ div >
164+ < button
165+ onClick = { ( ) => setBossOnlyMode ( ( prev ) => ! prev ) }
166+ className = "text-xs px-2 py-1 rounded"
167+ style = { {
168+ background : bossOnlyMode ? "#3498db" : "rgba(255,255,255,0.1)" ,
169+ color : bossOnlyMode ? "#fff" : "rgba(255,255,255,0.7)"
170+ } }
171+ >
172+ { bossOnlyMode ? "Bosses Only" : "All Monsters" }
173+ </ button >
174+ </ div >
153175 < div className = "flex items-center gap-2" >
154176 < label className = "text-xs" > Sort:</ label >
155177 < div className = "min-w-[140px]" >
@@ -162,16 +184,20 @@ export default function MonstersApp(): React.JSX.Element {
162184 < table className = "monsters-table w-full border-collapse" >
163185 < thead >
164186 < tr >
165- < th className = "text-left p-1" > ID</ th >
166187 < th className = "text-left p-1" > Name</ th >
167188 < th className = "text-right p-1" > HP</ th >
168189 < th className = "text-right p-1" > Max HP</ th >
169190 < th className = "text-right p-1" > HP %</ th >
191+ < th className = "text-right p-1 pl-5" > Distance</ th >
170192 </ tr >
171193 </ thead >
172194 < tbody >
173195 { Object . entries ( monsters )
174196 . map ( ( [ id , m ] ) => ( { id, ...m } ) )
197+ . filter ( ( m ) => {
198+ if ( ! bossOnlyMode ) return true ;
199+ return m . monster_id && TRACKED_MONSTER_IDS . has ( String ( m . monster_id ) ) ;
200+ } )
175201 . sort ( ( a , b ) => {
176202 if ( sortKey === "hp" ) {
177203 const ah = a . hp ?? - Infinity ;
@@ -183,17 +209,21 @@ export default function MonstersApp(): React.JSX.Element {
183209 const bn = ( b . name || "" ) . toString ( ) ;
184210 return sortDesc ? bn . localeCompare ( an ) : an . localeCompare ( bn ) ;
185211 }
186- // id
187- const ai = Number ( a . id ) ;
188- const bi = Number ( b . id ) ;
189- return sortDesc ? bi - ai : ai - bi ;
212+ if ( sortKey === "distance" ) {
213+ const ad = a . distance ?? Infinity ;
214+ const bd = b . distance ?? Infinity ;
215+ return sortDesc ? bd - ad : ad - bd ;
216+ }
217+ return 0 ;
190218 } )
191219 . map ( ( m ) => {
192- const displayName = `${ t ( `monsters.${ m . monster_id } ` , m ?. name ?? "Unknown" ) } ( ${ m . monster_id } ) ` ;
220+ const displayName = `${ t ( `monsters.${ m . monster_id } ` , m ?. name ?? "Unknown" ) } ` ;
193221 const pct = m . max_hp && m . hp ? Math . max ( 0 , Math . min ( 1 , ( m . hp / m . max_hp ) ) ) : null ;
222+ const distanceText = m . distance !== null && m . distance !== undefined
223+ ? `${ m . distance . toFixed ( 1 ) } m`
224+ : "-" ;
194225 return (
195226 < tr key = { m . id } style = { { borderTop : "1px solid rgba(255,255,255,0.06)" } } >
196- < td className = "p-2" > { m . id } </ td >
197227 < td className = "p-2" > { displayName } </ td >
198228 < td className = "p-2 text-right" > { m ?. hp ?? "-" } </ td >
199229 < td className = "p-2 text-right" > { m ?. max_hp ?? "-" } </ td >
@@ -209,6 +239,7 @@ export default function MonstersApp(): React.JSX.Element {
209239 </ div >
210240 ) }
211241 </ td >
242+ < td className = "p-2 text-right text-xs font-mono" > { distanceText } </ td >
212243 </ tr >
213244 ) ;
214245 } ) }
0 commit comments