UNPKG

bananas-commerce-admin

Version:

What's this, an admin for apes?

194 lines 10.5 kB
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