From 73b54f6bf531e22070e29cf6d4e4717c1197b51a Mon Sep 17 00:00:00 2001 From: calli Date: Fri, 2 May 2025 21:41:48 +0300 Subject: [PATCH] hoist calculations and alerts to accountCard level --- src/app/components/Account/AccountCard.tsx | 195 ++++++++++++- .../PlanetaryInteraction/PlanetCard.tsx | 207 ++++++-------- .../PlanetaryInteraction/PlanetTableRow.tsx | 256 +++++++----------- .../PlanetaryInteractionRow.tsx | 15 +- src/types/planet.ts | 83 ++++++ 5 files changed, 459 insertions(+), 297 deletions(-) create mode 100644 src/types/planet.ts diff --git a/src/app/components/Account/AccountCard.tsx b/src/app/components/Account/AccountCard.tsx index ab4dd4c..df4706b 100644 --- a/src/app/components/Account/AccountCard.tsx +++ b/src/app/components/Account/AccountCard.tsx @@ -1,4 +1,4 @@ -import { AccessToken } from "@/types"; +import { AccessToken, PlanetWithInfo, Pin } from "@/types"; import { Box, Stack, Typography, useTheme, Paper, IconButton, Divider } from "@mui/material"; import { CharacterRow } from "../Characters/CharacterRow"; import { PlanetaryInteractionRow } from "../PlanetaryInteraction/PlanetaryInteractionRow"; @@ -9,7 +9,9 @@ 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"; +import { STORAGE_IDS, PI_SCHEMATICS, PI_PRODUCT_VOLUMES, STORAGE_CAPACITIES } from "@/const"; +import { DateTime } from "luxon"; +import { PlanetCalculations, AlertState, StorageContent, StorageInfo } from "@/types/planet"; interface AccountTotals { monthlyEstimate: number; @@ -20,6 +22,146 @@ interface AccountTotals { totalExtractors: number; } +const calculateAlertState = (planetDetails: PlanetCalculations): AlertState => { + const hasLowStorage = planetDetails.storageInfo.some(storage => storage.fillRate > 60); + const hasLowImports = planetDetails.importDepletionTimes.some(depletion => depletion.hoursUntilDepletion < 24); + + return { + expired: planetDetails.expired, + hasLowStorage, + hasLowImports, + hasLargeExtractorDifference: planetDetails.hasLargeExtractorDifference + }; +}; + +const calculatePlanetDetails = (planet: PlanetWithInfo, piPrices: EvePraisalResult | undefined, balanceThreshold: number): PlanetCalculations => { + const { expired, extractors, localProduction: rawProduction, localImports, localExports: rawExports } = planetCalculations(planet); + + // Convert localProduction to include factoryCount + const localProduction = new Map(Array.from(rawProduction).map(([key, value]) => [ + key, + { + ...value, + factoryCount: value.count || 1 + } + ])); + + // Calculate extractor averages and check for large differences + const extractorAverages = extractors + .filter(e => e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) + .map(e => { + const cycleTime = e.extractor_details?.cycle_time || 3600; + const qtyPerCycle = e.extractor_details?.qty_per_cycle || 0; + return { + typeId: e.extractor_details!.product_type_id!, + averagePerHour: (qtyPerCycle * 3600) / cycleTime + }; + }); + + const hasLargeExtractorDifference = extractorAverages.length === 2 && + Math.abs(extractorAverages[0].averagePerHour - extractorAverages[1].averagePerHour) > balanceThreshold; + + // Calculate storage info + const storageFacilities = planet.info.pins.filter((pin: Pin) => + STORAGE_IDS().some(storage => storage.type_id === pin.type_id) + ); + + const storageInfo = storageFacilities.map((storage: Pin) => { + if (!storage || !storage.contents) return null; + + const storageType = STORAGE_IDS().find(s => s.type_id === storage.type_id)?.name || 'Unknown'; + const storageCapacity = STORAGE_CAPACITIES[storage.type_id] || 0; + + const totalVolume = (storage.contents || []) + .reduce((sum: number, item: StorageContent) => { + const volume = PI_PRODUCT_VOLUMES[item.type_id] || 0; + return sum + (item.amount * volume); + }, 0); + + const totalValue = (storage.contents || []) + .reduce((sum: number, item: StorageContent) => { + 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, + type_id: storage.type_id, + capacity: storageCapacity, + used: totalVolume, + fillRate: fillRate, + value: totalValue + }; + }).filter(Boolean) as StorageInfo[]; + + // Calculate import depletion times + const importDepletionTimes = localImports.map(i => { + // Find all storage facilities containing this import + const storagesWithImport = storageFacilities.filter((storage: Pin) => + storage.contents?.some((content: StorageContent) => content.type_id === i.type_id) + ); + + // Get the total amount in all storage facilities + const totalAmount = storagesWithImport.reduce((sum: number, storage: Pin) => { + const content = storage.contents?.find((content: StorageContent) => content.type_id === i.type_id); + return sum + (content?.amount ?? 0); + }, 0); + + // Calculate consumption rate per hour + const schematic = PI_SCHEMATICS.find(s => s.schematic_id === i.schematic_id); + const cycleTime = schematic?.cycle_time ?? 3600; + const consumptionPerHour = i.quantity * i.factoryCount * (3600 / cycleTime); + + // Calculate time until depletion in hours, starting from last_update + const lastUpdate = DateTime.fromISO(planet.last_update); + const now = DateTime.now(); + const hoursSinceUpdate = now.diff(lastUpdate, 'hours').hours; + const remainingAmount = Math.max(0, totalAmount - (consumptionPerHour * hoursSinceUpdate)); + const hoursUntilDepletion = consumptionPerHour > 0 ? remainingAmount / consumptionPerHour : 0; + + // Calculate monthly cost + const price = piPrices?.appraisal.items.find((a) => a.typeID === i.type_id)?.prices.sell.min ?? 0; + const monthlyCost = (consumptionPerHour * 24 * 30 * price) / 1000000; // Cost in millions + + return { + typeId: i.type_id, + hoursUntilDepletion, + monthlyCost + }; + }); + + // Convert localExports to match the LocalExport interface + const localExports = rawExports.map(e => { + const schematic = PI_SCHEMATICS.flatMap(s => s.outputs) + .find(s => s.type_id === e.typeId)?.schematic_id ?? 0; + const factoryCount = planet.info.pins + .filter(p => p.schematic_id === schematic) + .length; + + return { + type_id: e.typeId, + schematic_id: schematic, + quantity: e.amount / factoryCount, // Convert total amount back to per-factory quantity + factoryCount + }; + }); + + return { + expired, + extractors, + localProduction, + localImports, + localExports, + storageInfo, + extractorAverages, + hasLargeExtractorDifference, + importDepletionTimes, + visibility: 'visible' as const + }; +}; + const calculateAccountTotals = (characters: AccessToken[], piPrices: EvePraisalResult | undefined): AccountTotals => { let totalMonthlyEstimate = 0; let totalStorageValue = 0; @@ -86,14 +228,49 @@ const calculateAccountTotals = (characters: AccessToken[], piPrices: EvePraisalR export const AccountCard = ({ characters, isCollapsed: propIsCollapsed }: { characters: AccessToken[], isCollapsed?: boolean }) => { const theme = useTheme(); const [localIsCollapsed, setLocalIsCollapsed] = useState(false); - const { planMode, piPrices } = useContext(SessionContext); + const { planMode, piPrices, alertMode, balanceThreshold } = useContext(SessionContext); const { monthlyEstimate, storageValue, planetCount, characterCount, runningExtractors, totalExtractors } = calculateAccountTotals(characters, piPrices); + // Calculate planet details and alert states for each planet + const planetDetails = characters.reduce((acc, character) => { + character.planets.forEach(planet => { + const details = calculatePlanetDetails(planet, piPrices, balanceThreshold); + acc[`${character.character.characterId}-${planet.planet_id}`] = { + ...details, + alertState: calculateAlertState(details) + }; + }); + return acc; + }, {} as Record); + // Update local collapse state when prop changes useEffect(() => { setLocalIsCollapsed(propIsCollapsed ?? false); }, [propIsCollapsed]); + const getAlertVisibility = (alertState: AlertState) => { + if (!alertMode) return 'visible'; + if (alertState.expired) return 'visible'; + if (alertState.hasLowStorage) return 'visible'; + if (alertState.hasLowImports) return 'visible'; + if (alertState.hasLargeExtractorDifference) return 'visible'; + return 'hidden'; + }; + + // Check if any planet in the account has alerts + const hasAnyAlerts = Object.values(planetDetails).some(details => { + const alertState = calculateAlertState(details); + return alertState.expired || + alertState.hasLowStorage || + alertState.hasLowImports || + alertState.hasLargeExtractorDifference; + }); + + // If in alert mode and no alerts, hide the entire card + if (alertMode && !hasAnyAlerts) { + return null; + } + return ( ) : ( - + { + const details = planetDetails[`${c.character.characterId}-${planet.planet_id}`]; + acc[planet.planet_id] = { + ...details, + visibility: getAlertVisibility(details.alertState) + }; + return acc; + }, {} as Record)} + /> )} ))} diff --git a/src/app/components/PlanetaryInteraction/PlanetCard.tsx b/src/app/components/PlanetaryInteraction/PlanetCard.tsx index 1d1121b..92737ef 100644 --- a/src/app/components/PlanetaryInteraction/PlanetCard.tsx +++ b/src/app/components/PlanetaryInteraction/PlanetCard.tsx @@ -4,18 +4,21 @@ import { AccessToken, PlanetWithInfo, } from "@/types"; +import { PlanetCalculations } from "@/types/planet"; import React, { useContext } from "react"; import { DateTime } from "luxon"; -import { EXTRACTOR_TYPE_IDS } from "@/const"; import Countdown from "react-countdown"; -import { getProgramOutputPrediction } from "./ExtractionSimulation"; -import { - alertModeVisibility, - extractorsHaveExpired, - timeColor, -} from "./alerts"; -import { ColorContext, SessionContext } from "@/app/context/Context"; +import { ColorContext } from "@/app/context/Context"; import { ExtractionSimulationTooltip } from "./ExtractionSimulationTooltip"; +import { timeColor } from "./alerts"; + +interface ExtractorConfig { + typeId: number; + baseValue: number; + cycleTime: number; + installTime: string; + expiryTime: string; +} const StackItem = styled(Stack)(({ theme }) => ({ ...theme.typography.body2, @@ -29,82 +32,31 @@ const StackItem = styled(Stack)(({ theme }) => ({ export const PlanetCard = ({ character, planet, + planetDetails, }: { character: AccessToken; planet: PlanetWithInfo; + planetDetails: PlanetCalculations; }) => { - const { alertMode } = useContext(SessionContext); - - const planetInfo = planet.info; - const planetInfoUniverse = planet.infoUniverse; - const theme = useTheme(); - - const extractorsExpiryTime = - (planetInfo && - planetInfo.pins - .filter((p) => EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id)) - .map((p) => p.expiry_time)) ?? - []; - const { colors } = useContext(ColorContext); - const expired = extractorsHaveExpired(extractorsExpiryTime); - const CYCLE_TIME = 30 * 60; // 30 minutes in seconds - - const extractors = planetInfo?.pins - .filter((p) => EXTRACTOR_TYPE_IDS.some((e) => e === p.type_id)) - .map((p) => ({ - typeId: p.type_id, - baseValue: p.extractor_details?.qty_per_cycle || 0, - cycleTime: p.extractor_details?.cycle_time || 3600, - installTime: p.install_time || "", - expiryTime: p.expiry_time || "", - installedSchematicId: p.extractor_details?.product_type_id || undefined - })) || []; - - // Calculate program duration and cycles for each extractor - const extractorPrograms = extractors.map(extractor => { - const installDate = new Date(extractor.installTime); - const expiryDate = new Date(extractor.expiryTime); - const programDuration = (expiryDate.getTime() - installDate.getTime()) / 1000; // Convert to seconds - return { - ...extractor, - programDuration, - cycles: Math.floor(programDuration / CYCLE_TIME) - }; - }); - - - // Get output predictions for each extractor - const extractorOutputs = extractorPrograms.map(extractor => ({ - typeId: extractor.typeId, - cycleTime: CYCLE_TIME, - cycles: extractor.cycles, - prediction: getProgramOutputPrediction( - extractor.baseValue, - CYCLE_TIME, - extractor.cycles - ) - })); - - // Calculate average per hour for each extractor - const extractorAverages = extractorOutputs.map(extractor => { - const totalOutput = extractor.prediction.reduce((sum, val) => sum + val, 0); - const programDuration = extractor.cycles * CYCLE_TIME; - const averagePerHour = (totalOutput / programDuration) * 3600; - return { - typeId: extractor.typeId, - averagePerHour - }; - }); + const extractorConfigs: ExtractorConfig[] = planetDetails.extractors + .filter(e => e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) + .map(e => ({ + typeId: e.extractor_details!.product_type_id!, + baseValue: e.extractor_details!.qty_per_cycle!, + cycleTime: e.extractor_details?.cycle_time || 3600, + installTime: e.install_time ?? "", + expiryTime: e.expiry_time ?? "" + })); return ( 0 ? ( + planetDetails.extractors.length > 0 ? ( ) : null } @@ -121,14 +73,13 @@ export const PlanetCard = ({ } }} > - - +
- - {expired && ( - - )} -
- - {planetInfoUniverse?.name} - - {extractorsExpiryTime.map((e, idx) => { - const extractor = extractors[idx]; - const average = extractorAverages[idx]; - return ( -
- - {!expired && e && - } - - {!expired && extractor && average && ( -
- - - {average.averagePerHour.toFixed(1)}/h - -
- )} -
- ); - })} -
-
+ {planetDetails.expired && ( + + )} +
+ + {planet.infoUniverse?.name} + + {planetDetails.extractors.map((e, idx) => { + const average = planetDetails.extractorAverages[idx]; + return ( +
+ + {!planetDetails.expired && e.expiry_time && + } + + {!planetDetails.expired && e && average && ( +
+ + + {average.averagePerHour.toFixed(1)}/h + +
+ )} +
+ ); + })} +
+
); }; diff --git a/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx b/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx index 84c5287..befa908 100644 --- a/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx +++ b/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx @@ -2,6 +2,7 @@ import { ColorContext, SessionContext } from "@/app/context/Context"; import { PI_TYPES_MAP, STORAGE_IDS, STORAGE_CAPACITIES, PI_PRODUCT_VOLUMES, EVE_IMAGE_URL, PI_SCHEMATICS, LAUNCHPAD_IDS } from "@/const"; import { planetCalculations } from "@/planets"; import { AccessToken, PlanetWithInfo } from "@/types"; +import { PlanetCalculations, StorageInfo } from "@/types/planet"; import CloseIcon from "@mui/icons-material/Close"; import MoreVertIcon from '@mui/icons-material/MoreVert'; import { Button, Tooltip, Typography, useTheme, Menu, MenuItem, IconButton, Checkbox, FormControlLabel } from "@mui/material"; @@ -18,10 +19,9 @@ import React, { forwardRef, useContext, useState } from "react"; import Countdown from "react-countdown"; import { PlanetConfigDialog } from "../PlanetConfig/PlanetConfigDialog"; import PinsCanvas3D from "./PinsCanvas3D"; -import { alertModeVisibility, timeColor } from "./alerts"; +import { timeColor } from "./alerts"; import { ExtractionSimulationDisplay } from './ExtractionSimulationDisplay'; import { ExtractionSimulationTooltip } from './ExtractionSimulationTooltip'; -import { ProductionNode } from './ExtractionSimulation'; import { Collapse, Box, Stack } from "@mui/material"; const Transition = forwardRef(function Transition( @@ -33,15 +33,28 @@ const Transition = forwardRef(function Transition( return ; }); +interface SchematicInput { + type_id: number; + quantity: number; +} + +interface SchematicOutput { + type_id: number; + quantity: number; +} + export const PlanetTableRow = ({ planet, character, + planetDetails, }: { planet: PlanetWithInfo; character: AccessToken; + planetDetails: PlanetCalculations; }) => { const theme = useTheme(); - const { showProductIcons, extractionTimeMode } = useContext(SessionContext); + const { showProductIcons, extractionTimeMode, alertMode } = useContext(SessionContext); + const { colors } = useContext(ColorContext); const [planetRenderOpen, setPlanetRenderOpen] = useState(false); const [planetConfigOpen, setPlanetConfigOpen] = useState(false); @@ -72,80 +85,13 @@ export const PlanetTableRow = ({ setPlanetConfigOpen(false); }; - const { piPrices, alertMode, updatePlanetConfig, readPlanetConfig, balanceThreshold } = useContext(SessionContext); + const { piPrices, updatePlanetConfig, readPlanetConfig } = useContext(SessionContext); const planetInfo = planet.info; const planetInfoUniverse = planet.infoUniverse; - const { expired, extractors, localProduction, localImports, localExports } = - planetCalculations(planet); const planetConfig = readPlanetConfig({ characterId: character.character.characterId, planetId: planet.planet_id, }); - const { colors } = useContext(ColorContext); - // Convert local production to ProductionNode array for simulation - const productionNodes: ProductionNode[] = Array.from(localProduction).map(([schematicId, schematic]) => ({ - schematicId: schematicId, - typeId: schematic.outputs[0].type_id, - name: schematic.name, - inputs: schematic.inputs.map(input => ({ - typeId: input.type_id, - quantity: input.quantity - })), - outputs: schematic.outputs.map(output => ({ - typeId: output.type_id, - quantity: output.quantity - })), - cycleTime: schematic.cycle_time, - factoryCount: schematic.count || 1 - })); - - // Calculate extractor averages and check for large differences - const extractorAverages = extractors - .filter(e => e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) - .map(e => { - const cycleTime = e.extractor_details?.cycle_time || 3600; - const qtyPerCycle = e.extractor_details?.qty_per_cycle || 0; - return { - typeId: e.extractor_details!.product_type_id!, - averagePerHour: (qtyPerCycle * 3600) / cycleTime - }; - }); - - const hasLargeExtractorDifference = extractorAverages.length === 2 && - Math.abs(extractorAverages[0].averagePerHour - extractorAverages[1].averagePerHour) > balanceThreshold; - - const storageFacilities = planetInfo.pins.filter(pin => - STORAGE_IDS().some(storage => storage.type_id === pin.type_id) - ); - - const getStorageInfo = (pin: any) => { - if (!pin || !pin.contents) return null; - - const storageType = PI_TYPES_MAP[pin.type_id].name; - const storageCapacity = STORAGE_CAPACITIES[pin.type_id] || 0; - - 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, - value: totalValue - }; - }; const handleExcludeChange = (event: React.ChangeEvent) => { updatePlanetConfig({ @@ -154,6 +100,19 @@ export const PlanetTableRow = ({ }); }; + // Check if there are any alerts + const hasAlerts = alertMode && ( + planetDetails.expired || + planetDetails.storageInfo.some(storage => storage.fillRate > 60) || + planetDetails.importDepletionTimes.some(depletion => depletion.hoursUntilDepletion < 24) || + planetDetails.hasLargeExtractorDifference + ); + + // If in alert mode and no alerts, hide the row + if (alertMode && !hasAlerts) { + return null; + } + const renderProductDisplay = (typeId: number, amount?: number) => { if (!typeId || !PI_TYPES_MAP[typeId]) { return ( @@ -205,7 +164,7 @@ export const PlanetTableRow = ({ return ( <> { + onClick={(e: React.MouseEvent) => { if (!(e.target as HTMLElement).closest('.clickable-cell')) return; setSimulationOpen(!simulationOpen); }} @@ -236,9 +195,9 @@ export const PlanetTableRow = ({ 0 ? ( + planetDetails.extractors.length > 0 ? ( e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) .map(e => ({ typeId: e.extractor_details!.product_type_id!, @@ -266,11 +225,11 @@ export const PlanetTableRow = ({ {planetInfoUniverse?.name} - {hasLargeExtractorDifference && ( + {planetDetails.hasLargeExtractorDifference && ( {planet.upgrade_level}
- {extractors.length === 0 &&No extractors} - {extractors.map((e, idx) => { + {planetDetails.extractors.length === 0 &&No extractors} + {planetDetails.extractors.map((e, idx) => { return (
- {Array.from(localProduction).map((schematic, idx) => { + {Array.from(planetDetails.localProduction).map((schematic, idx) => { return (
- {localImports.map((i) => { - // Find all storage facilities (including launchpads) containing this import - const storagesWithImport = storageFacilities.filter(storage => - storage.contents?.some(content => content.type_id === i.type_id) - ); - - // Get the total amount in all storage facilities - const totalAmount = storagesWithImport.reduce((sum, storage) => { - const content = storage.contents?.find(content => content.type_id === i.type_id); - return sum + (content?.amount ?? 0); - }, 0); - - // Calculate consumption rate per hour - const schematic = PI_SCHEMATICS.find(s => s.schematic_id === i.schematic_id); - const cycleTime = schematic?.cycle_time ?? 3600; - const consumptionPerHour = i.quantity * i.factoryCount * (3600 / cycleTime); - - // Calculate time until depletion in hours, starting from last_update - const lastUpdate = DateTime.fromISO(planet.last_update); - const now = DateTime.now(); - const hoursSinceUpdate = now.diff(lastUpdate, 'hours').hours; - const remainingAmount = Math.max(0, totalAmount - (consumptionPerHour * hoursSinceUpdate)); - const hoursUntilDepletion = consumptionPerHour > 0 ? remainingAmount / consumptionPerHour : 0; - - // Calculate monthly cost - const price = piPrices?.appraisal.items.find((a) => a.typeID === i.type_id)?.prices.sell.min ?? 0; - const monthlyCost = (consumptionPerHour * 24 * 30 * price) / 1000000; // Cost in millions - + {planetDetails.localImports.map((i) => { + const depletionTime = planetDetails.importDepletionTimes.find(d => d.typeId === i.type_id); return (
-
Total in storage: {totalAmount.toFixed(1)} units
-
Consumption rate: {consumptionPerHour.toFixed(1)} units/hour
-
Last update: {lastUpdate.toFormat('yyyy-MM-dd HH:mm:ss')}
-
Will be depleted in {hoursUntilDepletion.toFixed(1)} hours
-
Monthly cost: {monthlyCost.toFixed(2)}M ISK
+
Will be depleted in {depletionTime?.hoursUntilDepletion.toFixed(1)} hours
+
Monthly cost: {depletionTime?.monthlyCost.toFixed(2)}M ISK
}>
{renderProductDisplay(i.type_id, i.quantity * i.factoryCount)} - {totalAmount > 0 && ( + {depletionTime && ( - ({hoursUntilDepletion.toFixed(1)}h) + ({depletionTime.hoursUntilDepletion.toFixed(1)}h) )}
@@ -396,21 +326,21 @@ export const PlanetTableRow = ({
- {localExports.map((exports) => ( + {planetDetails.localExports.map((exports) => (
- {renderProductDisplay(exports.typeId, exports.amount)} + {renderProductDisplay(exports.type_id, exports.quantity * exports.factoryCount)}
))}
- {localExports.map((exports) => ( + {planetDetails.localExports.map((exports) => (
- {localExports.map((exports) => ( + {planetDetails.localExports.map((exports) => ( - {exports.amount} + {exports.quantity * exports.factoryCount} ))}
@@ -444,11 +374,11 @@ export const PlanetTableRow = ({ textAlign: "end", }} > - {localExports.map((e) => { + {planetDetails.localExports.map((e) => { const valueInMillions = - (((piPrices?.appraisal.items.find((a) => a.typeID === e.typeId) + (((piPrices?.appraisal.items.find((a) => a.typeID === e.type_id) ?.prices.sell.min ?? 0) * - e.amount) / + e.quantity * e.factoryCount) / 1000000) * 24 * 30; @@ -459,7 +389,7 @@ export const PlanetTableRow = ({ return ( {displayValue} @@ -470,38 +400,28 @@ export const PlanetTableRow = ({
- {storageFacilities.length === 0 &&No storage} - {storageFacilities - .sort((a, b) => { - const isALaunchpad = LAUNCHPAD_IDS.includes(a.type_id); - const isBLaunchpad = LAUNCHPAD_IDS.includes(b.type_id); - return isALaunchpad === isBLaunchpad ? 0 : isALaunchpad ? -1 : 1; - }) - .map((storage) => { - const storageInfo = getStorageInfo(storage); - if (!storageInfo) return null; - - const isLaunchpad = LAUNCHPAD_IDS.includes(storage.type_id); - - const fillRate = storageInfo.fillRate; - const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; - - return ( -
- - {isLaunchpad ? 'L' : 'S'} + {planetDetails.storageInfo.length === 0 &&No storage} + {planetDetails.storageInfo.map((storage: StorageInfo) => { + const isLaunchpad = LAUNCHPAD_IDS.includes(storage.type_id); + const fillRate = storage.fillRate; + const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; + + return ( +
+ + {isLaunchpad ? 'L' : 'S'} + + + {fillRate.toFixed(1)}% + + {storage.value > 0 && ( + + ({Math.round(storage.value / 1000000)}M) - - {fillRate.toFixed(1)}% - - {storageInfo.value > 0 && ( - - ({Math.round(storageInfo.value / 1000000)}M) - - )} -
- ); - })} + )} +
+ ); + })}
@@ -540,7 +460,7 @@ export const PlanetTableRow = ({ e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) .map(e => ({ typeId: e.extractor_details!.product_type_id!, @@ -549,7 +469,21 @@ export const PlanetTableRow = ({ installTime: e.install_time ?? "", expiryTime: e.expiry_time ?? "" }))} - productionNodes={productionNodes} + productionNodes={Array.from(planetDetails.localProduction).map(([schematicId, schematic]) => ({ + schematicId: schematicId, + typeId: schematic.outputs[0].type_id, + name: schematic.name, + inputs: schematic.inputs.map((input: SchematicInput) => ({ + typeId: input.type_id, + quantity: input.quantity + })), + outputs: schematic.outputs.map((output: SchematicOutput) => ({ + typeId: output.type_id, + quantity: output.quantity + })), + cycleTime: schematic.cycle_time, + factoryCount: schematic.factoryCount || 1 + }))} /> diff --git a/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx b/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx index cbf47c8..4bc674d 100644 --- a/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx +++ b/src/app/components/PlanetaryInteraction/PlanetaryInteractionRow.tsx @@ -1,5 +1,5 @@ import { AccessToken } from "@/types"; -import { Icon, IconButton, Stack, Tooltip, Typography, styled, useTheme } from "@mui/material"; +import { IconButton, Stack, Tooltip, Typography, styled, useTheme } from "@mui/material"; import { PlanetCard } from "./PlanetCard"; import { NoPlanetCard } from "./NoPlanetCard"; import Table from "@mui/material/Table"; @@ -11,6 +11,7 @@ import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { PlanetTableRow } from "./PlanetTableRow"; import { Settings } from "@mui/icons-material"; +import { PlanetCalculations } from "@/types/planet"; const StackItem = styled(Stack)(({ theme }) => ({ ...theme.typography.body2, @@ -22,8 +23,10 @@ const StackItem = styled(Stack)(({ theme }) => ({ const PlanetaryIteractionTable = ({ character, + planetDetails, }: { character: AccessToken; + planetDetails: Record; }) => { const theme = useTheme(); @@ -117,6 +120,7 @@ const PlanetaryIteractionTable = ({ key={`${character.character.characterId}-${planet.planet_id}`} planet={planet} character={character} + planetDetails={planetDetails[planet.planet_id]} /> ))} @@ -128,8 +132,10 @@ const PlanetaryIteractionTable = ({ const PlanetaryInteractionIconsRow = ({ character, + planetDetails, }: { character: AccessToken; + planetDetails: Record; }) => { return ( @@ -139,6 +145,7 @@ const PlanetaryInteractionIconsRow = ({ key={`${character.character.characterId}-${planet.planet_id}`} planet={planet} character={character} + planetDetails={planetDetails[planet.planet_id]} /> ))} {Array.from(Array(6 - character.planets.length).keys()).map((i, id) => ( @@ -153,14 +160,16 @@ const PlanetaryInteractionIconsRow = ({ export const PlanetaryInteractionRow = ({ character, + planetDetails, }: { character: AccessToken; + planetDetails: Record; }) => { const theme = useTheme(); return theme.custom.compactMode ? ( -
+
) : ( -
+
); }; diff --git a/src/types/planet.ts b/src/types/planet.ts new file mode 100644 index 0000000..843ec43 --- /dev/null +++ b/src/types/planet.ts @@ -0,0 +1,83 @@ +import { Pin, PlanetWithInfo } from '../types'; + +export interface StorageContent { + type_id: number; + amount: number; +} + +export interface StorageInfo { + type: string; + type_id: number; + capacity: number; + used: number; + fillRate: number; + value: number; +} + +export interface PlanetCalculations { + expired: boolean; + extractors: Pin[]; + localProduction: Map; + localImports: LocalImport[]; + localExports: LocalExport[]; + storageInfo: StorageInfo[]; + extractorAverages: ExtractorAverage[]; + hasLargeExtractorDifference: boolean; + importDepletionTimes: ImportDepletionTime[]; + visibility: 'visible' | 'hidden'; +} + +export interface AlertState { + expired: boolean; + hasLowStorage: boolean; + hasLowImports: boolean; + hasLargeExtractorDifference: boolean; +} + +export interface ExtractorAverage { + typeId: number; + averagePerHour: number; +} + +export interface ImportDepletionTime { + typeId: number; + hoursUntilDepletion: number; + monthlyCost: number; +} + +export interface LocalProductionInfo { + name: string; + cycle_time: number; + schematic_id: number; + inputs: SchematicInput[]; + outputs: SchematicOutput[]; + factoryCount: number; +} + +export interface LocalImport { + type_id: number; + schematic_id: number; + quantity: number; + factoryCount: number; +} + +export interface LocalExport { + type_id: number; + schematic_id: number; + quantity: number; + factoryCount: number; +} + +export interface SchematicInput { + schematic_id: number; + type_id: number; + quantity: number; + is_input: number; +} + +export interface SchematicOutput { + schematic_id: number; + type_id: number; + quantity: number; + is_input: number; +} \ No newline at end of file