add realized production to totals and settings for comparing it to estimates
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user