1 |
8c45ccb0
|
hrubyjar
|
import 'antd/dist/antd.css';
|
2 |
cd2f7b19
|
Dominik Poch
|
import React, { useContext, useEffect, useState } from 'react';
|
3 |
8c45ccb0
|
hrubyjar
|
|
4 |
|
|
import { useUnauthRedirect } from '../../hooks';
|
5 |
|
|
import { useRouter } from 'next/router';
|
6 |
fe33e057
|
Dominik Poch
|
import { Button, Checkbox, Popconfirm, Typography } from 'antd';
|
7 |
8c45ccb0
|
hrubyjar
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
8 |
|
|
import { faTags } from '@fortawesome/free-solid-svg-icons';
|
9 |
|
|
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
|
10 |
51f70e00
|
Lukáš Vlček
|
import { MainLayout } from '../../layouts/MainLayout';
|
11 |
fe33e057
|
Dominik Poch
|
import { Container, Row, Stack } from 'react-bootstrap';
|
12 |
cd2f7b19
|
Dominik Poch
|
import { Table } from 'antd';
|
13 |
|
|
import { tagController } from '../../controllers';
|
14 |
fe33e057
|
Dominik Poch
|
import Search from 'antd/lib/input/Search';
|
15 |
|
|
import { DeleteOutlined, PlusOutlined, EditOutlined } from '@ant-design/icons';
|
16 |
|
|
import CategoryModal, {
|
17 |
|
|
CategoryModalValues,
|
18 |
|
|
} from '../../components/modals/CategoryModal';
|
19 |
|
|
import TagModal, { TagModalValues } from '../../components/modals/TagModal';
|
20 |
|
|
import SubTagModal, { SubTagModalValues } from '../../components/modals/SubTagModal';
|
21 |
8c45ccb0
|
hrubyjar
|
|
22 |
fe33e057
|
Dominik Poch
|
/**
|
23 |
|
|
* Creates a tag management page.
|
24 |
|
|
* @returns The tag management page.
|
25 |
|
|
*/
|
26 |
8c45ccb0
|
hrubyjar
|
function TagsPage() {
|
27 |
|
|
const redirecting = useUnauthRedirect('/login');
|
28 |
|
|
const { logout, role } = useContext(LoggedUserContext);
|
29 |
|
|
const router = useRouter();
|
30 |
|
|
|
31 |
41e65564
|
Dominik Poch
|
const [tagData, setTagData] = useState<any[] | null>([]);
|
32 |
|
|
|
33 |
fe33e057
|
Dominik Poch
|
/**
|
34 |
|
|
* Data in a table.
|
35 |
|
|
*/
|
36 |
41e65564
|
Dominik Poch
|
const [shownData, setShownData] = useState<any[] | undefined>([]);
|
37 |
cd2f7b19
|
Dominik Poch
|
|
38 |
fe33e057
|
Dominik Poch
|
// States that says if modal windows should be shown.
|
39 |
|
|
const [showAddCategoryModal, setShowAddCategoryModal] = useState(false);
|
40 |
|
|
const [showEditCategoryModal, setShowEditCategoryModal] = useState(false);
|
41 |
|
|
const [showAddTagModal, setShowAddTagModal] = useState(false);
|
42 |
|
|
const [showEditTagModal, setShowEditTagModal] = useState(false);
|
43 |
|
|
const [showAddSubTagModal, setShowAddSubTagModal] = useState(false);
|
44 |
|
|
const [showEditSubTagModal, setShowEditSubTagModal] = useState(false);
|
45 |
|
|
|
46 |
|
|
/**
|
47 |
|
|
* Currently selected record in a table.
|
48 |
|
|
*/
|
49 |
|
|
const [selectedRecord, setSelectedRecord] = useState({
|
50 |
|
|
key: '',
|
51 |
|
|
name: '',
|
52 |
|
|
description: '',
|
53 |
|
|
color: '',
|
54 |
|
|
disabledForAnnotators: false,
|
55 |
|
|
});
|
56 |
|
|
|
57 |
|
|
/**
|
58 |
|
|
* Loads data on start.
|
59 |
|
|
*/
|
60 |
8c45ccb0
|
hrubyjar
|
useEffect(() => {
|
61 |
06d1aa21
|
Jaroslav Hrubý
|
if (!redirecting && role === 'ADMINISTRATOR') {
|
62 |
fe33e057
|
Dominik Poch
|
loadData();
|
63 |
8c45ccb0
|
hrubyjar
|
}
|
64 |
|
|
}, [logout, redirecting, role, router]);
|
65 |
|
|
|
66 |
fe33e057
|
Dominik Poch
|
/**
|
67 |
|
|
* Definition of columns of a table.
|
68 |
|
|
*/
|
69 |
cd2f7b19
|
Dominik Poch
|
const columns = [
|
70 |
|
|
{ title: 'Název', dataIndex: 'name', key: 'name' },
|
71 |
fe33e057
|
Dominik Poch
|
{
|
72 |
|
|
title: 'Barva',
|
73 |
|
|
dataIndex: 'color',
|
74 |
|
|
key: 'color',
|
75 |
|
|
width: 75,
|
76 |
|
|
render: (
|
77 |
|
|
columnData: string | null | undefined,
|
78 |
|
|
record: any,
|
79 |
|
|
index: number
|
80 |
|
|
) => {
|
81 |
|
|
if (columnData) return <input type="color" value={columnData} disabled />;
|
82 |
|
|
},
|
83 |
|
|
},
|
84 |
|
|
{
|
85 |
|
|
title: 'Skrýt pro anotátory',
|
86 |
|
|
dataIndex: 'disabledForAnnotators',
|
87 |
|
|
key: 'disabledForAnnotators',
|
88 |
|
|
width: 150,
|
89 |
|
|
render: (columnData: boolean | undefined, record: any, index: number) =>
|
90 |
|
|
columnData !== undefined && <Checkbox checked={columnData} disabled />,
|
91 |
|
|
},
|
92 |
cd2f7b19
|
Dominik Poch
|
{ title: 'Popis', dataIndex: 'description', key: 'description' },
|
93 |
fe33e057
|
Dominik Poch
|
{
|
94 |
|
|
title: '',
|
95 |
|
|
dataIndex: 'operations',
|
96 |
|
|
key: 'operations',
|
97 |
|
|
width: 200,
|
98 |
|
|
render: (text: any, record: any, index: number) => (
|
99 |
|
|
<Stack direction="horizontal">
|
100 |
|
|
{record.depth < 2 && (
|
101 |
|
|
<Button type="primary" onClick={addChild(record)}>
|
102 |
|
|
<PlusOutlined />
|
103 |
|
|
</Button>
|
104 |
|
|
)}
|
105 |
|
|
<Button type="text" onClick={editRecord(record)}>
|
106 |
|
|
<EditOutlined />
|
107 |
|
|
</Button>
|
108 |
|
|
<Popconfirm
|
109 |
|
|
title="Opravdu chcete smazat?"
|
110 |
|
|
onConfirm={deleteRecord(record)}
|
111 |
|
|
>
|
112 |
|
|
<Button type="primary" danger>
|
113 |
|
|
<DeleteOutlined />
|
114 |
|
|
</Button>
|
115 |
|
|
</Popconfirm>
|
116 |
|
|
</Stack>
|
117 |
|
|
),
|
118 |
|
|
},
|
119 |
cd2f7b19
|
Dominik Poch
|
];
|
120 |
|
|
|
121 |
fe33e057
|
Dominik Poch
|
// ------------------------------------ ADD CATEGORY MODAL -------------------------------------
|
122 |
|
|
|
123 |
|
|
const openAddCategoryModal = () => {
|
124 |
|
|
setShowAddCategoryModal(true);
|
125 |
|
|
};
|
126 |
|
|
|
127 |
|
|
const submitAddCategoryModal = (val: CategoryModalValues) => {
|
128 |
|
|
tagController
|
129 |
|
|
.categoriesPost({
|
130 |
|
|
name: val.name,
|
131 |
|
|
description: val.description,
|
132 |
|
|
color: val.color,
|
133 |
|
|
disabledForAnnotators: val.disabledForAnnotators,
|
134 |
|
|
})
|
135 |
|
|
.then(
|
136 |
|
|
() => loadData(),
|
137 |
|
|
(reason) => console.log(reason)
|
138 |
|
|
);
|
139 |
|
|
|
140 |
|
|
setShowAddCategoryModal(false);
|
141 |
|
|
};
|
142 |
|
|
|
143 |
|
|
const cancelAddCategoryModal = () => {
|
144 |
|
|
setShowAddCategoryModal(false);
|
145 |
|
|
};
|
146 |
|
|
|
147 |
|
|
// --------------------------------------------------------------------------------------------
|
148 |
|
|
|
149 |
|
|
// ------------------------------------ EDIT CATEGORY MODAL -----------------------------------
|
150 |
|
|
|
151 |
|
|
const openEditCategoryModal = () => {
|
152 |
|
|
setShowEditCategoryModal(true);
|
153 |
|
|
};
|
154 |
|
|
|
155 |
|
|
const submitEditCategoryModel = (val: CategoryModalValues) => {
|
156 |
|
|
tagController
|
157 |
|
|
.categoryCategoryIdPut(selectedRecord.key, {
|
158 |
|
|
name: val.name,
|
159 |
|
|
description: val.description,
|
160 |
|
|
color: val.color,
|
161 |
|
|
disabledForAnnotators: val.disabledForAnnotators,
|
162 |
|
|
})
|
163 |
|
|
.then(() => loadData());
|
164 |
|
|
|
165 |
|
|
setShowEditCategoryModal(false);
|
166 |
|
|
};
|
167 |
|
|
|
168 |
|
|
const cancelEditCategoryModal = () => {
|
169 |
|
|
setShowEditCategoryModal(false);
|
170 |
|
|
};
|
171 |
|
|
|
172 |
|
|
// --------------------------------------------------------------------------------------------
|
173 |
|
|
|
174 |
|
|
// ------------------------------------ ADD TAG MODAL -------------------------------------
|
175 |
|
|
|
176 |
|
|
const openAddTagModal = () => {
|
177 |
|
|
setShowAddTagModal(true);
|
178 |
|
|
};
|
179 |
|
|
|
180 |
|
|
const submitAddTagModal = (val: TagModalValues) => {
|
181 |
|
|
tagController
|
182 |
|
|
.tagsPost({
|
183 |
|
|
categoryId: selectedRecord.key,
|
184 |
|
|
name: val.name,
|
185 |
|
|
description: val.description,
|
186 |
|
|
color: val.color,
|
187 |
|
|
})
|
188 |
|
|
.then(() => loadData());
|
189 |
|
|
|
190 |
|
|
setShowAddTagModal(false);
|
191 |
|
|
};
|
192 |
|
|
|
193 |
|
|
const cancelAddTagModal = () => {
|
194 |
|
|
setShowAddTagModal(false);
|
195 |
|
|
};
|
196 |
|
|
|
197 |
|
|
// --------------------------------------------------------------------------------------------
|
198 |
|
|
|
199 |
|
|
// ------------------------------------ EDIT TAG MODAL -------------------------------------
|
200 |
|
|
|
201 |
|
|
const openEditTagModal = () => {
|
202 |
|
|
setShowEditTagModal(true);
|
203 |
|
|
};
|
204 |
|
|
|
205 |
|
|
const submitEditTagModal = (val: TagModalValues) => {
|
206 |
|
|
tagController
|
207 |
|
|
.tagTagIdPut(selectedRecord.key, {
|
208 |
|
|
name: val.name,
|
209 |
|
|
description: val.description,
|
210 |
|
|
color: val.color,
|
211 |
|
|
})
|
212 |
|
|
.then(() => loadData());
|
213 |
|
|
|
214 |
|
|
setShowEditTagModal(false);
|
215 |
|
|
};
|
216 |
|
|
|
217 |
|
|
const cancelEditTagModal = () => {
|
218 |
|
|
setShowEditTagModal(false);
|
219 |
|
|
};
|
220 |
|
|
|
221 |
|
|
// --------------------------------------------------------------------------------------------
|
222 |
|
|
|
223 |
|
|
// ------------------------------------ ADD SUB TAG MODAL -------------------------------------
|
224 |
|
|
|
225 |
|
|
const openAddSubTagModal = () => {
|
226 |
|
|
setShowAddSubTagModal(true);
|
227 |
|
|
};
|
228 |
|
|
|
229 |
|
|
const submitAddSubTagModal = (val: SubTagModalValues) => {
|
230 |
|
|
tagController
|
231 |
|
|
.subtagsPost({
|
232 |
|
|
tagId: selectedRecord.key,
|
233 |
|
|
name: val.name,
|
234 |
|
|
description: val.description,
|
235 |
|
|
})
|
236 |
|
|
.then(() => loadData());
|
237 |
|
|
|
238 |
|
|
setShowAddSubTagModal(false);
|
239 |
|
|
};
|
240 |
|
|
|
241 |
|
|
const cancelAddSubTagModal = () => {
|
242 |
|
|
setShowAddSubTagModal(false);
|
243 |
|
|
};
|
244 |
|
|
|
245 |
|
|
// --------------------------------------------------------------------------------------------
|
246 |
|
|
|
247 |
|
|
// ------------------------------------ EDIT SUB TAG MODAL -------------------------------------
|
248 |
|
|
|
249 |
|
|
const openEditSubTagModal = () => {
|
250 |
|
|
setShowEditSubTagModal(true);
|
251 |
|
|
};
|
252 |
|
|
|
253 |
|
|
const submitEditSubTagModal = (val: SubTagModalValues) => {
|
254 |
|
|
tagController
|
255 |
|
|
.subtagSubtagIdPut(selectedRecord.key, {
|
256 |
|
|
name: val.name,
|
257 |
|
|
description: val.description,
|
258 |
|
|
})
|
259 |
|
|
.then(() => loadData());
|
260 |
|
|
|
261 |
|
|
setShowEditSubTagModal(false);
|
262 |
|
|
};
|
263 |
|
|
|
264 |
|
|
const cancelEditSubTagModal = () => {
|
265 |
|
|
setShowEditSubTagModal(false);
|
266 |
|
|
};
|
267 |
|
|
|
268 |
|
|
// --------------------------------------------------------------------------------------------
|
269 |
|
|
|
270 |
|
|
// ------------------------------------ RECORD OPERATIONS -------------------------------------
|
271 |
|
|
|
272 |
|
|
const addChild = (record: any) => (e: any) => {
|
273 |
|
|
setSelectedRecord(record);
|
274 |
|
|
|
275 |
|
|
if (record.depth === 0) {
|
276 |
|
|
openAddTagModal();
|
277 |
|
|
} else if (record.depth === 1) {
|
278 |
|
|
openAddSubTagModal();
|
279 |
|
|
}
|
280 |
|
|
};
|
281 |
|
|
|
282 |
|
|
const editRecord = (record: any) => (e: any) => {
|
283 |
|
|
setSelectedRecord(record);
|
284 |
|
|
|
285 |
|
|
if (record.depth === 0) {
|
286 |
|
|
openEditCategoryModal();
|
287 |
|
|
} else if (record.depth === 1) {
|
288 |
|
|
openEditTagModal();
|
289 |
|
|
} else if (record.depth === 2) {
|
290 |
|
|
openEditSubTagModal();
|
291 |
|
|
}
|
292 |
|
|
};
|
293 |
|
|
|
294 |
|
|
const deleteRecord = (record: any) => (e: any) => {
|
295 |
|
|
if (record.depth === 0) {
|
296 |
|
|
tagController.categoryCategoryIdDelete(record.key).then(() => loadData());
|
297 |
|
|
} else if (record.depth === 1) {
|
298 |
|
|
tagController.tagTagIdDelete(record.key).then(() => loadData());
|
299 |
|
|
} else if (record.depth === 2) {
|
300 |
|
|
tagController.subtagSubtagIdDelete(record.key).then(() => loadData());
|
301 |
|
|
}
|
302 |
|
|
};
|
303 |
|
|
|
304 |
|
|
// --------------------------------------------------------------------------------------------
|
305 |
|
|
|
306 |
|
|
/**
|
307 |
|
|
* Loads data from a server.
|
308 |
|
|
*/
|
309 |
|
|
const loadData = () => {
|
310 |
|
|
tagController.tagsGet().then((tagTree) => {
|
311 |
|
|
if (typeof tagTree.data.tagCategories != 'undefined') {
|
312 |
41e65564
|
Dominik Poch
|
setTagData(tagTree.data.tagCategories);
|
313 |
|
|
setShownData(mapData(tagTree.data.tagCategories));
|
314 |
fe33e057
|
Dominik Poch
|
}
|
315 |
|
|
});
|
316 |
|
|
};
|
317 |
|
|
|
318 |
41e65564
|
Dominik Poch
|
const mapData = (data: any[] | null) => {
|
319 |
|
|
return data?.map((catInfo) => {
|
320 |
|
|
return {
|
321 |
|
|
key: catInfo.id,
|
322 |
|
|
name: catInfo.name,
|
323 |
|
|
description: catInfo.description,
|
324 |
|
|
color: catInfo.color,
|
325 |
|
|
depth: 0,
|
326 |
|
|
disabledForAnnotators: catInfo.disabledForAnnotators,
|
327 |
|
|
...(catInfo.tags?.length && {
|
328 |
|
|
children: catInfo.tags?.map((tagInfo: any) => {
|
329 |
|
|
return {
|
330 |
|
|
key: tagInfo.id,
|
331 |
|
|
name: tagInfo.name,
|
332 |
|
|
description: tagInfo.description,
|
333 |
|
|
color: tagInfo.color,
|
334 |
|
|
depth: 1,
|
335 |
|
|
...(tagInfo.subTags?.length && {
|
336 |
|
|
children: tagInfo.subTags?.map((subInfo: any) => {
|
337 |
|
|
return {
|
338 |
|
|
key: subInfo.id,
|
339 |
|
|
name: subInfo.name,
|
340 |
|
|
description: subInfo.description,
|
341 |
|
|
depth: 2,
|
342 |
|
|
};
|
343 |
|
|
}),
|
344 |
|
|
}),
|
345 |
|
|
};
|
346 |
|
|
}),
|
347 |
|
|
}),
|
348 |
|
|
};
|
349 |
|
|
});
|
350 |
|
|
};
|
351 |
|
|
|
352 |
fe33e057
|
Dominik Poch
|
/**
|
353 |
|
|
* Searches given value in 'name' column in a table.
|
354 |
|
|
* If the provided value is empty the method loads all data from a server.
|
355 |
|
|
* @param value Value that is searched.
|
356 |
|
|
*/
|
357 |
|
|
const searchTag = (value: string) => {
|
358 |
41e65564
|
Dominik Poch
|
let data = mapData(tagData);
|
359 |
|
|
|
360 |
|
|
if (value) {
|
361 |
|
|
data = data?.filter((category) => {
|
362 |
|
|
category.children = category.children?.filter((tag: any) => {
|
363 |
|
|
tag.children = tag.children?.filter((subTag: any) =>
|
364 |
|
|
subTag.name.toLowerCase().includes(value.toLowerCase())
|
365 |
|
|
);
|
366 |
|
|
|
367 |
|
|
return (
|
368 |
|
|
tag.children?.length > 0 ||
|
369 |
|
|
tag.name.toLowerCase().includes(value.toLowerCase())
|
370 |
|
|
);
|
371 |
|
|
});
|
372 |
fe33e057
|
Dominik Poch
|
|
373 |
41e65564
|
Dominik Poch
|
return (
|
374 |
|
|
category.children?.length > 0 ||
|
375 |
|
|
category.name.toLowerCase().includes(value.toLowerCase())
|
376 |
fe33e057
|
Dominik Poch
|
);
|
377 |
|
|
});
|
378 |
41e65564
|
Dominik Poch
|
}
|
379 |
fe33e057
|
Dominik Poch
|
|
380 |
41e65564
|
Dominik Poch
|
setShownData(data);
|
381 |
fe33e057
|
Dominik Poch
|
};
|
382 |
|
|
|
383 |
8c45ccb0
|
hrubyjar
|
return redirecting || role !== 'ADMINISTRATOR' ? null : (
|
384 |
|
|
<MainLayout>
|
385 |
fe33e057
|
Dominik Poch
|
{showAddCategoryModal && (
|
386 |
|
|
<CategoryModal
|
387 |
|
|
title="Vytvořit kategorii"
|
388 |
|
|
submitText="Přidat"
|
389 |
|
|
onCancel={cancelAddCategoryModal}
|
390 |
|
|
onSubmit={submitAddCategoryModal}
|
391 |
|
|
/>
|
392 |
|
|
)}
|
393 |
|
|
|
394 |
|
|
{showEditCategoryModal && (
|
395 |
|
|
<CategoryModal
|
396 |
|
|
title="Editovat kategorii"
|
397 |
|
|
submitText="Změnit"
|
398 |
|
|
onCancel={cancelEditCategoryModal}
|
399 |
|
|
onSubmit={submitEditCategoryModel}
|
400 |
|
|
defaultValues={selectedRecord}
|
401 |
|
|
/>
|
402 |
|
|
)}
|
403 |
|
|
|
404 |
|
|
{showAddTagModal && (
|
405 |
|
|
<TagModal
|
406 |
|
|
title="Vytvořit tag"
|
407 |
|
|
submitText="Přidat"
|
408 |
|
|
onCancel={cancelAddTagModal}
|
409 |
|
|
onSubmit={submitAddTagModal}
|
410 |
67683ef3
|
Dominik Poch
|
defaultValues={{ color: selectedRecord.color }}
|
411 |
fe33e057
|
Dominik Poch
|
/>
|
412 |
|
|
)}
|
413 |
|
|
|
414 |
|
|
{showEditTagModal && (
|
415 |
|
|
<TagModal
|
416 |
|
|
title="Editovat tag"
|
417 |
|
|
submitText="Změnit"
|
418 |
|
|
onCancel={cancelEditTagModal}
|
419 |
|
|
onSubmit={submitEditTagModal}
|
420 |
|
|
defaultValues={selectedRecord}
|
421 |
|
|
/>
|
422 |
|
|
)}
|
423 |
|
|
|
424 |
|
|
{showAddSubTagModal && (
|
425 |
|
|
<SubTagModal
|
426 |
|
|
title="Vytvořit sub tag"
|
427 |
|
|
submitText="Přidat"
|
428 |
|
|
onCancel={cancelAddSubTagModal}
|
429 |
|
|
onSubmit={submitAddSubTagModal}
|
430 |
|
|
/>
|
431 |
|
|
)}
|
432 |
|
|
|
433 |
|
|
{showEditSubTagModal && (
|
434 |
|
|
<SubTagModal
|
435 |
|
|
title="Editovat sub tag"
|
436 |
|
|
submitText="Změnit"
|
437 |
|
|
onCancel={cancelEditSubTagModal}
|
438 |
|
|
onSubmit={submitEditSubTagModal}
|
439 |
|
|
defaultValues={selectedRecord}
|
440 |
|
|
/>
|
441 |
|
|
)}
|
442 |
|
|
|
443 |
cd2f7b19
|
Dominik Poch
|
<Container>
|
444 |
|
|
<Row>
|
445 |
|
|
<Typography.Title level={2}>
|
446 |
|
|
<FontAwesomeIcon icon={faTags} /> Značky
|
447 |
|
|
</Typography.Title>
|
448 |
|
|
</Row>
|
449 |
|
|
<Row>
|
450 |
|
|
<Table
|
451 |
|
|
columns={columns}
|
452 |
41e65564
|
Dominik Poch
|
dataSource={shownData}
|
453 |
cd2f7b19
|
Dominik Poch
|
scroll={{ y: 'calc(100vh - 300px)' }}
|
454 |
fe33e057
|
Dominik Poch
|
size="small"
|
455 |
|
|
title={() => (
|
456 |
|
|
<Stack direction="horizontal" gap={3}>
|
457 |
|
|
<Button type="primary" onClick={openAddCategoryModal}>
|
458 |
|
|
Přidat kategorii
|
459 |
|
|
</Button>
|
460 |
|
|
<Search
|
461 |
|
|
placeholder="Vyhledání tagu"
|
462 |
|
|
onSearch={searchTag}
|
463 |
|
|
style={{ width: 200 }}
|
464 |
|
|
className="ms-auto"
|
465 |
8755ddb8
|
Dominik Poch
|
allowClear
|
466 |
fe33e057
|
Dominik Poch
|
/>
|
467 |
|
|
</Stack>
|
468 |
|
|
)}
|
469 |
cd2f7b19
|
Dominik Poch
|
/>
|
470 |
|
|
</Row>
|
471 |
|
|
</Container>
|
472 |
8c45ccb0
|
hrubyjar
|
</MainLayout>
|
473 |
|
|
);
|
474 |
|
|
}
|
475 |
|
|
|
476 |
|
|
export default TagsPage;
|