refactor types and add account level totals
This commit is contained in:
@@ -7,11 +7,64 @@ import { useContext, useState } from "react";
|
||||
import { PlanRow } from "./PlanRow";
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
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[] }) => {
|
||||
const theme = useTheme();
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const { planMode } = useContext(SessionContext);
|
||||
const { planMode, piPrices } = useContext(SessionContext);
|
||||
const { monthlyEstimate, storageValue } = calculateAccountTotals(characters, piPrices);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@@ -46,6 +99,7 @@ export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "0.9rem",
|
||||
@@ -57,6 +111,27 @@ export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
|
||||
? `Account: ${characters[0].account}`
|
||||
: "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
|
||||
size="small"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
|
@@ -97,15 +97,6 @@ export const PlanetTableRow = ({
|
||||
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 =>
|
||||
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 storageCapacity = STORAGE_CAPACITIES[pin.type_id] || 0;
|
||||
|
||||
// Calculate total volume of stored products for this specific pin
|
||||
const totalVolume = (pin.contents || [])
|
||||
.reduce((sum: number, item: any) => {
|
||||
const volume = PI_PRODUCT_VOLUMES[item.type_id] || 0;
|
||||
return sum + (item.amount * volume);
|
||||
}, 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;
|
||||
|
||||
return {
|
||||
type: storageType,
|
||||
capacity: storageCapacity,
|
||||
used: totalVolume,
|
||||
fillRate: fillRate
|
||||
fillRate: fillRate,
|
||||
value: totalValue
|
||||
};
|
||||
};
|
||||
|
||||
@@ -319,6 +316,11 @@ export const PlanetTableRow = ({
|
||||
<Typography fontSize={theme.custom.smallText} style={{ color }}>
|
||||
{fillRate.toFixed(1)}%
|
||||
</Typography>
|
||||
{storageInfo.value > 0 && (
|
||||
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px" }}>
|
||||
({Math.round(storageInfo.value / 1000000)}M)
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@@ -51,7 +51,7 @@ export const getPlanetUniverse = async (
|
||||
export const planetCalculations = (planet: PlanetWithInfo) => {
|
||||
const planetInfo = planet.info;
|
||||
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),
|
||||
);
|
||||
const localProduction = planetInfo.pins
|
||||
|
101
src/types.ts
101
src/types.ts
@@ -1,5 +1,4 @@
|
||||
import { PlanetConfig } from "./app/components/PlanetConfig/PlanetConfigDialog";
|
||||
import { Api } from "./esi-api";
|
||||
|
||||
export interface AccessToken {
|
||||
access_token: string;
|
||||
@@ -20,10 +19,50 @@ export interface Character {
|
||||
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 {
|
||||
info: PlanetInfo;
|
||||
infoUniverse: PlanetInfoUniverse;
|
||||
}
|
||||
|
||||
export interface CharacterPlanets {
|
||||
name: string;
|
||||
characterId: number;
|
||||
@@ -38,31 +77,45 @@ export interface CharacterUpdate {
|
||||
system?: string;
|
||||
}
|
||||
|
||||
export type Planet = EsiType<"v1", "getCharactersCharacterIdPlanets">[number];
|
||||
|
||||
export type PlanetInfoUniverse = EsiType<"v1", "getUniversePlanetsPlanetId">;
|
||||
|
||||
export type PlanetInfo = EsiType<
|
||||
"v3",
|
||||
"getCharactersCharacterIdPlanetsPlanetId"
|
||||
>;
|
||||
|
||||
export interface Env {
|
||||
EVE_SSO_CALLBACK_URL: string;
|
||||
EVE_SSO_CLIENT_ID: string;
|
||||
}
|
||||
|
||||
type EsiApiVersionType = keyof InstanceType<typeof Api<unknown>>;
|
||||
type EsiApiPathType<V extends EsiApiVersionType> = keyof InstanceType<
|
||||
typeof Api<unknown>
|
||||
>[V];
|
||||
type EsiApiResponseType<
|
||||
V extends EsiApiVersionType,
|
||||
T extends EsiApiPathType<V>,
|
||||
> = 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 EvePraisalResult {
|
||||
appraisal: {
|
||||
items: Array<{
|
||||
typeID: number;
|
||||
prices: {
|
||||
sell: {
|
||||
min: number;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}>;
|
||||
}
|
||||
|
Reference in New Issue
Block a user