@electrolux-oss/plugin-infrawallet 0.1.11 → 0.1.12

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 (36) hide show
  1. package/dist/api/InfraWalletApiClient.esm.js +4 -2
  2. package/dist/api/InfraWalletApiClient.esm.js.map +1 -1
  3. package/dist/api/functions.esm.js +15 -3
  4. package/dist/api/functions.esm.js.map +1 -1
  5. package/dist/components/ColumnsChartComponent/ColumnsChartComponent.esm.js +142 -173
  6. package/dist/components/ColumnsChartComponent/ColumnsChartComponent.esm.js.map +1 -1
  7. package/dist/components/CostReportsTableComponent/CostReportsTableComponent.esm.js +147 -91
  8. package/dist/components/CostReportsTableComponent/CostReportsTableComponent.esm.js.map +1 -1
  9. package/dist/components/EntityInfraWalletCard/EntityInfraWalletCard.esm.js +254 -0
  10. package/dist/components/EntityInfraWalletCard/EntityInfraWalletCard.esm.js.map +1 -0
  11. package/dist/components/EntityInfraWalletCard/index.esm.js +2 -0
  12. package/dist/components/EntityInfraWalletCard/index.esm.js.map +1 -0
  13. package/dist/components/ErrorsAlertComponent/ErrorsAlertComponent.esm.js +4 -4
  14. package/dist/components/ErrorsAlertComponent/ErrorsAlertComponent.esm.js.map +1 -1
  15. package/dist/components/FiltersComponent/FiltersComponent.esm.js +27 -27
  16. package/dist/components/FiltersComponent/FiltersComponent.esm.js.map +1 -1
  17. package/dist/components/InfraWalletAppData.esm.js +9 -0
  18. package/dist/components/InfraWalletAppData.esm.js.map +1 -0
  19. package/dist/components/MetricConfigurationComponent/MetricConfigurationComponent.esm.js +22 -10
  20. package/dist/components/MetricConfigurationComponent/MetricConfigurationComponent.esm.js.map +1 -1
  21. package/dist/components/PieChartComponent/PieChartComponent.esm.js +63 -59
  22. package/dist/components/PieChartComponent/PieChartComponent.esm.js.map +1 -1
  23. package/dist/components/ProviderIcons/ProviderIcons.esm.js +8 -8
  24. package/dist/components/ProviderIcons/ProviderIcons.esm.js.map +1 -1
  25. package/dist/components/ReportsComponent/ReportsComponent.esm.js +24 -20
  26. package/dist/components/ReportsComponent/ReportsComponent.esm.js.map +1 -1
  27. package/dist/components/SettingsComponent/SettingsComponent.esm.js +4 -4
  28. package/dist/components/SettingsComponent/SettingsComponent.esm.js.map +1 -1
  29. package/dist/components/index.esm.js +36 -0
  30. package/dist/components/index.esm.js.map +1 -0
  31. package/dist/index.d.ts +5 -1
  32. package/dist/index.esm.js +2 -1
  33. package/dist/index.esm.js.map +1 -1
  34. package/dist/plugin.esm.js +10 -2
  35. package/dist/plugin.esm.js.map +1 -1
  36. package/package.json +9 -11
@@ -1,25 +1,11 @@
1
1
  import Typography from '@material-ui/core/Typography';
2
- import { makeStyles } from '@material-ui/core/styles';
3
2
  import Box from '@mui/material/Box';
3
+ import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
4
4
  import { DataGrid, GridToolbarContainer, GridToolbarExport } from '@mui/x-data-grid';
5
- import humanFormat from 'human-format';
6
5
  import React from 'react';
7
- import { extractAccountInfo, getPreviousMonth } from '../../api/functions.esm.js';
6
+ import { extractAccountInfo, formatCurrency, getPreviousMonth, getPreviousDay } from '../../api/functions.esm.js';
8
7
  import { getProviderIcon } from '../ProviderIcons/ProviderIcons.esm.js';
9
- import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
10
8
 
11
- const useStyles = makeStyles({
12
- increase: {
13
- color: "red"
14
- },
15
- decrease: {
16
- color: "green"
17
- },
18
- container: {
19
- display: "flex",
20
- alignItems: "center"
21
- }
22
- });
23
9
  function CustomToolbar() {
24
10
  return /* @__PURE__ */ React.createElement(GridToolbarContainer, null, /* @__PURE__ */ React.createElement(
25
11
  GridToolbarExport,
@@ -30,46 +16,44 @@ function CustomToolbar() {
30
16
  ));
31
17
  }
32
18
  const CostReportsTableComponent = ({ reports, aggregatedBy, periods }) => {
33
- const classes = useStyles();
34
- const customScale = humanFormat.Scale.create(["", "K", "M", "B"], 1e3);
35
- const formatCostValue = (params, period) => {
36
- const value = params.value;
37
- if (typeof value === "number") {
38
- const previousPeriod = period.length === 7 ? getPreviousMonth(params.field) : "";
39
- const formattedValue = humanFormat(value, {
40
- scale: customScale,
41
- separator: "",
42
- decimals: 2
43
- });
44
- if (periods.includes(previousPeriod) && params.row.reports[previousPeriod] > 0) {
45
- const diff = params.row.reports[params.field] - params.row.reports[previousPeriod];
46
- const percentage = Math.round(diff / params.row.reports[previousPeriod] * 100);
47
- const mark = diff > 0 ? "+" : "";
48
- if (percentage >= 1 || percentage <= -1) {
49
- return `$${formattedValue} (${mark}${percentage}%)`;
19
+ let rows = reports;
20
+ const columns = [];
21
+ const columnTotals = {};
22
+ const columnGroupingModel = [];
23
+ if (reports && aggregatedBy !== "none") {
24
+ for (const period of periods) {
25
+ for (const report of reports) {
26
+ if (columnTotals[period] === void 0) {
27
+ columnTotals[period] = 0;
50
28
  }
29
+ columnTotals[period] += report.reports[period] || 0;
51
30
  }
52
- return `$${formattedValue}`;
53
31
  }
54
- return "-";
55
- };
56
- const columns = [];
32
+ rows = [...reports, { id: "Total", reports: columnTotals }];
33
+ }
57
34
  if (["account", "provider", "service"].includes(aggregatedBy)) {
58
35
  columns.push({
59
- field: "PROVIDER",
36
+ field: "icon",
60
37
  headerName: "",
61
38
  width: 30,
39
+ display: "flex",
62
40
  disableExport: true,
41
+ sortable: false,
63
42
  renderCell: (params) => {
64
- return getProviderIcon(params.row.provider);
43
+ if (params.id === "Total") {
44
+ return void 0;
45
+ }
46
+ return /* @__PURE__ */ React.createElement("div", null, getProviderIcon(params.row.provider));
65
47
  }
66
48
  });
67
49
  }
68
50
  columns.push(
51
+ // groupBy column
69
52
  {
70
53
  field: aggregatedBy,
71
- headerName: aggregatedBy.toLocaleUpperCase("en-US"),
54
+ headerName: aggregatedBy === "none" ? "" : aggregatedBy.charAt(0).toUpperCase() + aggregatedBy.slice(1),
72
55
  minWidth: 200,
56
+ display: "flex",
73
57
  flex: 2,
74
58
  renderCell: (params) => {
75
59
  let formattedValue = params.formattedValue;
@@ -81,18 +65,29 @@ const CostReportsTableComponent = ({ reports, aggregatedBy, periods }) => {
81
65
  const account = extractAccountInfo(formattedValue);
82
66
  const accountName = account.accountName;
83
67
  const accountId = account.accountId;
84
- return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.container }, accountName), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", className: classes.container, color: "textSecondary" }, accountId));
68
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, accountName), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, accountId));
85
69
  }
86
70
  }
87
- return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", component: "div", className: classes.container }, formattedValue);
71
+ if (params.id === "Total") {
72
+ return /* @__PURE__ */ React.createElement("div", { style: { fontWeight: "bold" } }, "Total");
73
+ }
74
+ return /* @__PURE__ */ React.createElement("div", null, formattedValue);
75
+ },
76
+ sortComparator: (value1, value2, params1, params2) => {
77
+ if (params1.id === "Total" || params2.id === "Total") {
78
+ return 0;
79
+ }
80
+ return value1 - value2;
88
81
  }
89
82
  },
83
+ // trend line column
90
84
  {
91
- field: "TREND",
92
- headerName: "TREND",
85
+ field: "trend",
86
+ headerName: "Trend",
93
87
  width: 100,
88
+ display: "flex",
94
89
  disableExport: true,
95
- hideSortIcons: true,
90
+ sortable: false,
96
91
  renderCell: (params) => /* @__PURE__ */ React.createElement(SparkLineChart, { data: params.value ? params.value[0] : null, plotType: "bar" }),
97
92
  valueGetter: (_, row) => [
98
93
  periods.map((period) => row.reports[period] !== void 0 ? row.reports[period] : null)
@@ -100,65 +95,120 @@ const CostReportsTableComponent = ({ reports, aggregatedBy, periods }) => {
100
95
  }
101
96
  );
102
97
  periods.forEach((period) => {
103
- columns.push({
104
- field: period,
105
- headerName: period,
98
+ columns.push(
99
+ // cost column
100
+ {
101
+ field: `cost-${period}`,
102
+ headerName: "Cost",
103
+ type: "number",
104
+ valueGetter: (_, row) => {
105
+ return row.reports[period] ? row.reports[period] : null;
106
+ },
107
+ renderCell: (params) => {
108
+ const value = params.value;
109
+ let cost = "-";
110
+ if (typeof value === "number") {
111
+ cost = formatCurrency(value);
112
+ }
113
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { style: { fontWeight: params.id === "Total" ? "bold" : "normal" } }, cost));
114
+ },
115
+ sortComparator: (value1, value2, params1, params2) => {
116
+ if (params1.id === "Total" || params2.id === "Total") {
117
+ return 0;
118
+ }
119
+ return value1 - value2;
120
+ }
121
+ },
122
+ // change% column
123
+ {
124
+ field: `change-${period}`,
125
+ headerName: "Change",
126
+ disableExport: true,
127
+ sortable: false,
128
+ renderCell: (params) => {
129
+ let percentage = void 0;
130
+ const previousPeriod = period.length === 7 ? getPreviousMonth(period) : getPreviousDay(period);
131
+ if (params.row.reports[period] && periods.includes(previousPeriod) && params.row.reports[previousPeriod] > 0) {
132
+ const diff = params.row.reports[period] - params.row.reports[previousPeriod];
133
+ percentage = Math.round(diff / params.row.reports[previousPeriod] * 100);
134
+ }
135
+ if (percentage === void 0) {
136
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", null, "-"));
137
+ }
138
+ let color = "#0052cc";
139
+ let backgroundColor = "#e9f2ff";
140
+ let mark = "";
141
+ if (percentage < 0) {
142
+ color = "#216e4e";
143
+ backgroundColor = "#dcfff1";
144
+ mark = "\u25BC";
145
+ } else if (percentage > 0) {
146
+ color = "#ae2e24";
147
+ backgroundColor = "#ffeceb";
148
+ mark = "\u25B2";
149
+ }
150
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
151
+ "span",
152
+ {
153
+ style: {
154
+ fontSize: "0.82em",
155
+ paddingInline: "2px",
156
+ borderRadius: "4px",
157
+ color,
158
+ backgroundColor
159
+ }
160
+ },
161
+ mark,
162
+ Math.abs(percentage).toLocaleString(),
163
+ "%"
164
+ ));
165
+ }
166
+ }
167
+ );
168
+ columnGroupingModel.push({
169
+ groupId: period,
170
+ children: [{ field: `cost-${period}` }, { field: `change-${period}` }]
171
+ });
172
+ });
173
+ columns.push(
174
+ // total column
175
+ {
176
+ field: "total",
177
+ headerName: "Total",
106
178
  type: "number",
107
179
  minWidth: 150,
108
- flex: 1,
109
180
  valueGetter: (_, row) => {
110
- return row.reports[period] ? row.reports[period] : null;
181
+ let total = 0;
182
+ periods.forEach((period) => {
183
+ total += row.reports[period] ? row.reports[period] : 0;
184
+ });
185
+ return total;
111
186
  },
112
187
  renderCell: (params) => {
113
- const formattedValue = formatCostValue(params, period);
114
- let className = "";
115
- const percentageIndex = formattedValue.indexOf("(");
116
- const costStr = percentageIndex === -1 ? formattedValue : formattedValue.substring(0, percentageIndex);
117
- let percentageStr = percentageIndex === -1 ? "" : formattedValue.substring(percentageIndex);
118
- if (percentageStr.includes("-")) {
119
- className = classes.decrease;
120
- percentageStr = percentageStr.replace("-", "\u25BC");
121
- } else if (percentageStr.includes("+")) {
122
- className = classes.increase;
123
- percentageStr = percentageStr.replace("+", "\u25B2");
188
+ let formattedValue = "-";
189
+ if (typeof params.value === "number") {
190
+ formattedValue = formatCurrency(params.value);
124
191
  }
125
- return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, costStr, /* @__PURE__ */ React.createElement(Typography, { variant: "inherit", className }, percentageStr));
126
- }
127
- });
128
- });
129
- columns.push({
130
- field: "TOTAL",
131
- headerName: "TOTAL",
132
- type: "number",
133
- minWidth: 150,
134
- flex: 1,
135
- valueGetter: (_, row) => {
136
- let total = 0;
137
- periods.forEach((period) => {
138
- total += row.reports[period] ? row.reports[period] : 0;
139
- });
140
- return total;
141
- },
142
- renderCell: (params) => {
143
- let formattedValue = "-";
144
- if (typeof params.value === "number") {
145
- formattedValue = `$${humanFormat(params.value, {
146
- scale: customScale,
147
- separator: "",
148
- decimals: 2
149
- })}`;
192
+ return /* @__PURE__ */ React.createElement("div", { style: { fontWeight: "bold" } }, formattedValue);
193
+ },
194
+ sortComparator: (value1, value2, params1, params2) => {
195
+ if (params1.id === "Total" || params2.id === "Total") {
196
+ return 0;
197
+ }
198
+ return value1 - value2;
150
199
  }
151
- return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, formattedValue);
152
200
  }
153
- });
154
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
201
+ );
202
+ return /* @__PURE__ */ React.createElement(Box, { sx: { height: 700 } }, /* @__PURE__ */ React.createElement(
155
203
  DataGrid,
156
204
  {
157
- rows: reports,
205
+ loading: rows === void 0,
206
+ rows,
158
207
  columns,
208
+ columnGroupingModel,
159
209
  initialState: {
160
210
  sorting: {
161
- sortModel: [{ field: "TOTAL", sort: "desc" }]
211
+ sortModel: [{ field: "total", sort: "desc" }]
162
212
  },
163
213
  pagination: {
164
214
  paginationModel: {
@@ -168,6 +218,12 @@ const CostReportsTableComponent = ({ reports, aggregatedBy, periods }) => {
168
218
  },
169
219
  pageSizeOptions: [15],
170
220
  slots: { toolbar: CustomToolbar },
221
+ slotProps: {
222
+ loadingOverlay: {
223
+ variant: "skeleton",
224
+ noRowsVariant: "skeleton"
225
+ }
226
+ },
171
227
  disableRowSelectionOnClick: true,
172
228
  disableColumnMenu: true,
173
229
  density: aggregatedBy === "account" ? "standard" : "compact"
@@ -1 +1 @@
1
- {"version":3,"file":"CostReportsTableComponent.esm.js","sources":["../../../src/components/CostReportsTableComponent/CostReportsTableComponent.tsx"],"sourcesContent":["import Typography from '@material-ui/core/Typography';\nimport { makeStyles } from '@material-ui/core/styles';\nimport Box from '@mui/material/Box';\nimport { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarExport } from '@mui/x-data-grid';\nimport humanFormat from 'human-format';\nimport React, { FC } from 'react';\nimport { extractAccountInfo, getPreviousMonth } from '../../api/functions';\nimport { CostReportsTableComponentProps } from '../types';\nimport { getProviderIcon } from '../ProviderIcons';\nimport { SparkLineChart } from '@mui/x-charts/SparkLineChart';\n\nconst useStyles = makeStyles({\n increase: {\n color: 'red',\n },\n decrease: {\n color: 'green',\n },\n container: {\n display: 'flex',\n alignItems: 'center',\n },\n});\n\nfunction CustomToolbar() {\n return (\n <GridToolbarContainer>\n <GridToolbarExport\n csvOptions={{ fileName: 'InfraWallet-export' }}\n printOptions={{ disableToolbarButton: true }}\n />\n </GridToolbarContainer>\n );\n}\n\nexport const CostReportsTableComponent: FC<CostReportsTableComponentProps> = ({ reports, aggregatedBy, periods }) => {\n const classes = useStyles();\n const customScale = humanFormat.Scale.create(['', 'K', 'M', 'B'], 1000);\n\n const formatCostValue = (params: GridRenderCellParams, period: string): string => {\n const value = params.value;\n if (typeof value === 'number') {\n const previousPeriod = period.length === 7 ? getPreviousMonth(params.field) : '';\n const formattedValue = humanFormat(value, {\n scale: customScale,\n separator: '',\n decimals: 2,\n });\n if (periods.includes(previousPeriod) && params.row.reports[previousPeriod] > 0) {\n const diff = params.row.reports[params.field] - params.row.reports[previousPeriod];\n const percentage = Math.round((diff / params.row.reports[previousPeriod]) * 100);\n const mark = diff > 0 ? '+' : '';\n // only display percentage change if it is larger than 1% or less than -1%\n if (percentage >= 1 || percentage <= -1) {\n return `$${formattedValue} (${mark}${percentage}%)`;\n }\n }\n return `$${formattedValue}`;\n }\n return '-';\n };\n\n const columns: GridColDef[] = [];\n if (['account', 'provider', 'service'].includes(aggregatedBy)) {\n columns.push({\n field: 'PROVIDER',\n headerName: '',\n width: 30,\n disableExport: true,\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n return getProviderIcon(params.row.provider);\n },\n });\n }\n\n columns.push(\n {\n field: aggregatedBy,\n headerName: aggregatedBy.toLocaleUpperCase('en-US'),\n minWidth: 200,\n flex: 2,\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n let formattedValue = params.formattedValue;\n\n if (aggregatedBy === 'service' || aggregatedBy === 'account') {\n // remove provider names\n if (params.formattedValue !== undefined && params.formattedValue.indexOf('/') !== -1) {\n formattedValue = params.formattedValue.split('/')[1];\n }\n\n if (aggregatedBy === 'account' && formattedValue) {\n const account = extractAccountInfo(formattedValue);\n const accountName = account.accountName;\n const accountId = account.accountId;\n\n return (\n <div>\n <Typography variant=\"body2\" className={classes.container}>\n {accountName}\n </Typography>\n <Typography variant=\"caption\" className={classes.container} color=\"textSecondary\">\n {accountId}\n </Typography>\n </div>\n );\n }\n }\n\n return (\n <Typography variant=\"body2\" component=\"div\" className={classes.container}>\n {formattedValue}\n </Typography>\n );\n },\n },\n {\n field: 'TREND',\n headerName: 'TREND',\n width: 100,\n disableExport: true,\n hideSortIcons: true,\n renderCell: (params: GridRenderCellParams) => (\n <SparkLineChart data={params.value ? params.value[0] : null} plotType=\"bar\" />\n ),\n valueGetter: (_, row) => [\n periods.map(period => (row.reports[period] !== undefined ? row.reports[period] : null)),\n ],\n },\n );\n\n periods.forEach(period => {\n columns.push({\n field: period,\n headerName: period,\n type: 'number',\n minWidth: 150,\n flex: 1,\n valueGetter: (_, row) => {\n return row.reports[period] ? row.reports[period] : null;\n },\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n const formattedValue = formatCostValue(params, period);\n let className = '';\n const percentageIndex = formattedValue.indexOf('(');\n const costStr = percentageIndex === -1 ? formattedValue : formattedValue.substring(0, percentageIndex);\n let percentageStr = percentageIndex === -1 ? '' : formattedValue.substring(percentageIndex);\n if (percentageStr.includes('-')) {\n className = classes.decrease;\n percentageStr = percentageStr.replace('-', '▼');\n } else if (percentageStr.includes('+')) {\n className = classes.increase;\n percentageStr = percentageStr.replace('+', '▲');\n }\n\n return (\n <Typography variant=\"body2\">\n {costStr}\n <Typography variant=\"inherit\" className={className}>\n {percentageStr}\n </Typography>\n </Typography>\n );\n },\n });\n });\n\n columns.push({\n field: 'TOTAL',\n headerName: 'TOTAL',\n type: 'number',\n minWidth: 150,\n flex: 1,\n valueGetter: (_, row) => {\n let total = 0;\n periods.forEach(period => {\n total += row.reports[period] ? row.reports[period] : 0;\n });\n return total;\n },\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n let formattedValue = '-';\n if (typeof params.value === 'number') {\n formattedValue = `$${humanFormat(params.value, {\n scale: customScale,\n separator: '',\n decimals: 2,\n })}`;\n }\n return <Typography variant=\"body2\">{formattedValue}</Typography>;\n },\n });\n\n return (\n <Box>\n <DataGrid\n rows={reports}\n columns={columns}\n initialState={{\n sorting: {\n sortModel: [{ field: 'TOTAL', sort: 'desc' }],\n },\n pagination: {\n paginationModel: {\n pageSize: 15,\n },\n },\n }}\n pageSizeOptions={[15]}\n slots={{ toolbar: CustomToolbar }}\n disableRowSelectionOnClick\n disableColumnMenu\n density={aggregatedBy === 'account' ? 'standard' : 'compact'}\n />\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;AAWA,MAAM,YAAY,UAAW,CAAA;AAAA,EAC3B,QAAU,EAAA;AAAA,IACR,KAAO,EAAA,KAAA;AAAA,GACT;AAAA,EACA,QAAU,EAAA;AAAA,IACR,KAAO,EAAA,OAAA;AAAA,GACT;AAAA,EACA,SAAW,EAAA;AAAA,IACT,OAAS,EAAA,MAAA;AAAA,IACT,UAAY,EAAA,QAAA;AAAA,GACd;AACF,CAAC,CAAA,CAAA;AAED,SAAS,aAAgB,GAAA;AACvB,EAAA,2CACG,oBACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,iBAAA;AAAA,IAAA;AAAA,MACC,UAAA,EAAY,EAAE,QAAA,EAAU,oBAAqB,EAAA;AAAA,MAC7C,YAAA,EAAc,EAAE,oBAAA,EAAsB,IAAK,EAAA;AAAA,KAAA;AAAA,GAE/C,CAAA,CAAA;AAEJ,CAAA;AAEO,MAAM,4BAAgE,CAAC,EAAE,OAAS,EAAA,YAAA,EAAc,SAAc,KAAA;AACnH,EAAA,MAAM,UAAU,SAAU,EAAA,CAAA;AAC1B,EAAM,MAAA,WAAA,GAAc,WAAY,CAAA,KAAA,CAAM,MAAO,CAAA,CAAC,IAAI,GAAK,EAAA,GAAA,EAAK,GAAG,CAAA,EAAG,GAAI,CAAA,CAAA;AAEtE,EAAM,MAAA,eAAA,GAAkB,CAAC,MAAA,EAA8B,MAA2B,KAAA;AAChF,IAAA,MAAM,QAAQ,MAAO,CAAA,KAAA,CAAA;AACrB,IAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,MAAA,MAAM,iBAAiB,MAAO,CAAA,MAAA,KAAW,IAAI,gBAAiB,CAAA,MAAA,CAAO,KAAK,CAAI,GAAA,EAAA,CAAA;AAC9E,MAAM,MAAA,cAAA,GAAiB,YAAY,KAAO,EAAA;AAAA,QACxC,KAAO,EAAA,WAAA;AAAA,QACP,SAAW,EAAA,EAAA;AAAA,QACX,QAAU,EAAA,CAAA;AAAA,OACX,CAAA,CAAA;AACD,MAAI,IAAA,OAAA,CAAQ,SAAS,cAAc,CAAA,IAAK,OAAO,GAAI,CAAA,OAAA,CAAQ,cAAc,CAAA,GAAI,CAAG,EAAA;AAC9E,QAAM,MAAA,IAAA,GAAO,MAAO,CAAA,GAAA,CAAI,OAAQ,CAAA,MAAA,CAAO,KAAK,CAAI,GAAA,MAAA,CAAO,GAAI,CAAA,OAAA,CAAQ,cAAc,CAAA,CAAA;AACjF,QAAM,MAAA,UAAA,GAAa,KAAK,KAAO,CAAA,IAAA,GAAO,OAAO,GAAI,CAAA,OAAA,CAAQ,cAAc,CAAA,GAAK,GAAG,CAAA,CAAA;AAC/E,QAAM,MAAA,IAAA,GAAO,IAAO,GAAA,CAAA,GAAI,GAAM,GAAA,EAAA,CAAA;AAE9B,QAAI,IAAA,UAAA,IAAc,CAAK,IAAA,UAAA,IAAc,CAAI,CAAA,EAAA;AACvC,UAAA,OAAO,CAAI,CAAA,EAAA,cAAc,CAAK,EAAA,EAAA,IAAI,GAAG,UAAU,CAAA,EAAA,CAAA,CAAA;AAAA,SACjD;AAAA,OACF;AACA,MAAA,OAAO,IAAI,cAAc,CAAA,CAAA,CAAA;AAAA,KAC3B;AACA,IAAO,OAAA,GAAA,CAAA;AAAA,GACT,CAAA;AAEA,EAAA,MAAM,UAAwB,EAAC,CAAA;AAC/B,EAAA,IAAI,CAAC,SAAW,EAAA,UAAA,EAAY,SAAS,CAAE,CAAA,QAAA,CAAS,YAAY,CAAG,EAAA;AAC7D,IAAA,OAAA,CAAQ,IAAK,CAAA;AAAA,MACX,KAAO,EAAA,UAAA;AAAA,MACP,UAAY,EAAA,EAAA;AAAA,MACZ,KAAO,EAAA,EAAA;AAAA,MACP,aAAe,EAAA,IAAA;AAAA,MACf,UAAA,EAAY,CAAC,MAAkD,KAAA;AAC7D,QAAO,OAAA,eAAA,CAAgB,MAAO,CAAA,GAAA,CAAI,QAAQ,CAAA,CAAA;AAAA,OAC5C;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAEA,EAAQ,OAAA,CAAA,IAAA;AAAA,IACN;AAAA,MACE,KAAO,EAAA,YAAA;AAAA,MACP,UAAA,EAAY,YAAa,CAAA,iBAAA,CAAkB,OAAO,CAAA;AAAA,MAClD,QAAU,EAAA,GAAA;AAAA,MACV,IAAM,EAAA,CAAA;AAAA,MACN,UAAA,EAAY,CAAC,MAAkD,KAAA;AAC7D,QAAA,IAAI,iBAAiB,MAAO,CAAA,cAAA,CAAA;AAE5B,QAAI,IAAA,YAAA,KAAiB,SAAa,IAAA,YAAA,KAAiB,SAAW,EAAA;AAE5D,UAAI,IAAA,MAAA,CAAO,mBAAmB,KAAa,CAAA,IAAA,MAAA,CAAO,eAAe,OAAQ,CAAA,GAAG,MAAM,CAAI,CAAA,EAAA;AACpF,YAAA,cAAA,GAAiB,MAAO,CAAA,cAAA,CAAe,KAAM,CAAA,GAAG,EAAE,CAAC,CAAA,CAAA;AAAA,WACrD;AAEA,UAAI,IAAA,YAAA,KAAiB,aAAa,cAAgB,EAAA;AAChD,YAAM,MAAA,OAAA,GAAU,mBAAmB,cAAc,CAAA,CAAA;AACjD,YAAA,MAAM,cAAc,OAAQ,CAAA,WAAA,CAAA;AAC5B,YAAA,MAAM,YAAY,OAAQ,CAAA,SAAA,CAAA;AAE1B,YACE,uBAAA,KAAA,CAAA,aAAA,CAAC,6BACE,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,OAAQ,EAAA,SAAA,EAAW,QAAQ,SAC5C,EAAA,EAAA,WACH,mBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,SAAU,EAAA,SAAA,EAAW,QAAQ,SAAW,EAAA,KAAA,EAAM,eAC/D,EAAA,EAAA,SACH,CACF,CAAA,CAAA;AAAA,WAEJ;AAAA,SACF;AAEA,QACE,uBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,OAAQ,EAAA,OAAA,EAAQ,WAAU,KAAM,EAAA,SAAA,EAAW,OAAQ,CAAA,SAAA,EAAA,EAC5D,cACH,CAAA,CAAA;AAAA,OAEJ;AAAA,KACF;AAAA,IACA;AAAA,MACE,KAAO,EAAA,OAAA;AAAA,MACP,UAAY,EAAA,OAAA;AAAA,MACZ,KAAO,EAAA,GAAA;AAAA,MACP,aAAe,EAAA,IAAA;AAAA,MACf,aAAe,EAAA,IAAA;AAAA,MACf,UAAY,EAAA,CAAC,MACX,qBAAA,KAAA,CAAA,aAAA,CAAC,kBAAe,IAAM,EAAA,MAAA,CAAO,KAAQ,GAAA,MAAA,CAAO,KAAM,CAAA,CAAC,CAAI,GAAA,IAAA,EAAM,UAAS,KAAM,EAAA,CAAA;AAAA,MAE9E,WAAA,EAAa,CAAC,CAAA,EAAG,GAAQ,KAAA;AAAA,QACvB,OAAQ,CAAA,GAAA,CAAI,CAAW,MAAA,KAAA,GAAA,CAAI,OAAQ,CAAA,MAAM,CAAM,KAAA,KAAA,CAAA,GAAY,GAAI,CAAA,OAAA,CAAQ,MAAM,CAAA,GAAI,IAAK,CAAA;AAAA,OACxF;AAAA,KACF;AAAA,GACF,CAAA;AAEA,EAAA,OAAA,CAAQ,QAAQ,CAAU,MAAA,KAAA;AACxB,IAAA,OAAA,CAAQ,IAAK,CAAA;AAAA,MACX,KAAO,EAAA,MAAA;AAAA,MACP,UAAY,EAAA,MAAA;AAAA,MACZ,IAAM,EAAA,QAAA;AAAA,MACN,QAAU,EAAA,GAAA;AAAA,MACV,IAAM,EAAA,CAAA;AAAA,MACN,WAAA,EAAa,CAAC,CAAA,EAAG,GAAQ,KAAA;AACvB,QAAA,OAAO,IAAI,OAAQ,CAAA,MAAM,IAAI,GAAI,CAAA,OAAA,CAAQ,MAAM,CAAI,GAAA,IAAA,CAAA;AAAA,OACrD;AAAA,MACA,UAAA,EAAY,CAAC,MAAkD,KAAA;AAC7D,QAAM,MAAA,cAAA,GAAiB,eAAgB,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AACrD,QAAA,IAAI,SAAY,GAAA,EAAA,CAAA;AAChB,QAAM,MAAA,eAAA,GAAkB,cAAe,CAAA,OAAA,CAAQ,GAAG,CAAA,CAAA;AAClD,QAAA,MAAM,UAAU,eAAoB,KAAA,CAAA,CAAA,GAAK,iBAAiB,cAAe,CAAA,SAAA,CAAU,GAAG,eAAe,CAAA,CAAA;AACrG,QAAA,IAAI,gBAAgB,eAAoB,KAAA,CAAA,CAAA,GAAK,EAAK,GAAA,cAAA,CAAe,UAAU,eAAe,CAAA,CAAA;AAC1F,QAAI,IAAA,aAAA,CAAc,QAAS,CAAA,GAAG,CAAG,EAAA;AAC/B,UAAA,SAAA,GAAY,OAAQ,CAAA,QAAA,CAAA;AACpB,UAAgB,aAAA,GAAA,aAAA,CAAc,OAAQ,CAAA,GAAA,EAAK,QAAG,CAAA,CAAA;AAAA,SACrC,MAAA,IAAA,aAAA,CAAc,QAAS,CAAA,GAAG,CAAG,EAAA;AACtC,UAAA,SAAA,GAAY,OAAQ,CAAA,QAAA,CAAA;AACpB,UAAgB,aAAA,GAAA,aAAA,CAAc,OAAQ,CAAA,GAAA,EAAK,QAAG,CAAA,CAAA;AAAA,SAChD;AAEA,QACE,uBAAA,KAAA,CAAA,aAAA,CAAC,UAAW,EAAA,EAAA,OAAA,EAAQ,OACjB,EAAA,EAAA,OAAA,kBACA,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,SAAA,EAAU,SAC3B,EAAA,EAAA,aACH,CACF,CAAA,CAAA;AAAA,OAEJ;AAAA,KACD,CAAA,CAAA;AAAA,GACF,CAAA,CAAA;AAED,EAAA,OAAA,CAAQ,IAAK,CAAA;AAAA,IACX,KAAO,EAAA,OAAA;AAAA,IACP,UAAY,EAAA,OAAA;AAAA,IACZ,IAAM,EAAA,QAAA;AAAA,IACN,QAAU,EAAA,GAAA;AAAA,IACV,IAAM,EAAA,CAAA;AAAA,IACN,WAAA,EAAa,CAAC,CAAA,EAAG,GAAQ,KAAA;AACvB,MAAA,IAAI,KAAQ,GAAA,CAAA,CAAA;AACZ,MAAA,OAAA,CAAQ,QAAQ,CAAU,MAAA,KAAA;AACxB,QAAA,KAAA,IAAS,IAAI,OAAQ,CAAA,MAAM,IAAI,GAAI,CAAA,OAAA,CAAQ,MAAM,CAAI,GAAA,CAAA,CAAA;AAAA,OACtD,CAAA,CAAA;AACD,MAAO,OAAA,KAAA,CAAA;AAAA,KACT;AAAA,IACA,UAAA,EAAY,CAAC,MAAkD,KAAA;AAC7D,MAAA,IAAI,cAAiB,GAAA,GAAA,CAAA;AACrB,MAAI,IAAA,OAAO,MAAO,CAAA,KAAA,KAAU,QAAU,EAAA;AACpC,QAAiB,cAAA,GAAA,CAAA,CAAA,EAAI,WAAY,CAAA,MAAA,CAAO,KAAO,EAAA;AAAA,UAC7C,KAAO,EAAA,WAAA;AAAA,UACP,SAAW,EAAA,EAAA;AAAA,UACX,QAAU,EAAA,CAAA;AAAA,SACX,CAAC,CAAA,CAAA,CAAA;AAAA,OACJ;AACA,MAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,OAAA,EAAA,EAAS,cAAe,CAAA,CAAA;AAAA,KACrD;AAAA,GACD,CAAA,CAAA;AAED,EAAA,2CACG,GACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAM,EAAA,OAAA;AAAA,MACN,OAAA;AAAA,MACA,YAAc,EAAA;AAAA,QACZ,OAAS,EAAA;AAAA,UACP,WAAW,CAAC,EAAE,OAAO,OAAS,EAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,SAC9C;AAAA,QACA,UAAY,EAAA;AAAA,UACV,eAAiB,EAAA;AAAA,YACf,QAAU,EAAA,EAAA;AAAA,WACZ;AAAA,SACF;AAAA,OACF;AAAA,MACA,eAAA,EAAiB,CAAC,EAAE,CAAA;AAAA,MACpB,KAAA,EAAO,EAAE,OAAA,EAAS,aAAc,EAAA;AAAA,MAChC,0BAA0B,EAAA,IAAA;AAAA,MAC1B,iBAAiB,EAAA,IAAA;AAAA,MACjB,OAAA,EAAS,YAAiB,KAAA,SAAA,GAAY,UAAa,GAAA,SAAA;AAAA,KAAA;AAAA,GAEvD,CAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"CostReportsTableComponent.esm.js","sources":["../../../src/components/CostReportsTableComponent/CostReportsTableComponent.tsx"],"sourcesContent":["import Typography from '@material-ui/core/Typography';\nimport Box from '@mui/material/Box';\nimport { SparkLineChart } from '@mui/x-charts/SparkLineChart';\nimport {\n DataGrid,\n GridColDef,\n GridColumnGroupingModel,\n GridRenderCellParams,\n GridToolbarContainer,\n GridToolbarExport,\n} from '@mui/x-data-grid';\nimport React, { FC } from 'react';\nimport { extractAccountInfo, formatCurrency, getPreviousDay, getPreviousMonth } from '../../api/functions';\nimport { getProviderIcon } from '../ProviderIcons';\nimport { CostReportsTableComponentProps } from '../types';\n\nfunction CustomToolbar() {\n return (\n <GridToolbarContainer>\n <GridToolbarExport\n csvOptions={{ fileName: 'InfraWallet-export' }}\n printOptions={{ disableToolbarButton: true }}\n />\n </GridToolbarContainer>\n );\n}\n\nexport const CostReportsTableComponent: FC<CostReportsTableComponentProps> = ({ reports, aggregatedBy, periods }) => {\n let rows: any[] | undefined = reports;\n const columns: GridColDef[] = [];\n const columnTotals: { [key: string]: number } = {};\n const columnGroupingModel: GridColumnGroupingModel = [];\n\n if (reports && aggregatedBy !== 'none') {\n for (const period of periods) {\n for (const report of reports) {\n if (columnTotals[period] === undefined) {\n columnTotals[period] = 0;\n }\n\n columnTotals[period] += report.reports[period] || 0;\n }\n }\n\n rows = [...reports, { id: 'Total', reports: columnTotals }];\n }\n\n if (['account', 'provider', 'service'].includes(aggregatedBy)) {\n // provider icon column\n columns.push({\n field: 'icon',\n headerName: '',\n width: 30,\n display: 'flex',\n disableExport: true,\n sortable: false,\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n if (params.id === 'Total') {\n return undefined;\n }\n\n return <div>{getProviderIcon(params.row.provider)}</div>;\n },\n });\n }\n\n columns.push(\n // groupBy column\n {\n field: aggregatedBy,\n headerName: aggregatedBy === 'none' ? '' : aggregatedBy.charAt(0).toUpperCase() + aggregatedBy.slice(1),\n minWidth: 200,\n display: 'flex',\n flex: 2,\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n let formattedValue = params.formattedValue;\n\n if (aggregatedBy === 'service' || aggregatedBy === 'account') {\n // remove provider names\n if (params.formattedValue !== undefined && params.formattedValue.indexOf('/') !== -1) {\n formattedValue = params.formattedValue.split('/')[1];\n }\n\n if (aggregatedBy === 'account' && formattedValue) {\n const account = extractAccountInfo(formattedValue);\n const accountName = account.accountName;\n const accountId = account.accountId;\n\n return (\n <div>\n <Typography variant=\"body2\">{accountName}</Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {accountId}\n </Typography>\n </div>\n );\n }\n }\n\n if (params.id === 'Total') {\n return <div style={{ fontWeight: 'bold' }}>Total</div>;\n }\n\n return <div>{formattedValue}</div>;\n },\n sortComparator: (value1, value2, params1, params2) => {\n if (params1.id === 'Total' || params2.id === 'Total') {\n return 0;\n }\n return value1 - value2;\n },\n },\n // trend line column\n {\n field: 'trend',\n headerName: 'Trend',\n width: 100,\n display: 'flex',\n disableExport: true,\n sortable: false,\n renderCell: (params: GridRenderCellParams) => (\n <SparkLineChart data={params.value ? params.value[0] : null} plotType=\"bar\" />\n ),\n valueGetter: (_, row) => [\n periods.map(period => (row.reports[period] !== undefined ? row.reports[period] : null)),\n ],\n },\n );\n\n periods.forEach(period => {\n columns.push(\n // cost column\n {\n field: `cost-${period}`,\n headerName: 'Cost',\n type: 'number',\n valueGetter: (_, row) => {\n return row.reports[period] ? row.reports[period] : null;\n },\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n const value = params.value;\n let cost = '-';\n\n if (typeof value === 'number') {\n cost = formatCurrency(value);\n }\n\n return (\n <div>\n <span style={{ fontWeight: params.id === 'Total' ? 'bold' : 'normal' }}>{cost}</span>\n </div>\n );\n },\n sortComparator: (value1, value2, params1, params2) => {\n if (params1.id === 'Total' || params2.id === 'Total') {\n return 0;\n }\n return value1 - value2;\n },\n },\n // change% column\n {\n field: `change-${period}`,\n headerName: 'Change',\n disableExport: true,\n sortable: false,\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n let percentage = undefined;\n const previousPeriod = period.length === 7 ? getPreviousMonth(period) : getPreviousDay(period);\n if (\n params.row.reports[period] &&\n periods.includes(previousPeriod) &&\n params.row.reports[previousPeriod] > 0\n ) {\n const diff = params.row.reports[period] - params.row.reports[previousPeriod];\n percentage = Math.round((diff / params.row.reports[previousPeriod]) * 100);\n }\n\n if (percentage === undefined) {\n return (\n <div>\n <span>-</span>\n </div>\n );\n }\n\n let color = '#0052cc';\n let backgroundColor = '#e9f2ff';\n let mark = '';\n if (percentage < 0) {\n color = '#216e4e';\n backgroundColor = '#dcfff1';\n mark = '▼';\n } else if (percentage > 0) {\n color = '#ae2e24';\n backgroundColor = '#ffeceb';\n mark = '▲';\n }\n\n return (\n <div>\n <span\n style={{\n fontSize: '0.82em',\n paddingInline: '2px',\n borderRadius: '4px',\n color: color,\n backgroundColor: backgroundColor,\n }}\n >\n {mark}\n {Math.abs(percentage).toLocaleString()}%\n </span>\n </div>\n );\n },\n },\n );\n\n columnGroupingModel.push({\n groupId: period,\n children: [{ field: `cost-${period}` }, { field: `change-${period}` }],\n });\n });\n\n columns.push(\n // total column\n {\n field: 'total',\n headerName: 'Total',\n type: 'number',\n minWidth: 150,\n valueGetter: (_, row) => {\n let total = 0;\n periods.forEach(period => {\n total += row.reports[period] ? row.reports[period] : 0;\n });\n return total;\n },\n renderCell: (params: GridRenderCellParams): React.ReactNode => {\n let formattedValue = '-';\n if (typeof params.value === 'number') {\n formattedValue = formatCurrency(params.value);\n }\n return <div style={{ fontWeight: 'bold' }}>{formattedValue}</div>;\n },\n sortComparator: (value1, value2, params1, params2) => {\n if (params1.id === 'Total' || params2.id === 'Total') {\n return 0;\n }\n return value1 - value2;\n },\n },\n );\n\n return (\n <Box sx={{ height: 700 }}>\n <DataGrid\n loading={rows === undefined}\n rows={rows}\n columns={columns}\n columnGroupingModel={columnGroupingModel}\n initialState={{\n sorting: {\n sortModel: [{ field: 'total', sort: 'desc' }],\n },\n pagination: {\n paginationModel: {\n pageSize: 15,\n },\n },\n }}\n pageSizeOptions={[15]}\n slots={{ toolbar: CustomToolbar }}\n slotProps={{\n loadingOverlay: {\n variant: 'skeleton',\n noRowsVariant: 'skeleton',\n },\n }}\n disableRowSelectionOnClick\n disableColumnMenu\n density={aggregatedBy === 'account' ? 'standard' : 'compact'}\n />\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,254 @@
1
+ import { Progress, InfoCard } from '@backstage/core-components';
2
+ import { useApi } from '@backstage/core-plugin-api';
3
+ import { useEntity } from '@backstage/plugin-catalog-react';
4
+ import { Tabs, Tab, Box, Typography, Table, TableHead, TableRow, TableCell, TableBody } from '@material-ui/core';
5
+ import { makeStyles } from '@material-ui/core/styles';
6
+ import Alert from '@mui/material/Alert';
7
+ import React, { useState, useEffect } from 'react';
8
+ import { ResponsiveContainer, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Line } from 'recharts';
9
+ import { infraWalletApiRef } from '../../api/InfraWalletApi.esm.js';
10
+
11
+ const useStyles = makeStyles({
12
+ increase: {
13
+ color: "#ae2e24",
14
+ backgroundColor: "#ffeceb",
15
+ borderRadius: "4px",
16
+ padding: "2px 4px",
17
+ fontSize: "0.82em",
18
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif'
19
+ },
20
+ decrease: {
21
+ color: "#216e4e",
22
+ backgroundColor: "#dcfff1",
23
+ borderRadius: "4px",
24
+ padding: "2px 4px",
25
+ fontSize: "0.82em",
26
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif'
27
+ },
28
+ noChange: {
29
+ color: "#0052cc",
30
+ backgroundColor: "#e9f2ff",
31
+ borderRadius: "4px",
32
+ padding: "2px 4px",
33
+ fontSize: "0.82em",
34
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif'
35
+ },
36
+ regular: {
37
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif'
38
+ },
39
+ bold: {
40
+ fontWeight: "bold",
41
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif'
42
+ }
43
+ });
44
+ const COLORS = [
45
+ "#8884d8",
46
+ "#82ca9d",
47
+ "#ffc658",
48
+ "#ff7300",
49
+ "#413ea0",
50
+ "#ff0000",
51
+ "#00ff00",
52
+ "#0000ff",
53
+ "#ff00ff",
54
+ "#00ffff"
55
+ ];
56
+ const EntityInfraWalletCard = () => {
57
+ var _a, _b;
58
+ const classes = useStyles();
59
+ const { entity } = useEntity();
60
+ const infrawalletApi = useApi(infraWalletApiRef);
61
+ const [costData, setCostData] = useState(null);
62
+ const [loading, setLoading] = useState(true);
63
+ const [error, setError] = useState(null);
64
+ const [tabIndex, setTabIndex] = useState(0);
65
+ useEffect(() => {
66
+ const fetchCostData = async () => {
67
+ setLoading(true);
68
+ setError(null);
69
+ try {
70
+ const annotations = entity.metadata.annotations || {};
71
+ const annotationKeys = [
72
+ "infrawallet.io/project",
73
+ "infrawallet.io/account",
74
+ "infrawallet.io/service",
75
+ "infrawallet.io/category",
76
+ "infrawallet.io/provider"
77
+ ];
78
+ const filtersObj = {};
79
+ annotationKeys.forEach((key) => {
80
+ if (annotations[key]) {
81
+ const shortKey = key.replace("infrawallet.io/", "");
82
+ const values = annotations[key].split(",").map((v) => v.trim()).filter((v) => v.length > 0);
83
+ filtersObj[shortKey] = values;
84
+ }
85
+ });
86
+ if (annotations["infrawallet.io/extra-filters"]) {
87
+ const extraFilters = annotations["infrawallet.io/extra-filters"];
88
+ extraFilters.split(",").forEach((pair) => {
89
+ const [key, value] = pair.split(":").map((s) => s.trim());
90
+ if (key && value) {
91
+ const values = value.split("|").map((v) => v.trim()).filter((v) => v.length > 0);
92
+ filtersObj[key] = values;
93
+ }
94
+ });
95
+ }
96
+ const filtersArray = Object.entries(filtersObj).map(([key, values]) => {
97
+ if (values.length === 1) {
98
+ return `${key}:${values[0]}`;
99
+ }
100
+ const valuesString = values.join("|");
101
+ return `${key}:(${valuesString})`;
102
+ });
103
+ const filters = `(${filtersArray.join(",")})`;
104
+ const tags = [];
105
+ const groups = "";
106
+ const granularity = "monthly";
107
+ const endTime = /* @__PURE__ */ new Date();
108
+ const startTime = /* @__PURE__ */ new Date();
109
+ startTime.setMonth(endTime.getMonth() - 2);
110
+ const costReportsResponse = await infrawalletApi.getCostReports(
111
+ filters,
112
+ tags,
113
+ groups,
114
+ granularity,
115
+ startTime,
116
+ endTime
117
+ );
118
+ if (costReportsResponse.status !== 200) {
119
+ throw new Error("Failed to fetch cost reports");
120
+ }
121
+ setCostData(costReportsResponse.data || null);
122
+ } catch (e) {
123
+ setError(e instanceof Error ? e.message : "Unknown error occurred");
124
+ } finally {
125
+ setLoading(false);
126
+ }
127
+ };
128
+ fetchCostData();
129
+ }, [entity, infrawalletApi]);
130
+ if (loading) {
131
+ return /* @__PURE__ */ React.createElement(Progress, null);
132
+ }
133
+ if (error) {
134
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error);
135
+ }
136
+ if (!costData || costData.length === 0) {
137
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, "No cost data available for this entity.");
138
+ }
139
+ const periods = /* @__PURE__ */ new Set();
140
+ costData.forEach((report) => {
141
+ Object.keys(report.reports).forEach((period) => periods.add(period));
142
+ });
143
+ const sortedPeriods = Array.from(periods).sort();
144
+ const totalCostsByPeriod = sortedPeriods.map((period) => {
145
+ const total = costData.reduce((sum, report) => {
146
+ return sum + (report.reports[period] || 0);
147
+ }, 0);
148
+ return { period, total };
149
+ });
150
+ const currentPeriod = sortedPeriods[sortedPeriods.length - 1];
151
+ const previousPeriod = sortedPeriods.length > 1 ? sortedPeriods[sortedPeriods.length - 2] : null;
152
+ const totalCost = ((_a = totalCostsByPeriod.find((item) => item.period === currentPeriod)) == null ? void 0 : _a.total) || 0;
153
+ const previousTotalCost = previousPeriod ? ((_b = totalCostsByPeriod.find((item) => item.period === previousPeriod)) == null ? void 0 : _b.total) || 0 : 0;
154
+ const percentageChange = previousTotalCost !== 0 ? (totalCost - previousTotalCost) / previousTotalCost * 100 : 0;
155
+ const percentageChangeFormatted = Math.abs(percentageChange).toFixed(2);
156
+ let mark = "";
157
+ if (percentageChange < 0) {
158
+ mark = "\u25BC";
159
+ } else if (percentageChange > 0) {
160
+ mark = "\u25B2";
161
+ }
162
+ const projects = Array.from(
163
+ new Set(
164
+ costData.map((report) => {
165
+ const project = report.project;
166
+ return typeof project === "string" ? project : void 0;
167
+ }).filter((project) => !!project)
168
+ )
169
+ );
170
+ if (projects.length === 0) {
171
+ return /* @__PURE__ */ React.createElement(Alert, { severity: "warning" }, "No project data available to display in the chart.");
172
+ }
173
+ const chartData = sortedPeriods.map((period) => {
174
+ const dataPoint = { period };
175
+ projects.forEach((project) => {
176
+ const projectReports = costData.filter((report) => report.project === project);
177
+ const total = projectReports.reduce((sum, report) => {
178
+ return sum + (report.reports[period] || 0);
179
+ }, 0);
180
+ dataPoint[project] = total;
181
+ });
182
+ return dataPoint;
183
+ });
184
+ const perServiceCosts = costData.reduce((acc, report) => {
185
+ const service = report.service;
186
+ const cost = report.reports[currentPeriod] || 0;
187
+ if (typeof service === "string") {
188
+ if (!acc[service]) {
189
+ acc[service] = 0;
190
+ }
191
+ acc[service] += cost;
192
+ }
193
+ return acc;
194
+ }, {});
195
+ const prevPerServiceCosts = previousPeriod ? costData.reduce((acc, report) => {
196
+ const service = report.service;
197
+ const cost = report.reports[previousPeriod] || 0;
198
+ if (typeof service === "string") {
199
+ if (!acc[service]) {
200
+ acc[service] = 0;
201
+ }
202
+ acc[service] += cost;
203
+ }
204
+ return acc;
205
+ }, {}) : {};
206
+ const serviceRows = Object.entries(perServiceCosts).map(([service, cost]) => {
207
+ const prevCost = prevPerServiceCosts[service] || 0;
208
+ const change = prevCost !== 0 ? (cost - prevCost) / prevCost * 100 : 0;
209
+ return {
210
+ service,
211
+ cost,
212
+ change
213
+ };
214
+ });
215
+ const getChangeClass = (change) => {
216
+ if (change > 0) {
217
+ return classes.increase;
218
+ }
219
+ if (change === 0) {
220
+ return classes.noChange;
221
+ }
222
+ return classes.decrease;
223
+ };
224
+ const getChangeMark = (change) => {
225
+ if (change < 0) {
226
+ return "\u25BC";
227
+ }
228
+ if (change > 0) {
229
+ return "\u25B2";
230
+ }
231
+ return "";
232
+ };
233
+ const handleTabChange = (_event, newValue) => {
234
+ setTabIndex(newValue);
235
+ };
236
+ return /* @__PURE__ */ React.createElement(InfoCard, { title: "InfraWallet" }, /* @__PURE__ */ React.createElement(Tabs, { value: tabIndex, onChange: handleTabChange, "aria-label": "Cost Tabs" }, /* @__PURE__ */ React.createElement(Tab, { label: "Total Cost" }), /* @__PURE__ */ React.createElement(Tab, { label: "Service Breakdown" })), tabIndex === 0 && /* @__PURE__ */ React.createElement(Box, { p: 2 }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center" }, previousTotalCost > 0 && /* @__PURE__ */ React.createElement(Box, { className: getChangeClass(percentageChange), mr: 1 }, mark, " ", percentageChangeFormatted, "%"), /* @__PURE__ */ React.createElement(Typography, { variant: "h6", className: classes.bold }, "Current Month: $", totalCost.toFixed(2))), /* @__PURE__ */ React.createElement(ResponsiveContainer, { width: "100%", height: 300 }, /* @__PURE__ */ React.createElement(LineChart, { data: chartData }, /* @__PURE__ */ React.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React.createElement(XAxis, { dataKey: "period" }), /* @__PURE__ */ React.createElement(YAxis, null), /* @__PURE__ */ React.createElement(Tooltip, null), /* @__PURE__ */ React.createElement(Legend, null), projects.map((project, index) => /* @__PURE__ */ React.createElement(
237
+ Line,
238
+ {
239
+ key: project,
240
+ type: "monotone",
241
+ dataKey: project,
242
+ stroke: COLORS[index % COLORS.length],
243
+ activeDot: { r: 8 }
244
+ }
245
+ ))))), tabIndex === 1 && /* @__PURE__ */ React.createElement(Box, { p: 2 }, /* @__PURE__ */ React.createElement(Table, { size: "small" }, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, { className: classes.bold }, "Service"), /* @__PURE__ */ React.createElement(TableCell, { align: "right", className: classes.bold }, "Monthly Cost"), /* @__PURE__ */ React.createElement(TableCell, { align: "right", className: classes.bold }, "Monthly Change"))), /* @__PURE__ */ React.createElement(TableBody, null, serviceRows.map((row) => {
246
+ const serviceChangeClass = getChangeClass(row.change);
247
+ const serviceMark = getChangeMark(row.change);
248
+ const changeFormatted = Math.abs(row.change).toFixed(2);
249
+ return /* @__PURE__ */ React.createElement(TableRow, { key: row.service }, /* @__PURE__ */ React.createElement(TableCell, { component: "th", scope: "row" }, row.service), /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, "$", row.cost.toFixed(2)), /* @__PURE__ */ React.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React.createElement(Box, { className: serviceChangeClass, display: "inline" }, serviceMark, " ", changeFormatted, "%")));
250
+ })))));
251
+ };
252
+
253
+ export { EntityInfraWalletCard };
254
+ //# sourceMappingURL=EntityInfraWalletCard.esm.js.map