UNPKG

bananas-commerce-admin

Version:

What's this, an admin for apes?

213 lines 11.2 kB
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