Files
smart-rgb/frontend/src/shared/components/AttackRow.tsx
2025-10-12 22:12:23 -05:00

74 lines
2.6 KiB
TypeScript

import type { AttackEntry, LeaderboardEntry } from "@/shared/api/types";
// Format troop count for human-readable display
// 100 => "100", 12,493 => "12.4k", 980,455 => "980k"
export function formatTroopCount(count: number): string {
if (count < 1000) return count.toString();
if (count < 1000000) {
const k = count / 1000;
return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
}
const m = count / 1000000;
return m % 1 === 0 ? `${m}M` : `${m.toFixed(1)}M`;
}
// Calculate the background width percentage based on troop count
// Uses power scale (x^0.4): 100 troops = 5%, 1k = 7.9%, 10k = 15.3%, 100k = 33.7%, 1M = 80%
function calculateBackgroundWidth(troops: number): number {
const minTroops = 100;
const maxTroops = 1000000;
const minWidth = 5;
const maxWidth = 80;
// Clamp troops to range
const clampedTroops = Math.max(minTroops, Math.min(maxTroops, troops));
// Power scale with exponent 0.4 provides gentler progression than logarithmic
const powerMin = Math.pow(minTroops, 0.4);
const powerMax = Math.pow(maxTroops, 0.4);
const powerTroops = Math.pow(clampedTroops, 0.4);
// Map to 5-80% range
const normalized = (powerTroops - powerMin) / (powerMax - powerMin);
return minWidth + normalized * (maxWidth - minWidth);
}
interface AttackRowProps {
attack: AttackEntry;
playerMap: Map<number, LeaderboardEntry>;
onNationHover?: (nationId: number | null) => void;
}
export function AttackRow({ attack, playerMap, onNationHover }: AttackRowProps) {
// For outgoing attacks, show target's name (who we're attacking)
// For incoming attacks, show attacker's name (who is attacking us)
const displayPlayerId = attack.is_outgoing ? attack.target_id : attack.attacker_id;
const displayPlayer = displayPlayerId !== null ? playerMap.get(displayPlayerId) : null;
const displayName = displayPlayer?.name || "Unclaimed Territory";
const backgroundWidth = calculateBackgroundWidth(attack.troops);
const backgroundColor = attack.is_outgoing
? "rgba(59, 130, 246, 0.3)" // Blue with 30% opacity
: "rgba(239, 68, 68, 0.3)"; // Red with 30% opacity
return (
<button
className="attacks__row"
onClick={() => console.log("Attack clicked:", attack)}
onMouseEnter={() => displayPlayerId !== null && onNationHover?.(displayPlayerId)}
onMouseLeave={() => onNationHover?.(null)}
>
<div
className="attacks__background"
style={{
width: `${backgroundWidth}%`,
backgroundColor,
}}
/>
<div className="attacks__nation">{displayName}</div>
<div className="attacks__troops">{formatTroopCount(attack.troops)}</div>
</button>
);
}