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 { 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,6 +99,7 @@ export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.9rem",
|
fontSize: "0.9rem",
|
||||||
@@ -57,6 +111,27 @@ export const AccountCard = ({ characters }: { characters: AccessToken[] }) => {
|
|||||||
? `Account: ${characters[0].account}`
|
? `Account: ${characters[0].account}`
|
||||||
: "No account name"}
|
: "No account name"}
|
||||||
</Typography>
|
</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)}
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -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
|
||||||
|
101
src/types.ts
101
src/types.ts
@@ -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;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user