@evenicanpm/admin-integrate 1.2.0 → 1.2.2
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/documents/Process/fragments.ts +13 -0
- package/documents/Process/list.ts +27 -0
- package/package.json +2 -2
- package/src/api/Integrations/queries/get-process.query.ts +48 -0
- package/src/api/Integrations/queries/get-process.server.ts +9 -0
- package/src/api/Integrations/queries/index.ts +1 -0
- package/src/components/integration-list/index.ts +2 -0
- package/src/components/integration-list/integration-filter.ts +21 -0
- package/src/components/integration-list/integration-status-icon.tsx +33 -0
- package/src/components/integration-list/integration-tag.tsx +77 -0
- package/src/components/list-filter/list-filter.tsx +1 -1
- package/src/pages/integrations/integration-list/index.ts +1 -0
- package/src/pages/integrations/integration-list/integration-list-row.tsx +147 -0
- package/src/pages/integrations/integration-list/integration-list-table.tsx +78 -0
- package/src/pages/integrations/integration-list/integration-list.tsx +262 -0
- package/src/pages/integrations/integrations.tsx +64 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { gql } from "graphql-request";
|
|
2
|
+
|
|
3
|
+
import { PROCESS_FRAGMENT_SM } from "./fragments";
|
|
4
|
+
|
|
5
|
+
const GET_PROCESS = gql`
|
|
6
|
+
${PROCESS_FRAGMENT_SM}
|
|
7
|
+
query GetProcesses($input: ProcessInput) {
|
|
8
|
+
processes(input: $input) {
|
|
9
|
+
nodes {
|
|
10
|
+
...e4ProcessFragmentSM
|
|
11
|
+
}
|
|
12
|
+
pageInfo {
|
|
13
|
+
resultsReturned
|
|
14
|
+
currentPage
|
|
15
|
+
hasNextPage
|
|
16
|
+
hasPreviousPage
|
|
17
|
+
skip
|
|
18
|
+
top
|
|
19
|
+
sort
|
|
20
|
+
sortDesc
|
|
21
|
+
search
|
|
22
|
+
integrationTags
|
|
23
|
+
}
|
|
24
|
+
recordCount
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/admin-integrate",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "e4Integrate Admin Panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"@xyflow/react": "^12.8.4",
|
|
20
20
|
"elkjs": "^0.10.0"
|
|
21
21
|
},
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "608903e897229fead2b663cd82c019451ed65a45"
|
|
23
23
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useQuery, QueryClient, UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
import { queryFn } from "./get-process.server";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
GetProcessesQuery,
|
|
6
|
+
GetProcessesQueryVariables,
|
|
7
|
+
} from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
8
|
+
|
|
9
|
+
export const buildProcessExecutionsQueryKey = {
|
|
10
|
+
all: () => ["getProcesses"] as const,
|
|
11
|
+
byInput: (variables?: GetProcessesQueryVariables) => [
|
|
12
|
+
"getProcesses",
|
|
13
|
+
variables,
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getProcesses = {
|
|
18
|
+
queryKey: buildProcessExecutionsQueryKey.all(),
|
|
19
|
+
|
|
20
|
+
prefetch: (
|
|
21
|
+
queryClient: QueryClient,
|
|
22
|
+
variables?: GetProcessesQueryVariables,
|
|
23
|
+
) =>
|
|
24
|
+
queryClient.prefetchQuery({
|
|
25
|
+
queryKey: buildProcessExecutionsQueryKey.byInput(variables),
|
|
26
|
+
queryFn: () => queryFn(variables),
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
fetchData: async (
|
|
30
|
+
queryClient: QueryClient,
|
|
31
|
+
variables?: GetProcessesQueryVariables,
|
|
32
|
+
) => {
|
|
33
|
+
return await queryClient.fetchQuery({
|
|
34
|
+
queryKey: buildProcessExecutionsQueryKey.byInput(variables),
|
|
35
|
+
queryFn: () => queryFn(variables),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
useData: (
|
|
40
|
+
variables?: GetProcessesQueryVariables,
|
|
41
|
+
options?: UseQueryOptions,
|
|
42
|
+
) =>
|
|
43
|
+
useQuery<GetProcessesQuery>({
|
|
44
|
+
queryKey: buildProcessExecutionsQueryKey.byInput(variables),
|
|
45
|
+
queryFn: () => queryFn(variables),
|
|
46
|
+
...options,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import createGraphqlclient from "@evenicanpm/admin-core/api/sdk/graphqlClient.client";
|
|
3
|
+
import { GetProcessesQueryVariables } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
4
|
+
|
|
5
|
+
export const queryFn = async (variables?: GetProcessesQueryVariables) => {
|
|
6
|
+
const sdk = await createGraphqlclient();
|
|
7
|
+
const { data } = await sdk.GetProcesses(variables);
|
|
8
|
+
return data;
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getProcesses } from "./get-process.query";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Filters,
|
|
3
|
+
MultiSelectFilter,
|
|
4
|
+
} from "@evenicanpm/admin-integrate/components/list-filter/list-filter-types";
|
|
5
|
+
|
|
6
|
+
export interface IntegrationListFilters extends Filters {
|
|
7
|
+
multiSelectFilter: MultiSelectFilter;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const integrationListFilters: IntegrationListFilters = {
|
|
11
|
+
multiSelectFilter: {
|
|
12
|
+
type: "multiSelect",
|
|
13
|
+
label: "IntegrationList.tags",
|
|
14
|
+
value: null,
|
|
15
|
+
options: [
|
|
16
|
+
{ id: "Schedule Pending", value: "IntegrationList.scheduledPending" },
|
|
17
|
+
{ id: "Scheduled", value: "IntegrationList.scheduled" },
|
|
18
|
+
{ id: "Triggered", value: "IntegrationList.triggered" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { SxProps } from "@mui/material";
|
|
4
|
+
import StorageIcon from "@mui/icons-material/Storage";
|
|
5
|
+
import ElectricBoltIcon from "@mui/icons-material/ElectricBolt";
|
|
6
|
+
import CodeIcon from "@mui/icons-material/Code";
|
|
7
|
+
import TableChartIcon from "@mui/icons-material/TableChart";
|
|
8
|
+
import EmailIcon from "@mui/icons-material/Email";
|
|
9
|
+
import IosShareIcon from "@mui/icons-material/IosShare";
|
|
10
|
+
import QuestionMarkIcon from "@mui/icons-material/QuestionMark";
|
|
11
|
+
import { purple } from "@mui/material/colors";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
processIcon: string;
|
|
15
|
+
sx?: SxProps;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const IntegrationStatusIcon: React.FC<Props> = (props: Props) => {
|
|
19
|
+
const color = purple[500];
|
|
20
|
+
const statusIconMap = {
|
|
21
|
+
DB: <StorageIcon color="success" />,
|
|
22
|
+
Webhook: <ElectricBoltIcon color="error" />,
|
|
23
|
+
Restful: <CodeIcon color="info" />,
|
|
24
|
+
Table: <TableChartIcon sx={{ color: color }} />,
|
|
25
|
+
Email: <EmailIcon color="warning" />,
|
|
26
|
+
Export: <IosShareIcon color="primary" />,
|
|
27
|
+
noIcon: <QuestionMarkIcon color="info" />,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return statusIconMap[props.processIcon] || statusIconMap["noIcon"];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default IntegrationStatusIcon;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { styled } from "@mui/material/styles";
|
|
3
|
+
import { Box, Typography, SxProps } from "@mui/material";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
|
|
6
|
+
const StyledTagBox = styled(Box)(({ theme }) => ({
|
|
7
|
+
padding: theme.spacing(0, 1.5),
|
|
8
|
+
border: "solid 1px",
|
|
9
|
+
borderRadius: 4,
|
|
10
|
+
alignContent: "center",
|
|
11
|
+
maxWidth: "fit-content",
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const StyledTag = styled(Typography)(({ theme }) => ({
|
|
15
|
+
fontWeight: "bold",
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
process: {
|
|
20
|
+
isScheduled: number;
|
|
21
|
+
isTriggered: number;
|
|
22
|
+
};
|
|
23
|
+
sx?: SxProps;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const IntegrationTag: React.FC<Props> = ({ process, sx }) => {
|
|
27
|
+
const t = useTranslations("Integrate.IntegrationList");
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Box display="flex" gap={1} justifyContent="center">
|
|
31
|
+
{process.isTriggered === 1 && (
|
|
32
|
+
<StyledTagBox
|
|
33
|
+
sx={{
|
|
34
|
+
backgroundColor: "info.100",
|
|
35
|
+
borderColor: "transparent",
|
|
36
|
+
...sx,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<StyledTag color="info" variant="caption">
|
|
40
|
+
{t("triggered")}
|
|
41
|
+
</StyledTag>
|
|
42
|
+
</StyledTagBox>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
{process.isScheduled === 1 && (
|
|
46
|
+
<StyledTagBox
|
|
47
|
+
sx={{
|
|
48
|
+
backgroundColor: "success.100",
|
|
49
|
+
borderColor: "transparent",
|
|
50
|
+
...sx,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<StyledTag color="success" variant="caption">
|
|
54
|
+
{t("scheduled")}
|
|
55
|
+
</StyledTag>
|
|
56
|
+
</StyledTagBox>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{process.isScheduled === 0 && process.isTriggered === 0 && (
|
|
60
|
+
<StyledTagBox
|
|
61
|
+
sx={{
|
|
62
|
+
backgroundColor: "warning.100",
|
|
63
|
+
borderColor: "transparent",
|
|
64
|
+
color: "orange.700",
|
|
65
|
+
...sx,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<StyledTag color="inherit" variant="caption">
|
|
69
|
+
{t("scheduledPending")}
|
|
70
|
+
</StyledTag>
|
|
71
|
+
</StyledTagBox>
|
|
72
|
+
)}
|
|
73
|
+
</Box>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default IntegrationTag;
|
|
@@ -306,7 +306,7 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
|
|
|
306
306
|
|
|
307
307
|
<Button
|
|
308
308
|
variant="contained"
|
|
309
|
-
color="
|
|
309
|
+
color="info"
|
|
310
310
|
startIcon={<ArrowForwardIos />}
|
|
311
311
|
disabled={Object.values(filterErrors).some((v) => v)}
|
|
312
312
|
onClick={handleFilterSet}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./integration-list";
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { redirect, RedirectType } from "next/navigation";
|
|
3
|
+
|
|
4
|
+
// MUI
|
|
5
|
+
import { Theme, useTheme } from "@mui/material";
|
|
6
|
+
import { Box, Stack, Typography, Popover } from "@mui/material";
|
|
7
|
+
import { RemoveRedEye } from "@mui/icons-material";
|
|
8
|
+
import { SxProps } from "@mui/material";
|
|
9
|
+
|
|
10
|
+
// Custom components
|
|
11
|
+
import {
|
|
12
|
+
StyledTableCell,
|
|
13
|
+
StyledTableRow,
|
|
14
|
+
StyledIconButton,
|
|
15
|
+
} from "@evenicanpm/admin-integrate/components/data-table/table-row";
|
|
16
|
+
import {
|
|
17
|
+
IntegrationTag,
|
|
18
|
+
IntegrationStatusIcon,
|
|
19
|
+
} from "@evenicanpm/admin-integrate/components/integration-list";
|
|
20
|
+
|
|
21
|
+
// Hooks
|
|
22
|
+
import useTimestamp from "@evenicanpm/admin-integrate/hooks/use-timestamp";
|
|
23
|
+
|
|
24
|
+
// Types
|
|
25
|
+
import { executionItem } from "./integration-list";
|
|
26
|
+
import { Head } from "@evenicanpm/admin-integrate/components/data-table/table-header";
|
|
27
|
+
import { TableCellProps } from "@mui/material";
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
item: executionItem;
|
|
31
|
+
heading: Head[]; // for alignment
|
|
32
|
+
sx?: SxProps;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const IntegrationListRow: React.FC<Props> = (props: Props) => {
|
|
36
|
+
const theme = useTheme();
|
|
37
|
+
const styles = getStyles(theme);
|
|
38
|
+
|
|
39
|
+
// e.g. "xx days ago"
|
|
40
|
+
const time = useTimestamp(props.item.modified);
|
|
41
|
+
const localTime = new Date(props.item.modified).toString();
|
|
42
|
+
|
|
43
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
44
|
+
const open = Boolean(anchorEl);
|
|
45
|
+
|
|
46
|
+
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
47
|
+
setAnchorEl(event.currentTarget);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handlePopoverClose = () => {
|
|
51
|
+
setAnchorEl(null);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleRedirect = () => {
|
|
55
|
+
redirect(
|
|
56
|
+
`/e4integrate/integration/${props.item.id}?IntegrationId=${props.item.id}`,
|
|
57
|
+
RedirectType.push,
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<StyledTableRow sx={props.sx}>
|
|
63
|
+
<StyledTableCell
|
|
64
|
+
align={props.heading[0].align as TableCellProps["align"]}
|
|
65
|
+
width={props.heading[0].width}
|
|
66
|
+
>
|
|
67
|
+
<Stack spacing={1} direction="row">
|
|
68
|
+
<Box display="flex" justifyContent="flex-end" alignItems="center">
|
|
69
|
+
<IntegrationStatusIcon processIcon={props.item.processIcon} />
|
|
70
|
+
</Box>
|
|
71
|
+
<Box display="flex" justifyContent="flex-end" alignItems="center">
|
|
72
|
+
<Typography>{props.item.name}</Typography>
|
|
73
|
+
</Box>
|
|
74
|
+
</Stack>
|
|
75
|
+
</StyledTableCell>
|
|
76
|
+
<StyledTableCell
|
|
77
|
+
align={props.heading[1].align as TableCellProps["align"]}
|
|
78
|
+
width={props.heading[1].width}
|
|
79
|
+
>
|
|
80
|
+
<Typography>{props.item.id}</Typography>
|
|
81
|
+
</StyledTableCell>
|
|
82
|
+
<StyledTableCell
|
|
83
|
+
align={props.heading[1].align as TableCellProps["align"]}
|
|
84
|
+
width={props.heading[1].width}
|
|
85
|
+
>
|
|
86
|
+
<IntegrationTag
|
|
87
|
+
process={{
|
|
88
|
+
isScheduled: props.item.isScheduled,
|
|
89
|
+
isTriggered: props.item.isTriggered,
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
</StyledTableCell>
|
|
93
|
+
<StyledTableCell
|
|
94
|
+
align={props.heading[2].align as TableCellProps["align"]}
|
|
95
|
+
width={props.heading[2].width}
|
|
96
|
+
sx={styles.timeCell}
|
|
97
|
+
>
|
|
98
|
+
<Typography
|
|
99
|
+
onMouseEnter={handlePopoverOpen}
|
|
100
|
+
onMouseLeave={handlePopoverClose}
|
|
101
|
+
>
|
|
102
|
+
{time}
|
|
103
|
+
</Typography>
|
|
104
|
+
<Popover
|
|
105
|
+
sx={{ pointerEvents: "none" }}
|
|
106
|
+
open={open}
|
|
107
|
+
anchorEl={anchorEl}
|
|
108
|
+
anchorOrigin={{
|
|
109
|
+
vertical: "bottom",
|
|
110
|
+
horizontal: "center",
|
|
111
|
+
}}
|
|
112
|
+
transformOrigin={{
|
|
113
|
+
vertical: "top",
|
|
114
|
+
horizontal: "center",
|
|
115
|
+
}}
|
|
116
|
+
onClose={handlePopoverClose}
|
|
117
|
+
disableRestoreFocus
|
|
118
|
+
>
|
|
119
|
+
<Box sx={styles.popover}>
|
|
120
|
+
<Typography>{localTime}</Typography>
|
|
121
|
+
</Box>
|
|
122
|
+
</Popover>
|
|
123
|
+
</StyledTableCell>
|
|
124
|
+
<StyledTableCell
|
|
125
|
+
align={props.heading[3].align as TableCellProps["align"]}
|
|
126
|
+
width={props.heading[3].width}
|
|
127
|
+
>
|
|
128
|
+
<StyledIconButton onClick={handleRedirect}>
|
|
129
|
+
<RemoveRedEye />
|
|
130
|
+
</StyledIconButton>
|
|
131
|
+
</StyledTableCell>
|
|
132
|
+
</StyledTableRow>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getStyles = (theme: Theme) => {
|
|
137
|
+
return {
|
|
138
|
+
timeCell: {
|
|
139
|
+
cursor: "default",
|
|
140
|
+
},
|
|
141
|
+
popover: {
|
|
142
|
+
padding: theme.spacing(1),
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default IntegrationListRow;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
import { Head } from "@evenicanpm/admin-integrate/components/data-table/table-header";
|
|
5
|
+
import { Item } from "./integration-list";
|
|
6
|
+
|
|
7
|
+
// API
|
|
8
|
+
import { ProcessExecutionInput } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
9
|
+
|
|
10
|
+
// MUI
|
|
11
|
+
import { Theme, useTheme } from "@mui/material";
|
|
12
|
+
import { TableContainer, Table, TableBody } from "@mui/material";
|
|
13
|
+
import { SxProps } from "@mui/material";
|
|
14
|
+
|
|
15
|
+
// Custom components
|
|
16
|
+
import { TableHeader } from "@evenicanpm/admin-integrate/components/data-table";
|
|
17
|
+
|
|
18
|
+
// Local components
|
|
19
|
+
import IntegrationListRow from "./integration-list-row";
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
heading: Head[];
|
|
23
|
+
items: Item[];
|
|
24
|
+
input: ProcessExecutionInput;
|
|
25
|
+
setInput: (newInput: ProcessExecutionInput) => void;
|
|
26
|
+
rowSx?: SxProps;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function IntegrationListTable(props: Props) {
|
|
30
|
+
const theme = useTheme();
|
|
31
|
+
const styles = getStyles(theme);
|
|
32
|
+
|
|
33
|
+
const handleRequestSort = (property: string) => {
|
|
34
|
+
const current = props.input.sort;
|
|
35
|
+
if (property === current) {
|
|
36
|
+
props.setInput({
|
|
37
|
+
...props.input,
|
|
38
|
+
sortDesc: !props.input.sortDesc,
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
props.setInput({
|
|
42
|
+
...props.input,
|
|
43
|
+
sort: property,
|
|
44
|
+
sortDesc: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<TableContainer>
|
|
52
|
+
<Table>
|
|
53
|
+
<TableHeader
|
|
54
|
+
heading={props.heading}
|
|
55
|
+
orderBy={props.input.sort}
|
|
56
|
+
order={props.input.sortDesc ? "desc" : "asc"}
|
|
57
|
+
onRequestSort={handleRequestSort}
|
|
58
|
+
rowSx={props.rowSx}
|
|
59
|
+
/>
|
|
60
|
+
<TableBody>
|
|
61
|
+
{props.items.map((item, index) => (
|
|
62
|
+
<IntegrationListRow
|
|
63
|
+
key={index}
|
|
64
|
+
item={item}
|
|
65
|
+
heading={props.heading}
|
|
66
|
+
sx={props.rowSx}
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</TableBody>
|
|
70
|
+
</Table>
|
|
71
|
+
</TableContainer>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getStyles = (theme: Theme) => {
|
|
77
|
+
return {};
|
|
78
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
3
|
+
|
|
4
|
+
// API
|
|
5
|
+
import {
|
|
6
|
+
GetProcessesQuery,
|
|
7
|
+
ProcessInput,
|
|
8
|
+
} from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
9
|
+
import { ProcessExecutionInput } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
10
|
+
|
|
11
|
+
// MUI
|
|
12
|
+
import { Theme, useTheme } from "@mui/material";
|
|
13
|
+
import {
|
|
14
|
+
Card,
|
|
15
|
+
Box,
|
|
16
|
+
Stack,
|
|
17
|
+
Typography,
|
|
18
|
+
Grid2 as Grid,
|
|
19
|
+
Skeleton,
|
|
20
|
+
} from "@mui/material";
|
|
21
|
+
|
|
22
|
+
// Custom components
|
|
23
|
+
import { FlexBox } from "@evenicanpm/admin-core/components/flex-box";
|
|
24
|
+
import {
|
|
25
|
+
TablePagination,
|
|
26
|
+
TableSkeleton,
|
|
27
|
+
} from "@evenicanpm/admin-integrate/components/data-table";
|
|
28
|
+
import { ListFilter } from "@evenicanpm/admin-integrate/components/list-filter";
|
|
29
|
+
|
|
30
|
+
// Local components
|
|
31
|
+
import IntegrationListTable from "./integration-list-table";
|
|
32
|
+
|
|
33
|
+
// Types
|
|
34
|
+
import { Head } from "@evenicanpm/admin-integrate/components/data-table/table-header";
|
|
35
|
+
import { ListFilterOutput } from "@evenicanpm/admin-integrate/components/list-filter/list-filter-types";
|
|
36
|
+
import {
|
|
37
|
+
IntegrationListFilters,
|
|
38
|
+
integrationListFilters,
|
|
39
|
+
} from "@evenicanpm/admin-integrate/components/integration-list/integration-filter";
|
|
40
|
+
import { log } from "console";
|
|
41
|
+
|
|
42
|
+
// Returned from processExecutions query
|
|
43
|
+
export interface executionItem {
|
|
44
|
+
isScheduled: 0 | 1;
|
|
45
|
+
isTriggered: 0 | 1;
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
modified: string;
|
|
49
|
+
processIcon: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Row height in terms of multiples of theme.spacing(1) (e.g. theme.spacing(7.5 & rows per page))
|
|
53
|
+
const rowHeight = 7.5;
|
|
54
|
+
|
|
55
|
+
interface Props {
|
|
56
|
+
heading: Head[];
|
|
57
|
+
data: GetProcessesQuery;
|
|
58
|
+
loading: boolean;
|
|
59
|
+
input: ProcessInput;
|
|
60
|
+
setInput: (newInput: ProcessInput) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const IntegrationList: React.FC<Props> = (props: Props) => {
|
|
64
|
+
const t = useTranslations("Integrate.IntegrationList");
|
|
65
|
+
|
|
66
|
+
const theme = useTheme();
|
|
67
|
+
const styles = getStyles(theme);
|
|
68
|
+
|
|
69
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
70
|
+
const [fetched, setFetched] = useState<boolean>(false); // prevents "empty list" msg from showing for split second
|
|
71
|
+
const [page, setPage] = useState(props.input.skip / props.input.top + 1);
|
|
72
|
+
const [perPage, setPerPage] = useState(props.input.top);
|
|
73
|
+
|
|
74
|
+
const handleChangePage = (
|
|
75
|
+
event: React.ChangeEvent<unknown>,
|
|
76
|
+
value: number,
|
|
77
|
+
) => {
|
|
78
|
+
if (page !== value) {
|
|
79
|
+
setPage(value);
|
|
80
|
+
props.setInput({
|
|
81
|
+
...props.input,
|
|
82
|
+
skip: props.input.top * (value - 1),
|
|
83
|
+
});
|
|
84
|
+
setFetched(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleSetFilters = (
|
|
89
|
+
filterUpdate: ListFilterOutput<IntegrationListFilters>,
|
|
90
|
+
) => {
|
|
91
|
+
const { itemsPerPage, search, filters } = filterUpdate;
|
|
92
|
+
console.log("filters", filters?.multiSelectFilter);
|
|
93
|
+
|
|
94
|
+
props.setInput({
|
|
95
|
+
...props.input,
|
|
96
|
+
top: itemsPerPage,
|
|
97
|
+
skip: 0,
|
|
98
|
+
search: search,
|
|
99
|
+
integrationTags:
|
|
100
|
+
filters?.multiSelectFilter?.map((f) => `${f.id}`) || null,
|
|
101
|
+
sortDesc: true,
|
|
102
|
+
});
|
|
103
|
+
setFetched(false);
|
|
104
|
+
setPerPage(itemsPerPage);
|
|
105
|
+
setPage(1);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleClearFilters = () => {
|
|
109
|
+
props.setInput({
|
|
110
|
+
...props.input,
|
|
111
|
+
skip: 0,
|
|
112
|
+
integrationTags: null,
|
|
113
|
+
});
|
|
114
|
+
setFetched(false);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (props.data) {
|
|
119
|
+
setItems(props.data.processes.nodes as Item[]);
|
|
120
|
+
setFetched(true);
|
|
121
|
+
}
|
|
122
|
+
}, [props.data]);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<Box sx={styles.list}>
|
|
127
|
+
<ListFilter<IntegrationListFilters>
|
|
128
|
+
setFilters={handleSetFilters}
|
|
129
|
+
clearFilters={handleClearFilters}
|
|
130
|
+
filters={integrationListFilters}
|
|
131
|
+
config={{
|
|
132
|
+
pagination: {
|
|
133
|
+
pageDefault: 10,
|
|
134
|
+
pageOptions: [10, 20, 50],
|
|
135
|
+
},
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
</Box>
|
|
139
|
+
|
|
140
|
+
{props.loading && !props.data && (
|
|
141
|
+
<Card>
|
|
142
|
+
<TableSkeleton
|
|
143
|
+
heading={props.heading}
|
|
144
|
+
perPage={perPage}
|
|
145
|
+
rowSx={styles.rowSx}
|
|
146
|
+
/>
|
|
147
|
+
</Card>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{!props.loading && props.data && fetched && items.length > 0 ? (
|
|
151
|
+
<Box
|
|
152
|
+
sx={{
|
|
153
|
+
minHeight:
|
|
154
|
+
props.data.processes.pageInfo.resultsReturned < perPage &&
|
|
155
|
+
page === 1
|
|
156
|
+
? theme.spacing(
|
|
157
|
+
rowHeight *
|
|
158
|
+
(props.data.processes.pageInfo.resultsReturned + 1),
|
|
159
|
+
)
|
|
160
|
+
: theme.spacing(rowHeight * (perPage + 1)),
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<Card>
|
|
164
|
+
<IntegrationListTable
|
|
165
|
+
heading={props.heading}
|
|
166
|
+
items={items}
|
|
167
|
+
input={props.input}
|
|
168
|
+
setInput={props.setInput}
|
|
169
|
+
rowSx={styles.rowSx}
|
|
170
|
+
/>
|
|
171
|
+
</Card>
|
|
172
|
+
</Box>
|
|
173
|
+
) : null}
|
|
174
|
+
|
|
175
|
+
{!props.loading && fetched && items.length === 0 ? (
|
|
176
|
+
<Card>
|
|
177
|
+
<FlexBox
|
|
178
|
+
justifyContent="center"
|
|
179
|
+
alignItems="center"
|
|
180
|
+
sx={{
|
|
181
|
+
minHeight: "335px",
|
|
182
|
+
marginBottom: theme.spacing(2),
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<Stack spacing={1} justifyContent="center" alignItems="center">
|
|
186
|
+
<Typography variant="h6" color="info">
|
|
187
|
+
{t("ListEmpty.title")}
|
|
188
|
+
</Typography>
|
|
189
|
+
<Typography variant="body1">{t("ListEmpty.body")}</Typography>
|
|
190
|
+
</Stack>
|
|
191
|
+
</FlexBox>
|
|
192
|
+
</Card>
|
|
193
|
+
) : null}
|
|
194
|
+
|
|
195
|
+
{!props.loading && props.data && fetched && items.length > 0 ? (
|
|
196
|
+
<Grid container spacing={1} sx={styles.pagination}>
|
|
197
|
+
<Grid size={{ xs: 10.5 }}>
|
|
198
|
+
<FlexBox
|
|
199
|
+
justifyContent="center"
|
|
200
|
+
alignItems="center"
|
|
201
|
+
sx={styles.paginationBox}
|
|
202
|
+
>
|
|
203
|
+
<TablePagination
|
|
204
|
+
page={page}
|
|
205
|
+
onChange={handleChangePage}
|
|
206
|
+
count={Math.ceil(
|
|
207
|
+
props.data.processes.recordCount / props.input.top,
|
|
208
|
+
)}
|
|
209
|
+
/>
|
|
210
|
+
</FlexBox>
|
|
211
|
+
</Grid>
|
|
212
|
+
<Grid size={{ xs: 1.5 }}>
|
|
213
|
+
<FlexBox
|
|
214
|
+
justifyContent="flex-end"
|
|
215
|
+
alignItems="center"
|
|
216
|
+
sx={{ height: "100%" }}
|
|
217
|
+
>
|
|
218
|
+
<Typography color="grey">
|
|
219
|
+
{`${props.data.processes.pageInfo.resultsReturned} ${t("Pagination.of")} ${props.data.processes.recordCount} ${t("Pagination.items")}`}
|
|
220
|
+
</Typography>
|
|
221
|
+
</FlexBox>
|
|
222
|
+
</Grid>
|
|
223
|
+
</Grid>
|
|
224
|
+
) : null}
|
|
225
|
+
</>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const getStyles = (theme: Theme) => {
|
|
230
|
+
return {
|
|
231
|
+
title: {
|
|
232
|
+
display: "flex",
|
|
233
|
+
alignItems: "center",
|
|
234
|
+
height: "100%",
|
|
235
|
+
},
|
|
236
|
+
typography: {
|
|
237
|
+
fontWeight: "medium",
|
|
238
|
+
},
|
|
239
|
+
button: {
|
|
240
|
+
borderWidth: 2,
|
|
241
|
+
borderRadius: 1,
|
|
242
|
+
marginRight: theme.spacing(1),
|
|
243
|
+
},
|
|
244
|
+
iconButton: {
|
|
245
|
+
borderRadius: 1,
|
|
246
|
+
},
|
|
247
|
+
pagination: {
|
|
248
|
+
marginTop: theme.spacing(2),
|
|
249
|
+
},
|
|
250
|
+
list: {
|
|
251
|
+
margin: theme.spacing(2, 0),
|
|
252
|
+
},
|
|
253
|
+
paginationBox: {
|
|
254
|
+
paddingLeft: theme.spacing(15),
|
|
255
|
+
},
|
|
256
|
+
rowSx: {
|
|
257
|
+
height: theme.spacing(rowHeight),
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export default IntegrationList;
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { useTranslations } from "next-intl";
|
|
3
3
|
|
|
4
4
|
// MUI
|
|
5
5
|
import { Box } from "@mui/material";
|
|
6
6
|
|
|
7
|
+
// API
|
|
8
|
+
import { getProcesses } from "@evenicanpm/admin-integrate/api/Integrations/queries";
|
|
9
|
+
import { ProcessInput } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
|
|
10
|
+
|
|
7
11
|
// Custom components
|
|
8
12
|
import {
|
|
9
13
|
IntegrateBreadcrumbs,
|
|
10
14
|
Crumb,
|
|
11
15
|
} from "@evenicanpm/admin-integrate/components/breadcrumbs";
|
|
16
|
+
import { Head } from "@evenicanpm/admin-integrate/components/data-table/table-header";
|
|
17
|
+
import IntegrationList from "./integration-list";
|
|
12
18
|
|
|
13
19
|
export default function Integrations() {
|
|
14
20
|
const t = useTranslations("Integrate");
|
|
21
|
+
const l = useTranslations("Integrate.IntegrationList.ListHeader");
|
|
15
22
|
|
|
16
23
|
const breadcrumbs: Crumb[] = [
|
|
17
24
|
{
|
|
@@ -23,9 +30,65 @@ export default function Integrations() {
|
|
|
23
30
|
},
|
|
24
31
|
];
|
|
25
32
|
|
|
33
|
+
const heading: Head[] = [
|
|
34
|
+
{
|
|
35
|
+
id: "name",
|
|
36
|
+
label: l("integrationName"),
|
|
37
|
+
align: "left",
|
|
38
|
+
width: "30%",
|
|
39
|
+
sortable: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "id",
|
|
43
|
+
label: l("id"),
|
|
44
|
+
align: "left",
|
|
45
|
+
width: "15%",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "isScheduled",
|
|
49
|
+
label: l("status"),
|
|
50
|
+
align: "center",
|
|
51
|
+
width: "30%",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "modified",
|
|
55
|
+
label: l("lastModified"),
|
|
56
|
+
align: "center",
|
|
57
|
+
width: "15%",
|
|
58
|
+
sortable: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "view",
|
|
62
|
+
label: l("actions"),
|
|
63
|
+
align: "center",
|
|
64
|
+
width: "5%",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
// TODO: initialize input based on url query params
|
|
69
|
+
const [input, setInput] = useState<ProcessInput>({
|
|
70
|
+
skip: 0,
|
|
71
|
+
sort: "modified",
|
|
72
|
+
sortDesc: true,
|
|
73
|
+
top: 10,
|
|
74
|
+
integrationTags: null,
|
|
75
|
+
search: null,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const { data, isLoading } = getProcesses.useData({
|
|
79
|
+
input: input,
|
|
80
|
+
});
|
|
81
|
+
|
|
26
82
|
return (
|
|
27
83
|
<Box>
|
|
28
84
|
<IntegrateBreadcrumbs breadcrumbs={breadcrumbs} />
|
|
85
|
+
<IntegrationList
|
|
86
|
+
heading={heading}
|
|
87
|
+
data={data}
|
|
88
|
+
loading={isLoading}
|
|
89
|
+
input={input}
|
|
90
|
+
setInput={setInput}
|
|
91
|
+
/>
|
|
29
92
|
</Box>
|
|
30
93
|
);
|
|
31
94
|
}
|