fix(v2): all reported issues — missing windows, broken features, layouts

Missing features (now added):
1. Vital Sharing window — polls /vital-sharing/peers, shows peer vitals
   with HP/STA/MANA bars, connection status, position, tags
2. Combat Stats window — full Mag-Tools style with monster list (left),
   damage breakdown grid (right), session/lifetime toggle, element matrix
3. Issues Board window — CRUD with categories, resolve/reopen, comments
4. Quest Status — links to /quest-status.html (separate page like v1)
5. Sidebar: added Issues + Vitals buttons, Quest link, Combat button
   per player row (6 buttons now: Chat/Stats/Inv/Char/Combat/Radar)

Fixed functionality:
6. Radar — fixed command to "start_radar"/"stop_radar" (was wrong path)
7. Character window — redesigned with v1-style tabbed layout:
   Left tabs: Attributes (vitals bars + attribute grid) | Skills
   (specialized/trained grouped) | Titles
   Right tabs: Augs | Ratings | Other (allegiance)
   Header: level, race, gender, XP, luminance, deaths, skill credits
8. Stats window — proper Grafana iframe grid (4 panels 2x2) with
   time range selector (1H/6H/24H/7D)

Color palette: expanded to 60 distinct colors (was 30)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 18:49:16 +02:00
parent b77450b6eb
commit 52e1bcd6b8
12 changed files with 710 additions and 226 deletions

View file

@ -1,52 +1,65 @@
import React, { useEffect } from 'react';
import { DraggableWindow } from './DraggableWindow';
interface NearbyObject {
id: number;
name: string;
type: string;
distance: number;
bearing?: number;
}
interface Props {
id: string;
charName: string;
zIndex: number;
socket: WebSocket | null;
nearbyObjects: any[];
nearbyObjects: NearbyObject[];
}
export const RadarWindow: React.FC<Props> = ({ id, charName, zIndex, socket, nearbyObjects }) => {
// Send start_radar when window opens, stop_radar when it closes
// Send start_radar when window opens, stop_radar on close
useEffect(() => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ player_name: charName, command: '/mm radar start' }));
socket.send(JSON.stringify({ player_name: charName, command: 'start_radar' }));
}
return () => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ player_name: charName, command: '/mm radar stop' }));
socket.send(JSON.stringify({ player_name: charName, command: 'stop_radar' }));
}
};
}, [charName, socket]);
const objects = nearbyObjects || [];
const sorted = [...objects].sort((a, b) => (a.distance ?? 999) - (b.distance ?? 999));
return (
<DraggableWindow id={id} title={`Radar: ${charName}`} zIndex={zIndex} width={450} height={400}>
<DraggableWindow id={id} title={`Radar: ${charName}`} zIndex={zIndex} width={480} height={420}>
<div style={{ padding: '4px 8px', fontSize: '0.7rem', color: '#888', borderBottom: '1px solid #333' }}>
Range: ~120m &middot; {objects.length} objects nearby
</div>
<div style={{ flex: 1, overflowY: 'auto', fontSize: '0.73rem' }}>
{objects.length === 0 ? (
<div style={{ padding: 16, color: '#666', textAlign: 'center' }}>
Waiting for nearby objects data...<br/>
<span style={{ fontSize: '0.65rem' }}>The plugin will start sending radar data shortly.</span>
<div style={{ padding: 20, color: '#555', textAlign: 'center' }}>
Waiting for radar data from plugin...
</div>
) : (
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '1px solid #444', color: '#888', fontSize: '0.65rem' }}>
<th style={{ textAlign: 'left', padding: '3px 6px' }}>Name</th>
<th style={{ textAlign: 'left', padding: '3px 4px' }}>Type</th>
<th style={{ textAlign: 'right', padding: '3px 4px' }}>Dist</th>
<tr style={{ borderBottom: '1px solid #444', color: '#777', fontSize: '0.65rem', textTransform: 'uppercase' }}>
<th style={{ textAlign: 'left', padding: '4px 6px' }}>Name</th>
<th style={{ textAlign: 'left', padding: '4px 4px' }}>Type</th>
<th style={{ textAlign: 'right', padding: '4px 6px' }}>Distance</th>
</tr>
</thead>
<tbody>
{objects.map((obj: any, i: number) => (
<tr key={i} style={{ borderBottom: '1px solid #1a1a1a', color: '#ccc' }}>
<td style={{ padding: '2px 6px' }}>{obj.name}</td>
<td style={{ padding: '2px 4px', color: '#888' }}>{obj.type || obj.object_class || ''}</td>
<td style={{ textAlign: 'right', padding: '2px 4px' }}>{obj.distance ? `${Math.round(obj.distance)}m` : ''}</td>
{sorted.map((obj, i) => (
<tr key={obj.id ?? i} style={{ borderBottom: '1px solid #1a1a1a', color: '#ccc' }}>
<td style={{ padding: '3px 6px', fontWeight: 500 }}>{obj.name}</td>
<td style={{ padding: '3px 4px', color: '#888', fontSize: '0.68rem' }}>{obj.type || ''}</td>
<td style={{ textAlign: 'right', padding: '3px 6px', fontVariantNumeric: 'tabular-nums' }}>
{obj.distance != null ? `${Math.round(obj.distance)}m` : ''}
</td>
</tr>
))}
</tbody>