add realized production to totals and settings for comparing it to estimates

This commit is contained in:
calli
2025-04-23 12:31:43 +03:00
parent d433f5420e
commit 0e141321b6

View File

@@ -1,5 +1,5 @@
import { SessionContext } from "@/app/context/Context"; import { SessionContext } from "@/app/context/Context";
import { PI_TYPES_MAP } from "@/const"; import { PI_TYPES_MAP, STORAGE_IDS } from "@/const";
import { planetCalculations } from "@/planets"; import { planetCalculations } from "@/planets";
import { AccessToken } from "@/types"; import { AccessToken } from "@/types";
import { import {
@@ -19,10 +19,12 @@ import {
AccordionSummary, AccordionSummary,
AccordionDetails, AccordionDetails,
TableSortLabel, TableSortLabel,
Box,
TextField,
} from "@mui/material"; } from "@mui/material";
import { useContext, useState } from "react"; import { useContext, useState, useEffect } from "react";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { argv0 } from "process"; import { DateTime } from "luxon";
const StackItem = styled(Stack)(({ theme }) => ({ const StackItem = styled(Stack)(({ theme }) => ({
...theme.typography.body2, ...theme.typography.body2,
@@ -41,13 +43,35 @@ const displayValue = (valueInMillions: number): string =>
? `${(valueInMillions / 1000).toFixed(2)} B` ? `${(valueInMillions / 1000).toFixed(2)} B`
: `${valueInMillions.toFixed(2)} M`; : `${valueInMillions.toFixed(2)} M`;
type SortBy = "name" | "perHour" | "price"; type SortBy = "name" | "perHour" | "storage" | "price" | "storagePrice" | "progress";
type SortDirection = "asc" | "desc"; type SortDirection = "asc" | "desc";
export const Summary = ({ characters }: { characters: AccessToken[] }) => { export const Summary = ({ characters }: { characters: AccessToken[] }) => {
const { piPrices } = useContext(SessionContext); const { piPrices } = useContext(SessionContext);
const [sortDirection, setSortDirection] = useState<SortDirection>("asc"); const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
const [sortBy, setSortBy] = useState<SortBy>("name"); const [sortBy, setSortBy] = useState<SortBy>("name");
const [startDate, setStartDate] = useState<string>(DateTime.now().startOf('day').toISO());
const [activityPercentage, setActivityPercentage] = useState<number>(() => {
const saved = localStorage.getItem('activityPercentage');
return saved ? parseFloat(saved) : 100;
});
useEffect(() => {
const savedDate = localStorage.getItem('productionStartDate');
if (savedDate) {
setStartDate(savedDate);
}
}, []);
useEffect(() => {
localStorage.setItem('productionStartDate', startDate);
}, [startDate]);
useEffect(() => {
localStorage.setItem('activityPercentage', activityPercentage.toString());
}, [activityPercentage]);
// Calculate exports and storage amounts
const exports = characters.flatMap((char) => { const exports = characters.flatMap((char) => {
return char.planets return char.planets
.filter( .filter(
@@ -62,6 +86,28 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
}); });
}); });
// Calculate storage amounts
const storageAmounts = characters.reduce<Grouped>((totals, character) => {
character.planets
.filter(
(p) =>
!character.planetConfig.some(
(c) => c.planetId == p.planet_id && c.excludeFromTotals,
),
)
.forEach((planet) => {
planet.info.pins
.filter(pin => STORAGE_IDS().some(storage => storage.type_id === pin.type_id))
.forEach(storage => {
storage.contents?.forEach(content => {
const current = totals[content.type_id] || 0;
totals[content.type_id] = current + content.amount;
});
});
});
return totals;
}, {});
const groupedByMaterial = exports.reduce<Grouped>((totals, material) => { const groupedByMaterial = exports.reduce<Grouped>((totals, material) => {
const { typeId, amount } = material; const { typeId, amount } = material;
const newTotal = isNaN(totals[typeId]) ? amount : totals[typeId] + amount; const newTotal = isNaN(totals[typeId]) ? amount : totals[typeId] + amount;
@@ -69,27 +115,44 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
return totals; return totals;
}, {}); }, {});
const startDateTime = DateTime.fromISO(startDate);
const hoursSinceStart = DateTime.now().diff(startDateTime, 'hours').hours;
const withProductNameAndPrice = Object.keys(groupedByMaterial).map( const withProductNameAndPrice = Object.keys(groupedByMaterial).map(
(typeIdString) => { (typeIdString) => {
const typeId = parseInt(typeIdString); const typeId = parseInt(typeIdString);
const amount = groupedByMaterial[typeId]; const amount = groupedByMaterial[typeId];
const storageAmount = storageAmounts[typeId] || 0;
const adjustedAmount = amount * (activityPercentage / 100);
const valueInMillions = const valueInMillions =
(((piPrices?.appraisal.items.find((a) => a.typeID === typeId)?.prices (((piPrices?.appraisal.items.find((a) => a.typeID === typeId)?.prices
.sell.min ?? 0) * .sell.min ?? 0) *
amount) / adjustedAmount) /
1000000) * 1000000) *
24 * 24 *
30; 30;
const storageValueInMillions =
((piPrices?.appraisal.items.find((a) => a.typeID === typeId)?.prices
.sell.min ?? 0) *
storageAmount) /
1000000;
// Calculate expected production and progress
const expectedProduction = adjustedAmount * hoursSinceStart;
const progress = hoursSinceStart > 0 ? (storageAmount / expectedProduction) * 100 : 0;
return { return {
typeId, typeId,
amount, amount: adjustedAmount,
storageAmount,
materialName: PI_TYPES_MAP[typeId].name, materialName: PI_TYPES_MAP[typeId].name,
price: valueInMillions, price: valueInMillions,
storageValue: storageValueInMillions,
progress,
}; };
}, },
); );
const theme = useTheme();
return ( return (
<StackItem width="100%"> <StackItem width="100%">
@@ -98,14 +161,48 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
<h2>Totals</h2> <h2>Totals</h2>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<TextField
label="Production Start Date"
type="date"
value={DateTime.fromISO(startDate).toFormat('yyyy-MM-dd')}
onChange={(e) => {
const newDate = DateTime.fromFormat(e.target.value, 'yyyy-MM-dd').startOf('day').toISO();
if (newDate) setStartDate(newDate);
}}
InputLabelProps={{
shrink: true,
}}
size="small"
/>
<TextField
label="Activity %"
type="number"
value={activityPercentage}
onChange={(e) => {
const value = Math.min(100, Math.max(0, parseFloat(e.target.value) || 0));
setActivityPercentage(value);
}}
InputLabelProps={{
shrink: true,
}}
inputProps={{
min: 0,
max: 100,
step: 1,
}}
size="small"
sx={{ width: '100px' }}
/>
</Box>
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table size="small" aria-label="a dense table"> <Table size="small" aria-label="a dense table">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell width="40%"> <TableCell width="20%">
<Tooltip title="What exports factories are producing"> <Tooltip title="What exports factories are producing">
<TableSortLabel <TableSortLabel
active={true} active={sortBy === "name"}
direction={sortDirection} direction={sortDirection}
onClick={() => { onClick={() => {
setSortDirection( setSortDirection(
@@ -118,10 +215,10 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
</TableSortLabel> </TableSortLabel>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell width="10%"> <TableCell width="10%" align="right">
<Tooltip title="How many units per hour factories are producing"> <Tooltip title={`Adjusted production rate (${activityPercentage}% activity)`}>
<TableSortLabel <TableSortLabel
active={true} active={sortBy === "perHour"}
direction={sortDirection} direction={sortDirection}
onClick={() => { onClick={() => {
setSortDirection( setSortDirection(
@@ -135,9 +232,41 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell width="10%" align="right"> <TableCell width="10%" align="right">
<Tooltip title="How many million ISK per month this planet is exporting (Jita sell min)"> <Tooltip title="Amount currently in storage">
<TableSortLabel <TableSortLabel
active={true} active={sortBy === "storage"}
direction={sortDirection}
onClick={() => {
setSortDirection(
sortDirection === "asc" ? "desc" : "asc",
);
setSortBy("storage");
}}
>
Units in storage
</TableSortLabel>
</Tooltip>
</TableCell>
<TableCell width="15%" align="right">
<Tooltip title="Current ISK value of storage">
<TableSortLabel
active={sortBy === "storagePrice"}
direction={sortDirection}
onClick={() => {
setSortDirection(
sortDirection === "asc" ? "desc" : "asc",
);
setSortBy("storagePrice");
}}
>
Produced ISK value
</TableSortLabel>
</Tooltip>
</TableCell>
<TableCell width="15%" align="right">
<Tooltip title="Monthly ISK value from production">
<TableSortLabel
active={sortBy === "price"}
direction={sortDirection} direction={sortDirection}
onClick={() => { onClick={() => {
setSortDirection( setSortDirection(
@@ -146,7 +275,23 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
setSortBy("price"); setSortBy("price");
}} }}
> >
ISK/M Maximum possible ISK/M
</TableSortLabel>
</Tooltip>
</TableCell>
<TableCell width="15%" align="right">
<Tooltip title="Progress towards monthly production target">
<TableSortLabel
active={sortBy === "progress"}
direction={sortDirection}
onClick={() => {
setSortDirection(
sortDirection === "asc" ? "desc" : "asc",
);
setSortBy("progress");
}}
>
Progress from start date
</TableSortLabel> </TableSortLabel>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
@@ -167,12 +312,30 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
if (sortDirection === "desc") if (sortDirection === "desc")
return a.amount > b.amount ? -1 : 1; return a.amount > b.amount ? -1 : 1;
} }
if (sortBy === "storage") {
if (sortDirection === "asc")
return a.storageAmount > b.storageAmount ? 1 : -1;
if (sortDirection === "desc")
return a.storageAmount > b.storageAmount ? -1 : 1;
}
if (sortBy === "price") { if (sortBy === "price") {
if (sortDirection === "asc") if (sortDirection === "asc")
return a.price > b.price ? 1 : -1; return a.price > b.price ? 1 : -1;
if (sortDirection === "desc") if (sortDirection === "desc")
return a.price > b.price ? -1 : 1; return a.price > b.price ? -1 : 1;
} }
if (sortBy === "storagePrice") {
if (sortDirection === "asc")
return a.storageValue > b.storageValue ? 1 : -1;
if (sortDirection === "desc")
return a.storageValue > b.storageValue ? -1 : 1;
}
if (sortBy === "progress") {
if (sortDirection === "asc")
return a.progress > b.progress ? 1 : -1;
if (sortDirection === "desc")
return a.progress > b.progress ? -1 : 1;
}
return 0; return 0;
}) })
.map((product) => ( .map((product) => (
@@ -180,7 +343,10 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
key={product.materialName} key={product.materialName}
material={product.materialName} material={product.materialName}
amount={product.amount} amount={product.amount}
storageAmount={product.storageAmount}
price={product.price} price={product.price}
storageValue={product.storageValue}
progress={product.progress}
/> />
))} ))}
<SummaryRow <SummaryRow
@@ -189,6 +355,10 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
(amount, p) => amount + p.price, (amount, p) => amount + p.price,
0, 0,
)} )}
storageValue={withProductNameAndPrice.reduce(
(amount, p) => amount + p.storageValue,
0,
)}
/> />
</TableBody> </TableBody>
</Table> </Table>
@@ -202,17 +372,32 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
const SummaryRow = ({ const SummaryRow = ({
material, material,
amount, amount,
storageAmount,
price, price,
storageValue,
progress,
}: { }: {
material: string; material: string;
amount?: number; amount?: number;
storageAmount?: number;
price: number; price: number;
storageValue?: number;
progress?: number;
}) => ( }) => (
<TableRow> <TableRow>
<TableCell component="th" scope="row"> <TableCell component="th" scope="row">
{material} {material}
</TableCell> </TableCell>
<TableCell>{amount}</TableCell> <TableCell align="right">{amount?.toFixed(1)}</TableCell>
<TableCell align="right">{storageAmount?.toFixed(1)}</TableCell>
<TableCell align="right">{storageValue !== undefined && displayValue(storageValue)}</TableCell>
<TableCell align="right">{displayValue(price)}</TableCell> <TableCell align="right">{displayValue(price)}</TableCell>
<TableCell align="right">
{progress !== undefined && (
<Typography color={progress >= 100 ? 'success.main' : progress >= 75 ? 'warning.main' : 'error.main'}>
{progress.toFixed(1)}%
</Typography>
)}
</TableCell>
</TableRow> </TableRow>
); );