- {Array.from(production).map((schematic, idx) => {
+ {Array.from(localProduction).map((schematic, idx) => {
return (
- {imports.map((i) => (
+ {localImports.map((i) => (
- {exports.map((exports) => (
+ {localExports.map((exports) => (
- {exports.map((exports) => (
+ {localExports.map((exports) => (
- {exports.map((e) => {
+ {localExports.map((e) => {
const valueInMillions =
(((piPrices?.appraisal.items.find((a) => a.typeID === e.typeId)
?.prices.sell.min ?? 0) *
diff --git a/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx b/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx
index 24c60fe..e9c1d99 100644
--- a/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx
+++ b/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx
@@ -1,7 +1,5 @@
-import { Api } from "@/esi-api";
-import { AccessToken, Planet } from "@/types";
+import { AccessToken } from "@/types";
import { Stack, Tooltip, Typography, styled, useTheme } from "@mui/material";
-import { useEffect, useState } from "react";
import { PlanetCard } from "./PlanetCard";
import { NoPlanetCard } from "./NoPlanetCard";
import Table from "@mui/material/Table";
@@ -21,25 +19,10 @@ const StackItem = styled(Stack)(({ theme }) => ({
alignItems: "center",
}));
-const getPlanets = async (character: AccessToken): Promise => {
- const api = new Api();
- const planets = (
- await api.v1.getCharactersCharacterIdPlanets(
- character.character.characterId,
- {
- token: character.access_token,
- }
- )
- ).data;
- return planets;
-};
-
const PlanetaryIteractionTable = ({
character,
- planets,
}: {
character: AccessToken;
- planets: Planet[];
}) => {
const theme = useTheme();
@@ -102,7 +85,7 @@ const PlanetaryIteractionTable = ({
- {planets.map((planet) => (
+ {character.planets.map((planet) => (
{
return (
- {planets.map((planet) => (
+ {character.planets.map((planet) => (
))}
- {Array.from(Array(6 - planets.length).keys()).map((i, id) => (
+ {Array.from(Array(6 - character.planets.length).keys()).map((i, id) => (
@@ -148,15 +129,11 @@ export const PlanetaryInteractionRow = ({
}: {
character: AccessToken;
}) => {
- const [planets, setPlanets] = useState([]);
const theme = useTheme();
- useEffect(() => {
- getPlanets(character).then(setPlanets).catch(console.log);
- }, [character]);
return theme.custom.compactMode ? (
-
+
) : (
-
+
);
};
diff --git a/src/app/components/Summary/Summary.tsx b/src/app/components/Summary/Summary.tsx
new file mode 100644
index 0000000..962d8a2
--- /dev/null
+++ b/src/app/components/Summary/Summary.tsx
@@ -0,0 +1,119 @@
+import { PI_TYPES_MAP } from "@/const";
+import { planetCalculations } from "@/planets";
+import { AccessToken } from "@/types";
+import {
+ TableContainer,
+ Paper,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ Typography,
+ Tooltip,
+ TableBody,
+ useTheme,
+ Stack,
+ styled,
+} from "@mui/material";
+
+const StackItem = styled(Stack)(({ theme }) => ({
+ ...theme.typography.body2,
+ padding: theme.custom.compactMode ? theme.spacing(1) : theme.spacing(2),
+ textAlign: "left",
+ justifyContent: "center",
+ alignItems: "center",
+}));
+
+interface Grouped {
+ [key: number]: number;
+}
+
+export const Summary = ({ characters }: { characters: AccessToken[] }) => {
+ const exports = characters.flatMap((char) => {
+ return char.planets.flatMap((planet) => {
+ const { localExports } = planetCalculations(planet);
+ return localExports;
+ });
+ });
+
+ const groupedByMaterial = exports.reduce((totals, material) => {
+ const { typeId, amount } = material;
+ const newTotal = isNaN(totals[typeId]) ? amount : totals[typeId] + amount;
+ totals[typeId] = newTotal;
+ return totals;
+ }, {});
+
+ const withProductNameAndPrice = Object.keys(groupedByMaterial).map(
+ (typeIdString) => {
+ const typeId = parseInt(typeIdString);
+ return {
+ typeId,
+ amount: groupedByMaterial[typeId],
+ materialName: PI_TYPES_MAP[typeId].name,
+ price: 0,
+ };
+ },
+ );
+ const theme = useTheme();
+
+ return (
+
+ Totals
+
+
+
+
+
+
+
+ Exports
+
+
+
+
+
+ u/h
+
+
+
+
+
+ ISK/M
+
+
+
+
+
+
+ {withProductNameAndPrice.map((product) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+const SummaryRow = ({
+ material,
+ amount,
+ price,
+}: {
+ material: string;
+ amount: number;
+ price: number;
+}) => (
+
+
+ {material}
+
+ {amount}
+ {price}
+
+);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6087f33..a3895ae 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -4,7 +4,7 @@ import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import { memo, useCallback, useEffect, useState } from "react";
-import { AccessToken, CharacterUpdate, Env } from "../types";
+import { AccessToken, CharacterUpdate, Env, PlanetWithInfo } from "../types";
import { MainGrid } from "./components/MainGrid";
import { refreshToken } from "@/esi-sso";
import {
@@ -16,6 +16,7 @@ import {
} from "./context/Context";
import { useSearchParams } from "next/navigation";
import { EvePraisalResult, fetchAllPrices } from "@/eve-praisal";
+import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets";
const Home = () => {
const [characters, setCharacters] = useState([]);
@@ -31,8 +32,6 @@ const Home = () => {
const searchParams = useSearchParams();
const code = searchParams && searchParams.get("code");
- // Memoize chracter state manipulations
-
const deleteCharacter = (character: AccessToken) => {
const charactersToSave = characters.filter(
(c) => character.character.characterId !== c.character.characterId,
@@ -83,6 +82,26 @@ const Home = () => {
return [];
}, []);
+ const initializeCharacterPlanets = (
+ characters: AccessToken[],
+ ): Promise =>
+ Promise.all(
+ characters.map(async (c) => {
+ const planets = await getPlanets(c);
+ const planetsWithInfo: PlanetWithInfo[] = await Promise.all(
+ planets.map(async (p) => ({
+ ...p,
+ info: await getPlanet(c, p),
+ infoUniverse: await getPlanetUniverse(p),
+ })),
+ );
+ return {
+ ...c,
+ planets: planetsWithInfo,
+ };
+ }),
+ );
+
const saveCharacters = (characters: AccessToken[]): AccessToken[] => {
localStorage.setItem("characters", JSON.stringify(characters));
return characters;
@@ -148,6 +167,7 @@ const Home = () => {
.then(refreshSession)
.then(handleCallback)
.then(saveCharacters)
+ .then(initializeCharacterPlanets)
.then(setCharacters)
.then(() => setSessionReady(true));
}, []);
diff --git a/src/planets.ts b/src/planets.ts
new file mode 100644
index 0000000..df774e4
--- /dev/null
+++ b/src/planets.ts
@@ -0,0 +1,122 @@
+import {
+ AccessToken,
+ Planet,
+ PlanetInfo,
+ PlanetInfoUniverse,
+ PlanetWithInfo,
+} from "@/types";
+import { Api } from "@/esi-api";
+import { EXTRACTOR_TYPE_IDS, FACTORY_IDS, PI_SCHEMATICS } from "@/const";
+import { extractorsHaveExpired } from "./app/components/PlanetaryInteraction/timeColors";
+
+export const getPlanets = async (character: AccessToken): Promise => {
+ const api = new Api();
+ const planets = (
+ await api.v1.getCharactersCharacterIdPlanets(
+ character.character.characterId,
+ {
+ token: character.access_token,
+ },
+ )
+ ).data;
+ return planets;
+};
+
+export const getPlanet = async (
+ character: AccessToken,
+ planet: Planet,
+): Promise => {
+ const api = new Api();
+ const planetInfo = (
+ await api.v3.getCharactersCharacterIdPlanetsPlanetId(
+ character.character.characterId,
+ planet.planet_id,
+ {
+ token: character.access_token,
+ },
+ )
+ ).data;
+ return planetInfo;
+};
+
+export const getPlanetUniverse = async (
+ planet: Planet,
+): Promise => {
+ const api = new Api();
+ const planetInfo = (await api.v1.getUniversePlanetsPlanetId(planet.planet_id))
+ .data;
+ return planetInfo;
+};
+
+export const planetCalculations = (planet: PlanetWithInfo) => {
+ const planetInfo = planet.info;
+ type SchematicId = number;
+ const extractors: PlanetInfo["pins"] = planetInfo.pins.filter((p) =>
+ EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id),
+ );
+ const localProduction = planetInfo.pins
+ .filter((p) => FACTORY_IDS().some((e) => e.type_id === p.type_id))
+ .reduce((acc, f) => {
+ if (f.schematic_id) {
+ const schematic = PI_SCHEMATICS.find(
+ (s) => s.schematic_id == f.schematic_id,
+ );
+ if (schematic) acc.set(f.schematic_id, schematic);
+ }
+ return acc;
+ }, new Map());
+
+ const locallyProduced = Array.from(localProduction)
+ .flatMap((p) => p[1].outputs)
+ .map((p) => p.type_id);
+
+ const locallyConsumed = Array.from(localProduction)
+ .flatMap((p) => p[1].inputs)
+ .map((p) => p.type_id);
+
+ const locallyExcavated = planetInfo.pins
+ .filter((p) => EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id))
+ .map((e) => e.extractor_details?.product_type_id ?? 0);
+
+ const localImports = Array.from(localProduction)
+ .flatMap((p) => p[1].inputs)
+ .filter(
+ (p) =>
+ ![...locallyProduced, ...locallyExcavated].some(
+ (lp) => lp === p.type_id,
+ ),
+ );
+
+ const localExports = locallyProduced
+ .filter((p) => !locallyConsumed.some((lp) => lp === p))
+ .map((typeId) => {
+ const outputs = PI_SCHEMATICS.flatMap((s) => s.outputs).find(
+ (s) => s.type_id === typeId,
+ );
+ if (!outputs) return { typeId, amount: 0 };
+ const cycleTime =
+ PI_SCHEMATICS.find((s) => s.schematic_id === outputs.schematic_id)
+ ?.cycle_time ?? 3600;
+ const factoriesProducing = planetInfo.pins
+ .filter((p) => FACTORY_IDS().some((e) => e.type_id === p.type_id))
+ .filter((f) => f.schematic_id === outputs?.schematic_id);
+ const amount = outputs.quantity
+ ? factoriesProducing.length * outputs.quantity * (3600 / cycleTime)
+ : 0;
+ return {
+ typeId,
+ amount,
+ };
+ });
+
+ const expired = extractorsHaveExpired(extractors.map((e) => e.expiry_time));
+ return {
+ expired,
+ localExports,
+ localImports,
+ locallyConsumed,
+ locallyProduced,
+ localProduction,
+ extractors,
+ };
+};
diff --git a/src/types.ts b/src/types.ts
index 083e7f7..f359f6f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -10,6 +10,7 @@ export interface AccessToken {
needsLogin: boolean;
comment: string;
system: string;
+ planets: PlanetWithInfo[];
}
export interface Character {
@@ -25,6 +26,7 @@ export interface CharacterPlanets {
name: string;
characterId: number;
account?: string;
+ system?: string;
planets: PlanetWithInfo[];
}