bananas-commerce-admin 0.17.20 → 0.17.22
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/components/ActionMenu/ActionMenuItem.js +2 -2
- package/dist/esm/components/ActionMenu/ActionMenuItem.js.map +1 -1
- package/dist/esm/components/ActionMenu/index.js +23 -3
- package/dist/esm/components/ActionMenu/index.js.map +1 -1
- package/dist/esm/components/Chart.js +49 -0
- package/dist/esm/components/Chart.js.map +1 -0
- package/dist/esm/components/Icons/AreaChartIcon.js +7 -0
- package/dist/esm/components/Icons/AreaChartIcon.js.map +1 -0
- package/dist/esm/components/StatsDataTable.js +94 -0
- package/dist/esm/components/StatsDataTable.js.map +1 -0
- package/dist/esm/components/Table/TableHead.js +3 -3
- package/dist/esm/components/Table/TableHead.js.map +1 -1
- package/dist/esm/components/TitleBar/index.js +5 -2
- package/dist/esm/components/TitleBar/index.js.map +1 -1
- package/dist/esm/components/charts/LineChart.js +2 -1
- package/dist/esm/components/charts/LineChart.js.map +1 -1
- package/dist/esm/extensions/pos/contrib/PurchaseCountWidget.js +2 -2
- package/dist/esm/extensions/pos/contrib/PurchaseCountWidget.js.map +1 -1
- package/dist/esm/util/format_date.js +23 -0
- package/dist/esm/util/format_date.js.map +1 -0
- package/dist/types/components/ActionMenu/ActionMenuItem.d.ts +1 -1
- package/dist/types/components/Chart.d.ts +16 -0
- package/dist/types/components/Icons/AreaChartIcon.d.ts +4 -0
- package/dist/types/components/StatsDataTable.d.ts +11 -0
- package/dist/types/components/Table/TableHead.d.ts +2 -0
- package/dist/types/components/charts/LineChart.d.ts +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/util/format_date.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ActionMenu/ActionMenuItem.tsx +3 -3
- package/src/components/ActionMenu/index.tsx +30 -3
- package/src/components/Chart.tsx +117 -0
- package/src/components/Icons/AreaChartIcon.tsx +19 -0
- package/src/components/StatsDataTable.tsx +152 -0
- package/src/components/Table/TableHead.tsx +10 -3
- package/src/components/TitleBar/index.tsx +8 -2
- package/src/components/charts/LineChart.tsx +3 -0
- package/src/extensions/pos/contrib/PurchaseCountWidget.tsx +6 -4
- package/src/types/index.ts +1 -1
- package/src/util/format_date.ts +28 -0
|
@@ -4,8 +4,8 @@ import { ListItemIcon, ListItemText, MenuItem } from "@mui/material";
|
|
|
4
4
|
import Logo from "../Logo";
|
|
5
5
|
export const ActionMenuItem = ({ operation, onAction }) => {
|
|
6
6
|
const handleClick = useCallback(() => {
|
|
7
|
-
onAction(operation
|
|
8
|
-
}, [operation
|
|
7
|
+
onAction(operation);
|
|
8
|
+
}, [operation, onAction]);
|
|
9
9
|
return (React.createElement(MenuItem, { onClick: handleClick },
|
|
10
10
|
operation.component?.icon && (React.createElement(ListItemIcon, { sx: { width: 20 } },
|
|
11
11
|
React.createElement(Logo, { src: operation.component.icon }))),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionMenuItem.js","sourceRoot":"","sources":["../../../../src/components/ActionMenu/ActionMenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGrE,OAAO,IAAI,MAAM,SAAS,CAAC;AAO3B,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;IACvF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"ActionMenuItem.js","sourceRoot":"","sources":["../../../../src/components/ActionMenu/ActionMenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGrE,OAAO,IAAI,MAAM,SAAS,CAAC;AAO3B,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;IACvF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1B,OAAO,CACL,oBAAC,QAAQ,IAAC,OAAO,EAAE,WAAW;QAC3B,SAAS,CAAC,SAAS,EAAE,IAAI,IAAI,CAC5B,oBAAC,YAAY,IAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YAC7B,oBAAC,IAAI,IAAC,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,IAAI,GAAI,CAC1B,CAChB;QACD,oBAAC,YAAY,QAAE,SAAS,CAAC,SAAS,EAAE,KAAK,IAAI,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,EAAE,CAAgB,CACrF,CACZ,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC,cAAc,CAAC,CAAC"}
|
|
@@ -7,12 +7,16 @@ import Menu from "@mui/material/Menu";
|
|
|
7
7
|
import { enqueueSnackbar } from "notistack";
|
|
8
8
|
import { useApi } from "../../contexts/ApiContext";
|
|
9
9
|
import { useI18n } from "../../contexts/I18nContext";
|
|
10
|
+
import { useUser } from "../../contexts/UserContext";
|
|
11
|
+
import { hasAccess } from "../../util/has_access";
|
|
10
12
|
import { usePage } from "../Page";
|
|
11
13
|
import ActionMenuItem from "./ActionMenuItem";
|
|
12
14
|
export const ActionMenu = () => {
|
|
13
15
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
16
|
+
const [renderedComponent, setRenderedComponent] = useState();
|
|
14
17
|
const params = useParams();
|
|
15
18
|
const page = usePage();
|
|
19
|
+
const { user } = useUser();
|
|
16
20
|
const api = useApi();
|
|
17
21
|
const { t } = useI18n();
|
|
18
22
|
const title = useCallback((operation) => t(operation.component?.title ?? operation.summary ?? operation.id), [t]);
|
|
@@ -24,8 +28,17 @@ export const ActionMenu = () => {
|
|
|
24
28
|
setAnchorEl(null);
|
|
25
29
|
};
|
|
26
30
|
const handleAction = async (operation) => {
|
|
31
|
+
if (operation.component != null) {
|
|
32
|
+
let component = operation.component.component;
|
|
33
|
+
setAnchorEl(null);
|
|
34
|
+
if (component instanceof Promise) {
|
|
35
|
+
component = await component;
|
|
36
|
+
}
|
|
37
|
+
setRenderedComponent(() => component);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
27
40
|
try {
|
|
28
|
-
const action = api.operations[operation];
|
|
41
|
+
const action = api.operations[operation.id];
|
|
29
42
|
const response = await action.call({ params });
|
|
30
43
|
if (response.ok) {
|
|
31
44
|
enqueueSnackbar(t(`Completed successfully: ${title(action)}`), {
|
|
@@ -55,12 +68,19 @@ export const ActionMenu = () => {
|
|
|
55
68
|
},
|
|
56
69
|
},
|
|
57
70
|
}, onClose: handleClose }, page?.contrib
|
|
58
|
-
.filter((operation) => operation.component?.variant === "action"
|
|
71
|
+
.filter((operation) => operation.component?.variant === "action" &&
|
|
72
|
+
hasAccess(user, operation.component.permission, operation.component.group))
|
|
59
73
|
.map((operation) => (React.createElement(ActionMenuItem, { key: operation.id, operation: operation, onAction: handleAction })))),
|
|
60
74
|
React.createElement(Button, { "aria-haspopup": true, disableElevation: true, "aria-expanded": open ? "true" : "false", endIcon: React.createElement(KeyboardArrowDownIcon, null), sx: {
|
|
61
75
|
borderRadius: 6,
|
|
62
76
|
height: 46,
|
|
63
|
-
}, variant: "outlined", onClick: handleClick }, "Actions")
|
|
77
|
+
}, variant: "outlined", onClick: handleClick }, "Actions"),
|
|
78
|
+
renderedComponent &&
|
|
79
|
+
React.createElement(renderedComponent, {
|
|
80
|
+
close: () => setRenderedComponent(undefined),
|
|
81
|
+
data: {},
|
|
82
|
+
refresh: () => { },
|
|
83
|
+
})));
|
|
64
84
|
};
|
|
65
85
|
export default ActionMenu;
|
|
66
86
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/ActionMenu/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,IAAI,MAAM,oBAAoB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAa,GAAG,EAAE;IACvC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,SAAuB,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,IAAI,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,EAAE,CAAC,EAC/F,CAAC,CAAC,CAAC,CACJ,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE/B,MAAM,WAAW,GAAG,CAAC,KAAoC,EAAE,EAAE;QAC3D,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/ActionMenu/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,IAAI,MAAM,oBAAoB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAa,GAAG,EAAE;IACvC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IACnE,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,EAAoB,CAAC;IAC/E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,SAAuB,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,IAAI,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,EAAE,CAAC,EAC/F,CAAC,CAAC,CAAC,CACJ,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE/B,MAAM,WAAW,GAAG,CAAC,KAAoC,EAAE,EAAE;QAC3D,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,EAAE,SAAuB,EAAE,EAAE;QACrD,IAAI,SAAS,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;YAChC,IAAI,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC;YAC9C,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,IAAI,SAAS,YAAY,OAAO,EAAE,CAAC;gBACjC,SAAS,GAAG,MAAM,SAAS,CAAC;YAC9B,CAAC;YAED,oBAAoB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAE5C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAE/C,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,eAAe,CAAC,CAAC,CAAC,2BAA2B,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;oBAC7D,OAAO,EAAE,SAAS;iBACnB,CAAC,CAAC;gBACH,WAAW,EAAE,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,CAAC,CAAC,kBAAkB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;oBACpD,OAAO,EAAE,OAAO;iBACjB,CAAC,CAAC;gBACH,MAAM,QAAQ,CAAC;YACjB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE;gBAClC,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL;QACE,oBAAC,IAAI,IACH,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE;gBACT,KAAK,EAAE;oBACL,KAAK,EAAE;wBACL,KAAK,EAAE,aAAa;qBACrB;iBACF;aACF,EACD,OAAO,EAAE,WAAW,IAEnB,IAAI,EAAE,OAAO;aACX,MAAM,CACL,CAAC,SAAS,EAAE,EAAE,CACZ,SAAS,CAAC,SAAS,EAAE,OAAO,KAAK,QAAQ;YACzC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAC7E;aACA,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAClB,oBAAC,cAAc,IAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,GAAI,CACpF,CAAC,CACC;QAEP,oBAAC,MAAM,2BAEL,gBAAgB,yBACD,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EACtC,OAAO,EAAE,oBAAC,qBAAqB,OAAG,EAClC,EAAE,EAAE;gBACF,YAAY,EAAE,CAAC;gBACf,MAAM,EAAE,EAAE;aACX,EACD,OAAO,EAAC,UAAU,EAClB,OAAO,EAAE,WAAW,cAGb;QAER,iBAAiB;YAChB,KAAK,CAAC,aAAa,CAAC,iBAAiB,EAAE;gBACrC,KAAK,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,SAAS,CAAC;gBAC5C,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;aAClB,CAAC,CACH,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import { BarChartRounded, ShowChartRounded, TableRowsRounded } from "@mui/icons-material";
|
|
3
|
+
import { ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material";
|
|
4
|
+
import { useDashboardFilter } from "../contexts/DashboardFilterContext";
|
|
5
|
+
import { useI18n } from "../contexts/I18nContext";
|
|
6
|
+
import BarChart from "./charts/BarChart";
|
|
7
|
+
import LineChart from "./charts/LineChart";
|
|
8
|
+
import AreaChartIcon from "./Icons/AreaChartIcon";
|
|
9
|
+
import StatsDataTable from "./StatsDataTable";
|
|
10
|
+
const chartData = {
|
|
11
|
+
line: {
|
|
12
|
+
Icon: ShowChartRounded,
|
|
13
|
+
name: "Line Chart",
|
|
14
|
+
},
|
|
15
|
+
area: {
|
|
16
|
+
Icon: AreaChartIcon,
|
|
17
|
+
name: "Area Chart",
|
|
18
|
+
},
|
|
19
|
+
bar: {
|
|
20
|
+
Icon: BarChartRounded,
|
|
21
|
+
name: "Bar Chart",
|
|
22
|
+
},
|
|
23
|
+
table: {
|
|
24
|
+
Icon: TableRowsRounded,
|
|
25
|
+
name: "Table",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export default function Chart({ data, charts, dataTable = true, dataKeyX, dataKeyY, csvFileName, }) {
|
|
29
|
+
const { t } = useI18n();
|
|
30
|
+
const { filter } = useDashboardFilter();
|
|
31
|
+
const [selectedChart, setSelectedChart] = useState(charts[0]);
|
|
32
|
+
const chartsWithTable = dataTable ? [...charts, "table"] : charts;
|
|
33
|
+
const handleSelectedChartChange = useCallback((_, newChart) => {
|
|
34
|
+
if (newChart !== null) {
|
|
35
|
+
setSelectedChart(newChart);
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
return (React.createElement(React.Fragment, null,
|
|
39
|
+
chartsWithTable.length > 1 && (React.createElement(ToggleButtonGroup, { exclusive: true, sx: { position: "absolute", right: 16, top: 16 }, unselectable: "off", value: selectedChart, onChange: handleSelectedChartChange }, chartsWithTable.map((chart) => {
|
|
40
|
+
const { Icon, name } = chartData[chart];
|
|
41
|
+
return (React.createElement(Tooltip, { key: chart, title: t(name) },
|
|
42
|
+
React.createElement(ToggleButton, { size: "small", value: chart },
|
|
43
|
+
React.createElement(Icon, { fontSize: "small" }))));
|
|
44
|
+
}))),
|
|
45
|
+
selectedChart === "bar" && (React.createElement(BarChart, { dataKeyX: dataKeyX, dataKeyY: dataKeyY, dataset: data.map(({ date, ...props }) => ({ ...props, date: new Date(date) })), granularity: filter.granularity })),
|
|
46
|
+
(selectedChart === "line" || selectedChart === "area") && (React.createElement(LineChart, { area: selectedChart === "area", dataKeyX: dataKeyX, dataKeyY: dataKeyY, dataset: data.map(({ date, ...props }) => ({ ...props, date: new Date(date) })), granularity: filter.granularity })),
|
|
47
|
+
selectedChart === "table" && (React.createElement(StatsDataTable, { csvFileName: csvFileName, data: data, dateKey: dataKeyX }))));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=Chart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Chart.js","sourceRoot":"","sources":["../../../src/components/Chart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,SAAS,MAAM,oBAAoB,CAAC;AAC3C,OAAO,aAAa,MAAM,uBAAuB,CAAC;AAClD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAmB9C,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,YAAY;KACnB;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,YAAY;KACnB;IACD,GAAG,EAAE;QACH,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,WAAW;KAClB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,OAAO;KACd;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,EAC5B,IAAI,EACJ,MAAM,EACN,SAAS,GAAG,IAAI,EAChB,QAAQ,EACR,QAAQ,EACR,WAAW,GACA;IACX,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAsB,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnF,MAAM,eAAe,GAA0B,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEzF,MAAM,yBAAyB,GAAG,WAAW,CAC3C,CAAC,CAAgC,EAAE,QAAgB,EAAE,EAAE;QACrD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,gBAAgB,CAAC,QAA+B,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO,CACL;QACG,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CAC7B,oBAAC,iBAAiB,IAChB,SAAS,QACT,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAChD,YAAY,EAAC,KAAK,EAClB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,yBAAyB,IAElC,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,CACL,oBAAC,OAAO,IAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC;gBACjC,oBAAC,YAAY,IAAC,IAAI,EAAC,OAAO,EAAC,KAAK,EAAE,KAAK;oBACrC,oBAAC,IAAI,IAAC,QAAQ,EAAC,OAAO,GAAG,CACZ,CACP,CACX,CAAC;QACJ,CAAC,CAAC,CACgB,CACrB;QACA,aAAa,KAAK,KAAK,IAAI,CAC1B,oBAAC,QAAQ,IACP,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAC/E,WAAW,EAAE,MAAM,CAAC,WAAW,GAC/B,CACH;QACA,CAAC,aAAa,KAAK,MAAM,IAAI,aAAa,KAAK,MAAM,CAAC,IAAI,CACzD,oBAAC,SAAS,IACR,IAAI,EAAE,aAAa,KAAK,MAAM,EAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAC/E,WAAW,EAAE,MAAM,CAAC,WAAW,GAC/B,CACH;QACA,aAAa,KAAK,OAAO,IAAI,CAC5B,oBAAC,cAAc,IAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAI,CAC5E,CACA,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { SvgIcon } from "@mui/material";
|
|
3
|
+
const AreaChartIcon = (props) => (React.createElement(SvgIcon, { ...props, viewBox: "0 0 24 24" },
|
|
4
|
+
React.createElement("path", { d: "M3 17L9 10L14 15L21 7V15Q21 17 19 17H3Z", fill: "currentColor", opacity: "0.4" }),
|
|
5
|
+
React.createElement("path", { d: "M4 16L9 10L14 15L20 8", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2" })));
|
|
6
|
+
export default AreaChartIcon;
|
|
7
|
+
//# sourceMappingURL=AreaChartIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AreaChartIcon.js","sourceRoot":"","sources":["../../../../src/components/Icons/AreaChartIcon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,MAAM,aAAa,GAAG,CAAC,KAAmB,EAAE,EAAE,CAAC,CAC7C,oBAAC,OAAO,OAAK,KAAK,EAAE,OAAO,EAAC,WAAW;IACrC,8BAAM,CAAC,EAAC,yCAAyC,EAAC,IAAI,EAAC,cAAc,EAAC,OAAO,EAAC,KAAK,GAAG;IACtF,8BACE,CAAC,EAAC,uBAAuB,EACzB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAC,GAAG,GACf,CACM,CACX,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from "react";
|
|
2
|
+
import { Button, Stack, TableBody, TableRow } from "@mui/material";
|
|
3
|
+
import { useDashboardFilter } from "../contexts/DashboardFilterContext";
|
|
4
|
+
import { formatDate } from "../util/format_date";
|
|
5
|
+
import { TableCell } from "./Table/TableCell";
|
|
6
|
+
import TableHead from "./Table/TableHead";
|
|
7
|
+
import TableHeading from "./Table/TableHeading";
|
|
8
|
+
import Table from "./Table";
|
|
9
|
+
export default function StatsDataTable({ data, dateKey, csvFileName }) {
|
|
10
|
+
const { filter } = useDashboardFilter();
|
|
11
|
+
const mappedData = useMemo(() => {
|
|
12
|
+
return data
|
|
13
|
+
.map((item) => {
|
|
14
|
+
const dateEntry = getDateEntry(item, dateKey);
|
|
15
|
+
if (dateEntry === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const [key, date] = dateEntry;
|
|
18
|
+
const { [key]: _, ...rest } = item;
|
|
19
|
+
return { [key]: date, ...rest };
|
|
20
|
+
})
|
|
21
|
+
.filter((item) => item !== undefined);
|
|
22
|
+
}, [data, dateKey]);
|
|
23
|
+
const handleExportToCSV = useCallback(() => {
|
|
24
|
+
if (mappedData.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
const headers = Object.keys(mappedData[0]);
|
|
27
|
+
const csvRows = [
|
|
28
|
+
headers.join(","), // Header row
|
|
29
|
+
...mappedData.map((row) => headers
|
|
30
|
+
.map((key) => {
|
|
31
|
+
const value = row[key];
|
|
32
|
+
if (value instanceof Date)
|
|
33
|
+
return `"${value.toISOString()}"`;
|
|
34
|
+
if (typeof value === "string")
|
|
35
|
+
return `"${value.replace(/"/g, '""')}"`; // Escape quotes
|
|
36
|
+
return value;
|
|
37
|
+
})
|
|
38
|
+
.join(",")),
|
|
39
|
+
];
|
|
40
|
+
const csvContent = csvRows.join("\n");
|
|
41
|
+
const blob = new Blob([csvContent], { type: "text/csv" });
|
|
42
|
+
const url = URL.createObjectURL(blob);
|
|
43
|
+
const link = document.createElement("a");
|
|
44
|
+
link.href = url;
|
|
45
|
+
link.download = `${csvFileName || "data"}_${filter.startDate.toISO()?.split("T")[0]}_${filter.endDate.toISO()?.split("T")[0]}.csv`;
|
|
46
|
+
document.body.appendChild(link);
|
|
47
|
+
link.click();
|
|
48
|
+
document.body.removeChild(link);
|
|
49
|
+
URL.revokeObjectURL(url);
|
|
50
|
+
}, [mappedData, filter.startDate, filter.endDate, filter.granularity]);
|
|
51
|
+
return (React.createElement(Stack, { maxHeight: 300 },
|
|
52
|
+
React.createElement(Table, { count: mappedData.length },
|
|
53
|
+
React.createElement(TableHead, { tableRowProps: {
|
|
54
|
+
sx: {
|
|
55
|
+
position: "sticky",
|
|
56
|
+
top: 0,
|
|
57
|
+
bgcolor: "background.paper",
|
|
58
|
+
backgroundImage: "var(--Paper-overlay)",
|
|
59
|
+
boxShadow: "inset 0 -1px var(--mui-palette-divider)",
|
|
60
|
+
},
|
|
61
|
+
} }, mappedData.length > 0 &&
|
|
62
|
+
Object.keys(mappedData[0]).map((key) => {
|
|
63
|
+
return React.createElement(TableHeading, { key: key }, formatKey(key));
|
|
64
|
+
})),
|
|
65
|
+
React.createElement(TableBody, { sx: { overflowY: "auto" } }, mappedData.map((item, i) => (React.createElement(TableRow, { key: i }, Object.entries(item).map(([key, value]) => (React.createElement(TableCell, { key: key, sx: { borderBottomWidth: i === mappedData.length - 1 ? 0 : 1 } }, value instanceof Date ? formatDate(value, filter.granularity) : value.toString())))))))),
|
|
66
|
+
React.createElement(Stack, { alignItems: "flex-end", pt: 2, px: 2, spacing: 1, sx: { borderTop: "1px solid", borderColor: "divider" } },
|
|
67
|
+
React.createElement(Button, { color: "inherit", variant: "outlined", onClick: handleExportToCSV }, "Export to CSV"))));
|
|
68
|
+
}
|
|
69
|
+
function formatKey(key) {
|
|
70
|
+
return key
|
|
71
|
+
.split("_")
|
|
72
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
73
|
+
.join(" ");
|
|
74
|
+
}
|
|
75
|
+
function getDateEntry(item, dateKey) {
|
|
76
|
+
if (dateKey !== undefined && item.hasOwnProperty(dateKey)) {
|
|
77
|
+
let date = item[dateKey];
|
|
78
|
+
date = typeof date === "string" ? new Date(date) : date;
|
|
79
|
+
if (!(date instanceof Date))
|
|
80
|
+
return undefined;
|
|
81
|
+
return [dateKey, date];
|
|
82
|
+
}
|
|
83
|
+
const dateEntry = Object.entries(item).find(([_, value]) => isDateOrString(value));
|
|
84
|
+
if (dateEntry === undefined) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const dateObj = dateEntry[1] instanceof Date ? dateEntry[1] : new Date(dateEntry[1]);
|
|
88
|
+
return [dateEntry[0], dateObj];
|
|
89
|
+
}
|
|
90
|
+
function isDateOrString(value) {
|
|
91
|
+
return ((value instanceof Date && !Number.isNaN(value.getTime())) ||
|
|
92
|
+
(typeof value === "string" && !Number.isNaN(Date.parse(value))));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=StatsDataTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatsDataTable.js","sourceRoot":"","sources":["../../../src/components/StatsDataTable.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Stack, TableHead as MuiTableHead, TableRow, Typography } from "@mui/material";
|
|
3
|
-
export const TableHead = ({ children, icon, title }) => (React.createElement(MuiTableHead, null,
|
|
2
|
+
import { Stack, TableHead as MuiTableHead, TableRow, Typography, } from "@mui/material";
|
|
3
|
+
export const TableHead = ({ children, icon, title, tableRowProps }) => (React.createElement(MuiTableHead, null,
|
|
4
4
|
title != null && (React.createElement(Stack, { alignItems: "center", direction: "row", gap: 1, p: 2, pb: 0, sx: {
|
|
5
5
|
color: "text.primary",
|
|
6
6
|
wordSpacing: "6px",
|
|
@@ -8,6 +8,6 @@ export const TableHead = ({ children, icon, title }) => (React.createElement(Mui
|
|
|
8
8
|
} },
|
|
9
9
|
icon,
|
|
10
10
|
React.createElement(Typography, { fontWeight: 600, variant: "body1" }, title))),
|
|
11
|
-
React.createElement(TableRow,
|
|
11
|
+
React.createElement(TableRow, { ...tableRowProps }, children)));
|
|
12
12
|
export default TableHead;
|
|
13
13
|
//# sourceMappingURL=TableHead.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableHead.js","sourceRoot":"","sources":["../../../../src/components/Table/TableHead.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,OAAO,
|
|
1
|
+
{"version":3,"file":"TableHead.js","sourceRoot":"","sources":["../../../../src/components/Table/TableHead.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,OAAO,EACL,KAAK,EACL,SAAS,IAAI,YAAY,EACzB,QAAQ,EAER,UAAU,GACX,MAAM,eAAe,CAAC;AAQvB,MAAM,CAAC,MAAM,SAAS,GAA6B,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAC/F,oBAAC,YAAY;IACV,KAAK,IAAI,IAAI,IAAI,CAChB,oBAAC,KAAK,IACJ,UAAU,EAAC,QAAQ,EACnB,SAAS,EAAC,KAAK,EACf,GAAG,EAAE,CAAC,EACN,CAAC,EAAE,CAAC,EACJ,EAAE,EAAE,CAAC,EACL,EAAE,EAAE;YACF,KAAK,EAAE,cAAc;YACrB,WAAW,EAAE,KAAK;YAClB,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;SACnC;QAEA,IAAI;QACL,oBAAC,UAAU,IAAC,UAAU,EAAE,GAAG,EAAE,OAAO,EAAC,OAAO,IACzC,KAAK,CACK,CACP,CACT;IAED,oBAAC,QAAQ,OAAK,aAAa,IAAG,QAAQ,CAAY,CACrC,CAChB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -5,6 +5,8 @@ import NavigateNextIcon from "@mui/icons-material/NavigateNext";
|
|
|
5
5
|
import { AppBar, Breadcrumbs, IconButton, Link, Stack, Toolbar, Typography, } from "@mui/material";
|
|
6
6
|
import { useI18n } from "../../contexts/I18nContext";
|
|
7
7
|
import { useRouter } from "../../contexts/RouterContext";
|
|
8
|
+
import { useUser } from "../../contexts/UserContext";
|
|
9
|
+
import { hasAccess } from "../../util/has_access";
|
|
8
10
|
import ActionMenu from "../ActionMenu";
|
|
9
11
|
import { usePage } from "../Page";
|
|
10
12
|
export const TitleBar = ({ back = false, title, children, ...props }) => {
|
|
@@ -12,6 +14,7 @@ export const TitleBar = ({ back = false, title, children, ...props }) => {
|
|
|
12
14
|
const { navigate, getCurrent, routes } = useRouter();
|
|
13
15
|
const routerNavigate = useNavigate();
|
|
14
16
|
const page = usePage();
|
|
17
|
+
const { user } = useUser();
|
|
15
18
|
if (typeof back === "boolean" && back)
|
|
16
19
|
back = -1;
|
|
17
20
|
const { route: currentRoute } = getCurrent();
|
|
@@ -39,8 +42,8 @@ export const TitleBar = ({ back = false, title, children, ...props }) => {
|
|
|
39
42
|
React.createElement(Link, { key: "home", color: "inherit", underline: "hover", onClick: () => routerNavigate("/", { replace: true }) }, t("Home")),
|
|
40
43
|
breadcrumbRoutes.map((route) => (React.createElement(Link, { key: route.id, color: "inherit", underline: "hover", onClick: () => navigate(route) }, route.title)))))),
|
|
41
44
|
children,
|
|
42
|
-
page?.contrib.filter((operation) => operation.component?.variant === "action"
|
|
43
|
-
0 && (React.createElement(Stack, { width: "fit-content" },
|
|
45
|
+
page?.contrib.filter((operation) => operation.component?.variant === "action" &&
|
|
46
|
+
hasAccess(user, operation.component.permission, operation.component.group)).length > 0 && (React.createElement(Stack, { width: "fit-content" },
|
|
44
47
|
React.createElement(ActionMenu, null))))));
|
|
45
48
|
};
|
|
46
49
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/TitleBar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,aAAa,MAAM,+BAA+B,CAAC;AAC1D,OAAO,gBAAgB,MAAM,kCAAkC,CAAC;AAChE,OAAO,EACL,MAAM,EAEN,WAAW,EACX,UAAU,EACV,IAAI,EACJ,KAAK,EACL,OAAO,EACP,UAAU,GACX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAa,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,UAAU,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAOlC,MAAM,CAAC,MAAM,QAAQ,GAA4B,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE;IAC/F,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACrD,MAAM,cAAc,GAAG,WAAW,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/TitleBar/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ const StyledLineChart = styled(MuiLineChart, {
|
|
|
11
11
|
stroke: theme.palette.divider,
|
|
12
12
|
},
|
|
13
13
|
}));
|
|
14
|
-
export const LineChart = ({ dataset, series, dataKeyX, dataKeyY, height = 300, granularity = "day", yAxisOptions, legendProps, }) => {
|
|
14
|
+
export const LineChart = ({ dataset, series, dataKeyX, dataKeyY, height = 300, area, granularity = "day", yAxisOptions, legendProps, }) => {
|
|
15
15
|
const theme = useTheme();
|
|
16
16
|
return (React.createElement(StyledLineChart, { dataset: dataset, grid: { horizontal: true }, height: height, series: series ?? [
|
|
17
17
|
{
|
|
@@ -19,6 +19,7 @@ export const LineChart = ({ dataset, series, dataKeyX, dataKeyY, height = 300, g
|
|
|
19
19
|
showMark: false,
|
|
20
20
|
curve: "monotoneX",
|
|
21
21
|
color: theme.palette.graphColorPrimary?.main ?? theme.palette.primary.main,
|
|
22
|
+
area,
|
|
22
23
|
},
|
|
23
24
|
], slotProps: {
|
|
24
25
|
legend: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LineChart.js","sourceRoot":"","sources":["../../../../src/components/charts/LineChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAIpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAQ5D,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,EAAE;IAC3C,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,OAAO;CACd,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,WAAW,CAAC;IAChB,0CAA0C,EAAE;QAC1C,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;KAC9B;CACF,CAAC,CACH,CAAC;
|
|
1
|
+
{"version":3,"file":"LineChart.js","sourceRoot":"","sources":["../../../../src/components/charts/LineChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAIpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAQ5D,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,EAAE;IAC3C,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,OAAO;CACd,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,WAAW,CAAC;IAChB,0CAA0C,EAAE;QAC1C,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;KAC9B;CACF,CAAC,CACH,CAAC;AAcF,MAAM,CAAC,MAAM,SAAS,GAA6B,CAAC,EAClD,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,MAAM,GAAG,GAAG,EACZ,IAAI,EACJ,WAAW,GAAG,KAAK,EACnB,YAAY,EACZ,WAAW,GACZ,EAAE,EAAE;IACH,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IAEzB,OAAO,CACL,oBAAC,eAAe,IACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAC1B,MAAM,EAAE,MAAM,EACd,MAAM,EACJ,MAAM,IAAI;YACR;gBACE,OAAO,EAAE,QAAQ;gBACjB,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAE,WAAW;gBAClB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI;gBAC1E,IAAI;aACL;SACF,EAEH,SAAS,EAAE;YACT,MAAM,EAAE;gBACN,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE;gBACnD,OAAO,EAAE,CAAC;gBACV,aAAa,EAAE,EAAE;gBACjB,cAAc,EAAE,EAAE;gBAClB,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE;oBACV,QAAQ,EAAE,QAAQ;iBACnB;gBACD,GAAG,WAAW;aACf;YACD,yEAAyE;YACzE,UAAU;YACV,cAAc;YACd,eAAe;YACf,+BAA+B;YAC/B,OAAO;YACP,YAAY;SACb,EACD,KAAK,EAAE;YACL;gBACE,OAAO,EAAE,QAAQ;gBACjB,UAAU,EAAE,CAAC;gBACb,SAAS,EAAE,OAAO;gBAClB,cAAc,EAAE,qBAAqB,CAAC,WAAW,CAAC;aACnD;SACF,EACD,KAAK,EAAE;YACL;gBACE,GAAG,EAAE,CAAC;gBACN,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,CAAC;gBACd,GAAG,YAAY;aAChB;SACF,GACD,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Stack, Typography } from "@mui/material";
|
|
3
|
-
import
|
|
3
|
+
import Chart from "../../../components/Chart";
|
|
4
4
|
import WidgetCard from "../../../components/WidgetCard";
|
|
5
5
|
import { useApi } from "../../../contexts/ApiContext";
|
|
6
6
|
import { useDashboardFilter } from "../../../contexts/DashboardFilterContext";
|
|
@@ -41,7 +41,7 @@ const PurchaseCountWidget = ({ data: initialData, sx }) => {
|
|
|
41
41
|
}, [api, user]);
|
|
42
42
|
return (React.createElement(React.Fragment, null,
|
|
43
43
|
React.createElement(WidgetCard, { gridColumn: { md: "span 6", sm: "span 1" }, gridRow: "span 2", sx: sx, title: t(`${granularityLabel(filter.granularity)} Purchases`) },
|
|
44
|
-
React.createElement(
|
|
44
|
+
React.createElement(Chart, { charts: ["bar", "area"], csvFileName: "purchase_count", data: data.map((item) => ({ ...item })), dataKeyX: "date", dataKeyY: "count", dataTable: true })),
|
|
45
45
|
React.createElement(WidgetCard, { gridColumn: { md: isFourColumn ? "span 3" : "span 4", sm: "span 1" }, gridRow: "span 1", sx: sx, title: t("Purchase Count") },
|
|
46
46
|
React.createElement(Stack, { direction: "column", height: "100%", px: 2 },
|
|
47
47
|
React.createElement(Stack, { borderBottom: "1px solid", borderColor: "divider", height: "100%" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PurchaseCountWidget.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/contrib/PurchaseCountWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,
|
|
1
|
+
{"version":3,"file":"PurchaseCountWidget.js","sourceRoot":"","sources":["../../../../../src/extensions/pos/contrib/PurchaseCountWidget.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const formatDate = (date, granularity, variant = "short", locale = "sv-SE") => {
|
|
2
|
+
let options = {};
|
|
3
|
+
if (granularity === "day") {
|
|
4
|
+
options = {
|
|
5
|
+
day: "numeric",
|
|
6
|
+
month: "long",
|
|
7
|
+
...(variant === "long" && { year: "numeric" }),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
else if (granularity === "month") {
|
|
11
|
+
options = {
|
|
12
|
+
month: "long",
|
|
13
|
+
year: "numeric",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
else if (granularity === "year") {
|
|
17
|
+
options = {
|
|
18
|
+
year: "numeric",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=format_date.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format_date.js","sourceRoot":"","sources":["../../../src/util/format_date.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAU,EACV,WAAwB,EACxB,UAA4B,OAAO,EACnC,SAAiB,OAAO,EACxB,EAAE;IACF,IAAI,OAAO,GAA+B,EAAE,CAAC;IAC7C,IAAI,WAAW,KAAK,KAAK,EAAE,CAAC;QAC1B,OAAO,GAAG;YACR,GAAG,EAAE,SAAS;YACd,KAAK,EAAE,MAAM;YACb,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;SAC/C,CAAC;IACJ,CAAC;SAAM,IAAI,WAAW,KAAK,OAAO,EAAE,CAAC;QACnC,OAAO,GAAG;YACR,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;SAAM,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;QAClC,OAAO,GAAG;YACR,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC,CAAC"}
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { ApiOperation } from "../../api";
|
|
3
3
|
export interface ActionMenuItemProps {
|
|
4
4
|
operation: ApiOperation;
|
|
5
|
-
onAction: (
|
|
5
|
+
onAction: (operations: ApiOperation) => Promise<void>;
|
|
6
6
|
}
|
|
7
7
|
export declare const ActionMenuItem: React.FC<ActionMenuItemProps>;
|
|
8
8
|
declare const _default: React.NamedExoticComponent<ActionMenuItemProps>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type ChartType = "line" | "area" | "bar";
|
|
3
|
+
export type ChartsWithTableType = ChartType | "table";
|
|
4
|
+
export interface DataItem {
|
|
5
|
+
date: string;
|
|
6
|
+
[key: string]: string | number | boolean | Date;
|
|
7
|
+
}
|
|
8
|
+
export interface ChartProps {
|
|
9
|
+
data: DataItem[];
|
|
10
|
+
charts: ChartType[];
|
|
11
|
+
dataTable?: boolean;
|
|
12
|
+
dataKeyX: string;
|
|
13
|
+
dataKeyY?: string;
|
|
14
|
+
csvFileName?: string;
|
|
15
|
+
}
|
|
16
|
+
export default function Chart({ data, charts, dataTable, dataKeyX, dataKeyY, csvFileName, }: ChartProps): React.JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface DataItem {
|
|
3
|
+
[key: string]: string | number | boolean | Date;
|
|
4
|
+
}
|
|
5
|
+
interface StatsDataTableProps {
|
|
6
|
+
data: DataItem[];
|
|
7
|
+
dateKey?: string;
|
|
8
|
+
csvFileName?: string;
|
|
9
|
+
}
|
|
10
|
+
export default function StatsDataTable({ data, dateKey, csvFileName }: StatsDataTableProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from "react";
|
|
2
|
+
import { TableRowProps } from "@mui/material";
|
|
2
3
|
export interface TableHeadProps extends PropsWithChildren {
|
|
3
4
|
title?: string;
|
|
4
5
|
icon?: React.ReactNode;
|
|
6
|
+
tableRowProps?: TableRowProps;
|
|
5
7
|
}
|
|
6
8
|
export declare const TableHead: React.FC<TableHeadProps>;
|
|
7
9
|
export default TableHead;
|
|
@@ -51,6 +51,7 @@ export interface NavigationItem {
|
|
|
51
51
|
export type NavigationOverrides = Record<string, NavigationItem | null | undefined | false>;
|
|
52
52
|
export type ContribComponent<T = any> = React.ComponentType<PageProps<T> & OpenAPI.Request & {
|
|
53
53
|
sx?: SxProps;
|
|
54
|
+
close?: () => void;
|
|
54
55
|
}>;
|
|
55
56
|
/**
|
|
56
57
|
* The variant of the component, either a tab, inline or action menu item.
|
package/package.json
CHANGED
|
@@ -8,13 +8,13 @@ import Logo from "../Logo";
|
|
|
8
8
|
|
|
9
9
|
export interface ActionMenuItemProps {
|
|
10
10
|
operation: ApiOperation;
|
|
11
|
-
onAction: (
|
|
11
|
+
onAction: (operations: ApiOperation) => Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const ActionMenuItem: React.FC<ActionMenuItemProps> = ({ operation, onAction }) => {
|
|
15
15
|
const handleClick = useCallback(() => {
|
|
16
|
-
onAction(operation
|
|
17
|
-
}, [operation
|
|
16
|
+
onAction(operation);
|
|
17
|
+
}, [operation, onAction]);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<MenuItem onClick={handleClick}>
|
|
@@ -11,14 +11,19 @@ import { enqueueSnackbar } from "notistack";
|
|
|
11
11
|
import { ApiOperation } from "../../api";
|
|
12
12
|
import { useApi } from "../../contexts/ApiContext";
|
|
13
13
|
import { useI18n } from "../../contexts/I18nContext";
|
|
14
|
+
import { useUser } from "../../contexts/UserContext";
|
|
15
|
+
import { ContribComponent } from "../../types";
|
|
16
|
+
import { hasAccess } from "../../util/has_access";
|
|
14
17
|
import { usePage } from "../Page";
|
|
15
18
|
|
|
16
19
|
import ActionMenuItem from "./ActionMenuItem";
|
|
17
20
|
|
|
18
21
|
export const ActionMenu: React.FC = () => {
|
|
19
22
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
23
|
+
const [renderedComponent, setRenderedComponent] = useState<ContribComponent>();
|
|
20
24
|
const params = useParams();
|
|
21
25
|
const page = usePage();
|
|
26
|
+
const { user } = useUser();
|
|
22
27
|
const api = useApi();
|
|
23
28
|
const { t } = useI18n();
|
|
24
29
|
const title = useCallback(
|
|
@@ -36,9 +41,20 @@ export const ActionMenu: React.FC = () => {
|
|
|
36
41
|
setAnchorEl(null);
|
|
37
42
|
};
|
|
38
43
|
|
|
39
|
-
const handleAction = async (operation:
|
|
44
|
+
const handleAction = async (operation: ApiOperation) => {
|
|
45
|
+
if (operation.component != null) {
|
|
46
|
+
let component = operation.component.component;
|
|
47
|
+
setAnchorEl(null);
|
|
48
|
+
if (component instanceof Promise) {
|
|
49
|
+
component = await component;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setRenderedComponent(() => component);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
try {
|
|
41
|
-
const action = api.operations[operation];
|
|
57
|
+
const action = api.operations[operation.id];
|
|
42
58
|
|
|
43
59
|
const response = await action.call({ params });
|
|
44
60
|
|
|
@@ -76,7 +92,11 @@ export const ActionMenu: React.FC = () => {
|
|
|
76
92
|
onClose={handleClose}
|
|
77
93
|
>
|
|
78
94
|
{page?.contrib
|
|
79
|
-
.filter(
|
|
95
|
+
.filter(
|
|
96
|
+
(operation) =>
|
|
97
|
+
operation.component?.variant === "action" &&
|
|
98
|
+
hasAccess(user, operation.component.permission, operation.component.group),
|
|
99
|
+
)
|
|
80
100
|
.map((operation) => (
|
|
81
101
|
<ActionMenuItem key={operation.id} operation={operation} onAction={handleAction} />
|
|
82
102
|
))}
|
|
@@ -96,6 +116,13 @@ export const ActionMenu: React.FC = () => {
|
|
|
96
116
|
>
|
|
97
117
|
Actions
|
|
98
118
|
</Button>
|
|
119
|
+
|
|
120
|
+
{renderedComponent &&
|
|
121
|
+
React.createElement(renderedComponent, {
|
|
122
|
+
close: () => setRenderedComponent(undefined),
|
|
123
|
+
data: {},
|
|
124
|
+
refresh: () => {},
|
|
125
|
+
})}
|
|
99
126
|
</>
|
|
100
127
|
);
|
|
101
128
|
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { BarChartRounded, ShowChartRounded, TableRowsRounded } from "@mui/icons-material";
|
|
4
|
+
import { ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material";
|
|
5
|
+
|
|
6
|
+
import { useDashboardFilter } from "../contexts/DashboardFilterContext";
|
|
7
|
+
import { useI18n } from "../contexts/I18nContext";
|
|
8
|
+
|
|
9
|
+
import BarChart from "./charts/BarChart";
|
|
10
|
+
import LineChart from "./charts/LineChart";
|
|
11
|
+
import AreaChartIcon from "./Icons/AreaChartIcon";
|
|
12
|
+
import StatsDataTable from "./StatsDataTable";
|
|
13
|
+
|
|
14
|
+
export type ChartType = "line" | "area" | "bar";
|
|
15
|
+
export type ChartsWithTableType = ChartType | "table";
|
|
16
|
+
|
|
17
|
+
export interface DataItem {
|
|
18
|
+
date: string;
|
|
19
|
+
[key: string]: string | number | boolean | Date;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ChartProps {
|
|
23
|
+
data: DataItem[];
|
|
24
|
+
charts: ChartType[];
|
|
25
|
+
dataTable?: boolean;
|
|
26
|
+
dataKeyX: string;
|
|
27
|
+
dataKeyY?: string;
|
|
28
|
+
csvFileName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const chartData = {
|
|
32
|
+
line: {
|
|
33
|
+
Icon: ShowChartRounded,
|
|
34
|
+
name: "Line Chart",
|
|
35
|
+
},
|
|
36
|
+
area: {
|
|
37
|
+
Icon: AreaChartIcon,
|
|
38
|
+
name: "Area Chart",
|
|
39
|
+
},
|
|
40
|
+
bar: {
|
|
41
|
+
Icon: BarChartRounded,
|
|
42
|
+
name: "Bar Chart",
|
|
43
|
+
},
|
|
44
|
+
table: {
|
|
45
|
+
Icon: TableRowsRounded,
|
|
46
|
+
name: "Table",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default function Chart({
|
|
51
|
+
data,
|
|
52
|
+
charts,
|
|
53
|
+
dataTable = true,
|
|
54
|
+
dataKeyX,
|
|
55
|
+
dataKeyY,
|
|
56
|
+
csvFileName,
|
|
57
|
+
}: ChartProps) {
|
|
58
|
+
const { t } = useI18n();
|
|
59
|
+
const { filter } = useDashboardFilter();
|
|
60
|
+
const [selectedChart, setSelectedChart] = useState<ChartsWithTableType>(charts[0]);
|
|
61
|
+
|
|
62
|
+
const chartsWithTable: ChartsWithTableType[] = dataTable ? [...charts, "table"] : charts;
|
|
63
|
+
|
|
64
|
+
const handleSelectedChartChange = useCallback(
|
|
65
|
+
(_: React.MouseEvent<HTMLElement>, newChart: string) => {
|
|
66
|
+
if (newChart !== null) {
|
|
67
|
+
setSelectedChart(newChart as ChartsWithTableType);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
[],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
{chartsWithTable.length > 1 && (
|
|
76
|
+
<ToggleButtonGroup
|
|
77
|
+
exclusive
|
|
78
|
+
sx={{ position: "absolute", right: 16, top: 16 }}
|
|
79
|
+
unselectable="off"
|
|
80
|
+
value={selectedChart}
|
|
81
|
+
onChange={handleSelectedChartChange}
|
|
82
|
+
>
|
|
83
|
+
{chartsWithTable.map((chart) => {
|
|
84
|
+
const { Icon, name } = chartData[chart];
|
|
85
|
+
return (
|
|
86
|
+
<Tooltip key={chart} title={t(name)}>
|
|
87
|
+
<ToggleButton size="small" value={chart}>
|
|
88
|
+
<Icon fontSize="small" />
|
|
89
|
+
</ToggleButton>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
);
|
|
92
|
+
})}
|
|
93
|
+
</ToggleButtonGroup>
|
|
94
|
+
)}
|
|
95
|
+
{selectedChart === "bar" && (
|
|
96
|
+
<BarChart
|
|
97
|
+
dataKeyX={dataKeyX}
|
|
98
|
+
dataKeyY={dataKeyY}
|
|
99
|
+
dataset={data.map(({ date, ...props }) => ({ ...props, date: new Date(date) }))}
|
|
100
|
+
granularity={filter.granularity}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
{(selectedChart === "line" || selectedChart === "area") && (
|
|
104
|
+
<LineChart
|
|
105
|
+
area={selectedChart === "area"}
|
|
106
|
+
dataKeyX={dataKeyX}
|
|
107
|
+
dataKeyY={dataKeyY}
|
|
108
|
+
dataset={data.map(({ date, ...props }) => ({ ...props, date: new Date(date) }))}
|
|
109
|
+
granularity={filter.granularity}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
{selectedChart === "table" && (
|
|
113
|
+
<StatsDataTable csvFileName={csvFileName} data={data} dateKey={dataKeyX} />
|
|
114
|
+
)}
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { SvgIcon, SvgIconProps } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
const AreaChartIcon = (props: SvgIconProps) => (
|
|
6
|
+
<SvgIcon {...props} viewBox="0 0 24 24">
|
|
7
|
+
<path d="M3 17L9 10L14 15L21 7V15Q21 17 19 17H3Z" fill="currentColor" opacity="0.4" />
|
|
8
|
+
<path
|
|
9
|
+
d="M4 16L9 10L14 15L20 8"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeLinecap="round"
|
|
13
|
+
strokeLinejoin="round"
|
|
14
|
+
strokeWidth="2"
|
|
15
|
+
/>
|
|
16
|
+
</SvgIcon>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default AreaChartIcon;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { Button, Stack, TableBody, TableRow } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
import { useDashboardFilter } from "../contexts/DashboardFilterContext";
|
|
6
|
+
import { formatDate } from "../util/format_date";
|
|
7
|
+
|
|
8
|
+
import { TableCell } from "./Table/TableCell";
|
|
9
|
+
import TableHead from "./Table/TableHead";
|
|
10
|
+
import TableHeading from "./Table/TableHeading";
|
|
11
|
+
import Table from "./Table";
|
|
12
|
+
|
|
13
|
+
interface DataItem {
|
|
14
|
+
[key: string]: string | number | boolean | Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface StatsDataTableProps {
|
|
18
|
+
data: DataItem[];
|
|
19
|
+
dateKey?: string;
|
|
20
|
+
csvFileName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function StatsDataTable({ data, dateKey, csvFileName }: StatsDataTableProps) {
|
|
24
|
+
const { filter } = useDashboardFilter();
|
|
25
|
+
|
|
26
|
+
const mappedData = useMemo(() => {
|
|
27
|
+
return data
|
|
28
|
+
.map((item) => {
|
|
29
|
+
const dateEntry = getDateEntry(item, dateKey);
|
|
30
|
+
if (dateEntry === undefined) return undefined;
|
|
31
|
+
|
|
32
|
+
const [key, date] = dateEntry;
|
|
33
|
+
const { [key]: _, ...rest } = item;
|
|
34
|
+
|
|
35
|
+
return { [key]: date, ...rest };
|
|
36
|
+
})
|
|
37
|
+
.filter((item) => item !== undefined);
|
|
38
|
+
}, [data, dateKey]);
|
|
39
|
+
|
|
40
|
+
const handleExportToCSV = useCallback(() => {
|
|
41
|
+
if (mappedData.length === 0) return;
|
|
42
|
+
|
|
43
|
+
const headers = Object.keys(mappedData[0]);
|
|
44
|
+
const csvRows = [
|
|
45
|
+
headers.join(","), // Header row
|
|
46
|
+
...mappedData.map((row) =>
|
|
47
|
+
headers
|
|
48
|
+
.map((key) => {
|
|
49
|
+
const value = row[key];
|
|
50
|
+
if (value instanceof Date) return `"${value.toISOString()}"`;
|
|
51
|
+
if (typeof value === "string") return `"${value.replace(/"/g, '""')}"`; // Escape quotes
|
|
52
|
+
return value;
|
|
53
|
+
})
|
|
54
|
+
.join(","),
|
|
55
|
+
),
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const csvContent = csvRows.join("\n");
|
|
59
|
+
const blob = new Blob([csvContent], { type: "text/csv" });
|
|
60
|
+
const url = URL.createObjectURL(blob);
|
|
61
|
+
|
|
62
|
+
const link = document.createElement("a");
|
|
63
|
+
link.href = url;
|
|
64
|
+
link.download = `${csvFileName || "data"}_${filter.startDate.toISO()?.split("T")[0]}_${filter.endDate.toISO()?.split("T")[0]}.csv`;
|
|
65
|
+
document.body.appendChild(link);
|
|
66
|
+
link.click();
|
|
67
|
+
document.body.removeChild(link);
|
|
68
|
+
URL.revokeObjectURL(url);
|
|
69
|
+
}, [mappedData, filter.startDate, filter.endDate, filter.granularity]);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Stack maxHeight={300}>
|
|
73
|
+
<Table count={mappedData.length}>
|
|
74
|
+
<TableHead
|
|
75
|
+
tableRowProps={{
|
|
76
|
+
sx: {
|
|
77
|
+
position: "sticky",
|
|
78
|
+
top: 0,
|
|
79
|
+
bgcolor: "background.paper",
|
|
80
|
+
backgroundImage: "var(--Paper-overlay)",
|
|
81
|
+
boxShadow: "inset 0 -1px var(--mui-palette-divider)",
|
|
82
|
+
},
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{mappedData.length > 0 &&
|
|
86
|
+
Object.keys(mappedData[0]).map((key) => {
|
|
87
|
+
return <TableHeading key={key}>{formatKey(key)}</TableHeading>;
|
|
88
|
+
})}
|
|
89
|
+
</TableHead>
|
|
90
|
+
<TableBody sx={{ overflowY: "auto" }}>
|
|
91
|
+
{mappedData.map((item, i) => (
|
|
92
|
+
<TableRow key={i}>
|
|
93
|
+
{Object.entries(item).map(([key, value]) => (
|
|
94
|
+
<TableCell
|
|
95
|
+
key={key}
|
|
96
|
+
sx={{ borderBottomWidth: i === mappedData.length - 1 ? 0 : 1 }}
|
|
97
|
+
>
|
|
98
|
+
{value instanceof Date ? formatDate(value, filter.granularity) : value.toString()}
|
|
99
|
+
</TableCell>
|
|
100
|
+
))}
|
|
101
|
+
</TableRow>
|
|
102
|
+
))}
|
|
103
|
+
</TableBody>
|
|
104
|
+
</Table>
|
|
105
|
+
<Stack
|
|
106
|
+
alignItems="flex-end"
|
|
107
|
+
pt={2}
|
|
108
|
+
px={2}
|
|
109
|
+
spacing={1}
|
|
110
|
+
sx={{ borderTop: "1px solid", borderColor: "divider" }}
|
|
111
|
+
>
|
|
112
|
+
<Button color="inherit" variant="outlined" onClick={handleExportToCSV}>
|
|
113
|
+
Export to CSV
|
|
114
|
+
</Button>
|
|
115
|
+
</Stack>
|
|
116
|
+
</Stack>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatKey(key: string) {
|
|
121
|
+
return key
|
|
122
|
+
.split("_")
|
|
123
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
124
|
+
.join(" ");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getDateEntry(item: DataItem, dateKey?: string): [string, Date] | undefined {
|
|
128
|
+
if (dateKey !== undefined && item.hasOwnProperty(dateKey)) {
|
|
129
|
+
let date = item[dateKey];
|
|
130
|
+
date = typeof date === "string" ? new Date(date) : date;
|
|
131
|
+
if (!(date instanceof Date)) return undefined;
|
|
132
|
+
|
|
133
|
+
return [dateKey, date];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const dateEntry = Object.entries(item).find(([_, value]) => isDateOrString(value)) as
|
|
137
|
+
| [string, Date | string]
|
|
138
|
+
| undefined;
|
|
139
|
+
if (dateEntry === undefined) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dateObj = dateEntry[1] instanceof Date ? dateEntry[1] : new Date(dateEntry[1]);
|
|
144
|
+
return [dateEntry[0], dateObj];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isDateOrString(value: unknown): value is Date | string {
|
|
148
|
+
return (
|
|
149
|
+
(value instanceof Date && !Number.isNaN(value.getTime())) ||
|
|
150
|
+
(typeof value === "string" && !Number.isNaN(Date.parse(value)))
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Stack,
|
|
5
|
+
TableHead as MuiTableHead,
|
|
6
|
+
TableRow,
|
|
7
|
+
TableRowProps,
|
|
8
|
+
Typography,
|
|
9
|
+
} from "@mui/material";
|
|
4
10
|
|
|
5
11
|
export interface TableHeadProps extends PropsWithChildren {
|
|
6
12
|
title?: string;
|
|
7
13
|
icon?: React.ReactNode;
|
|
14
|
+
tableRowProps?: TableRowProps;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
export const TableHead: React.FC<TableHeadProps> = ({ children, icon, title }) => (
|
|
17
|
+
export const TableHead: React.FC<TableHeadProps> = ({ children, icon, title, tableRowProps }) => (
|
|
11
18
|
<MuiTableHead>
|
|
12
19
|
{title != null && (
|
|
13
20
|
<Stack
|
|
@@ -29,7 +36,7 @@ export const TableHead: React.FC<TableHeadProps> = ({ children, icon, title }) =
|
|
|
29
36
|
</Stack>
|
|
30
37
|
)}
|
|
31
38
|
|
|
32
|
-
<TableRow>{children}</TableRow>
|
|
39
|
+
<TableRow {...tableRowProps}>{children}</TableRow>
|
|
33
40
|
</MuiTableHead>
|
|
34
41
|
);
|
|
35
42
|
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
|
|
17
17
|
import { useI18n } from "../../contexts/I18nContext";
|
|
18
18
|
import { RouteInfo, useRouter } from "../../contexts/RouterContext";
|
|
19
|
+
import { useUser } from "../../contexts/UserContext";
|
|
20
|
+
import { hasAccess } from "../../util/has_access";
|
|
19
21
|
import ActionMenu from "../ActionMenu";
|
|
20
22
|
import { usePage } from "../Page";
|
|
21
23
|
|
|
@@ -29,6 +31,7 @@ export const TitleBar: React.FC<TitleBarProps> = ({ back = false, title, childre
|
|
|
29
31
|
const { navigate, getCurrent, routes } = useRouter();
|
|
30
32
|
const routerNavigate = useNavigate();
|
|
31
33
|
const page = usePage();
|
|
34
|
+
const { user } = useUser();
|
|
32
35
|
|
|
33
36
|
if (typeof back === "boolean" && back) back = -1;
|
|
34
37
|
|
|
@@ -105,8 +108,11 @@ export const TitleBar: React.FC<TitleBarProps> = ({ back = false, title, childre
|
|
|
105
108
|
</Stack>
|
|
106
109
|
</Stack>
|
|
107
110
|
{children}
|
|
108
|
-
{page?.contrib.filter(
|
|
109
|
-
|
|
111
|
+
{page?.contrib.filter(
|
|
112
|
+
(operation) =>
|
|
113
|
+
operation.component?.variant === "action" &&
|
|
114
|
+
hasAccess(user, operation.component.permission, operation.component.group),
|
|
115
|
+
).length > 0 && (
|
|
110
116
|
<Stack width="fit-content">
|
|
111
117
|
<ActionMenu />
|
|
112
118
|
</Stack>
|
|
@@ -33,6 +33,7 @@ export interface LineChartProps {
|
|
|
33
33
|
dataKeyX: string;
|
|
34
34
|
dataKeyY?: string;
|
|
35
35
|
height?: number;
|
|
36
|
+
area?: boolean;
|
|
36
37
|
granularity?: Granularity;
|
|
37
38
|
yAxisOptions?: object;
|
|
38
39
|
legendProps?: ChartsLegendProps;
|
|
@@ -44,6 +45,7 @@ export const LineChart: React.FC<LineChartProps> = ({
|
|
|
44
45
|
dataKeyX,
|
|
45
46
|
dataKeyY,
|
|
46
47
|
height = 300,
|
|
48
|
+
area,
|
|
47
49
|
granularity = "day",
|
|
48
50
|
yAxisOptions,
|
|
49
51
|
legendProps,
|
|
@@ -62,6 +64,7 @@ export const LineChart: React.FC<LineChartProps> = ({
|
|
|
62
64
|
showMark: false,
|
|
63
65
|
curve: "monotoneX",
|
|
64
66
|
color: theme.palette.graphColorPrimary?.main ?? theme.palette.primary.main,
|
|
67
|
+
area,
|
|
65
68
|
},
|
|
66
69
|
]
|
|
67
70
|
}
|
|
@@ -2,7 +2,7 @@ import React, { useMemo } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { Stack, Typography } from "@mui/material";
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import Chart from "../../../components/Chart";
|
|
6
6
|
import WidgetCard from "../../../components/WidgetCard";
|
|
7
7
|
import { useApi } from "../../../contexts/ApiContext";
|
|
8
8
|
import { useDashboardFilter } from "../../../contexts/DashboardFilterContext";
|
|
@@ -64,11 +64,13 @@ const PurchaseCountWidget: ContribComponent<PurchaseCountItem[]> = ({ data: init
|
|
|
64
64
|
sx={sx}
|
|
65
65
|
title={t(`${granularityLabel(filter.granularity)} Purchases`)}
|
|
66
66
|
>
|
|
67
|
-
<
|
|
67
|
+
<Chart
|
|
68
|
+
charts={["bar", "area"]}
|
|
69
|
+
csvFileName="purchase_count"
|
|
70
|
+
data={data.map((item) => ({ ...item }))}
|
|
68
71
|
dataKeyX="date"
|
|
69
72
|
dataKeyY="count"
|
|
70
|
-
|
|
71
|
-
granularity={filter.granularity}
|
|
73
|
+
dataTable={true}
|
|
72
74
|
/>
|
|
73
75
|
</WidgetCard>
|
|
74
76
|
|
package/src/types/index.ts
CHANGED
|
@@ -60,7 +60,7 @@ export type NavigationOverrides = Record<string, NavigationItem | null | undefin
|
|
|
60
60
|
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
62
|
export type ContribComponent<T = any> = React.ComponentType<
|
|
63
|
-
PageProps<T> & OpenAPI.Request & { sx?: SxProps }
|
|
63
|
+
PageProps<T> & OpenAPI.Request & { sx?: SxProps; close?: () => void }
|
|
64
64
|
>;
|
|
65
65
|
|
|
66
66
|
/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Granularity } from "../types";
|
|
2
|
+
|
|
3
|
+
export const formatDate = (
|
|
4
|
+
date: Date,
|
|
5
|
+
granularity: Granularity,
|
|
6
|
+
variant: "short" | "long" = "short",
|
|
7
|
+
locale: string = "sv-SE",
|
|
8
|
+
) => {
|
|
9
|
+
let options: Intl.DateTimeFormatOptions = {};
|
|
10
|
+
if (granularity === "day") {
|
|
11
|
+
options = {
|
|
12
|
+
day: "numeric",
|
|
13
|
+
month: "long",
|
|
14
|
+
...(variant === "long" && { year: "numeric" }),
|
|
15
|
+
};
|
|
16
|
+
} else if (granularity === "month") {
|
|
17
|
+
options = {
|
|
18
|
+
month: "long",
|
|
19
|
+
year: "numeric",
|
|
20
|
+
};
|
|
21
|
+
} else if (granularity === "year") {
|
|
22
|
+
options = {
|
|
23
|
+
year: "numeric",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
28
|
+
};
|