refactor types and add account level totals

This commit is contained in:
calli
2025-04-22 12:47:12 +03:00
parent 8809fec6e0
commit 42f95c17de
4 changed files with 178 additions and 48 deletions

View File

@@ -7,11 +7,64 @@ import { useContext, useState } from "react";
import { PlanRow } from "./PlanRow"; import { PlanRow } from "./PlanRow";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import { planetCalculations } from "@/planets";
import { EvePraisalResult } from "@/eve-praisal";
import { STORAGE_IDS } from "@/const";
interface AccountTotals {
monthlyEstimate: number;
storageValue: number;
}
const calculateAccountTotals = (characters: AccessToken[], piPrices: EvePraisalResult | undefined): AccountTotals => {
let totalMonthlyEstimate = 0;
let totalStorageValue = 0;
characters.forEach((character) => {
character.planets.forEach((planet) => {
const { localExports } = planetCalculations(planet);
const planetConfig = character.planetConfig.find(p => p.planetId === planet.planet_id);
// Calculate monthly estimate
if (!planetConfig?.excludeFromTotals) {
localExports.forEach((exportItem) => {
const valueInMillions = (((piPrices?.appraisal.items.find(
(a) => a.typeID === exportItem.typeId,
)?.prices.sell.min ?? 0) *
exportItem.amount) /
1000000) *
24 *
30;
totalMonthlyEstimate += valueInMillions;
});
}
if (!planetConfig?.excludeFromTotals) {
planet.info.pins
.filter(pin => STORAGE_IDS().some(storage => storage.type_id === pin.type_id))
.forEach(storage => {
storage.contents?.forEach(content => {
const valueInMillions = (piPrices?.appraisal.items.find(
(a) => a.typeID === content.type_id,
)?.prices.sell.min ?? 0) * content.amount / 1000000;
totalStorageValue += valueInMillions;
});
});
}
});
});
return {
monthlyEstimate: totalMonthlyEstimate,
storageValue: totalStorageValue
};
};
export const AccountCard = ({ characters }: { characters: AccessToken[] }) => { export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
const theme = useTheme(); const theme = useTheme();
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);
const { planMode } = useContext(SessionContext); const { planMode, piPrices } = useContext(SessionContext);
const { monthlyEstimate, storageValue } = calculateAccountTotals(characters, piPrices);
return ( return (
<Paper <Paper
@@ -46,17 +99,39 @@ export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<Typography <Box>
sx={{ <Typography
fontSize: "0.9rem", sx={{
fontWeight: 500, fontSize: "0.9rem",
color: theme.palette.text.primary, fontWeight: 500,
}} color: theme.palette.text.primary,
> }}
{characters.length > 0 && characters[0].account !== "-" >
? `Account: ${characters[0].account}` {characters.length > 0 && characters[0].account !== "-"
: "No account name"} ? `Account: ${characters[0].account}`
</Typography> : "No account name"}
</Typography>
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Monthly Estimate: {monthlyEstimate >= 1000
? `${(monthlyEstimate / 1000).toFixed(2)} B`
: `${monthlyEstimate.toFixed(2)} M`} ISK
</Typography>
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Storage Value: {storageValue >= 1000
? `${(storageValue / 1000).toFixed(2)} B`
: `${storageValue.toFixed(2)} M`} ISK
</Typography>
</Box>
<IconButton <IconButton
size="small" size="small"
onClick={() => setIsCollapsed(!isCollapsed)} onClick={() => setIsCollapsed(!isCollapsed)}

View File

@@ -97,15 +97,6 @@ export const PlanetTableRow = ({
cycleTime: schematic.cycle_time cycleTime: schematic.cycle_time
})); }));
// Convert Map to Array for schematic IDs
const installedSchematicIds = Array.from(localProduction.values()).map(p => p.schematic_id);
// Get extractor head types safely
const extractedTypeIds = extractors
.map(e => e.extractor_details?.product_type_id)
.filter((id): id is number => id !== undefined);
// Get storage facilities
const storageFacilities = planetInfo.pins.filter(pin => const storageFacilities = planetInfo.pins.filter(pin =>
STORAGE_IDS().some(storage => storage.type_id === pin.type_id) STORAGE_IDS().some(storage => storage.type_id === pin.type_id)
); );
@@ -116,20 +107,26 @@ export const PlanetTableRow = ({
const storageType = PI_TYPES_MAP[pin.type_id].name; const storageType = PI_TYPES_MAP[pin.type_id].name;
const storageCapacity = STORAGE_CAPACITIES[pin.type_id] || 0; const storageCapacity = STORAGE_CAPACITIES[pin.type_id] || 0;
// Calculate total volume of stored products for this specific pin
const totalVolume = (pin.contents || []) const totalVolume = (pin.contents || [])
.reduce((sum: number, item: any) => { .reduce((sum: number, item: any) => {
const volume = PI_PRODUCT_VOLUMES[item.type_id] || 0; const volume = PI_PRODUCT_VOLUMES[item.type_id] || 0;
return sum + (item.amount * volume); return sum + (item.amount * volume);
}, 0); }, 0);
const totalValue = (pin.contents || [])
.reduce((sum: number, item: any) => {
const price = piPrices?.appraisal.items.find((a) => a.typeID === item.type_id)?.prices.sell.min ?? 0;
return sum + (item.amount * price);
}, 0);
const fillRate = storageCapacity > 0 ? (totalVolume / storageCapacity) * 100 : 0; const fillRate = storageCapacity > 0 ? (totalVolume / storageCapacity) * 100 : 0;
return { return {
type: storageType, type: storageType,
capacity: storageCapacity, capacity: storageCapacity,
used: totalVolume, used: totalVolume,
fillRate: fillRate fillRate: fillRate,
value: totalValue
}; };
}; };
@@ -319,6 +316,11 @@ export const PlanetTableRow = ({
<Typography fontSize={theme.custom.smallText} style={{ color }}> <Typography fontSize={theme.custom.smallText} style={{ color }}>
{fillRate.toFixed(1)}% {fillRate.toFixed(1)}%
</Typography> </Typography>
{storageInfo.value > 0 && (
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px" }}>
({Math.round(storageInfo.value / 1000000)}M)
</Typography>
)}
</div> </div>
); );
})} })}

View File

@@ -51,7 +51,7 @@ export const getPlanetUniverse = async (
export const planetCalculations = (planet: PlanetWithInfo) => { export const planetCalculations = (planet: PlanetWithInfo) => {
const planetInfo = planet.info; const planetInfo = planet.info;
type SchematicId = number; type SchematicId = number;
const extractors: PlanetInfo["pins"] = planetInfo.pins.filter((p) => const extractors = planetInfo.pins.filter((p) =>
EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id), EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id),
); );
const localProduction = planetInfo.pins const localProduction = planetInfo.pins

View File

@@ -1,5 +1,4 @@
import { PlanetConfig } from "./app/components/PlanetConfig/PlanetConfigDialog"; import { PlanetConfig } from "./app/components/PlanetConfig/PlanetConfigDialog";
import { Api } from "./esi-api";
export interface AccessToken { export interface AccessToken {
access_token: string; access_token: string;
@@ -20,10 +19,50 @@ export interface Character {
characterId: number; characterId: number;
} }
export interface Planet {
planet_id: number;
solar_system_id: number;
planet_type: "temperate" | "barren" | "oceanic" | "ice" | "gas" | "lava" | "storm" | "plasma";
last_update: string;
num_pins: number;
owner_id: number;
upgrade_level: number;
}
export interface PlanetInfo {
links: Array<{
destination_pin_id: number;
link_level: number;
source_pin_id: number;
}>;
pins: Pin[];
routes: Array<{
content_type_id: number;
destination_pin_id: number;
quantity: number;
route_id: number;
source_pin_id: number;
waypoints?: number[];
}>;
}
export interface PlanetInfoUniverse {
name: string;
planet_id: number;
system_id: number;
type_id: number;
position: {
x: number;
y: number;
z: number;
};
}
export interface PlanetWithInfo extends Planet { export interface PlanetWithInfo extends Planet {
info: PlanetInfo; info: PlanetInfo;
infoUniverse: PlanetInfoUniverse; infoUniverse: PlanetInfoUniverse;
} }
export interface CharacterPlanets { export interface CharacterPlanets {
name: string; name: string;
characterId: number; characterId: number;
@@ -38,31 +77,45 @@ export interface CharacterUpdate {
system?: string; system?: string;
} }
export type Planet = EsiType<"v1", "getCharactersCharacterIdPlanets">[number];
export type PlanetInfoUniverse = EsiType<"v1", "getUniversePlanetsPlanetId">;
export type PlanetInfo = EsiType<
"v3",
"getCharactersCharacterIdPlanetsPlanetId"
>;
export interface Env { export interface Env {
EVE_SSO_CALLBACK_URL: string; EVE_SSO_CALLBACK_URL: string;
EVE_SSO_CLIENT_ID: string; EVE_SSO_CLIENT_ID: string;
} }
type EsiApiVersionType = keyof InstanceType<typeof Api<unknown>>; export interface EvePraisalResult {
type EsiApiPathType<V extends EsiApiVersionType> = keyof InstanceType< appraisal: {
typeof Api<unknown> items: Array<{
>[V]; typeID: number;
type EsiApiResponseType< prices: {
V extends EsiApiVersionType, sell: {
T extends EsiApiPathType<V>, min: number;
> = Awaited<ReturnType<InstanceType<typeof Api<unknown>>[V][T]>>; };
export type EsiType< };
V extends EsiApiVersionType, }>;
T extends EsiApiPathType<V>, };
> = EsiApiResponseType<V, T> extends { data: any } }
? EsiApiResponseType<V, T>["data"]
: never; export interface Pin {
pin_id: number;
type_id: number;
schematic_id?: number;
expiry_time?: string;
install_time?: string;
latitude: number;
longitude: number;
extractor_details?: {
cycle_time?: number;
head_radius?: number;
heads: Array<{
head_id: number;
latitude: number;
longitude: number;
}>;
product_type_id?: number;
qty_per_cycle?: number;
};
contents?: Array<{
type_id: number;
amount: number;
}>;
}