bananas-commerce-admin
Version:
What's this, an admin for apes?
213 lines • 11.2 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useState } from "react";
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 Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
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 allPriceLists = useMemo(() => [...priceLists, ...prices.map((p) => p.price_list)], [priceLists, prices]);
const sortedRows = useMemo(() => {
const existingRows = shownPrices.map((price) => ({ kind: "existing", price }));
const createdRows = !isEditing
? []
: createdPrices
.map((price, i) => {
const priceList = allPriceLists.find((pl) => pl.id === price.price_list_id);
if (!priceList) {
return null;
}
return {
kind: "created",
price: { price_list: priceList, amount: price.amount },
index: i,
priceLists: [priceList, ...unusedPriceLists],
};
})
.filter((row) => row !== null);
return [...existingRows, ...createdRows].sort((a, b) => {
const plA = a.price.price_list;
const plB = b.price.price_list;
return (plA.channel.localeCompare(plB.channel) ||
plA.site_code.localeCompare(plB.site_code) ||
plA.name.localeCompare(plB.name));
});
}, [shownPrices, isEditing, createdPrices, allPriceLists, unusedPriceLists]);
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(TableHeading, { sx: { width: "30%" } }, t("Price List")),
React.createElement(TableHeading, { sx: { width: "15%" } }, t("Channel")),
React.createElement(TableHeading, { sx: { width: "15%" } }, t("Site")),
React.createElement(TableHeading, { align: "right", sx: { width: "20%" } }, t("Amount")),
React.createElement(TableHeading, { align: "right", sx: { width: "10%" } }, t("Active")),
React.createElement(TableHeading, { sx: { width: "10%" } }))),
React.createElement(TableBody, { sx: { ".MuiTableRow-root:last-child > .MuiTableCell-root": { borderBottom: "none" } } },
shownPrices.length === 0 && !(isEditing && createdPrices.length > 0) && (React.createElement(TableRow, null,
React.createElement(TableCell, { align: "center", colSpan: 6, sx: { color: "GrayText" } }, t("No prices")))),
sortedRows.map((row, i) => {
if (row.kind === "existing") {
return (React.createElement(ArticlePriceRow, { key: `existing-${row.price.price_list.id}-${i}`, isDisabled: isDisabled, isEditing: isEditing, price: row.price, onChangeAmount: (amount) => {
setChangedPrices((prev) => ({
...prev,
[row.price.price_list.id]: amount,
}));
}, onDelete: () => {
setDeletedPrices((prev) => [...prev, row.price.price_list.id]);
} }));
}
return (React.createElement(ArticlePriceRow, { key: `created-${row.price.price_list.id}-${row.index}`, isDisabled: isDisabled, isEditing: true, price: row.price, priceLists: row.priceLists, onChangeAmount: (amount) => {
setCreatedPrices((prev) => prev.map((p, j) => (row.index === j ? { ...p, amount } : p)));
}, onChangePriceList: (price_list) => {
setCreatedPrices((prev) => prev.map((p, j) => (row.index === j ? { ...p, price_list_id: price_list } : p)));
}, onDelete: () => {
setCreatedPrices((prev) => prev.filter((_, j) => j !== row.index));
} }));
}))),
isEditing && (React.createElement(Stack, { direction: "row", justifyContent: "space-between", sx: { borderTop: `1px solid ${theme.palette.divider}` } },
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