bananas-commerce-admin 0.17.16 → 0.17.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/esm/extensions/pos/components/ExchangeSwitch.js +10 -0
  2. package/dist/esm/extensions/pos/components/ExchangeSwitch.js.map +1 -0
  3. package/dist/esm/extensions/pos/components/SitesAmountsList.js +2 -2
  4. package/dist/esm/extensions/pos/components/SitesAmountsList.js.map +1 -1
  5. package/dist/esm/extensions/pos/components/SitesTotal.js +13 -0
  6. package/dist/esm/extensions/pos/components/SitesTotal.js.map +1 -0
  7. package/dist/esm/extensions/pos/contrib/PurchaseAmountWidget.js +30 -5
  8. package/dist/esm/extensions/pos/contrib/PurchaseAmountWidget.js.map +1 -1
  9. package/dist/esm/extensions/pos/hooks/useExchangedData.js +33 -0
  10. package/dist/esm/extensions/pos/hooks/useExchangedData.js.map +1 -0
  11. package/dist/esm/extensions/pos/utils/amountToCurrencyString.js +2 -2
  12. package/dist/esm/extensions/pos/utils/amountToCurrencyString.js.map +1 -1
  13. package/dist/types/extensions/pos/components/ExchangeSwitch.d.ts +7 -0
  14. package/dist/types/extensions/pos/components/SitesAmountsList.d.ts +1 -0
  15. package/dist/types/extensions/pos/components/SitesTotal.d.ts +7 -0
  16. package/dist/types/extensions/pos/hooks/useExchangedData.d.ts +10 -0
  17. package/dist/types/extensions/pos/utils/amountToCurrencyString.d.ts +1 -1
  18. package/package.json +1 -1
  19. package/src/extensions/pos/components/ExchangeSwitch.tsx +33 -0
  20. package/src/extensions/pos/components/SitesAmountsList.tsx +4 -3
  21. package/src/extensions/pos/components/SitesTotal.tsx +40 -0
  22. package/src/extensions/pos/contrib/PurchaseAmountWidget.tsx +45 -4
  23. package/src/extensions/pos/hooks/useExchangedData.ts +46 -0
  24. package/src/extensions/pos/utils/amountToCurrencyString.ts +7 -2
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { Box, FormControlLabel, Switch, Typography } from "@mui/material";
3
+ import { useI18n } from "../../../contexts/I18nContext";
4
+ const ExchangeSwitch = ({ checked, onChange }) => {
5
+ const { t } = useI18n();
6
+ return (React.createElement(Box, { position: "absolute", right: 16, top: 16 },
7
+ React.createElement(FormControlLabel, { checked: checked, control: React.createElement(Switch, { color: "primary", size: "small" }), label: React.createElement(Typography, { color: "textSecondary", variant: "caption" }, t("Exchange")), labelPlacement: "end", sx: { margin: 0, gap: 0.5 }, onChange: () => onChange() })));
8
+ };
9
+ export default ExchangeSwitch;
10
+ //# sourceMappingURL=ExchangeSwitch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExchangeSwitch.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/components/ExchangeSwitch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE1E,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAOxD,MAAM,cAAc,GAAkC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC9E,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IAExB,OAAO,CACL,oBAAC,GAAG,IAAC,QAAQ,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;QACzC,oBAAC,gBAAgB,IACf,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,oBAAC,MAAM,IAAC,KAAK,EAAC,SAAS,EAAC,IAAI,EAAC,OAAO,GAAG,EAChD,KAAK,EACH,oBAAC,UAAU,IAAC,KAAK,EAAC,eAAe,EAAC,OAAO,EAAC,SAAS,IAChD,CAAC,CAAC,UAAU,CAAC,CACH,EAEf,cAAc,EAAC,KAAK,EACpB,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAC3B,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,GAC1B,CACE,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -2,10 +2,10 @@ import React from "react";
2
2
  import Stack from "@mui/material/Stack";
3
3
  import Typography from "@mui/material/Typography";
4
4
  import { currencyFormat } from "../utils/amountToCurrencyString";
5
- const SitesAmountsList = ({ data }) => {
5
+ const SitesAmountsList = ({ data, isExchanged }) => {
6
6
  return (React.createElement(Stack, { direction: "column", px: 2 }, data.map((item) => (React.createElement(Stack, { key: item.site_code, alignItems: "center", direction: "row", justifyContent: "space-between", spacing: 1 },
7
7
  React.createElement(Typography, null, item.site_code),
8
- React.createElement(Typography, { component: "p", fontSize: 28, fontWeight: 700, variant: "h5" }, currencyFormat(Number(item.amount), item.currency)))))));
8
+ React.createElement(Typography, { component: "p", fontSize: 20, fontWeight: 700, variant: "h5" }, currencyFormat(Number(item.amount), item.currency, undefined, isExchanged ? 0 : 2)))))));
9
9
  };
10
10
  export default SitesAmountsList;
11
11
  //# sourceMappingURL=SitesAmountsList.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SitesAmountsList.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/components/SitesAmountsList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAMjE,MAAM,gBAAgB,GAAoC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;IACrE,OAAO,CACL,oBAAC,KAAK,IAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,IAC9B,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAClB,oBAAC,KAAK,IACJ,GAAG,EAAE,IAAI,CAAC,SAAS,EACnB,UAAU,EAAE,QAAQ,EACpB,SAAS,EAAE,KAAK,EAChB,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,CAAC;QAEV,oBAAC,UAAU,QAAE,IAAI,CAAC,SAAS,CAAc;QACzC,oBAAC,UAAU,IAAC,SAAS,EAAC,GAAG,EAAC,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAC,IAAI,IAClE,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CACxC,CACP,CACT,CAAC,CACI,CACT,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SitesAmountsList.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/components/SitesAmountsList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAOjE,MAAM,gBAAgB,GAAoC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE;IAClF,OAAO,CACL,oBAAC,KAAK,IAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,IAC9B,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAClB,oBAAC,KAAK,IACJ,GAAG,EAAE,IAAI,CAAC,SAAS,EACnB,UAAU,EAAE,QAAQ,EACpB,SAAS,EAAE,KAAK,EAChB,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,CAAC;QAEV,oBAAC,UAAU,QAAE,IAAI,CAAC,SAAS,CAAc;QACzC,oBAAC,UAAU,IAAC,SAAS,EAAC,GAAG,EAAC,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAC,IAAI,IAClE,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACxE,CACP,CACT,CAAC,CACI,CACT,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { Typography } from "@mui/material";
3
+ import Stack from "@mui/material/Stack";
4
+ import { useI18n } from "../../../contexts/I18nContext";
5
+ import { currencyFormat } from "../utils/amountToCurrencyString";
6
+ const SitesTotal = ({ amount, currency }) => {
7
+ const { t } = useI18n();
8
+ return (React.createElement(Stack, { alignItems: "center", border: 1, borderBottom: 0, borderColor: "divider", borderLeft: 0, borderRight: 0, direction: "row", justifyContent: "space-between", mt: 2, mx: 2 },
9
+ React.createElement(Typography, { color: "textSecondary", variant: "overline" }, t("Total")),
10
+ React.createElement(Typography, { component: "p", fontSize: 28, fontWeight: 700, variant: "h5" }, currencyFormat(amount, currency, undefined, 0))));
11
+ };
12
+ export default SitesTotal;
13
+ //# sourceMappingURL=SitesTotal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SitesTotal.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/components/SitesTotal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,MAAM,qBAAqB,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAOjE,MAAM,UAAU,GAA8B,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;IACrE,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IAExB,OAAO,CACL,oBAAC,KAAK,IACJ,UAAU,EAAC,QAAQ,EACnB,MAAM,EAAE,CAAC,EACT,YAAY,EAAE,CAAC,EACf,WAAW,EAAC,SAAS,EACrB,UAAU,EAAE,CAAC,EACb,WAAW,EAAE,CAAC,EACd,SAAS,EAAC,KAAK,EACf,cAAc,EAAC,eAAe,EAC9B,EAAE,EAAE,CAAC,EACL,EAAE,EAAE,CAAC;QAEL,oBAAC,UAAU,IAAC,KAAK,EAAC,eAAe,EAAC,OAAO,EAAC,UAAU,IACjD,CAAC,CAAC,OAAO,CAAC,CACA;QACb,oBAAC,UAAU,IAAC,SAAS,EAAC,GAAG,EAAC,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAC,IAAI,IAClE,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CACpC,CACP,CACT,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -1,21 +1,46 @@
1
- import React from "react";
1
+ import React, { useMemo, useState } from "react";
2
2
  import { Stack, Typography } from "@mui/material";
3
3
  import WidgetCard from "../../../components/WidgetCard";
4
+ import { useApi } from "../../../contexts/ApiContext";
4
5
  import { useDashboardFilter } from "../../../contexts/DashboardFilterContext";
5
6
  import { useI18n } from "../../../contexts/I18nContext";
7
+ import { useUser } from "../../../contexts/UserContext";
6
8
  import { pluralizeGranularity } from "../../../types/dashboard";
9
+ import { hasPermission } from "../../../util/has_permission";
10
+ import ExchangeSwitch from "../components/ExchangeSwitch";
7
11
  import SitesAmountsList from "../components/SitesAmountsList";
12
+ import SitesTotal from "../components/SitesTotal";
13
+ import { useExchangedData } from "../hooks/useExchangedData";
8
14
  import { currencyFormat } from "../utils/amountToCurrencyString";
9
15
  import { diffDatesByGranularityWithFilter } from "../utils/diffDatesByGranularity";
10
16
  const PurchaseAmountWidget = ({ data, sx }) => {
11
17
  const { t } = useI18n();
12
18
  const { filter } = useDashboardFilter();
19
+ const api = useApi();
20
+ const { user } = useUser();
21
+ const [shouldExchange, setShouldExchange] = useState(true);
22
+ const { exchangedData, totalAmount, onlyExchangedCurrency } = useExchangedData(data, shouldExchange);
13
23
  const purchaseAmounts = data.length;
14
24
  const filteredAmount = diffDatesByGranularityWithFilter(filter);
15
- return (React.createElement(WidgetCard, { gridColumn: { lg: "span 3", md: "span 6", sm: "span 1" }, gridRow: "span 1", sx: sx, title: t("Purchase Revenue") }, purchaseAmounts >= 2 ? (React.createElement(SitesAmountsList, { data: data })) : (React.createElement(Stack, { height: "100%", px: 2 },
16
- React.createElement(Stack, { borderBottom: "1px solid", borderColor: "divider", height: "100%" }, purchaseAmounts === 1 ? (React.createElement(Typography, { component: "p", fontSize: 28, fontWeight: 700, variant: "h5" }, currencyFormat(Number.parseFloat(data[0].amount), data[0].currency))) : purchaseAmounts === 0 ? (React.createElement(Stack, { height: "100%" },
17
- React.createElement(Typography, { color: "textSecondary", variant: "caption" }, t("No revenue data for current period")))) : null),
18
- React.createElement(Typography, { color: "textSecondary", variant: "overline" }, t(`Over ${pluralizeGranularity(filteredAmount, filter.granularity)}`))))));
25
+ const isFourColumn = useMemo(() => {
26
+ // Ugly check to see if the "Total Subscription Count" widget is present
27
+ if ("subscription.stats:total" in api.operations &&
28
+ "dashboard:stats:subscription:total" in api.contrib) {
29
+ const [{ component }] = Object.values(api.contrib["dashboard:stats:subscription:count"]);
30
+ if (component != null) {
31
+ return hasPermission(user, component.permission);
32
+ }
33
+ }
34
+ return false;
35
+ }, [api, user]);
36
+ return (React.createElement(WidgetCard, { gridColumn: { md: isFourColumn ? "span 3" : "span 4", sm: "span 1" }, gridRow: "span 1", sx: sx, title: t("Purchase Revenue") },
37
+ React.createElement(ExchangeSwitch, { checked: shouldExchange, onChange: () => setShouldExchange((prev) => !prev) }),
38
+ purchaseAmounts >= 2 ? (React.createElement(React.Fragment, null,
39
+ React.createElement(SitesAmountsList, { data: exchangedData, isExchanged: shouldExchange }),
40
+ shouldExchange && onlyExchangedCurrency && (React.createElement(SitesTotal, { amount: totalAmount, currency: "SEK" })))) : (React.createElement(Stack, { height: "100%", px: 2 },
41
+ React.createElement(Stack, { borderBottom: "1px solid", borderColor: "divider", height: "100%" }, purchaseAmounts === 1 ? (React.createElement(Typography, { component: "p", fontSize: 28, fontWeight: 700, variant: "h5" }, currencyFormat(Number.parseFloat(exchangedData[0].amount), exchangedData[0].currency))) : purchaseAmounts === 0 ? (React.createElement(Stack, { height: "100%" },
42
+ React.createElement(Typography, { color: "textSecondary", variant: "caption" }, t("No revenue data for current period")))) : null),
43
+ React.createElement(Typography, { color: "textSecondary", variant: "overline" }, t(`Over ${pluralizeGranularity(filteredAmount, filter.granularity)}`))))));
19
44
  };
20
45
  export default PurchaseAmountWidget;
21
46
  //# sourceMappingURL=PurchaseAmountWidget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PurchaseAmountWidget.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/contrib/PurchaseAmountWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,UAAU,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,OAAO,EAAE,oBAAoB,EAAsB,MAAM,0BAA0B,CAAC;AACpF,OAAO,gBAAgB,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AAEnF,MAAM,oBAAoB,GAA2C,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE;IACpF,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IAExC,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;IACpC,MAAM,cAAc,GAAG,gCAAgC,CAAC,MAAM,CAAC,CAAC;IAEhE,OAAO,CACL,oBAAC,UAAU,IACT,UAAU,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EACxD,OAAO,EAAC,QAAQ,EAChB,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,CAAC,CAAC,kBAAkB,CAAC,IAE3B,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,CACtB,oBAAC,gBAAgB,IAAC,IAAI,EAAE,IAAI,GAAI,CACjC,CAAC,CAAC,CAAC,CACF,oBAAC,KAAK,IAAC,MAAM,EAAC,MAAM,EAAC,EAAE,EAAE,CAAC;QACxB,oBAAC,KAAK,IAAC,YAAY,EAAC,WAAW,EAAC,WAAW,EAAC,SAAS,EAAC,MAAM,EAAC,MAAM,IAChE,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,oBAAC,UAAU,IAAC,SAAS,EAAC,GAAG,EAAC,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAC,IAAI,IAClE,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CACzD,CACd,CAAC,CAAC,CAAC,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,CAC1B,oBAAC,KAAK,IAAC,MAAM,EAAC,MAAM;YAClB,oBAAC,UAAU,IAAC,KAAK,EAAC,eAAe,EAAC,OAAO,EAAC,SAAS,IAChD,CAAC,CAAC,oCAAoC,CAAC,CAC7B,CACP,CACT,CAAC,CAAC,CAAC,IAAI,CACF;QAER,oBAAC,UAAU,IAAC,KAAK,EAAC,eAAe,EAAC,OAAO,EAAC,UAAU,IACjD,CAAC,CAAC,QAAQ,oBAAoB,CAAC,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAC3D,CACP,CACT,CACU,CACd,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"PurchaseAmountWidget.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/contrib/PurchaseAmountWidget.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export function useExchangedData(data, shouldExchange, toCurrency = "SEK", exchangeRates = sekExchangeRates) {
2
+ const exchangedData = data.map((item) => {
3
+ if (shouldExchange && item.currency.toUpperCase() in exchangeRates) {
4
+ return {
5
+ ...item,
6
+ amount: (parseFloat(item.amount) *
7
+ exchangeRates[item.currency.toUpperCase()]).toFixed(2),
8
+ currency: shouldExchange ? toCurrency : item.currency,
9
+ };
10
+ }
11
+ return {
12
+ ...item,
13
+ amount: item.amount,
14
+ currency: item.currency,
15
+ };
16
+ });
17
+ const totalAmount = exchangedData.reduce((acc, item) => {
18
+ return acc + parseFloat(item.amount);
19
+ }, 0);
20
+ const onlyExchangedCurrency = exchangedData.every((item) => item.currency === toCurrency);
21
+ return {
22
+ exchangedData,
23
+ totalAmount,
24
+ onlyExchangedCurrency,
25
+ };
26
+ }
27
+ const sekExchangeRates = {
28
+ EUR: 11.5,
29
+ USD: 10.5,
30
+ SEK: 1,
31
+ GBP: 13.5,
32
+ };
33
+ //# sourceMappingURL=useExchangedData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExchangedData.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/hooks/useExchangedData.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAC9B,IAA0B,EAC1B,cAAuB,EACvB,aAAqB,KAAK,EAC1B,gBAAwC,gBAAgB;IAExD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtC,IAAI,cAAc,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,aAAa,EAAE,CAAC;YACnE,OAAO;gBACL,GAAG,IAAI;gBACP,MAAM,EAAE,CACN,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;oBACvB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAgC,CAAC,CACzE,CAAC,OAAO,CAAC,CAAC,CAAC;gBACZ,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ;aACtD,CAAC;QACJ,CAAC;QAED,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACrD,OAAO,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEN,MAAM,qBAAqB,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IAE1F,OAAO;QACL,aAAa;QACb,WAAW;QACX,qBAAqB;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG;IACvB,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,IAAI;CACV,CAAC"}
@@ -1,10 +1,10 @@
1
- export const currencyFormat = (value, currency, locale = "sv-SE") => {
1
+ export const currencyFormat = (value, currency, locale = "sv-SE", maximumFractionDigits = 2) => {
2
2
  const formatter = new Intl.NumberFormat(locale, {
3
3
  style: "currency",
4
4
  currency: currency,
5
5
  currencyDisplay: "narrowSymbol",
6
6
  minimumFractionDigits: 0,
7
- maximumFractionDigits: 2,
7
+ maximumFractionDigits,
8
8
  });
9
9
  return formatter.format(value);
10
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"amountToCurrencyString.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/utils/amountToCurrencyString.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,QAAgB,EAAE,SAAiB,OAAO,EAAE,EAAE;IAC1F,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;QAC9C,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,QAAQ;QAClB,eAAe,EAAE,cAAc;QAC/B,qBAAqB,EAAE,CAAC;QACxB,qBAAqB,EAAE,CAAC;KACzB,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC"}
1
+ {"version":3,"file":"amountToCurrencyString.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/utils/amountToCurrencyString.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,KAAa,EACb,QAAgB,EAChB,SAAiB,OAAO,EACxB,wBAAgC,CAAC,EACjC,EAAE;IACF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;QAC9C,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,QAAQ;QAClB,eAAe,EAAE,cAAc;QAC/B,qBAAqB,EAAE,CAAC;QACxB,qBAAqB;KACtB,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export interface ExchangeSwitchProps {
3
+ checked: boolean;
4
+ onChange: () => void;
5
+ }
6
+ declare const ExchangeSwitch: React.FC<ExchangeSwitchProps>;
7
+ export default ExchangeSwitch;
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { PurchaseAmountItem } from "../../../types/dashboard";
3
3
  export interface SitesAmountsListProps {
4
4
  data: PurchaseAmountItem[];
5
+ isExchanged: boolean;
5
6
  }
6
7
  declare const SitesAmountsList: React.FC<SitesAmountsListProps>;
7
8
  export default SitesAmountsList;
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export interface SitesTotalProps {
3
+ amount: number;
4
+ currency: string;
5
+ }
6
+ declare const SitesTotal: React.FC<SitesTotalProps>;
7
+ export default SitesTotal;
@@ -0,0 +1,10 @@
1
+ import { PurchaseAmountItem } from "../../../types";
2
+ export declare function useExchangedData(data: PurchaseAmountItem[], shouldExchange: boolean, toCurrency?: string, exchangeRates?: Record<string, number>): {
3
+ exchangedData: {
4
+ amount: string;
5
+ currency: string;
6
+ site_code: string;
7
+ }[];
8
+ totalAmount: number;
9
+ onlyExchangedCurrency: boolean;
10
+ };
@@ -1 +1 @@
1
- export declare const currencyFormat: (value: number, currency: string, locale?: string) => string;
1
+ export declare const currencyFormat: (value: number, currency: string, locale?: string, maximumFractionDigits?: number) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bananas-commerce-admin",
3
- "version": "0.17.16",
3
+ "version": "0.17.18",
4
4
  "description": "What's this, an admin for apes?",
5
5
  "keywords": [
6
6
  "admin",
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+
3
+ import { Box, FormControlLabel, Switch, Typography } from "@mui/material";
4
+
5
+ import { useI18n } from "../../../contexts/I18nContext";
6
+
7
+ export interface ExchangeSwitchProps {
8
+ checked: boolean;
9
+ onChange: () => void;
10
+ }
11
+
12
+ const ExchangeSwitch: React.FC<ExchangeSwitchProps> = ({ checked, onChange }) => {
13
+ const { t } = useI18n();
14
+
15
+ return (
16
+ <Box position="absolute" right={16} top={16}>
17
+ <FormControlLabel
18
+ checked={checked}
19
+ control={<Switch color="primary" size="small" />}
20
+ label={
21
+ <Typography color="textSecondary" variant="caption">
22
+ {t("Exchange")}
23
+ </Typography>
24
+ }
25
+ labelPlacement="end"
26
+ sx={{ margin: 0, gap: 0.5 }}
27
+ onChange={() => onChange()}
28
+ />
29
+ </Box>
30
+ );
31
+ };
32
+
33
+ export default ExchangeSwitch;
@@ -8,9 +8,10 @@ import { currencyFormat } from "../utils/amountToCurrencyString";
8
8
 
9
9
  export interface SitesAmountsListProps {
10
10
  data: PurchaseAmountItem[];
11
+ isExchanged: boolean;
11
12
  }
12
13
 
13
- const SitesAmountsList: React.FC<SitesAmountsListProps> = ({ data }) => {
14
+ const SitesAmountsList: React.FC<SitesAmountsListProps> = ({ data, isExchanged }) => {
14
15
  return (
15
16
  <Stack direction={"column"} px={2}>
16
17
  {data.map((item) => (
@@ -22,8 +23,8 @@ const SitesAmountsList: React.FC<SitesAmountsListProps> = ({ data }) => {
22
23
  spacing={1}
23
24
  >
24
25
  <Typography>{item.site_code}</Typography>
25
- <Typography component="p" fontSize={28} fontWeight={700} variant="h5">
26
- {currencyFormat(Number(item.amount), item.currency)}
26
+ <Typography component="p" fontSize={20} fontWeight={700} variant="h5">
27
+ {currencyFormat(Number(item.amount), item.currency, undefined, isExchanged ? 0 : 2)}
27
28
  </Typography>
28
29
  </Stack>
29
30
  ))}
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+
3
+ import { Typography } from "@mui/material";
4
+ import Stack from "@mui/material/Stack";
5
+
6
+ import { useI18n } from "../../../contexts/I18nContext";
7
+ import { currencyFormat } from "../utils/amountToCurrencyString";
8
+
9
+ export interface SitesTotalProps {
10
+ amount: number;
11
+ currency: string;
12
+ }
13
+
14
+ const SitesTotal: React.FC<SitesTotalProps> = ({ amount, currency }) => {
15
+ const { t } = useI18n();
16
+
17
+ return (
18
+ <Stack
19
+ alignItems="center"
20
+ border={1}
21
+ borderBottom={0}
22
+ borderColor="divider"
23
+ borderLeft={0}
24
+ borderRight={0}
25
+ direction="row"
26
+ justifyContent="space-between"
27
+ mt={2}
28
+ mx={2}
29
+ >
30
+ <Typography color="textSecondary" variant="overline">
31
+ {t("Total")}
32
+ </Typography>
33
+ <Typography component="p" fontSize={28} fontWeight={700} variant="h5">
34
+ {currencyFormat(amount, currency, undefined, 0)}
35
+ </Typography>
36
+ </Stack>
37
+ );
38
+ };
39
+
40
+ export default SitesTotal;
@@ -1,38 +1,79 @@
1
- import React from "react";
1
+ import React, { useMemo, useState } from "react";
2
2
 
3
3
  import { Stack, Typography } from "@mui/material";
4
4
 
5
5
  import WidgetCard from "../../../components/WidgetCard";
6
+ import { useApi } from "../../../contexts/ApiContext";
6
7
  import { useDashboardFilter } from "../../../contexts/DashboardFilterContext";
7
8
  import { useI18n } from "../../../contexts/I18nContext";
9
+ import { useUser } from "../../../contexts/UserContext";
8
10
  import { ContribComponent } from "../../../types";
9
11
  import { pluralizeGranularity, PurchaseAmountItem } from "../../../types/dashboard";
12
+ import { hasPermission } from "../../../util/has_permission";
13
+ import ExchangeSwitch from "../components/ExchangeSwitch";
10
14
  import SitesAmountsList from "../components/SitesAmountsList";
15
+ import SitesTotal from "../components/SitesTotal";
16
+ import { useExchangedData } from "../hooks/useExchangedData";
11
17
  import { currencyFormat } from "../utils/amountToCurrencyString";
12
18
  import { diffDatesByGranularityWithFilter } from "../utils/diffDatesByGranularity";
13
19
 
14
20
  const PurchaseAmountWidget: ContribComponent<PurchaseAmountItem[]> = ({ data, sx }) => {
15
21
  const { t } = useI18n();
16
22
  const { filter } = useDashboardFilter();
23
+ const api = useApi();
24
+ const { user } = useUser();
25
+
26
+ const [shouldExchange, setShouldExchange] = useState(true);
27
+ const { exchangedData, totalAmount, onlyExchangedCurrency } = useExchangedData(
28
+ data,
29
+ shouldExchange,
30
+ );
17
31
 
18
32
  const purchaseAmounts = data.length;
19
33
  const filteredAmount = diffDatesByGranularityWithFilter(filter);
34
+ const isFourColumn = useMemo(() => {
35
+ // Ugly check to see if the "Total Subscription Count" widget is present
36
+ if (
37
+ "subscription.stats:total" in api.operations &&
38
+ "dashboard:stats:subscription:total" in api.contrib
39
+ ) {
40
+ const [{ component }] = Object.values(api.contrib["dashboard:stats:subscription:count"]);
41
+
42
+ if (component != null) {
43
+ return hasPermission(user, component.permission);
44
+ }
45
+ }
46
+
47
+ return false;
48
+ }, [api, user]);
20
49
 
21
50
  return (
22
51
  <WidgetCard
23
- gridColumn={{ lg: "span 3", md: "span 6", sm: "span 1" }}
52
+ gridColumn={{ md: isFourColumn ? "span 3" : "span 4", sm: "span 1" }}
24
53
  gridRow="span 1"
25
54
  sx={sx}
26
55
  title={t("Purchase Revenue")}
27
56
  >
57
+ <ExchangeSwitch
58
+ checked={shouldExchange}
59
+ onChange={() => setShouldExchange((prev) => !prev)}
60
+ />
28
61
  {purchaseAmounts >= 2 ? (
29
- <SitesAmountsList data={data} />
62
+ <>
63
+ <SitesAmountsList data={exchangedData} isExchanged={shouldExchange} />
64
+ {shouldExchange && onlyExchangedCurrency && (
65
+ <SitesTotal amount={totalAmount} currency="SEK" />
66
+ )}
67
+ </>
30
68
  ) : (
31
69
  <Stack height="100%" px={2}>
32
70
  <Stack borderBottom="1px solid" borderColor="divider" height="100%">
33
71
  {purchaseAmounts === 1 ? (
34
72
  <Typography component="p" fontSize={28} fontWeight={700} variant="h5">
35
- {currencyFormat(Number.parseFloat(data[0].amount), data[0].currency)}
73
+ {currencyFormat(
74
+ Number.parseFloat(exchangedData[0].amount),
75
+ exchangedData[0].currency,
76
+ )}
36
77
  </Typography>
37
78
  ) : purchaseAmounts === 0 ? (
38
79
  <Stack height="100%">
@@ -0,0 +1,46 @@
1
+ import { PurchaseAmountItem } from "../../../types";
2
+
3
+ export function useExchangedData(
4
+ data: PurchaseAmountItem[],
5
+ shouldExchange: boolean,
6
+ toCurrency: string = "SEK",
7
+ exchangeRates: Record<string, number> = sekExchangeRates,
8
+ ) {
9
+ const exchangedData = data.map((item) => {
10
+ if (shouldExchange && item.currency.toUpperCase() in exchangeRates) {
11
+ return {
12
+ ...item,
13
+ amount: (
14
+ parseFloat(item.amount) *
15
+ exchangeRates[item.currency.toUpperCase() as keyof typeof exchangeRates]
16
+ ).toFixed(2),
17
+ currency: shouldExchange ? toCurrency : item.currency,
18
+ };
19
+ }
20
+
21
+ return {
22
+ ...item,
23
+ amount: item.amount,
24
+ currency: item.currency,
25
+ };
26
+ });
27
+
28
+ const totalAmount = exchangedData.reduce((acc, item) => {
29
+ return acc + parseFloat(item.amount);
30
+ }, 0);
31
+
32
+ const onlyExchangedCurrency = exchangedData.every((item) => item.currency === toCurrency);
33
+
34
+ return {
35
+ exchangedData,
36
+ totalAmount,
37
+ onlyExchangedCurrency,
38
+ };
39
+ }
40
+
41
+ const sekExchangeRates = {
42
+ EUR: 11.5,
43
+ USD: 10.5,
44
+ SEK: 1,
45
+ GBP: 13.5,
46
+ };
@@ -1,10 +1,15 @@
1
- export const currencyFormat = (value: number, currency: string, locale: string = "sv-SE") => {
1
+ export const currencyFormat = (
2
+ value: number,
3
+ currency: string,
4
+ locale: string = "sv-SE",
5
+ maximumFractionDigits: number = 2,
6
+ ) => {
2
7
  const formatter = new Intl.NumberFormat(locale, {
3
8
  style: "currency",
4
9
  currency: currency,
5
10
  currencyDisplay: "narrowSymbol",
6
11
  minimumFractionDigits: 0,
7
- maximumFractionDigits: 2,
12
+ maximumFractionDigits,
8
13
  });
9
14
 
10
15
  return formatter.format(value);