bananas-commerce-admin
Version:
What's this, an admin for apes?
194 lines • 10.5 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useState } from "react";
import FilterListIcon from "@mui/icons-material/FilterList";
import { TableBody, TableHead, TableRow } from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
import IconButton from "@mui/material/IconButton";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import FilterChip from "../../../components/FilterChip";
import Table from "../../../components/Table";
import { TableCell } from "../../../components/Table/TableCell";
import TableHeading from "../../../components/Table/TableHeading";
import TableCardHeader from "../../../components/TableCardHeader";
import { useApi } from "../../../contexts/ApiContext";
import { useDialog } from "../../../contexts/DialogContext";
import { useI18n } from "../../../contexts/I18nContext";
import { useUser } from "../../../contexts/UserContext";
import { hasPermission } from "../../../util/has_permission";
import { ArticlePriceRow } from "./ArticlePriceRow";
export const ArticlePricingCard = ({ code, prices, setPrices, }) => {
const theme = useTheme();
const openDialog = useDialog();
const api = useApi();
const { t } = useI18n();
const { user } = useUser();
const [priceLists, setPriceLists] = useState([]);
const [isEditing, setIsEditing] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
const [showActive, setShowActive] = useState(true);
const [showInactive, setShowInactive] = useState(false);
const [changedPrices, setChangedPrices] = useState({});
const [deletedPrices, setDeletedPrices] = useState([]);
const [createdPrices, setCreatedPrices] = useState([]);
const hasChanges = useMemo(() => Object.entries(changedPrices).length > 0 ||
deletedPrices.length > 0 ||
createdPrices.length > 0, [changedPrices, deletedPrices, createdPrices]);
const shownPrices = useMemo(() => {
if (!isEditing) {
return prices.filter((price) => (showActive && price.price_list.is_active) ||
(showInactive && !price.price_list.is_active));
}
return prices
.map(({ amount, ...price }) => {
if (deletedPrices.includes(price.price_list.id)) {
return null;
}
return {
...price,
amount: changedPrices[price.price_list.id] ?? amount,
};
})
.filter((price) => price !== null)
.filter((price) => (showActive && price.price_list.is_active) ||
(showInactive && !price.price_list.is_active));
}, [prices, isEditing, showActive, showInactive, changedPrices, deletedPrices]);
const unusedPriceLists = useMemo(() => {
const usedPriceLists = [
...shownPrices.map((price) => price.price_list.id),
...createdPrices.map((price) => price.price_list_id),
];
return [
...priceLists.filter((pl) => !usedPriceLists.includes(pl.id)),
// Include deleted price lists in the list of unused price lists.
// These are not acquired from the OPTIONS call but are still guaranteed
// to be valid.
...prices
.map((p) => p.price_list)
.filter((pl) => deletedPrices.includes(pl.id) && !usedPriceLists.includes(pl.id)),
];
}, [priceLists, shownPrices, createdPrices, deletedPrices]);
const clear = useCallback(() => {
setChangedPrices({});
setDeletedPrices([]);
setCreatedPrices([]);
setIsEditing(false);
}, []);
const save = useCallback(async () => {
const action = api.operations["pricing.contrib:pricing-update"];
if (!action) {
throw new Error('Invalid action "pricing.contrib:pricing-update".');
}
setIsDisabled(true);
const response = await action.call({
params: { code },
body: {
updates: [
...Object.entries(changedPrices).map(([price_list, amount]) => ({
price_list_id: parseInt(price_list),
amount,
})),
...createdPrices,
],
deleted: deletedPrices.filter((pl) => !createdPrices.map((p) => p.price_list_id).includes(pl)),
},
});
setIsDisabled(false);
if (response.ok) {
const updatedPriceLists = await response.json();
setPrices(updatedPriceLists);
clear();
}
else {
console.error("[ARTICLE_PRICING_CARD]", response);
}
}, [api, prices, changedPrices, deletedPrices, createdPrices]);
useEffect(() => {
if (isEditing) {
api.operations["pricing.contrib:pricing-options"]
.call({ params: { code } })
.then(async (response) => {
if (response.ok) {
setPriceLists(await response.json());
}
});
}
}, [isEditing]);
return (React.createElement(Card, { sx: {
boxShadow: 0,
borderRadius: 3,
borderWidth: "1px",
borderStyle: "solid",
borderColor: theme.palette.divider,
backgroundColor: theme.palette.background.paper,
overflow: "visible",
width: "100%",
} },
React.createElement(TableCardHeader, { isDisabled: isDisabled, isEditable: hasPermission(user, "pricing.change_price"), title: "Prices", toggled: isEditing, onChange: async (isEditing) => {
if (hasChanges) {
if (await openDialog(t("Unsaved changes"), t("You have unsaved changes. Are you sure you want to discard your changes?"), {
ok: t("Discard changes"),
cancel: t("Cancel"),
})) {
clear();
}
}
else {
setIsEditing(isEditing);
}
} }),
React.createElement(Table, { count: shownPrices.length },
React.createElement(TableHead, null,
React.createElement(TableRow, null,
React.createElement(TableCell, { colSpan: 5, sx: { px: 2, py: 1 } },
React.createElement(Stack, { direction: "row", spacing: 1 },
React.createElement(IconButton, { size: "small" },
React.createElement(FilterListIcon, null)),
React.createElement(FilterChip, { checked: showActive, color: "primary", label: t("Active"), onChange: setShowActive }),
React.createElement(FilterChip, { checked: showInactive, color: "primary", label: t("Inactive"), onChange: setShowInactive })))),
React.createElement(TableRow, null,
React.createElement(TableHeading, null, t("Site")),
React.createElement(TableHeading, null, t("Price List")),
React.createElement(TableHeading, { align: "right" }, t("Amount")),
React.createElement(TableHeading, { align: "right" }, t("Active")),
React.createElement(TableHeading, null))),
React.createElement(TableBody, { sx: { ".MuiTableRow-root:last-child > .MuiTableCell-root": { borderBottom: "none" } } },
shownPrices.length === 0 && (React.createElement(TableRow, null,
React.createElement(TableCell, { align: "center", colSpan: 5, sx: { color: "GrayText" } }, showActive && showInactive
? t("No prices")
: t("Some prices are hidden by filters")))),
shownPrices.map((price, i) => (React.createElement(ArticlePriceRow, { key: i, isDisabled: isDisabled, isEditing: isEditing, price: price, onChangeAmount: (amount) => {
setChangedPrices((prev) => ({ ...prev, [price.price_list.id]: amount }));
}, onDelete: () => {
setDeletedPrices((prev) => [...prev, price.price_list.id]);
} }))),
isEditing && (React.createElement(React.Fragment, null, createdPrices.map((price, i) => {
const allPriceLists = [...priceLists, ...prices.map((p) => p.price_list)];
const priceList = allPriceLists.find((pl) => pl.id === price.price_list_id);
return (React.createElement(ArticlePriceRow, { key: i, isDisabled: isDisabled, isEditing: true, price: { price_list: priceList, amount: price.amount }, priceLists: [priceList, ...unusedPriceLists], onChangeAmount: (amount) => {
setCreatedPrices((prev) => prev.map((p, j) => (i === j ? { ...p, amount } : p)));
}, onChangePriceList: (price_list) => {
setCreatedPrices((prev) => prev.map((p, j) => (i === j ? { ...p, price_list_id: price_list } : p)));
}, onDelete: () => {
setCreatedPrices((prev) => prev.filter((_, j) => j !== i));
} }));
}))))),
isEditing && (React.createElement(Stack, { direction: "row", justifyContent: "space-between" },
React.createElement(Box, { p: [1, 2, 3, 2] },
React.createElement(Button, { disabled: isDisabled || unusedPriceLists.length === 0, size: "medium", variant: "outlined", onClick: () => {
setCreatedPrices((prev) => [
...prev,
{ price_list_id: unusedPriceLists[0].id, amount: "" },
]);
} }, t("Add price"))),
React.createElement(CardActions, { sx: {
p: [1, 2, 3, 2],
display: "flex",
justifyContent: "flex-end",
} },
React.createElement(Button, { disabled: isDisabled, size: "medium", variant: "outlined", onClick: clear }, t("Cancel")),
React.createElement(Button, { disabled: isDisabled, size: "medium", variant: "contained", onClick: save }, t("Save")))))));
};
//# sourceMappingURL=ArticlePricingCard.js.map