[WIP] change tree structure from dnd
This commit is contained in:
parent
9c0ada86e1
commit
c4e7ce54c0
|
@ -1,63 +1,47 @@
|
||||||
import { NodeModel } from "@minoru/react-dnd-treeview";
|
|
||||||
import { createContext, ReactNode, useCallback, useState } from "react";
|
import { createContext, ReactNode, useCallback, useState } from "react";
|
||||||
import axiosConfig from "../../config/axiosConfig";
|
import axiosConfig from "../../config/axiosConfig";
|
||||||
|
import { transformArrayToTree } from "../../util/group.util";
|
||||||
import { I_Folder, I_Installation } from "../../util/types";
|
import { I_Folder, I_Installation } from "../../util/types";
|
||||||
|
|
||||||
interface GroupContextProviderProps {
|
interface GroupContextProviderProps {
|
||||||
|
currentType: string;
|
||||||
|
setCurrentType: (value: string) => void;
|
||||||
|
isMove: boolean;
|
||||||
|
setIsMove: (value: boolean) => void;
|
||||||
data: (I_Folder | I_Installation)[];
|
data: (I_Folder | I_Installation)[];
|
||||||
setData: (value: (I_Folder | I_Installation)[]) => void;
|
setData: (value: (I_Folder | I_Installation)[]) => void;
|
||||||
fetchData: () => Promise<void>;
|
fetchData: () => Promise<void>;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
setLoading: (value: boolean) => void;
|
setLoading: (value: boolean) => void;
|
||||||
getError: boolean;
|
getError: boolean;
|
||||||
tree: NodeModel<I_Folder | I_Installation>[];
|
|
||||||
setTree: (value: NodeModel<I_Folder | I_Installation>[]) => void;
|
|
||||||
currentType: string;
|
|
||||||
setCurrentType: (value: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupContext = createContext<GroupContextProviderProps>({
|
export const GroupContext = createContext<GroupContextProviderProps>({
|
||||||
|
currentType: "",
|
||||||
|
setCurrentType: () => {},
|
||||||
|
isMove: false,
|
||||||
|
setIsMove: () => {},
|
||||||
data: [],
|
data: [],
|
||||||
setData: () => {},
|
setData: () => {},
|
||||||
fetchData: () => Promise.resolve(),
|
fetchData: () => Promise.resolve(),
|
||||||
loading: false,
|
loading: false,
|
||||||
setLoading: () => {},
|
setLoading: () => {},
|
||||||
getError: false,
|
getError: false,
|
||||||
tree: [],
|
|
||||||
setTree: () => {},
|
|
||||||
currentType: "",
|
|
||||||
setCurrentType: () => {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTreeData = (
|
|
||||||
data: (I_Folder | I_Installation)[]
|
|
||||||
): NodeModel<I_Folder | I_Installation>[] => {
|
|
||||||
return data.map((element) => {
|
|
||||||
const isFolder = element.type === "Folder";
|
|
||||||
return {
|
|
||||||
id: isFolder ? element.id : "installation-" + element.id,
|
|
||||||
parent: element.parentId,
|
|
||||||
text: element.name,
|
|
||||||
droppable: isFolder,
|
|
||||||
data: element,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const GroupContextProvider = ({ children }: { children: ReactNode }) => {
|
const GroupContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [currentType, setCurrentType] = useState("");
|
||||||
|
const [isMove, setIsMove] = useState(false);
|
||||||
const [data, setData] = useState<(I_Folder | I_Installation)[]>([]);
|
const [data, setData] = useState<(I_Folder | I_Installation)[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [getError, setGetError] = useState(false);
|
const [getError, setGetError] = useState(false);
|
||||||
const [tree, setTree] = useState<NodeModel<I_Folder | I_Installation>[]>([]);
|
|
||||||
const [currentType, setCurrentType] = useState("");
|
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
return axiosConfig
|
return axiosConfig
|
||||||
.get("/GetAllFoldersAndInstallations")
|
.get("/GetAllFoldersAndInstallations")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setData(res.data);
|
setData(transformArrayToTree(res.data));
|
||||||
setTree(getTreeData(res.data));
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -69,16 +53,16 @@ const GroupContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<GroupContext.Provider
|
<GroupContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
currentType,
|
||||||
|
setCurrentType,
|
||||||
|
isMove,
|
||||||
|
setIsMove,
|
||||||
data,
|
data,
|
||||||
setData,
|
setData,
|
||||||
fetchData,
|
fetchData,
|
||||||
loading,
|
loading,
|
||||||
setLoading,
|
setLoading,
|
||||||
getError,
|
getError,
|
||||||
tree,
|
|
||||||
setTree,
|
|
||||||
currentType,
|
|
||||||
setCurrentType,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, CircularProgress, Grid } from "@mui/material";
|
import { Button, CircularProgress, Grid, InputLabel } from "@mui/material";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
@ -7,6 +7,7 @@ import { I_Folder } from "../../util/types";
|
||||||
import { GroupContext } from "../Context/GroupContextProvider";
|
import { GroupContext } from "../Context/GroupContextProvider";
|
||||||
import InnovenergySnackbar from "../InnovenergySnackbar";
|
import InnovenergySnackbar from "../InnovenergySnackbar";
|
||||||
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
|
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
|
||||||
|
import MoveTree from "./Tree/MoveTree";
|
||||||
|
|
||||||
interface I_CustomerFormProps {
|
interface I_CustomerFormProps {
|
||||||
values: I_Folder;
|
values: I_Folder;
|
||||||
|
@ -20,23 +21,12 @@ const updateFolder = (data: I_Folder) => {
|
||||||
const FolderForm = (props: I_CustomerFormProps) => {
|
const FolderForm = (props: I_CustomerFormProps) => {
|
||||||
const { values, id } = props;
|
const { values, id } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { isMove, setIsMove } = useContext(GroupContext);
|
||||||
|
|
||||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [selectedParentId, setSelectedParentId] = useState<number>();
|
||||||
const { setTree, tree } = useContext(GroupContext);
|
|
||||||
|
|
||||||
const updateTree = (newValues: I_Folder) => {
|
|
||||||
const elementToUpdate = tree.find(
|
|
||||||
(element) => element.data?.id.toString() === id
|
|
||||||
);
|
|
||||||
if (elementToUpdate) {
|
|
||||||
elementToUpdate.data = newValues;
|
|
||||||
elementToUpdate.text = newValues.name;
|
|
||||||
setTree([...tree]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -49,10 +39,10 @@ const FolderForm = (props: I_CustomerFormProps) => {
|
||||||
updateFolder({
|
updateFolder({
|
||||||
...values,
|
...values,
|
||||||
...formikValues,
|
...formikValues,
|
||||||
|
parentId: selectedParentId ?? values.parentId,
|
||||||
id: idAsNumber,
|
id: idAsNumber,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateTree({ ...values, ...formikValues, id: idAsNumber });
|
|
||||||
setSnackbarOpen(true);
|
setSnackbarOpen(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
|
@ -86,6 +76,23 @@ const FolderForm = (props: I_CustomerFormProps) => {
|
||||||
value={formik.values.information}
|
value={formik.values.information}
|
||||||
handleChange={formik.handleChange}
|
handleChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
|
<Grid container direction="row" alignItems="center" spacing={2}>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<InputLabel>
|
||||||
|
<FormattedMessage id="location" defaultMessage="Location" />
|
||||||
|
</InputLabel>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={9} display="inline">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ height: 40, ml: 2 }}
|
||||||
|
onClick={() => setIsMove(true)}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="move" defaultMessage="Move" />
|
||||||
|
</Button>
|
||||||
|
{isMove && <MoveTree setSelectedParentId={setSelectedParentId} />}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
|
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
|
||||||
{loading && <CircularProgress />}
|
{loading && <CircularProgress />}
|
||||||
<Button variant="outlined" type="submit" sx={{ height: 40, ml: 2 }}>
|
<Button variant="outlined" type="submit" sx={{ height: 40, ml: 2 }}>
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { Container } from "@mui/system";
|
||||||
import { Routes, Route } from "react-router";
|
import { Routes, Route } from "react-router";
|
||||||
import routes from "../../routes.json";
|
import routes from "../../routes.json";
|
||||||
import Installation from "../Installations/Installation";
|
import Installation from "../Installations/Installation";
|
||||||
import NavigationButtons from "../Layout/NavigationButtons";
|
|
||||||
import Folder from "./Folder";
|
import Folder from "./Folder";
|
||||||
import GroupTabs from "./GroupTabs";
|
import GroupTabs from "./GroupTabs";
|
||||||
import GroupContextProvider from "../Context/GroupContextProvider";
|
import GroupContextProvider from "../Context/GroupContextProvider";
|
||||||
import GroupTree from "./Tree/UserTree";
|
import GroupTree from "./Tree/GroupTree";
|
||||||
|
import NavigationButtons from "../Layout/NavigationButtons";
|
||||||
|
|
||||||
const Groups = () => {
|
const Groups = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -15,6 +15,7 @@ const Groups = () => {
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={3}>
|
<Grid item xs={3}>
|
||||||
|
<NavigationButtons />
|
||||||
<GroupTree />
|
<GroupTree />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={9}>
|
<Grid item xs={9}>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
.root {
|
|
||||||
align-items: "center";
|
|
||||||
background-color: #1967d2;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 12px 24px -6px rgba(0, 0, 0, 0.25),
|
|
||||||
0 0 0 1px rgba(0, 0, 0, 0.08);
|
|
||||||
color: #fff;
|
|
||||||
display: inline-grid;
|
|
||||||
font-size: 14px;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
padding: 4px 8px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon,
|
|
||||||
.label {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { DragLayerMonitorProps } from "@minoru/react-dnd-treeview";
|
|
||||||
import { I_Installation, I_Folder } from "../../../util/types";
|
|
||||||
import styles from "./DragPreview.module.scss";
|
|
||||||
import TypeIcon from "../TypeIcon";
|
|
||||||
|
|
||||||
interface DragPreviewProps {
|
|
||||||
monitorProps: DragLayerMonitorProps<I_Installation | I_Folder>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DragPreview = (props: DragPreviewProps) => {
|
|
||||||
const item = props.monitorProps.item;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.root}>
|
|
||||||
<div className={styles.icon}>
|
|
||||||
<TypeIcon type={item.data?.type} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.label}>{item.text}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DragPreview;
|
|
|
@ -1,25 +0,0 @@
|
||||||
.tree {
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.treeRoot {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggingSource {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropTarget {
|
|
||||||
background-color: #e8f0fe;
|
|
||||||
}
|
|
||||||
|
|
||||||
.treeContainer {
|
|
||||||
max-height: 500px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
|
@ -1,117 +1,66 @@
|
||||||
import { useEffect, useState, useContext } from "react";
|
import TreeView from "@mui/lab/TreeView";
|
||||||
import axiosConfig from "../../../config/axiosConfig";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||||
|
import { ReactNode, useContext, useEffect } from "react";
|
||||||
|
import { TreeItem } from "@mui/lab";
|
||||||
import { I_Folder, I_Installation } from "../../../util/types";
|
import { I_Folder, I_Installation } from "../../../util/types";
|
||||||
import { Alert, CircularProgress, Grid } from "@mui/material";
|
import { Link } from "react-router-dom";
|
||||||
import { DndProvider } from "react-dnd";
|
import routes from "../../../routes.json";
|
||||||
import {
|
|
||||||
MultiBackend,
|
|
||||||
getBackendOptions,
|
|
||||||
Tree,
|
|
||||||
NodeModel,
|
|
||||||
DropOptions,
|
|
||||||
} from "@minoru/react-dnd-treeview";
|
|
||||||
import TreeNode from "./TreeNode";
|
|
||||||
import styles from "./GroupTree.module.scss";
|
|
||||||
import withScrolling from "react-dnd-scrolling";
|
|
||||||
import DragPreview from "./DragPreview";
|
|
||||||
import InnovenergySnackbar from "../../InnovenergySnackbar";
|
|
||||||
import { GroupContext } from "../../Context/GroupContextProvider";
|
import { GroupContext } from "../../Context/GroupContextProvider";
|
||||||
|
import { instanceOfFolder } from "../../../util/group.util";
|
||||||
|
|
||||||
const GroupTree = () => {
|
const GroupTree = () => {
|
||||||
const { data, fetchData, loading, setLoading, getError, setTree, tree } =
|
const { setCurrentType, fetchData, data } = useContext(GroupContext);
|
||||||
useContext(GroupContext);
|
|
||||||
const [putError, setPutError] = useState(false);
|
|
||||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
|
||||||
const [openNodes, setOpenNodes] = useState<(string | number)[]>([]);
|
|
||||||
|
|
||||||
const ScrollingComponent = withScrolling("div");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [fetchData]);
|
}, [fetchData]);
|
||||||
|
|
||||||
const handleDrop = (
|
const getNodes = (element: I_Folder | I_Installation): null | ReactNode => {
|
||||||
newTree: NodeModel<I_Folder | I_Installation>[],
|
if (instanceOfFolder(element)) {
|
||||||
{ dropTargetId, dragSource }: DropOptions<I_Folder | I_Installation>
|
return element.children ? renderTree(element.children) : null;
|
||||||
) => {
|
|
||||||
axiosConfig
|
|
||||||
.put(
|
|
||||||
dragSource?.data?.type === "Folder"
|
|
||||||
? "/UpdateFolder"
|
|
||||||
: "/UpdateInstallation",
|
|
||||||
{
|
|
||||||
...dragSource?.data,
|
|
||||||
parentId: dropTargetId,
|
|
||||||
}
|
}
|
||||||
)
|
return null;
|
||||||
.then(() => {
|
|
||||||
setSnackbarOpen(true);
|
|
||||||
setTree(newTree);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setPutError(err);
|
|
||||||
setLoading(false);
|
|
||||||
setSnackbarOpen(true);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
const renderTree = (data: (I_Folder | I_Installation)[]): ReactNode => {
|
||||||
|
return data.map((element) => {
|
||||||
return (
|
return (
|
||||||
<Grid container justifyContent="center" width="100%">
|
<Link
|
||||||
<CircularProgress sx={{ m: 6 }} />
|
key={element.id}
|
||||||
</Grid>
|
to={
|
||||||
);
|
element.type === "Folder"
|
||||||
} else if (data && data?.length > 1) {
|
? routes.folder + element.id
|
||||||
return (
|
: routes.installation + element.id
|
||||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
|
||||||
<ScrollingComponent className={styles.treeContainer}>
|
|
||||||
<Tree<I_Installation | I_Folder>
|
|
||||||
tree={tree}
|
|
||||||
rootId={0}
|
|
||||||
dragPreviewRender={(monitorProps) => (
|
|
||||||
<DragPreview monitorProps={monitorProps} />
|
|
||||||
)}
|
|
||||||
classes={{
|
|
||||||
container: styles.tree,
|
|
||||||
root: styles.treeRoot,
|
|
||||||
draggingSource: styles.draggingSource,
|
|
||||||
dropTarget: styles.dropTarget,
|
|
||||||
}}
|
|
||||||
render={(
|
|
||||||
node: NodeModel<I_Installation | I_Folder>,
|
|
||||||
{ depth, isOpen, onToggle, hasChild, handleRef }
|
|
||||||
) => (
|
|
||||||
<TreeNode
|
|
||||||
node={node}
|
|
||||||
depth={depth}
|
|
||||||
isOpen={isOpen}
|
|
||||||
onToggle={onToggle}
|
|
||||||
hasChild={hasChild}
|
|
||||||
handleRef={handleRef}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
onDrop={(
|
|
||||||
tree: NodeModel<I_Folder | I_Installation>[],
|
|
||||||
options: DropOptions<I_Folder | I_Installation>
|
|
||||||
) => handleDrop(tree, options)}
|
|
||||||
onChangeOpen={(nodeIds: (number | string)[]) =>
|
|
||||||
setOpenNodes(nodeIds)
|
|
||||||
}
|
}
|
||||||
initialOpen={openNodes}
|
style={{
|
||||||
/>
|
textDecoration: "none",
|
||||||
</ScrollingComponent>
|
color: "black",
|
||||||
<InnovenergySnackbar
|
}}
|
||||||
error={putError}
|
draggable={false}
|
||||||
open={snackbarOpen}
|
>
|
||||||
setOpen={setSnackbarOpen}
|
<TreeItem
|
||||||
/>
|
key={element.id}
|
||||||
</DndProvider>
|
nodeId={element.id.toString()}
|
||||||
|
label={element.name}
|
||||||
|
onClick={() => setCurrentType(element.type)}
|
||||||
|
>
|
||||||
|
{getNodes(element)}
|
||||||
|
</TreeItem>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
} else if (getError) {
|
});
|
||||||
|
};
|
||||||
|
if (data) {
|
||||||
return (
|
return (
|
||||||
<Alert severity="error" sx={{ mt: 1 }}>
|
<TreeView
|
||||||
Couldn't load data
|
aria-label="rich object"
|
||||||
</Alert>
|
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||||
|
defaultExpandIcon={<ChevronRightIcon />}
|
||||||
|
sx={{ height: 300, flexGrow: 1, maxWidth: 400 }}
|
||||||
|
>
|
||||||
|
{renderTree(data)}
|
||||||
|
</TreeView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import TreeView from "@mui/lab/TreeView";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||||
|
import { ReactNode, useContext } from "react";
|
||||||
|
import { TreeItem } from "@mui/lab";
|
||||||
|
import { I_Folder, I_Installation } from "../../../util/types";
|
||||||
|
import { GroupContext } from "../../Context/GroupContextProvider";
|
||||||
|
import { instanceOfFolder } from "../../../util/group.util";
|
||||||
|
|
||||||
|
interface GroupTreeProps {
|
||||||
|
setSelectedParentId: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GroupTree = (props: GroupTreeProps) => {
|
||||||
|
const { data } = useContext(GroupContext);
|
||||||
|
|
||||||
|
const getNodes = (element: I_Folder | I_Installation): null | ReactNode => {
|
||||||
|
if (instanceOfFolder(element)) {
|
||||||
|
return element.children ? renderTree(element.children) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTree = (data: (I_Folder | I_Installation)[]): ReactNode => {
|
||||||
|
return data
|
||||||
|
.filter((element) => element.type === "Folder")
|
||||||
|
.map((element) => {
|
||||||
|
return (
|
||||||
|
<TreeItem
|
||||||
|
key={"move-tree-element-" + element.id}
|
||||||
|
nodeId={element.id.toString()}
|
||||||
|
label={element.name}
|
||||||
|
>
|
||||||
|
{getNodes(element)}
|
||||||
|
</TreeItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
return (
|
||||||
|
<TreeView
|
||||||
|
aria-label="rich object"
|
||||||
|
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||||
|
defaultExpandIcon={<ChevronRightIcon />}
|
||||||
|
sx={{ height: 200, flexGrow: 1, width: 400, overflow: "auto" }}
|
||||||
|
onNodeSelect={(e: any, id: string) =>
|
||||||
|
props.setSelectedParentId(parseInt(id))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{renderTree(data)}
|
||||||
|
</TreeView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupTree;
|
|
@ -1,40 +0,0 @@
|
||||||
.root {
|
|
||||||
align-items: center;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto auto 1fr auto;
|
|
||||||
height: 32px;
|
|
||||||
padding-inline-end: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expandIconWrapper {
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
height: 24px;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
transition: transform linear 0.1s;
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.expandIconWrapper.isOpen {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.labelGridItem {
|
|
||||||
padding-inline-start: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle {
|
|
||||||
cursor: grab;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle > svg {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
background-color: #f5910014;
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
import React, { useContext, useEffect } from "react";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import ArrowRightIcon from "@mui/icons-material/ArrowRight";
|
|
||||||
import { NodeModel } from "@minoru/react-dnd-treeview";
|
|
||||||
import styles from "./TreeNode.module.scss";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import routes from "../../../routes.json";
|
|
||||||
import { I_Folder, I_Installation } from "../../../util/types";
|
|
||||||
import TypeIcon from "../TypeIcon";
|
|
||||||
import DragHandleIcon from "@mui/icons-material/DragHandle";
|
|
||||||
import useRouteMatch from "../../../hooks/useRouteMatch";
|
|
||||||
import { GroupContext } from "../../Context/GroupContextProvider";
|
|
||||||
|
|
||||||
interface TreeNodeProps {
|
|
||||||
node: NodeModel<I_Installation | I_Folder>;
|
|
||||||
depth: number;
|
|
||||||
isOpen: boolean;
|
|
||||||
onToggle: (id: NodeModel["id"]) => void;
|
|
||||||
hasChild: boolean;
|
|
||||||
handleRef: React.RefObject<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TreeNode = (props: TreeNodeProps) => {
|
|
||||||
const { node, isOpen, hasChild, onToggle, depth, handleRef } = props;
|
|
||||||
const indent = depth * 24;
|
|
||||||
|
|
||||||
const { setCurrentType } = useContext(GroupContext);
|
|
||||||
const routeMatch = useRouteMatch([
|
|
||||||
routes.groups + routes.installation + ":id",
|
|
||||||
routes.groups + routes.folder + ":id",
|
|
||||||
routes.groups + routes.users + ":id",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isSelected = routeMatch?.params.id === node.data?.id.toString();
|
|
||||||
|
|
||||||
const handleToggle = (e: React.MouseEvent) => {
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
document
|
|
||||||
.getElementById(node.id.toString())
|
|
||||||
?.scrollIntoView({ block: "center" }),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
e.stopPropagation();
|
|
||||||
onToggle(node.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (node.data && isSelected) {
|
|
||||||
setCurrentType(node.data?.type);
|
|
||||||
}
|
|
||||||
}, [isSelected, node.data, setCurrentType]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id={node.id.toString()}
|
|
||||||
className={`tree-node ${styles.root} ${
|
|
||||||
isSelected ? styles.selected : ""
|
|
||||||
}`}
|
|
||||||
style={{ paddingInlineStart: indent }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${styles.expandIconWrapper} ${isOpen ? styles.isOpen : ""}`}
|
|
||||||
>
|
|
||||||
{node.droppable && hasChild && (
|
|
||||||
<div onClick={handleToggle}>
|
|
||||||
<ArrowRightIcon />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<TypeIcon type={node.data?.type} />
|
|
||||||
<Link
|
|
||||||
to={
|
|
||||||
node.droppable
|
|
||||||
? routes.folder + node.id
|
|
||||||
: routes.installation + (node.data ? node.data.id : "")
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
textDecoration: "none",
|
|
||||||
color: "black",
|
|
||||||
}}
|
|
||||||
draggable={false}
|
|
||||||
>
|
|
||||||
<div className={styles.labelGridItem}>
|
|
||||||
<Typography variant="body2">{node.text}</Typography>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<div className={`${styles.handle} drag-handle`} ref={handleRef}>
|
|
||||||
<DragHandleIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TreeNode;
|
|
|
@ -1,75 +0,0 @@
|
||||||
import TreeView from "@mui/lab/TreeView";
|
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
||||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
|
||||||
import { TreeItem } from "@mui/lab";
|
|
||||||
import axiosConfig from "../../../config/axiosConfig";
|
|
||||||
import { I_Folder, I_Installation } from "../../../util/types";
|
|
||||||
|
|
||||||
const GroupTree = () => {
|
|
||||||
const [data, setData] = useState<(I_Folder | I_Installation)[]>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
axiosConfig.get("/GetAllFoldersAndInstallations").then((res) => {
|
|
||||||
setData(res.data);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClick = (e: any, nodes: any) => {
|
|
||||||
console.log(e);
|
|
||||||
console.log(nodes);
|
|
||||||
};
|
|
||||||
|
|
||||||
const instanceOfFolder = (object: any): object is I_Folder => {
|
|
||||||
return "children" in object;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNodes = (element: I_Folder | I_Installation): null | ReactNode => {
|
|
||||||
if (instanceOfFolder(element)) {
|
|
||||||
return element.children ? renderTree(element.children) : null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const nest = (
|
|
||||||
items: (I_Folder | I_Installation)[],
|
|
||||||
id: number | null = null
|
|
||||||
): any => {
|
|
||||||
return items.map((item) => {
|
|
||||||
if (item.parentId === 0 && item.type === "Installlation") {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
return { ...item, children: nest(items, item.id) };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTree = (data: (I_Folder | I_Installation)[]): ReactNode => {
|
|
||||||
return data.map((element) => {
|
|
||||||
return (
|
|
||||||
<TreeItem
|
|
||||||
key={element.id}
|
|
||||||
nodeId={element.id.toString()}
|
|
||||||
label={element.name}
|
|
||||||
>
|
|
||||||
{getNodes(element)}
|
|
||||||
</TreeItem>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<></>
|
|
||||||
// <TreeView
|
|
||||||
// aria-label="rich object"
|
|
||||||
// defaultCollapseIcon={<ExpandMoreIcon />}
|
|
||||||
// defaultExpandIcon={<ChevronRightIcon />}
|
|
||||||
// sx={{ height: 300, flexGrow: 1, maxWidth: 400 }}
|
|
||||||
// onNodeToggle={handleClick}
|
|
||||||
// >
|
|
||||||
// {renderTree(data)}
|
|
||||||
// {data && renderTree(data)}
|
|
||||||
// </TreeView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GroupTree;
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { Alert, Button, Grid, Snackbar } from "@mui/material";
|
import { Alert, Button, Grid, Snackbar } from "@mui/material";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
import { useContext, useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import axiosConfig from "../../config/axiosConfig";
|
import axiosConfig from "../../config/axiosConfig";
|
||||||
import useRouteMatch from "../../hooks/useRouteMatch";
|
|
||||||
import { I_Installation } from "../../util/types";
|
import { I_Installation } from "../../util/types";
|
||||||
import { GroupContext } from "../Context/GroupContextProvider";
|
|
||||||
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
|
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
|
||||||
import routes from "../../routes.json";
|
|
||||||
import { InstallationContext } from "../Context/InstallationContextProvider";
|
|
||||||
|
|
||||||
interface I_CustomerFormProps {
|
interface I_CustomerFormProps {
|
||||||
values: I_Installation;
|
values: I_Installation;
|
||||||
|
@ -19,12 +15,6 @@ const CustomerForm = (props: I_CustomerFormProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { fetchData: fetchGroupData } = useContext(GroupContext);
|
|
||||||
const { fetchData: fetchInstallationData } = useContext(InstallationContext);
|
|
||||||
|
|
||||||
const groupsMatch = useRouteMatch([
|
|
||||||
routes.groups + routes.installation + ":id",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -42,11 +32,6 @@ const CustomerForm = (props: I_CustomerFormProps) => {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
if (groupsMatch) {
|
|
||||||
fetchGroupData();
|
|
||||||
} else {
|
|
||||||
fetchInstallationData();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { I_Folder, I_Installation } from "./types";
|
||||||
|
|
||||||
|
export const transformArrayToTree = (
|
||||||
|
array: (I_Folder | I_Installation)[],
|
||||||
|
parentId: number = 0
|
||||||
|
): (I_Folder | I_Installation)[] => {
|
||||||
|
return array
|
||||||
|
.filter((item) => item.parentId === parentId)
|
||||||
|
.map((item) => {
|
||||||
|
if (item.type === "Installation") {
|
||||||
|
return item;
|
||||||
|
} else {
|
||||||
|
const folder = item as I_Folder;
|
||||||
|
folder.children = transformArrayToTree(array, folder.id);
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const instanceOfFolder = (object: any): object is I_Folder => {
|
||||||
|
return "children" in object;
|
||||||
|
};
|
Loading…
Reference in New Issue