Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added summary dialog #45

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/components/planner/depot/MaterialsNeeded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ItemNeeded from "./ItemNeeded";
import getGoalIngredients from "util/fns/depot/getGoalIngredients";
import DepotItem from "types/depotItem";
import ExportImportDialog from "./ExportImportDialog";
import MaterialsSummaryDialog from "./MaterialsSummaryDialog";
import Board from "components/base/Board";
import canCompleteByCrafting from "util/fns/depot/canCompleteByCrafting";
import { LocalStorageSettings } from "types/localStorageSettings";
Expand Down Expand Up @@ -43,6 +44,8 @@ const MaterialsNeeded = React.memo((props: Props) => {

const [exportImportOpen, setExportImportOpen] = useState<boolean>(false);

const [summaryOpen, setSummaryOpen] = useState<boolean>(false);

const {setAnchorEl, menuProps, menuButtonProps} = useMenu();

const craftToggleTooltips = ["Toggle only craftable materials ON - use with Goals and Filters","Toggle all crafting states ON","Reset all crafting states"];
Expand Down Expand Up @@ -78,6 +81,7 @@ const MaterialsNeeded = React.memo((props: Props) => {
const [ rawValues, setRawValues ] = useState({} as Record<string, DepotItem>);
//states to keep data beetwen renders
const [ savedStates, setSavedStates ] = useState({
goalsMaterials: {} as Record<string, number>,
materialsNeeded: {} as Record<string, number>,
craftableItems: {} as Record<string, boolean>,
sortedMaterialsNeeded: [] as [string, number][],
Expand All @@ -100,6 +104,8 @@ const MaterialsNeeded = React.memo((props: Props) => {
});
}

const goalsMaterials = {...materialsNeeded};

// 3. calculate what ingredients can be fulfilled by crafting
const _depot = { ...depot, ..._rawValues }; // need to hypothetically deduct from stock
const { craftableItems, ingredientToCraftedItemsMapping } = canCompleteByCrafting(
Expand Down Expand Up @@ -132,6 +138,7 @@ const MaterialsNeeded = React.memo((props: Props) => {
});

setSavedStates({
goalsMaterials,
materialsNeeded,
craftableItems,
sortedMaterialsNeeded,
Expand Down Expand Up @@ -442,8 +449,41 @@ const MaterialsNeeded = React.memo((props: Props) => {
xs: "repeat(auto-fill, minmax(96px, 1fr))",
sm: "repeat(auto-fill, minmax(108px, 1fr))",
},
position: "relative",
}}
>
<Box
onClick={() => setSummaryOpen(true)}
sx={{
backgroundColor: "primary.main",
color: "primary.contrastText",
position: "absolute",
top: "-20px",
left: "50%",
transform: "translateX(-50%)",
width: "min(40%,300px)",
height: "15px",
clipPath: "polygon(2% 0%, 98% 0%, 100% 100%, 0% 100%)",
borderTopLeftRadius: "50px",
borderTopRightRadius: "50px",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
opacity: 0.5,
transition: "opacity 0.3s ease-in-out",
"&:hover": {
opacity: 1,
height: "35px",
width: "min(45%,330px)",
top: "-40px",
fontWeight: "bold",
},
}}

>
<Typography>Summary</Typography>
</Box>
{savedStates.sortedMaterialsNeeded.map(([itemId, needed]) => (
<ItemNeeded
key={itemId}
Expand Down Expand Up @@ -481,6 +521,15 @@ const MaterialsNeeded = React.memo((props: Props) => {
}}
goals={goalData}
/>
<MaterialsSummaryDialog
depot={depot}
expOwned={savedStates.expOwned}
goalsMaterials={savedStates.goalsMaterials}
open={summaryOpen}
onClose={() => {
setSummaryOpen(false);
}}
/>
</>
);
});
Expand Down
209 changes: 209 additions & 0 deletions src/components/planner/depot/MaterialsSummaryDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React, { useCallback, useMemo } from "react";
import {
Dialog,
DialogContent,
DialogTitle,
IconButton,
Slide,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { TransitionProps } from '@mui/material/transitions';
import { Close } from "@mui/icons-material";
import itemsJson from "data/items.json";
import ItemBase from "../depot/ItemBase";
import DepotItem from "types/depotItem";
import { Item } from "types/item";
import canCompleteByCrafting from "util/fns/depot/canCompleteByCrafting";


interface Props {
open: boolean;
onClose: () => void;
depot: Record<string, DepotItem>;
expOwned: number;
goalsMaterials: Record<string, number>;
}

const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement<any, any>;
},
ref: React.Ref<unknown>,
) {
return <Slide direction="up" ref={ref} {...props} />;
});

const MaterialsSummaryDialog = React.memo((props: Props) => {
const { open, onClose, depot, expOwned, goalsMaterials } = props;
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isMdUp = useMediaQuery(theme.breakpoints.up("md"));

const calculateSummaryMaterials = useCallback(() => {

//dont do background calculcation
if (!open) return { sortedNeedToFarm: [], sortedNeedToCraft: [] };

const craftTier = 4;
//specific craftables of wrong tiers
const includeCraftIds: string[] = [
"30013", //Orirock Cluster
];
const excludeCraftIds: string[] = [
//"3302" //skill summary 2
];
const excludeCraftNames: string[] = [
" Chip", //any " Chips" and " Chip Packs"
];

const sortToEndKeywords = ["chip", "Chip", "Catalyst", "Summary"];

const _depot = { ...depot, EXP: { material_id: "EXP", stock: expOwned } };

const craftingList = Object.keys(depot)
.filter((id) => {
const item: Item = itemsJson[id as keyof typeof itemsJson];
return (
item.tier >= craftTier
&& item.ingredients
&& !excludeCraftIds.includes(id)
&& !excludeCraftNames.some(keyword => item.name.includes(keyword))
)
})
.concat(includeCraftIds);

const _materialsNeeded = { ...goalsMaterials };

//mutates _materialsNeeded
canCompleteByCrafting(_materialsNeeded, depot, craftingList);

const sortedNeedToCraft = Object.entries(_materialsNeeded)
.filter(([id, need]) => craftingList.includes(id) && need - depot[id].stock > 0)
.sort(([itemIdA], [itemIdB]) => {
const itemA = itemsJson[itemIdA as keyof typeof itemsJson];
const itemB = itemsJson[itemIdB as keyof typeof itemsJson];
const itemAHasKeyword = sortToEndKeywords.some(keyword => itemA.name.includes(keyword));
const itemBHasKeyword = sortToEndKeywords.some(keyword => itemB.name.includes(keyword));
return (
(itemAHasKeyword === itemBHasKeyword ? 0 : itemAHasKeyword ? 1 : -1) ||
(itemA.tier - itemB.tier) ||
(itemB.sortId - itemA.sortId)
)
});

const sortedNeedToFarm = Object.entries(_materialsNeeded)
.filter(([id, need]) => !craftingList.includes(id) && need - depot[id].stock > 0)
.sort(([itemIdA], [itemIdB]) => {
const itemA = itemsJson[itemIdA as keyof typeof itemsJson];
const itemB = itemsJson[itemIdB as keyof typeof itemsJson];
const itemAHasKeyword = sortToEndKeywords.some(keyword => itemA.name.includes(keyword));
const itemBHasKeyword = sortToEndKeywords.some(keyword => itemB.name.includes(keyword));
return (
(itemAHasKeyword === itemBHasKeyword ? 0 : itemAHasKeyword ? 1 : -1) ||
(_materialsNeeded[itemIdB] - depot[itemIdB].stock) - (_materialsNeeded[itemIdA] - depot[itemIdA].stock)
);
});

return { sortedNeedToFarm, sortedNeedToCraft }
}, [open, goalsMaterials, depot, expOwned]
);

const { sortedNeedToFarm, sortedNeedToCraft } = useMemo(calculateSummaryMaterials, [calculateSummaryMaterials]);

const formatNumber = (num: number) => {
return num < 1000
? num
: num < 1000000
? `${num % 1000 === 0 ? `${num / 1000}` : (num / 1000).toFixed(1)}K`
: `${num % 1000000 === 0 ? `${num / 1000000}` : (num / 1000000).toFixed(2)}M`;
};

const itemBaseSize = isMdUp ? 64 : 56;

const numberCSS = {
component: "span",
sx: {
display: "inline-block",
py: 0.25,
px: 0.5,
lineHeight: 1,
mr: `${itemBaseSize / 16}px`,
mb: `${itemBaseSize / 16}px`,
alignSelf: "end",
justifySelf: "end",
backgroundColor: "background.paper",
zIndex: 1,
fontSize: `${itemBaseSize / 24 + 12}px`,
},
};

return (
<>
<Dialog
open={open}
onClose={onClose}
TransitionComponent={Transition}
fullScreen={fullScreen}
keepMounted fullWidth maxWidth="md">
<DialogTitle
sx={{
display: "flex",
justifyContent: "space-between",
paddingBottom: "12px",
}}
>
<Typography
component="div"
variant="h2"
sx={{
marginLeft: "8px",
paddingTop: "12px",
}}
>
Active goals require:
</Typography>
<IconButton onClick={() => onClose()} sx={{ display: { sm: "none" } }}>
<Close />
</IconButton>
</DialogTitle>
<DialogContent>
{sortedNeedToFarm.length === 0 && sortedNeedToCraft.length === 0 ? (
<>
<Typography variant="h3" p={2} fontWeight="bold">All requirements are met</Typography>
</>
) : null}
{sortedNeedToFarm.length > 0 ? (
<>
<Typography variant="h3" p={1} fontWeight="bold">Farm missing tier 3 and lower materials</Typography>
{sortedNeedToFarm.map(([id, need]) => (
<ItemBase key={id} itemId={id} size={itemBaseSize}>
<Typography {...numberCSS}>
{formatNumber(need - depot[id].stock)}
</Typography>
</ItemBase>
))}
</>
) : null}
{sortedNeedToCraft.length > 0 ? (
<>
<Typography variant="h3" p={1} fontWeight="bold">Craft tier 4-5 materials</Typography>
{sortedNeedToCraft
.map(([id, need]) => (
<ItemBase key={id} itemId={id} size={itemBaseSize}>
<Typography {...numberCSS}>
{formatNumber(need - depot[id].stock)}
</Typography>
</ItemBase>
))}
</>
) : null}
</DialogContent>
</Dialog>
</>)

});

MaterialsSummaryDialog.displayName = "MaterialsSummaryDialog";
export default MaterialsSummaryDialog;