diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx new file mode 100644 index 00000000..d695769e --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/AttackerRowSubInfo.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import clsx from 'clsx'; +import { zkillLink } from '../helpers'; +import classes from './SystemKillRow.module.scss'; + +interface AttackerRowSubInfoProps { + finalBlowCharId: number | null | undefined; + finalBlowCharName?: string; + attackerPortraitUrl: string | null; + + finalBlowCorpId: number | null | undefined; + finalBlowCorpName?: string; + attackerCorpLogoUrl: string | null; + + finalBlowAllianceId: number | null | undefined; + finalBlowAllianceName?: string; + attackerAllianceLogoUrl: string | null; + + containerHeight?: number; +} + +export const AttackerRowSubInfo: React.FC = ({ + finalBlowCharId = 0, + finalBlowCharName, + attackerPortraitUrl, + containerHeight = 8, +}) => { + if (!attackerPortraitUrl || finalBlowCharId === null || finalBlowCharId <= 0) { + return null; + } + + const containerClass = `h-${containerHeight}`; + + return ( +
+
+ + {finalBlowCharName + +
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/CompactKillRow.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/CompactKillRow.tsx index 946c7c64..59451740 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/CompactKillRow.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/CompactKillRow.tsx @@ -7,9 +7,13 @@ import { zkillLink, getAttackerSubscript, buildVictimImageUrls, - buildAttackerShipUrl, + buildAttackerImageUrls, + getPrimaryLogoAndTooltip, + getAttackerPrimaryImageAndTooltip, } from '../helpers'; +import { WdTooltipWrapper } from '../../../../ui-kit/WdTooltipWrapper'; import classes from './SystemKillRow.module.scss'; +import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit'; export interface CompactKillRowProps { killDetails: DetailedKill; @@ -19,44 +23,69 @@ export interface CompactKillRowProps { export const CompactKillRow: React.FC = ({ killDetails, systemName, onlyOneSystem }) => { const { - killmail_id, + killmail_id = 0, + victim_char_name = 'Unknown Pilot', + victim_alliance_ticker = '', + victim_corp_ticker = '', victim_ship_name = 'Unknown Ship', - victim_alliance_ticker, - victim_corp_ticker, - victim_char_id, - victim_corp_id, - victim_alliance_id, - victim_ship_type_id, + victim_corp_name = '', + victim_alliance_name = '', + victim_char_id = 0, + victim_corp_id = 0, + victim_alliance_id = 0, + victim_ship_type_id = 0, - final_blow_char_id, + final_blow_char_id = 0, final_blow_char_name = '', - final_blow_alliance_ticker, - final_blow_corp_ticker, - final_blow_ship_type_id, + final_blow_alliance_ticker = '', + final_blow_alliance_name = '', + final_blow_alliance_id = 0, + final_blow_corp_ticker = '', + final_blow_corp_id = 0, + final_blow_corp_name = '', + final_blow_ship_type_id = 0, - kill_time, - total_value, - } = killDetails; - - const attackerIsNpc = final_blow_char_id == null; + kill_time = '', + total_value = 0, + } = killDetails || {}; + const attackerIsNpc = final_blow_char_id === 0; const victimAffiliationTicker = victim_alliance_ticker || victim_corp_ticker || 'No Ticker'; - const killValueFormatted = total_value && total_value > 0 ? `${formatISK(total_value)} ISK` : null; - + const killValueFormatted = total_value != null && total_value > 0 ? `${formatISK(total_value)} ISK` : null; const attackerName = attackerIsNpc ? '' : final_blow_char_name; const attackerTicker = attackerIsNpc ? '' : final_blow_alliance_ticker || final_blow_corp_ticker || ''; - const killTimeAgo = kill_time ? formatTimeMixed(kill_time) : '0h ago'; const attackerSubscript = getAttackerSubscript(killDetails); - const { victimShipUrl } = buildVictimImageUrls({ + const { victimCorpLogoUrl, victimAllianceLogoUrl } = buildVictimImageUrls({ victim_char_id, victim_ship_type_id, victim_corp_id, victim_alliance_id, }); - const finalBlowShipUrl = buildAttackerShipUrl(final_blow_ship_type_id); + const { attackerCorpLogoUrl, attackerAllianceLogoUrl } = buildAttackerImageUrls({ + final_blow_char_id, + final_blow_corp_id, + final_blow_alliance_id, + }); + + const { url: victimPrimaryLogoUrl, tooltip: victimPrimaryTooltip } = getPrimaryLogoAndTooltip( + victimAllianceLogoUrl, + victimCorpLogoUrl, + victim_alliance_name, + victim_corp_name, + 'Victim', + ); + + const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip( + attackerIsNpc, + attackerAllianceLogoUrl, + attackerCorpLogoUrl, + final_blow_alliance_name, + final_blow_corp_name, + final_blow_ship_type_id || 0, + ); return (
= ({ killDetails, sys 'text-xs whitespace-nowrap overflow-hidden leading-none', )} > - {victimShipUrl && ( - - VictimShip - + {victimPrimaryLogoUrl && ( + + + VictimPrimaryLogo + + )}
@@ -103,7 +138,7 @@ export const CompactKillRow: React.FC = ({ killDetails, sys
{!onlyOneSystem && systemName ? ( <> - {systemName} /{killTimeAgo} + {systemName} / {killTimeAgo} ) : ( {killTimeAgo} @@ -111,27 +146,33 @@ export const CompactKillRow: React.FC = ({ killDetails, sys
- {finalBlowShipUrl && ( - - AttackerShip - {attackerSubscript && ( - - {attackerSubscript.label} - - )} - + {attackerPrimaryImageUrl && ( + + + {attackerIsNpc + {attackerSubscript && ( + + {attackerSubscript.label} + + )} + + )}
diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx index b7847e64..80325859 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/FullKillRow.tsx @@ -1,16 +1,21 @@ +// FullKillRow.tsx import React from 'react'; import clsx from 'clsx'; import { DetailedKill } from '@/hooks/Mapper/types/kills'; -import { KillRowSubInfo } from './KillRowSubInfo'; import { formatISK, formatTimeMixed, zkillLink, getAttackerSubscript, buildVictimImageUrls, - buildAttackerShipUrl, + buildAttackerImageUrls, + getPrimaryLogoAndTooltip, + getAttackerPrimaryImageAndTooltip, } from '../helpers'; +import { VictimRowSubInfo } from './VictimRowSubInfo'; +import { WdTooltipWrapper } from '../../../../ui-kit/WdTooltipWrapper'; import classes from './SystemKillRow.module.scss'; +import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit'; export interface FullKillRowProps { killDetails: DetailedKill; @@ -20,73 +25,100 @@ export interface FullKillRowProps { export const FullKillRow: React.FC = ({ killDetails, systemName, onlyOneSystem }) => { const { - killmail_id, + killmail_id = 0, + victim_char_name = '', - victim_alliance_ticker, - victim_corp_ticker, + victim_alliance_ticker = '', + victim_corp_ticker = '', victim_ship_name = '', - victim_char_id, - victim_corp_id, - victim_alliance_id, - victim_ship_type_id, + victim_char_id = 0, + victim_corp_id = 0, + victim_alliance_id = 0, + victim_ship_type_id = 0, + victim_corp_name = '', + victim_alliance_name = '', - total_value, - kill_time, - - final_blow_char_id, + final_blow_char_id = 0, final_blow_char_name = '', - final_blow_alliance_ticker, - final_blow_corp_ticker, + final_blow_alliance_ticker = '', + final_blow_corp_ticker = '', + final_blow_corp_name = '', + final_blow_alliance_name = '', + final_blow_corp_id = 0, + final_blow_alliance_id = 0, final_blow_ship_name = '', - final_blow_ship_type_id, - } = killDetails; + final_blow_ship_type_id = 0, - const attackerIsNpc = final_blow_char_id == null; + total_value = 0, + kill_time = '', + } = killDetails || {}; - const victimAffiliation = victim_alliance_ticker || victim_corp_ticker || ''; + const attackerIsNpc = final_blow_char_id === 0; + const victimAffiliation = victim_alliance_ticker || victim_corp_ticker; const attackerAffiliation = attackerIsNpc ? '' : final_blow_alliance_ticker || final_blow_corp_ticker || ''; - const killValueFormatted = total_value && total_value > 0 ? `${formatISK(total_value)} ISK` : null; + const killValueFormatted = total_value !== null && total_value > 0 ? `${formatISK(total_value)} ISK` : null; const killTimeAgo = kill_time ? formatTimeMixed(kill_time) : '0h ago'; - const { victimPortraitUrl, victimCorpLogoUrl, victimAllianceLogoUrl, victimShipUrl } = buildVictimImageUrls({ + const { victimPortraitUrl, victimCorpLogoUrl, victimAllianceLogoUrl } = buildVictimImageUrls({ victim_char_id, victim_ship_type_id, victim_corp_id, victim_alliance_id, }); + const { attackerPortraitUrl, attackerCorpLogoUrl, attackerAllianceLogoUrl } = buildAttackerImageUrls({ + final_blow_char_id, + final_blow_corp_id, + final_blow_alliance_id, + }); - const finalBlowShipUrl = buildAttackerShipUrl(final_blow_ship_type_id); + const { url: victimPrimaryImageUrl, tooltip: victimPrimaryTooltip } = getPrimaryLogoAndTooltip( + victimAllianceLogoUrl, + victimCorpLogoUrl, + victim_alliance_name, + victim_corp_name, + 'Victim', + ); + + const { url: attackerPrimaryImageUrl, tooltip: attackerPrimaryTooltip } = getAttackerPrimaryImageAndTooltip( + attackerIsNpc, + attackerAllianceLogoUrl, + attackerCorpLogoUrl, + final_blow_alliance_name, + final_blow_corp_name, + final_blow_ship_type_id || 0, + ); const attackerSubscript = getAttackerSubscript(killDetails); return (
+ {/* ---------------- Victim Side ---------------- */}
- {victimShipUrl && ( -
- - VictimShip - -
+ {/* Victim top-level logo (corp or alliance), with tooltip */} + {victimPrimaryImageUrl && ( + +
+ + VictimPrimaryLogo + +
+
)} - -
- -
- +
{victim_char_name} @@ -105,7 +137,7 @@ export const FullKillRow: React.FC = ({ killDetails, systemNam
-
+
{!attackerIsNpc && (
@@ -118,23 +150,47 @@ export const FullKillRow: React.FC = ({ killDetails, systemNam )}
{killTimeAgo}
- {finalBlowShipUrl && ( + + {!attackerIsNpc && attackerPortraitUrl && final_blow_char_id !== null && final_blow_char_id > 0 && ( )} + + {attackerPrimaryImageUrl && ( + + + + )}
); diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/KillRowSubInfo.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/KillRowSubInfo.tsx deleted file mode 100644 index 250b5900..00000000 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/KillRowSubInfo.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import { zkillLink } from '../helpers'; -import classes from './SystemKillRow.module.scss'; - -interface KillRowSubInfoProps { - victimCorpId: number | null | undefined; - victimCorpLogoUrl: string | null; - victimAllianceId: number | null | undefined; - victimAllianceLogoUrl: string | null; - victimCharacterId: number | null | undefined; - victimPortraitUrl: string | null; -} - -export const KillRowSubInfo: React.FC = ({ - victimCorpId, - victimCorpLogoUrl, - victimAllianceId, - victimAllianceLogoUrl, - victimCharacterId, - victimPortraitUrl, -}) => { - const hasAnything = victimPortraitUrl || victimCorpLogoUrl || victimAllianceLogoUrl; - - if (!hasAnything) { - return null; - } - - return ( -
- {victimPortraitUrl && victimCharacterId && ( - - VictimPortrait - - )} -
- {victimCorpLogoUrl && victimCorpId && ( - - VictimCorp - - )} - {victimAllianceLogoUrl && victimAllianceId && ( - - VictimAlliance - - )} -
-
- ); -}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillRow.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillRow.module.scss index b4819f57..e6aa2880 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillRow.module.scss +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillRow.module.scss @@ -1,10 +1,8 @@ .killRowContainer { @apply flex items-center whitespace-nowrap overflow-hidden; - &:not(:last-child) { @apply border-b border-stone-800; } - @apply bg-transparent transition-all hover:bg-stone-900 hover:border-stone-700; } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsSettingsDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsSettingsDialog.tsx index 71bc7f6b..7898be95 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsSettingsDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/SystemKillsSettingsDialog.tsx @@ -54,7 +54,6 @@ export const KillsSettingsDialog: React.FC = ({ visibl }, []); const handleAddSystemSubmit: SearchOnSubmitCallback = useCallback(item => { - if (localRef.current.excludedSystems.includes(item.value)) { return; } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx new file mode 100644 index 00000000..a4171f86 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/components/VictimRowSubInfo.tsx @@ -0,0 +1,40 @@ +// VictimSubRowInfo.tsx +import React from 'react'; +import clsx from 'clsx'; +import { zkillLink } from '../helpers'; +import classes from './SystemKillRow.module.scss'; + +interface VictimRowSubInfoProps { + victimCharacterId: number | null; + victimPortraitUrl: string | null; + victimCharName?: string; +} + +export const VictimRowSubInfo: React.FC = ({ + victimCharacterId = 0, + victimPortraitUrl, + victimCharName, +}) => { + if (!victimPortraitUrl || victimCharacterId === null || victimCharacterId <= 0) { + return null; + } + + return ( +
+
+ + {victimCharName + +
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/killRowUtils.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/killRowUtils.ts index 8cd4746d..d33b142e 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/killRowUtils.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/killRowUtils.ts @@ -33,10 +33,6 @@ export function formatISK(value: number): string { return Math.round(value).toString(); } -/** - * Determines whether this was an NPC kill, solo kill, etc. - * Returns { label: string, cssClass: string } for display, or null if none. - */ export function getAttackerSubscript(kill: DetailedKill) { if (kill.npc) { return { label: 'npc', cssClass: 'text-purple-400' }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts index 20d6c0f7..44aec660 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/helpers/linkHelpers.ts @@ -50,3 +50,62 @@ export function buildVictimImageUrls(args: { export function buildAttackerShipUrl(final_blow_ship_type_id?: number | null): string | null { return eveImageUrl('types', final_blow_ship_type_id, 'render', 64); } + +export function buildAttackerImageUrls(args: { + final_blow_char_id?: number | null; + final_blow_corp_id?: number | null; + final_blow_alliance_id?: number | null; +}) { + const { final_blow_char_id, final_blow_corp_id, final_blow_alliance_id } = args; + + const attackerPortraitUrl = eveImageUrl('characters', final_blow_char_id, 'portrait', 64); + const attackerCorpLogoUrl = eveImageUrl('corporations', final_blow_corp_id, 'logo', 32); + const attackerAllianceLogoUrl = eveImageUrl('alliances', final_blow_alliance_id, 'logo', 32); + + return { + attackerPortraitUrl, + attackerCorpLogoUrl, + attackerAllianceLogoUrl, + }; +} + +export function getPrimaryLogoAndTooltip( + allianceUrl: string | null, + corpUrl: string | null, + allianceName: string, + corpName: string, + fallback: string, +) { + let url: string | null = null; + let tooltip = ''; + + if (allianceUrl) { + url = allianceUrl; + tooltip = allianceName || fallback; + } else if (corpUrl) { + url = corpUrl; + tooltip = corpName || fallback; + } + + return { url, tooltip }; +} + +export function getAttackerPrimaryImageAndTooltip( + isNpc: boolean, + allianceUrl: string | null, + corpUrl: string | null, + allianceName: string, + corpName: string, + finalBlowShipTypeId: number, + npcFallback: string = 'NPC Attacker', +) { + if (isNpc) { + const shipUrl = buildAttackerShipUrl(finalBlowShipTypeId); + return { + url: shipUrl, + tooltip: npcFallback, + }; + } + + return getPrimaryLogoAndTooltip(allianceUrl, corpUrl, allianceName, corpName, 'Attacker'); +} diff --git a/assets/js/hooks/Mapper/types/kills.ts b/assets/js/hooks/Mapper/types/kills.ts index 1fb6a671..dac134c7 100644 --- a/assets/js/hooks/Mapper/types/kills.ts +++ b/assets/js/hooks/Mapper/types/kills.ts @@ -14,17 +14,22 @@ export interface DetailedKill { victim_char_name?: string; victim_corp_id?: number | null; victim_corp_ticker?: string; + victim_corp_name?: string; victim_alliance_id?: number | null; victim_alliance_ticker?: string; + victim_alliance_name?: string; victim_ship_type_id?: number | null; victim_ship_name?: string; + final_blow_char_id?: number | null; final_blow_char_name?: string; final_blow_corp_id?: number | null; final_blow_corp_ticker?: string; + final_blow_corp_name?: string; final_blow_alliance_id?: number | null; final_blow_alliance_ticker?: string; + final_blow_alliance_name?: string; final_blow_ship_type_id?: number | null; final_blow_ship_name?: string; diff --git a/lib/wanderer_app/zkb/zkills_provider/parser.ex b/lib/wanderer_app/zkb/zkills_provider/parser.ex index dc48d72d..12458474 100644 --- a/lib/wanderer_app/zkb/zkills_provider/parser.ex +++ b/lib/wanderer_app/zkb/zkills_provider/parser.ex @@ -231,19 +231,21 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do |> enrich_final_blow() end + defp enrich_victim(km) do km |> maybe_put_character_name("victim_char_id", "victim_char_name") - |> maybe_put_corp_ticker("victim_corp_id", "victim_corp_ticker") - |> maybe_put_alliance_ticker("victim_alliance_id", "victim_alliance_ticker") + |> maybe_put_corp_info("victim_corp_id", "victim_corp_ticker", "victim_corp_name") + |> maybe_put_alliance_info("victim_alliance_id", "victim_alliance_ticker", "victim_alliance_name") |> maybe_put_ship_name("victim_ship_type_id", "victim_ship_name") end + defp enrich_final_blow(km) do km |> maybe_put_character_name("final_blow_char_id", "final_blow_char_name") - |> maybe_put_corp_ticker("final_blow_corp_id", "final_blow_corp_ticker") - |> maybe_put_alliance_ticker("final_blow_alliance_id", "final_blow_alliance_ticker") + |> maybe_put_corp_info("final_blow_corp_id", "final_blow_corp_ticker", "final_blow_corp_name") + |> maybe_put_alliance_info("final_blow_alliance_id", "final_blow_alliance_ticker", "final_blow_alliance_name") |> maybe_put_ship_name("final_blow_ship_type_id", "final_blow_ship_name") end @@ -262,14 +264,16 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do end end - defp maybe_put_corp_ticker(km, id_key, ticker_key) do + defp maybe_put_corp_info(km, id_key, ticker_key, name_key) do case Map.get(km, id_key) do nil -> km 0 -> km corp_id -> case WandererApp.Esi.get_corporation_info(corp_id) do - {:ok, %{"ticker" => ticker}} -> - Map.put(km, ticker_key, ticker) + {:ok, %{"ticker" => ticker, "name" => corp_name}} -> + km + |> Map.put(ticker_key, ticker) + |> Map.put(name_key, corp_name) {:error, reason} -> Logger.warning("[Parser] Failed to fetch corp info: ID=#{corp_id}, reason=#{inspect(reason)}") @@ -281,14 +285,16 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do end end - defp maybe_put_alliance_ticker(km, id_key, ticker_key) do + defp maybe_put_alliance_info(km, id_key, ticker_key, name_key) do case Map.get(km, id_key) do nil -> km 0 -> km alliance_id -> case WandererApp.Esi.get_alliance_info(alliance_id) do - {:ok, %{"ticker" => alliance_ticker}} -> - Map.put(km, ticker_key, alliance_ticker) + {:ok, %{"ticker" => alliance_ticker, "name" => alliance_name}} -> + km + |> Map.put(ticker_key, alliance_ticker) + |> Map.put(name_key, alliance_name) _ -> km