@truedat/df 5.0.1 → 5.0.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/CHANGELOG.md +6 -0
- package/package.json +5 -5
- package/src/api/hierarchies.js +4 -0
- package/src/components/hierarchies/Hierarchies.js +43 -0
- package/src/components/hierarchies/HierarchiesView.js +32 -0
- package/src/components/hierarchies/Hierarchy.js +301 -0
- package/src/components/hierarchies/HierarchyCrumbs.js +22 -0
- package/src/components/hierarchies/HierarchyRoutes.js +105 -0
- package/src/components/hierarchies/HierarchyView.js +261 -0
- package/src/components/hierarchies/__tests__/Hierarchies.spec.js +34 -0
- package/src/components/hierarchies/__tests__/HierarchiesView.spec.js +34 -0
- package/src/components/hierarchies/__tests__/Hierarchy.spec.js +42 -0
- package/src/components/hierarchies/__tests__/HierarchyCrumbs.spec.js +11 -0
- package/src/components/hierarchies/__tests__/HierarchyView.spec.js +36 -0
- package/src/components/hierarchies/__tests__/__snapshots__/Hierarchies.spec.js.snap +48 -0
- package/src/components/hierarchies/__tests__/__snapshots__/HierarchiesView.spec.js.snap +81 -0
- package/src/components/hierarchies/__tests__/__snapshots__/Hierarchy.spec.js.snap +186 -0
- package/src/components/hierarchies/__tests__/__snapshots__/HierarchyCrumbs.spec.js.snap +102 -0
- package/src/components/hierarchies/__tests__/__snapshots__/HierarchyView.spec.js.snap +145 -0
- package/src/components/hierarchies/index.js +3 -0
- package/src/components/index.js +1 -0
- package/src/hooks/useHierarchies.js +111 -0
- package/src/messages/en.js +94 -72
- package/src/messages/es.js +99 -77
- package/src/styles/hierarchy.less +91 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useState, useEffect } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { Button, Header, Icon, Segment, Label } from "semantic-ui-react";
|
|
5
|
+
import { useIntl } from "react-intl";
|
|
6
|
+
import { Link } from "react-router-dom";
|
|
7
|
+
import { linkTo } from "@truedat/core/routes";
|
|
8
|
+
import { CSVFileModal } from "@truedat/core/components";
|
|
9
|
+
import { HistoryBackButton } from "@truedat/core/components";
|
|
10
|
+
import { ConfirmModal } from "@truedat/core/components";
|
|
11
|
+
import HierarchyCrumbs from "./HierarchyCrumbs";
|
|
12
|
+
import Hierarchy from "./Hierarchy";
|
|
13
|
+
|
|
14
|
+
const getLastId = _.flow(
|
|
15
|
+
_.pathOr([], "nodes"),
|
|
16
|
+
_.map("id"),
|
|
17
|
+
_.concat(0),
|
|
18
|
+
_.max
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const validateHierarchy = (hierarchy) => {
|
|
22
|
+
const validatedHierarchy = {
|
|
23
|
+
...hierarchy,
|
|
24
|
+
nodes: _.flow(
|
|
25
|
+
_.pathOr([], "nodes"),
|
|
26
|
+
_.map((node) => {
|
|
27
|
+
const hasError =
|
|
28
|
+
_.isEmpty(node.name) ||
|
|
29
|
+
node.name === "" ||
|
|
30
|
+
hasDuplicatedSiblings(node, hierarchy);
|
|
31
|
+
return {
|
|
32
|
+
...node,
|
|
33
|
+
error: hasError,
|
|
34
|
+
};
|
|
35
|
+
})
|
|
36
|
+
)(hierarchy),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const valid =
|
|
40
|
+
hierarchy.name !== "" &&
|
|
41
|
+
_.flow(
|
|
42
|
+
_.pathOr([], "nodes"),
|
|
43
|
+
_.filter({ error: true }),
|
|
44
|
+
_.isEmpty
|
|
45
|
+
)(validatedHierarchy);
|
|
46
|
+
return [valid, validatedHierarchy];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const hasDuplicatedSiblings = ({ parentId, name }, { nodes }) => {
|
|
50
|
+
return _.size(_.filter({ parentId, name })(nodes)) > 1;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const HierarchyView = ({
|
|
54
|
+
isEditionMode = false,
|
|
55
|
+
originalHierarchy,
|
|
56
|
+
onSubmit,
|
|
57
|
+
onDelete,
|
|
58
|
+
}) => {
|
|
59
|
+
const { formatMessage } = useIntl();
|
|
60
|
+
const [hierarchy, setHierarchy] = useState(originalHierarchy);
|
|
61
|
+
const [maxId, setMaxId] = useState(getLastId(originalHierarchy));
|
|
62
|
+
const [deletedNodes, setDeletedNodes] = useState(0);
|
|
63
|
+
const [isValid, setIsValid] = useState();
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setIsValid(validateHierarchy(originalHierarchy)[0]);
|
|
66
|
+
}, [originalHierarchy]);
|
|
67
|
+
|
|
68
|
+
const validateAndSetHierarchy = (hierarchy) => {
|
|
69
|
+
const [isValid, validatedHierarchy] = validateHierarchy(hierarchy);
|
|
70
|
+
setIsValid(isValid);
|
|
71
|
+
setHierarchy(validatedHierarchy);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const addedNodes = _.flow(
|
|
75
|
+
_.pathOr([], "nodes"),
|
|
76
|
+
_.filter({ editionStatus: "added" }),
|
|
77
|
+
_.size
|
|
78
|
+
)(hierarchy);
|
|
79
|
+
|
|
80
|
+
const modifiedNodes = _.flow(
|
|
81
|
+
_.pathOr([], "nodes"),
|
|
82
|
+
_.filter({ editionStatus: "dirty" }),
|
|
83
|
+
_.size
|
|
84
|
+
)(hierarchy);
|
|
85
|
+
|
|
86
|
+
const updateMetadata = ({ name, description }) =>
|
|
87
|
+
validateAndSetHierarchy({
|
|
88
|
+
...hierarchy,
|
|
89
|
+
name: name,
|
|
90
|
+
description: description,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const updateNode = (id, { name, description }) => {
|
|
94
|
+
validateAndSetHierarchy({
|
|
95
|
+
...hierarchy,
|
|
96
|
+
nodes: _.map((node) =>
|
|
97
|
+
node.id == id
|
|
98
|
+
? {
|
|
99
|
+
...node,
|
|
100
|
+
name: name,
|
|
101
|
+
description: description,
|
|
102
|
+
error: false,
|
|
103
|
+
editionStatus: _.flow(_.find({ id }), (originalNode) =>
|
|
104
|
+
originalNode !== undefined &&
|
|
105
|
+
(originalNode?.name != name ||
|
|
106
|
+
originalNode?.description != description)
|
|
107
|
+
? "dirty"
|
|
108
|
+
: node.editionStatus == "dirty"
|
|
109
|
+
? null
|
|
110
|
+
: node.editionStatus
|
|
111
|
+
)(originalHierarchy.nodes),
|
|
112
|
+
}
|
|
113
|
+
: node
|
|
114
|
+
)(hierarchy.nodes),
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const addNode = (parentId = null, name) => {
|
|
119
|
+
const id = maxId + 1;
|
|
120
|
+
validateAndSetHierarchy({
|
|
121
|
+
...hierarchy,
|
|
122
|
+
nodes: [
|
|
123
|
+
...hierarchy.nodes,
|
|
124
|
+
{ id, parentId, name, editionStatus: "added", description: null },
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
setMaxId(id);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const removeNode = (id) => {
|
|
131
|
+
_.flow(
|
|
132
|
+
_.pathOr([], "nodes"),
|
|
133
|
+
_.find({ id: id }),
|
|
134
|
+
_.isEmpty
|
|
135
|
+
)(originalHierarchy)
|
|
136
|
+
? null
|
|
137
|
+
: setDeletedNodes(deletedNodes + 1);
|
|
138
|
+
validateAndSetHierarchy({
|
|
139
|
+
...hierarchy,
|
|
140
|
+
nodes: _.reject({ id })(hierarchy.nodes),
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<HierarchyCrumbs name={hierarchy.name} />
|
|
147
|
+
<Segment>
|
|
148
|
+
<Header as="h2">
|
|
149
|
+
<div className="actions_wrapper">
|
|
150
|
+
<div className="actions">
|
|
151
|
+
{isEditionMode ? (
|
|
152
|
+
<>
|
|
153
|
+
<Button
|
|
154
|
+
floated="right"
|
|
155
|
+
onClick={() => onSubmit(hierarchy)}
|
|
156
|
+
primary
|
|
157
|
+
loading={false}
|
|
158
|
+
disabled={!isValid}
|
|
159
|
+
content={formatMessage({ id: "actions.save" })}
|
|
160
|
+
title={formatMessage(
|
|
161
|
+
{
|
|
162
|
+
id: "hierarchies.modifications.summary",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
added: addedNodes,
|
|
166
|
+
modified: modifiedNodes,
|
|
167
|
+
deleted: deletedNodes,
|
|
168
|
+
}
|
|
169
|
+
)}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<HistoryBackButton
|
|
173
|
+
floated="right"
|
|
174
|
+
content={formatMessage({ id: "actions.cancel" })}
|
|
175
|
+
/>
|
|
176
|
+
|
|
177
|
+
<CSVFileModal
|
|
178
|
+
param="hierarchy"
|
|
179
|
+
onSubmit={(nodes) => {
|
|
180
|
+
const csvHierarchy = {
|
|
181
|
+
...hierarchy,
|
|
182
|
+
nodes: _.map((node) => {
|
|
183
|
+
const id = parseInt(node.id);
|
|
184
|
+
const parentId = parseInt(node.parentId);
|
|
185
|
+
return {
|
|
186
|
+
...node,
|
|
187
|
+
id: _.isNaN(id) ? null : id,
|
|
188
|
+
parentId: _.isNaN(parentId) ? null : parentId,
|
|
189
|
+
};
|
|
190
|
+
})(nodes),
|
|
191
|
+
};
|
|
192
|
+
validateAndSetHierarchy(csvHierarchy);
|
|
193
|
+
setMaxId(getLastId(hierarchy));
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
</>
|
|
197
|
+
) : (
|
|
198
|
+
<>
|
|
199
|
+
<Button
|
|
200
|
+
floated="right"
|
|
201
|
+
primary
|
|
202
|
+
as={Link}
|
|
203
|
+
to={linkTo.HIERARCHY_EDIT({ hierarchyId: hierarchy.id })}
|
|
204
|
+
content={formatMessage({ id: "actions.edit" })}
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
<ConfirmModal
|
|
208
|
+
icon="trash"
|
|
209
|
+
trigger={
|
|
210
|
+
<Button
|
|
211
|
+
floated="right"
|
|
212
|
+
secondary
|
|
213
|
+
loading={false}
|
|
214
|
+
disabled={false}
|
|
215
|
+
content={formatMessage({ id: "actions.delete" })}
|
|
216
|
+
/>
|
|
217
|
+
}
|
|
218
|
+
header={formatMessage({
|
|
219
|
+
id: "hierarchy.actions.delete.confirmation.header",
|
|
220
|
+
})}
|
|
221
|
+
size="small"
|
|
222
|
+
content={formatMessage({
|
|
223
|
+
id: "hierarchy.actions.delete.confirmation.content",
|
|
224
|
+
})}
|
|
225
|
+
onConfirm={() => onDelete(hierarchy.id)}
|
|
226
|
+
/>
|
|
227
|
+
</>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
<Icon circular name="tree" />
|
|
232
|
+
<Header.Content>
|
|
233
|
+
{hierarchy.name
|
|
234
|
+
? hierarchy.name
|
|
235
|
+
: formatMessage({ id: "hierarchy.name" })}
|
|
236
|
+
<Header.Subheader>{hierarchy.description}</Header.Subheader>
|
|
237
|
+
</Header.Content>
|
|
238
|
+
</Header>
|
|
239
|
+
<Segment attached="bottom">
|
|
240
|
+
<Hierarchy
|
|
241
|
+
hierarchy={hierarchy}
|
|
242
|
+
updateMetadata={updateMetadata}
|
|
243
|
+
updateNode={updateNode}
|
|
244
|
+
addNode={addNode}
|
|
245
|
+
removeNode={removeNode}
|
|
246
|
+
isEditionMode={isEditionMode}
|
|
247
|
+
/>
|
|
248
|
+
</Segment>
|
|
249
|
+
</Segment>
|
|
250
|
+
</>
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
HierarchyView.propTypes = {
|
|
255
|
+
isEditionMode: PropTypes.bool,
|
|
256
|
+
originalHierarchy: PropTypes.object,
|
|
257
|
+
onDelete: PropTypes.func,
|
|
258
|
+
onSubmit: PropTypes.func,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export default HierarchyView;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import Hierarchies from "../Hierarchies";
|
|
4
|
+
import en from "../../../messages/en";
|
|
5
|
+
|
|
6
|
+
const renderOpts = { messages: { en: en } };
|
|
7
|
+
|
|
8
|
+
const data = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
name: "Baggins",
|
|
12
|
+
nodes: [
|
|
13
|
+
{
|
|
14
|
+
id: 11,
|
|
15
|
+
name: "Fosco",
|
|
16
|
+
parentId: null,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
jest.mock("../../../hooks/useHierarchies", () => ({
|
|
23
|
+
useHierarchies: jest.fn(() => ({
|
|
24
|
+
loading: false,
|
|
25
|
+
data: data,
|
|
26
|
+
})),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
describe("<Hierarchies />", () => {
|
|
30
|
+
it("matches the last snapshot", () => {
|
|
31
|
+
const { container } = render(<Hierarchies />, renderOpts);
|
|
32
|
+
expect(container).toMatchSnapshot();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import HierarchiesView from "../HierarchiesView";
|
|
4
|
+
import en from "../../../messages/en";
|
|
5
|
+
|
|
6
|
+
const renderOpts = { messages: { en: en } };
|
|
7
|
+
|
|
8
|
+
const data = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
name: "Baggins",
|
|
12
|
+
nodes: [
|
|
13
|
+
{
|
|
14
|
+
id: 11,
|
|
15
|
+
name: "Fosco",
|
|
16
|
+
parentId: null,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
jest.mock("../../../hooks/useHierarchies", () => ({
|
|
23
|
+
useHierarchies: jest.fn(() => ({
|
|
24
|
+
loading: false,
|
|
25
|
+
data: data,
|
|
26
|
+
})),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
describe("<HierarchiesView />", () => {
|
|
30
|
+
it("matche the last snapshot", () => {
|
|
31
|
+
const { container } = render(<HierarchiesView />, renderOpts);
|
|
32
|
+
expect(container).toMatchSnapshot();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import Hierarchy from "../Hierarchy";
|
|
4
|
+
import en from "../../../messages/en";
|
|
5
|
+
|
|
6
|
+
const hierarchy = {
|
|
7
|
+
id: 1,
|
|
8
|
+
name: "Baggins",
|
|
9
|
+
description: "bar",
|
|
10
|
+
nodes: [
|
|
11
|
+
{
|
|
12
|
+
id: 11,
|
|
13
|
+
name: "Fosco",
|
|
14
|
+
parentId: null,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 12,
|
|
18
|
+
name: "Fosco children",
|
|
19
|
+
parentId: 11,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const props = { hierarchy: hierarchy, isEditionMode: false };
|
|
24
|
+
|
|
25
|
+
const renderOpts = {
|
|
26
|
+
messages: { en: en },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("<Hierarchy />", () => {
|
|
30
|
+
it("matches the last snapshot with edition mode false", () => {
|
|
31
|
+
const { container } = render(<Hierarchy {...props} />, renderOpts);
|
|
32
|
+
expect(container).toMatchSnapshot();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("matches snapshot with edition mode", () => {
|
|
36
|
+
const { container } = render(
|
|
37
|
+
<Hierarchy {...{ ...props, isEditionMode: true }} />,
|
|
38
|
+
renderOpts
|
|
39
|
+
);
|
|
40
|
+
expect(container).toMatchSnapshot();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import HierarchyCrumbs from "../HierarchyCrumbs";
|
|
4
|
+
|
|
5
|
+
describe("<HierarchyCrumbs />", () => {
|
|
6
|
+
it("matches the latest snapshot", () => {
|
|
7
|
+
const props = { name: "foo" };
|
|
8
|
+
const container = render(<HierarchyCrumbs {...props} />);
|
|
9
|
+
expect(container).toMatchSnapshot();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import HierarchyView from "../HierarchyView";
|
|
4
|
+
import en from "../../../messages/en";
|
|
5
|
+
|
|
6
|
+
const renderOpts = {
|
|
7
|
+
messages: {
|
|
8
|
+
en: { ...en, "actions.edit": "Edit", "actions.delete": "Delete" },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const originalHierarchy = {
|
|
13
|
+
id: 1,
|
|
14
|
+
name: "Baggins",
|
|
15
|
+
nodes: [
|
|
16
|
+
{
|
|
17
|
+
id: 11,
|
|
18
|
+
name: "Fosco",
|
|
19
|
+
parentId: null,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const opts = {
|
|
25
|
+
isEditionMode: false,
|
|
26
|
+
originalHierarchy,
|
|
27
|
+
loading: false,
|
|
28
|
+
onSubmit: jest.fn(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("<HierarchyView />", () => {
|
|
32
|
+
it("matches the last snapshot", () => {
|
|
33
|
+
const { container } = render(<HierarchyView {...opts} />, renderOpts);
|
|
34
|
+
expect(container).toMatchSnapshot();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<Hierarchies /> matches the last snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<table
|
|
6
|
+
class="ui celled table"
|
|
7
|
+
>
|
|
8
|
+
<thead
|
|
9
|
+
class=""
|
|
10
|
+
>
|
|
11
|
+
<tr
|
|
12
|
+
class=""
|
|
13
|
+
>
|
|
14
|
+
<th
|
|
15
|
+
class=""
|
|
16
|
+
>
|
|
17
|
+
Hierarchy name
|
|
18
|
+
</th>
|
|
19
|
+
<th
|
|
20
|
+
class=""
|
|
21
|
+
>
|
|
22
|
+
Description
|
|
23
|
+
</th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody
|
|
27
|
+
class=""
|
|
28
|
+
>
|
|
29
|
+
<tr
|
|
30
|
+
class=""
|
|
31
|
+
>
|
|
32
|
+
<td
|
|
33
|
+
class=""
|
|
34
|
+
>
|
|
35
|
+
<a
|
|
36
|
+
href="/hierarchies/1"
|
|
37
|
+
>
|
|
38
|
+
Baggins
|
|
39
|
+
</a>
|
|
40
|
+
</td>
|
|
41
|
+
<td
|
|
42
|
+
class=""
|
|
43
|
+
/>
|
|
44
|
+
</tr>
|
|
45
|
+
</tbody>
|
|
46
|
+
</table>
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<HierarchiesView /> matche the last snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="ui segment"
|
|
7
|
+
>
|
|
8
|
+
<h2
|
|
9
|
+
class="ui header"
|
|
10
|
+
>
|
|
11
|
+
<a
|
|
12
|
+
class="ui primary right floated button"
|
|
13
|
+
href="/hierarchies/new"
|
|
14
|
+
role="button"
|
|
15
|
+
>
|
|
16
|
+
New hierarchy
|
|
17
|
+
</a>
|
|
18
|
+
<i
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
class="tree circular icon"
|
|
21
|
+
/>
|
|
22
|
+
<div
|
|
23
|
+
class="content"
|
|
24
|
+
>
|
|
25
|
+
Hierarchies
|
|
26
|
+
<div
|
|
27
|
+
class="sub header"
|
|
28
|
+
>
|
|
29
|
+
List of hierarchies
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</h2>
|
|
33
|
+
<div
|
|
34
|
+
class="ui bottom attached segment"
|
|
35
|
+
>
|
|
36
|
+
<table
|
|
37
|
+
class="ui celled table"
|
|
38
|
+
>
|
|
39
|
+
<thead
|
|
40
|
+
class=""
|
|
41
|
+
>
|
|
42
|
+
<tr
|
|
43
|
+
class=""
|
|
44
|
+
>
|
|
45
|
+
<th
|
|
46
|
+
class=""
|
|
47
|
+
>
|
|
48
|
+
Hierarchy name
|
|
49
|
+
</th>
|
|
50
|
+
<th
|
|
51
|
+
class=""
|
|
52
|
+
>
|
|
53
|
+
Description
|
|
54
|
+
</th>
|
|
55
|
+
</tr>
|
|
56
|
+
</thead>
|
|
57
|
+
<tbody
|
|
58
|
+
class=""
|
|
59
|
+
>
|
|
60
|
+
<tr
|
|
61
|
+
class=""
|
|
62
|
+
>
|
|
63
|
+
<td
|
|
64
|
+
class=""
|
|
65
|
+
>
|
|
66
|
+
<a
|
|
67
|
+
href="/hierarchies/1"
|
|
68
|
+
>
|
|
69
|
+
Baggins
|
|
70
|
+
</a>
|
|
71
|
+
</td>
|
|
72
|
+
<td
|
|
73
|
+
class=""
|
|
74
|
+
/>
|
|
75
|
+
</tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
`;
|