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.
- package/dist/esm/extensions/pos/components/ExchangeSwitch.js +10 -0
- package/dist/esm/extensions/pos/components/ExchangeSwitch.js.map +1 -0
- package/dist/esm/extensions/pos/components/SitesAmountsList.js +2 -2
- package/dist/esm/extensions/pos/components/SitesAmountsList.js.map +1 -1
- package/dist/esm/extensions/pos/components/SitesTotal.js +13 -0
- package/dist/esm/extensions/pos/components/SitesTotal.js.map +1 -0
- package/dist/esm/extensions/pos/contrib/PurchaseAmountWidget.js +30 -5
- package/dist/esm/extensions/pos/contrib/PurchaseAmountWidget.js.map +1 -1
- package/dist/esm/extensions/pos/hooks/useExchangedData.js +33 -0
- package/dist/esm/extensions/pos/hooks/useExchangedData.js.map +1 -0
- package/dist/esm/extensions/pos/utils/amountToCurrencyString.js +2 -2
- package/dist/esm/extensions/pos/utils/amountToCurrencyString.js.map +1 -1
- package/dist/types/extensions/pos/components/ExchangeSwitch.d.ts +7 -0
- package/dist/types/extensions/pos/components/SitesAmountsList.d.ts +1 -0
- package/dist/types/extensions/pos/components/SitesTotal.d.ts +7 -0
- package/dist/types/extensions/pos/hooks/useExchangedData.d.ts +10 -0
- package/dist/types/extensions/pos/utils/amountToCurrencyString.d.ts +1 -1
- package/package.json +1 -1
- package/src/extensions/pos/components/ExchangeSwitch.tsx +33 -0
- package/src/extensions/pos/components/SitesAmountsList.tsx +4 -3
- package/src/extensions/pos/components/SitesTotal.tsx +40 -0
- package/src/extensions/pos/contrib/PurchaseAmountWidget.tsx +45 -4
- package/src/extensions/pos/hooks/useExchangedData.ts +46 -0
- 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:
|
|
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;
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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;
|
|
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
|
|
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,
|
|
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"}
|
|
@@ -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,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
|
@@ -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={
|
|
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={{
|
|
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
|
-
|
|
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(
|
|
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 = (
|
|
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
|
|
12
|
+
maximumFractionDigits,
|
|
8
13
|
});
|
|
9
14
|
|
|
10
15
|
return formatter.format(value);
|