diff --git a/src/app/components/Summary/Summary.tsx b/src/app/components/Summary/Summary.tsx index d986a73..0555a17 100644 --- a/src/app/components/Summary/Summary.tsx +++ b/src/app/components/Summary/Summary.tsx @@ -1,5 +1,5 @@ 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 { AccessToken } from "@/types"; import { @@ -19,10 +19,12 @@ import { AccordionSummary, AccordionDetails, TableSortLabel, + Box, + TextField, } from "@mui/material"; -import { useContext, useState } from "react"; +import { useContext, useState, useEffect } from "react"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { argv0 } from "process"; +import { DateTime } from "luxon"; const StackItem = styled(Stack)(({ theme }) => ({ ...theme.typography.body2, @@ -41,13 +43,35 @@ const displayValue = (valueInMillions: number): string => ? `${(valueInMillions / 1000).toFixed(2)} B` : `${valueInMillions.toFixed(2)} M`; -type SortBy = "name" | "perHour" | "price"; +type SortBy = "name" | "perHour" | "storage" | "price" | "storagePrice" | "progress"; type SortDirection = "asc" | "desc"; export const Summary = ({ characters }: { characters: AccessToken[] }) => { const { piPrices } = useContext(SessionContext); const [sortDirection, setSortDirection] = useState("asc"); const [sortBy, setSortBy] = useState("name"); + const [startDate, setStartDate] = useState(DateTime.now().startOf('day').toISO()); + const [activityPercentage, setActivityPercentage] = useState(() => { + 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) => { return char.planets .filter( @@ -62,6 +86,28 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { }); }); + // Calculate storage amounts + const storageAmounts = characters.reduce((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((totals, material) => { const { typeId, amount } = material; const newTotal = isNaN(totals[typeId]) ? amount : totals[typeId] + amount; @@ -69,27 +115,44 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { return totals; }, {}); + const startDateTime = DateTime.fromISO(startDate); + const hoursSinceStart = DateTime.now().diff(startDateTime, 'hours').hours; + const withProductNameAndPrice = Object.keys(groupedByMaterial).map( (typeIdString) => { const typeId = parseInt(typeIdString); const amount = groupedByMaterial[typeId]; + const storageAmount = storageAmounts[typeId] || 0; + const adjustedAmount = amount * (activityPercentage / 100); const valueInMillions = (((piPrices?.appraisal.items.find((a) => a.typeID === typeId)?.prices .sell.min ?? 0) * - amount) / + adjustedAmount) / 1000000) * 24 * 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 { typeId, - amount, + amount: adjustedAmount, + storageAmount, materialName: PI_TYPES_MAP[typeId].name, price: valueInMillions, + storageValue: storageValueInMillions, + progress, }; }, ); - const theme = useTheme(); + return ( @@ -98,14 +161,48 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {

Totals

+ + { + const newDate = DateTime.fromFormat(e.target.value, 'yyyy-MM-dd').startOf('day').toISO(); + if (newDate) setStartDate(newDate); + }} + InputLabelProps={{ + shrink: true, + }} + size="small" + /> + { + 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' }} + /> + - + { setSortDirection( @@ -118,10 +215,10 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { - - + + { setSortDirection( @@ -135,9 +232,41 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { - + { + setSortDirection( + sortDirection === "asc" ? "desc" : "asc", + ); + setSortBy("storage"); + }} + > + Units in storage + + + + + + { + setSortDirection( + sortDirection === "asc" ? "desc" : "asc", + ); + setSortBy("storagePrice"); + }} + > + Produced ISK value + + + + + + { setSortDirection( @@ -146,7 +275,23 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { setSortBy("price"); }} > - ISK/M + Maximum possible ISK/M + + + + + + { + setSortDirection( + sortDirection === "asc" ? "desc" : "asc", + ); + setSortBy("progress"); + }} + > + Progress from start date @@ -167,12 +312,30 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { if (sortDirection === "desc") 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 (sortDirection === "asc") return a.price > b.price ? 1 : -1; if (sortDirection === "desc") 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; }) .map((product) => ( @@ -180,7 +343,10 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { key={product.materialName} material={product.materialName} amount={product.amount} + storageAmount={product.storageAmount} price={product.price} + storageValue={product.storageValue} + progress={product.progress} /> ))} { (amount, p) => amount + p.price, 0, )} + storageValue={withProductNameAndPrice.reduce( + (amount, p) => amount + p.storageValue, + 0, + )} />
@@ -202,17 +372,32 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => { const SummaryRow = ({ material, amount, + storageAmount, price, + storageValue, + progress, }: { material: string; amount?: number; + storageAmount?: number; price: number; + storageValue?: number; + progress?: number; }) => ( {material} - {amount} + {amount?.toFixed(1)} + {storageAmount?.toFixed(1)} + {storageValue !== undefined && displayValue(storageValue)} {displayValue(price)} + + {progress !== undefined && ( + = 100 ? 'success.main' : progress >= 75 ? 'warning.main' : 'error.main'}> + {progress.toFixed(1)}% + + )} + );