Revize 669ffe38
Přidáno uživatelem Jaroslav Hrubý před téměř 3 roky(ů)
webapp/api/api.ts | ||
---|---|---|
793 | 793 |
* @memberof UserInfo |
794 | 794 |
*/ |
795 | 795 |
'assignedDocumentsCount'?: number; |
796 |
/** |
|
797 |
* |
|
798 |
* @type {ERole} |
|
799 |
* @memberof UserInfo |
|
800 |
*/ |
|
801 |
'role'?: ERole; |
|
796 | 802 |
} |
797 | 803 |
/** |
798 | 804 |
* |
webapp/components/modals/AddUserModal.tsx | ||
---|---|---|
62 | 62 |
visible={true} |
63 | 63 |
onCancel={handleCancel} |
64 | 64 |
footer={null} |
65 |
centered |
|
65 | 66 |
> |
66 | 67 |
<Form |
67 | 68 |
onFinish={onFinish} |
webapp/components/modals/AssignDocumentModal.tsx | ||
---|---|---|
5 | 5 |
import { annotationController, userController } from '../../controllers'; |
6 | 6 |
import { useUnauthRedirect } from '../../hooks'; |
7 | 7 |
import { LoggedUserContext } from '../../contexts/LoggedUserContext'; |
8 |
import { ShowToast } from '../../utils/alerts'; |
|
8 | 9 |
|
9 | 10 |
interface ModalProps { |
10 | 11 |
onCancel: () => void; |
... | ... | |
37 | 38 |
const handleUpload = async () => { |
38 | 39 |
// console.log(documentsIds, selectedUsers); |
39 | 40 |
if (documentsIds && documentsIds.length !== 0) { |
40 |
await annotationController.annotationsPost({ |
|
41 |
// @ts-ignore |
|
42 |
documentIdList: documentsIds, |
|
43 |
userIdList: selectedUsers, |
|
44 |
}); |
|
41 |
await annotationController |
|
42 |
.annotationsPost({ |
|
43 |
// @ts-ignore |
|
44 |
documentIdList: documentsIds, |
|
45 |
userIdList: selectedUsers, |
|
46 |
}) |
|
47 |
.then((r) => { |
|
48 |
ShowToast( |
|
49 |
'Přiřazení dokumentů proběhlo úspěšně', |
|
50 |
'success', |
|
51 |
3000, |
|
52 |
'top-end' |
|
53 |
); |
|
54 |
onCancel(); |
|
55 |
}); |
|
45 | 56 |
} |
46 |
onCancel(); |
|
47 | 57 |
}; |
48 | 58 |
|
49 | 59 |
function onChange(event: any) { |
webapp/components/modals/EditUserModal.tsx | ||
---|---|---|
38 | 38 |
role: values.role, |
39 | 39 |
}; |
40 | 40 |
if (userInfo.id) { |
41 |
await userController.userUserIdPut(userInfo.id, req); |
|
41 |
userController.userUserIdPut(userInfo.id, req).then((r) => { |
|
42 |
ShowToast( |
|
43 |
'Úprava uživatele proběhla úspěšně', |
|
44 |
'success', |
|
45 |
3000, |
|
46 |
'top-end' |
|
47 |
); |
|
48 |
onCancel(); |
|
49 |
}); |
|
42 | 50 |
} |
43 |
onCancel(); |
|
44 | 51 |
}; |
45 | 52 |
|
46 | 53 |
const onFinishFailed = (errorInfo: any) => { |
... | ... | |
54 | 61 |
visible={true} |
55 | 62 |
onCancel={handleCancel} |
56 | 63 |
footer={null} |
64 |
centered |
|
57 | 65 |
> |
58 | 66 |
<Form |
59 | 67 |
onFinish={onFinish} |
... | ... | |
62 | 70 |
labelCol={{ span: 4 }} |
63 | 71 |
layout="horizontal" |
64 | 72 |
> |
65 |
<Form.Item |
|
66 |
label="" |
|
67 |
name="role" |
|
68 |
rules={[ |
|
69 |
{ |
|
70 |
required: true, |
|
71 |
message: 'Prosím vyberte roli', |
|
72 |
}, |
|
73 |
]} |
|
74 |
> |
|
75 |
<Radio.Group> |
|
73 |
<Form.Item label="" name="role"> |
|
74 |
<Radio.Group defaultValue={userInfo.role}> |
|
76 | 75 |
<Radio.Button value={ERole.Annotator}>Anotátor</Radio.Button> |
77 | 76 |
<Radio.Button value={ERole.Administrator}>Admin</Radio.Button> |
78 | 77 |
</Radio.Group> |
... | ... | |
89 | 88 |
> |
90 | 89 |
<Input placeholder="Uživatelské jméno" /> |
91 | 90 |
</Form.Item> |
92 |
<Form.Item |
|
93 |
name="password" |
|
94 |
rules={[{ required: true, message: 'Prosím zadejte heslo' }]} |
|
95 |
> |
|
96 |
<Input.Password placeholder="Heslo" /> |
|
97 |
</Form.Item> |
|
98 |
|
|
99 | 91 |
<Form.Item |
100 | 92 |
name="name" |
101 | 93 |
initialValue={userInfo.name} |
webapp/components/modals/EditUserPasswordModal.tsx | ||
---|---|---|
1 |
import { Button, Form, Input, Modal, Radio } from 'antd'; |
|
2 |
import 'antd/dist/antd.css'; |
|
3 |
import React from 'react'; |
|
4 |
import { ChangeUserInfoRequest, ERole, UserInfo } from '../../api'; |
|
5 |
import { userController } from '../../controllers'; |
|
6 |
import { ShowToast } from '../../utils/alerts'; |
|
7 |
|
|
8 |
interface ModalProps { |
|
9 |
onCancel: () => void; |
|
10 |
userInfo: UserInfo; |
|
11 |
} |
|
12 |
|
|
13 |
/** |
|
14 |
* Creates a modal window that loads documents to the app. |
|
15 |
* @returns The modal window. |
|
16 |
*/ |
|
17 |
const EditUserModal: React.FC<ModalProps> = ({ onCancel, userInfo }: ModalProps) => { |
|
18 |
/** |
|
19 |
* Handles successful closing of the modal window. |
|
20 |
*/ |
|
21 |
const handleOk = () => { |
|
22 |
onCancel(); |
|
23 |
}; |
|
24 |
|
|
25 |
/** |
|
26 |
* Handles cancelling of the model window. |
|
27 |
*/ |
|
28 |
const handleCancel = () => { |
|
29 |
onCancel(); |
|
30 |
}; |
|
31 |
|
|
32 |
const onFinish = async (values: any) => { |
|
33 |
const req: ChangeUserInfoRequest = { |
|
34 |
password: values.password, |
|
35 |
}; |
|
36 |
if (userInfo.id) { |
|
37 |
userController.userUserIdPut(userInfo.id, req).then((r) => { |
|
38 |
ShowToast('Heslo úspěšně změněno', 'success', 3000, 'top-end'); |
|
39 |
onCancel(); |
|
40 |
}); |
|
41 |
} |
|
42 |
}; |
|
43 |
|
|
44 |
const onFinishFailed = (errorInfo: any) => { |
|
45 |
ShowToast('Zadané údaje nejsou validní', 'error', 3000, 'top-end'); |
|
46 |
}; |
|
47 |
|
|
48 |
return ( |
|
49 |
<Modal |
|
50 |
title={'Změnit heslo uživatele ' + userInfo.username} |
|
51 |
onOk={handleOk} |
|
52 |
visible={true} |
|
53 |
onCancel={handleCancel} |
|
54 |
footer={null} |
|
55 |
width={400} |
|
56 |
centered |
|
57 |
> |
|
58 |
<Form |
|
59 |
onFinish={onFinish} |
|
60 |
onFinishFailed={onFinishFailed} |
|
61 |
autoComplete="off" |
|
62 |
labelCol={{ span: 4 }} |
|
63 |
layout="horizontal" |
|
64 |
> |
|
65 |
<Form.Item |
|
66 |
name="password" |
|
67 |
rules={[{ required: true, message: 'Prosím zadejte heslo' }]} |
|
68 |
> |
|
69 |
<Input.Password placeholder="Heslo" /> |
|
70 |
</Form.Item> |
|
71 |
<Form.Item> |
|
72 |
<Button type="primary" htmlType="submit" className="w-100"> |
|
73 |
Změnit |
|
74 |
</Button> |
|
75 |
</Form.Item> |
|
76 |
</Form> |
|
77 |
</Modal> |
|
78 |
); |
|
79 |
}; |
|
80 |
|
|
81 |
export default EditUserModal; |
webapp/components/modals/UserDocumentsModal.tsx | ||
---|---|---|
1 |
import { List, Modal } from 'antd'; |
|
1 |
import { List, Modal, Skeleton, Tag } from 'antd';
|
|
2 | 2 |
import 'antd/dist/antd.css'; |
3 | 3 |
import React, { useContext, useEffect, useState } from 'react'; |
4 |
import { AnnotationListInfo } from '../../api'; |
|
4 |
import { AnnotationListInfo, EState } from '../../api';
|
|
5 | 5 |
import { userController } from '../../controllers'; |
6 | 6 |
import { useUnauthRedirect } from '../../hooks'; |
7 | 7 |
import { LoggedUserContext } from '../../contexts/LoggedUserContext'; |
8 |
import { |
|
9 |
CheckCircleOutlined, |
|
10 |
ClockCircleOutlined, |
|
11 |
SyncOutlined, |
|
12 |
} from '@ant-design/icons'; |
|
8 | 13 |
|
9 | 14 |
interface ModalProps { |
10 | 15 |
onCancel: () => void; |
... | ... | |
24 | 29 |
onCancel(); |
25 | 30 |
}; |
26 | 31 |
|
32 |
const getStateTag = (state: EState) => { |
|
33 |
let color = 'green'; |
|
34 |
let label = 'Hotovo'; |
|
35 |
let icon = <CheckCircleOutlined />; |
|
36 |
if (state === EState.New) { |
|
37 |
color = 'volcano'; |
|
38 |
label = 'Nový'; |
|
39 |
icon = <ClockCircleOutlined />; |
|
40 |
} |
|
41 |
if (state === EState.InProgress) { |
|
42 |
color = 'orange'; |
|
43 |
label = 'Rozpracováno'; |
|
44 |
icon = <SyncOutlined spin />; |
|
45 |
} |
|
46 |
return ( |
|
47 |
<Tag icon={icon} color={color} key={label}> |
|
48 |
{label.toUpperCase()} |
|
49 |
</Tag> |
|
50 |
); |
|
51 |
}; |
|
52 |
|
|
27 | 53 |
useEffect(() => { |
28 | 54 |
if (!redirecting && role === 'ADMINISTRATOR' && userId) { |
29 | 55 |
userController |
... | ... | |
49 | 75 |
style={{ maxHeight: 400, overflowY: 'auto', overflowX: 'hidden' }} |
50 | 76 |
renderItem={(item) => ( |
51 | 77 |
<List.Item key={item.annotationId}> |
52 |
<List.Item.Meta title={item.documentName} /> |
|
78 |
<List.Item.Meta |
|
79 |
title={item.documentName} |
|
80 |
// @ts-ignore |
|
81 |
avatar={getStateTag(item.state)} |
|
82 |
/> |
|
53 | 83 |
</List.Item> |
54 | 84 |
)} |
55 | 85 |
/> |
webapp/pages/_app.tsx | ||
---|---|---|
1 | 1 |
// @ts-nocheck |
2 | 2 |
import '../styles/globals.css'; |
3 |
import 'bootstrap/dist/css/bootstrap.css'; |
|
4 | 3 |
import '@fortawesome/fontawesome-svg-core/styles.css'; |
5 | 4 |
|
6 | 5 |
import type { AppProps } from 'next/app'; |
webapp/pages/documents/admin/index.tsx | ||
---|---|---|
3 | 3 |
|
4 | 4 |
import { useUnauthRedirect } from '../../../hooks'; |
5 | 5 |
import { useRouter } from 'next/router'; |
6 |
import { Button, Input, Space, Table, Typography } from 'antd';
|
|
6 |
import { Button, Space, Table, Typography } from 'antd'; |
|
7 | 7 |
import { faFileLines, faUser } from '@fortawesome/free-solid-svg-icons'; |
8 | 8 |
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
9 | 9 |
import { LoggedUserContext } from '../../../contexts/LoggedUserContext'; |
... | ... | |
12 | 12 |
import { DocumentListInfo, UserInfo } from '../../../api'; |
13 | 13 |
import { documentController, userController } from '../../../controllers'; |
14 | 14 |
import AssignDocumentModal from '../../../components/modals/AssignDocumentModal'; |
15 |
import { ShowToast } from '../../../utils/alerts'; |
|
15 |
import { ShowConfirmDelete, ShowToast } from '../../../utils/alerts';
|
|
16 | 16 |
import { TableDocInfo } from '../../../components/types/TableDocInfo'; |
17 |
import { SearchOutlined } from '@ant-design/icons'; |
|
18 | 17 |
import { UserFilter } from '../../../components/types/UserFilter'; |
18 |
import { getColumnSearchProps, getLocaleProps } from '../../../utils/tableUtils'; |
|
19 |
import { UserOutlined } from '@ant-design/icons'; |
|
20 |
import Swal from 'sweetalert2'; |
|
19 | 21 |
|
20 | 22 |
function AdminDocumentPage() { |
21 | 23 |
const redirecting = useUnauthRedirect('/login'); |
... | ... | |
76 | 78 |
setVisibleAssign(false); |
77 | 79 |
}; |
78 | 80 |
|
79 |
const handleSearch = ( |
|
80 |
selectedKeys: React.SetStateAction<string>[], |
|
81 |
confirm: () => void |
|
82 |
) => { |
|
83 |
confirm(); |
|
84 |
}; |
|
85 |
|
|
86 |
const handleReset = (clearFilters: () => void) => { |
|
87 |
clearFilters(); |
|
81 |
const removeUserDocument = (userId: string, documentId: string) => { |
|
82 |
Swal.fire({ |
|
83 |
title: 'Opravdu si přejete uživateli odebrat dokument?', |
|
84 |
showCancelButton: true, |
|
85 |
confirmButtonText: 'Odebrat', |
|
86 |
}).then((result) => { |
|
87 |
// TODO call API |
|
88 |
}); |
|
88 | 89 |
}; |
89 | 90 |
|
90 |
const getColumnSearchProps = (dataIndex: string, searchLabel: string) => ({ |
|
91 |
// @ts-ignore |
|
92 |
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( |
|
93 |
<div style={{ padding: 8 }}> |
|
94 |
<Input |
|
95 |
placeholder={`Vyhledat ${searchLabel}`} |
|
96 |
value={selectedKeys[0]} |
|
97 |
onChange={(e) => |
|
98 |
setSelectedKeys(e.target.value ? [e.target.value] : []) |
|
99 |
} |
|
100 |
onPressEnter={() => handleSearch(selectedKeys, confirm)} |
|
101 |
style={{ marginBottom: 8, display: 'block' }} |
|
102 |
/> |
|
103 |
<Space> |
|
104 |
<Button |
|
105 |
type="primary" |
|
106 |
onClick={() => handleSearch(selectedKeys, confirm)} |
|
107 |
icon={<SearchOutlined />} |
|
108 |
style={{ width: 90 }} |
|
109 |
> |
|
110 |
Hledat |
|
111 |
</Button> |
|
112 |
<Button |
|
113 |
onClick={() => handleReset(clearFilters)} |
|
114 |
style={{ width: 90 }} |
|
115 |
> |
|
116 |
Smazat |
|
117 |
</Button> |
|
118 |
</Space> |
|
119 |
</div> |
|
120 |
), |
|
121 |
filterIcon: (filtered: any) => ( |
|
122 |
<SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} /> |
|
123 |
), |
|
124 |
onFilter: (value: string, record: { [x: string]: { toString: () => string } }) => |
|
125 |
record[dataIndex] |
|
126 |
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
|
127 |
: '', |
|
128 |
}); |
|
129 |
|
|
130 | 91 |
const columns = [ |
131 | 92 |
{ |
132 | 93 |
title: 'Název dokumentu', |
133 | 94 |
dataIndex: 'name', |
134 | 95 |
key: 'name', |
135 | 96 |
...getColumnSearchProps('name', 'název'), |
97 |
sorter: { |
|
98 |
// @ts-ignore |
|
99 |
compare: (a, b) => a.name.localeCompare(b.name), |
|
100 |
multiple: 2, |
|
101 |
}, |
|
136 | 102 |
}, |
137 | 103 |
{ |
138 | 104 |
title: 'Délka', |
... | ... | |
143 | 109 |
compare: (a, b) => a.length - b.length, |
144 | 110 |
multiple: 1, |
145 | 111 |
}, |
146 |
sortDirections: ['descend', 'ascend'], |
|
147 | 112 |
}, |
148 | 113 |
{ |
149 | 114 |
title: 'Anotátoři', |
... | ... | |
152 | 117 |
render: (columnData: UserInfo[], record: DocumentListInfo, index: number) => { |
153 | 118 |
return ( |
154 | 119 |
<div> |
155 |
{columnData.map((e) => ( |
|
156 |
<span |
|
157 |
key={e.username + '.' + record.id} |
|
158 |
title={e.username ?? ''} |
|
159 |
> |
|
160 |
<FontAwesomeIcon |
|
161 |
icon={faUser} |
|
120 |
<Space> |
|
121 |
{columnData.map((e) => ( |
|
122 |
<span |
|
123 |
key={e.username + '.' + record.id} |
|
162 | 124 |
title={e.username ?? ''} |
163 |
className={'me-2'} |
|
164 |
/> |
|
165 |
</span> |
|
166 |
))} |
|
125 |
> |
|
126 |
<FontAwesomeIcon |
|
127 |
icon={faUser} |
|
128 |
size={'2x'} |
|
129 |
onClick={() => |
|
130 |
// @ts-ignore |
|
131 |
removeUserDocument(e.id, record.id) |
|
132 |
} |
|
133 |
title={e.username ?? ''} |
|
134 |
className={'me-2'} |
|
135 |
/> |
|
136 |
</span> |
|
137 |
))} |
|
138 |
</Space> |
|
167 | 139 |
</div> |
168 | 140 |
); |
169 | 141 |
}, |
... | ... | |
176 | 148 |
sorter: { |
177 | 149 |
// @ts-ignore |
178 | 150 |
compare: (a, b) => a.annotatingUsers.length - b.annotatingUsers.length, |
179 |
multiple: 1,
|
|
151 |
multiple: 3,
|
|
180 | 152 |
}, |
181 |
sortDirections: ['descend', 'ascend'], |
|
182 | 153 |
}, |
183 | 154 |
]; |
184 | 155 |
|
... | ... | |
204 | 175 |
)} |
205 | 176 |
|
206 | 177 |
<Table |
207 |
locale={{ |
|
208 |
filterSearchPlaceholder: 'Hledat uživatele', |
|
209 |
triggerDesc: 'Seřadit sestupně', |
|
210 |
triggerAsc: 'Seřadit vzestupně', |
|
211 |
cancelSort: 'Vypnout řazení', |
|
212 |
}} |
|
178 |
locale={{ ...getLocaleProps() }} |
|
213 | 179 |
rowSelection={{ |
214 | 180 |
type: 'checkbox', |
215 | 181 |
...rowSelection, |
webapp/pages/documents/annotator/index.tsx | ||
---|---|---|
13 | 13 |
import { |
14 | 14 |
CheckCircleOutlined, |
15 | 15 |
ClockCircleOutlined, |
16 |
SearchOutlined, |
|
17 | 16 |
SyncOutlined, |
18 | 17 |
} from '@ant-design/icons'; |
18 |
import { getColumnSearchProps, getLocaleProps } from '../../../utils/tableUtils'; |
|
19 | 19 |
|
20 | 20 |
function UserDocumentPage() { |
21 | 21 |
const redirecting = useUnauthRedirect('/login'); |
... | ... | |
39 | 39 |
} |
40 | 40 |
}, [logout, redirecting, role, router]); |
41 | 41 |
|
42 |
const handleSearch = ( |
|
43 |
selectedKeys: React.SetStateAction<string>[], |
|
44 |
confirm: () => void |
|
45 |
) => { |
|
46 |
confirm(); |
|
47 |
}; |
|
48 |
|
|
49 |
const handleReset = (clearFilters: () => void) => { |
|
50 |
clearFilters(); |
|
51 |
}; |
|
52 |
|
|
53 |
const getColumnSearchProps = (dataIndex: string, searchLabel: string) => ({ |
|
54 |
// @ts-ignore |
|
55 |
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( |
|
56 |
<div style={{ padding: 8 }}> |
|
57 |
<Input |
|
58 |
placeholder={`Vyhledat ${searchLabel}`} |
|
59 |
value={selectedKeys[0]} |
|
60 |
onChange={(e) => |
|
61 |
setSelectedKeys(e.target.value ? [e.target.value] : []) |
|
62 |
} |
|
63 |
onPressEnter={() => handleSearch(selectedKeys, confirm)} |
|
64 |
style={{ marginBottom: 8, display: 'block' }} |
|
65 |
/> |
|
66 |
<Space> |
|
67 |
<Button |
|
68 |
type="primary" |
|
69 |
onClick={() => handleSearch(selectedKeys, confirm)} |
|
70 |
icon={<SearchOutlined />} |
|
71 |
style={{ width: 90 }} |
|
72 |
> |
|
73 |
Hledat |
|
74 |
</Button> |
|
75 |
<Button |
|
76 |
onClick={() => handleReset(clearFilters)} |
|
77 |
style={{ width: 90 }} |
|
78 |
> |
|
79 |
Smazat |
|
80 |
</Button> |
|
81 |
</Space> |
|
82 |
</div> |
|
83 |
), |
|
84 |
filterIcon: (filtered: any) => ( |
|
85 |
<SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} /> |
|
86 |
), |
|
87 |
onFilter: (value: string, record: { [x: string]: { toString: () => string } }) => |
|
88 |
record[dataIndex] |
|
89 |
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
|
90 |
: '', |
|
91 |
}); |
|
92 |
|
|
93 | 42 |
const columns = [ |
94 | 43 |
{ |
95 | 44 |
title: 'Název dokumentu', |
96 | 45 |
dataIndex: 'documentName', |
97 | 46 |
key: 'documentName', |
98 | 47 |
...getColumnSearchProps('documentName', 'název'), |
48 |
// @ts-ignore |
|
49 |
sorter: (a, b) => a.documentName.localeCompare(b.documentName), |
|
99 | 50 |
}, |
100 | 51 |
{ |
101 | 52 |
title: 'Stav anotace', |
... | ... | |
163 | 114 |
<FontAwesomeIcon icon={faFileLines} /> Dokumenty |
164 | 115 |
</Typography.Title> |
165 | 116 |
<Table |
166 |
locale={{ |
|
167 |
triggerDesc: 'Seřadit sestupně', |
|
168 |
triggerAsc: 'Seřadit vzestupně', |
|
169 |
cancelSort: 'Vypnout řazení', |
|
170 |
}} |
|
117 |
locale={{ ...getLocaleProps() }} |
|
171 | 118 |
// @ts-ignore |
172 | 119 |
columns={columns} |
173 | 120 |
// @ts-ignore |
webapp/pages/users/index.tsx | ||
---|---|---|
3 | 3 |
|
4 | 4 |
import { useUnauthRedirect } from '../../hooks'; |
5 | 5 |
import { useRouter } from 'next/router'; |
6 |
import { Badge, Button, Input, Space, Table, Typography } from 'antd';
|
|
6 |
import { Badge, Button, Dropdown, Menu, Space, Table, Typography } from 'antd';
|
|
7 | 7 |
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
8 | 8 |
import { faUsers } from '@fortawesome/free-solid-svg-icons'; |
9 | 9 |
import { LoggedUserContext } from '../../contexts/LoggedUserContext'; |
... | ... | |
12 | 12 |
import { userController } from '../../controllers'; |
13 | 13 |
import AddUserModal from '../../components/modals/AddUserModal'; |
14 | 14 |
import UserDocumentsModal from '../../components/modals/UserDocumentsModal'; |
15 |
import { ShowConfirmDelete } from '../../utils/alerts'; |
|
15 |
import { ShowConfirmDelete, ShowToast } from '../../utils/alerts';
|
|
16 | 16 |
import EditUserModal from '../../components/modals/EditUserModal'; |
17 |
import { SearchOutlined } from '@ant-design/icons'; |
|
17 |
import { DeleteOutlined, EditOutlined, LockOutlined } from '@ant-design/icons'; |
|
18 |
import { getColumnSearchProps, getLocaleProps } from '../../utils/tableUtils'; |
|
19 |
import EditUserPasswordModal from '../../components/modals/EditUserPasswordModal'; |
|
18 | 20 |
|
19 | 21 |
function UsersPage() { |
20 | 22 |
const redirecting = useUnauthRedirect('/login'); |
... | ... | |
23 | 25 |
|
24 | 26 |
const [addVisible, setAddVisible] = React.useState(false); |
25 | 27 |
const [editVisible, setEditVisible] = React.useState(false); |
28 |
const [passwordEditVisible, setPasswordEditVisible] = React.useState(false); |
|
26 | 29 |
const [docsVisible, setDocsVisible] = React.useState(false); |
27 | 30 |
const [users, setUsers] = useState<UserInfo[]>([]); |
28 | 31 |
|
... | ... | |
52 | 55 |
|
53 | 56 |
const deleteUser = (userId: string) => { |
54 | 57 |
ShowConfirmDelete(() => { |
55 |
userController.userUserIdDelete(userId).then(() => fetchData()); |
|
58 |
userController.userUserIdDelete(userId).then(() => { |
|
59 |
ShowToast('Uživatel úspěšně odstraněn', 'success', 3000, 'top-end'); |
|
60 |
fetchData(); |
|
61 |
}); |
|
56 | 62 |
}, 'uživatele'); |
57 | 63 |
}; |
58 | 64 |
|
... | ... | |
63 | 69 |
setUserInfo(userId); |
64 | 70 |
setEditVisible(true); |
65 | 71 |
}; |
72 |
const showEditPasswordModal = (userId: string) => { |
|
73 |
setUserInfo(userId); |
|
74 |
setPasswordEditVisible(true); |
|
75 |
}; |
|
66 | 76 |
const showDocsModal = (userId: string) => { |
67 | 77 |
setUserInfo(userId); |
68 | 78 |
setDocsVisible(true); |
... | ... | |
73 | 83 |
setAddVisible(false); |
74 | 84 |
setDocsVisible(false); |
75 | 85 |
setEditVisible(false); |
86 |
setPasswordEditVisible(false); |
|
76 | 87 |
}; |
77 | 88 |
|
78 |
const handleSearch = ( |
|
79 |
selectedKeys: React.SetStateAction<string>[], |
|
80 |
confirm: () => void |
|
81 |
) => { |
|
82 |
confirm(); |
|
83 |
}; |
|
84 |
|
|
85 |
const handleReset = (clearFilters: () => void) => { |
|
86 |
clearFilters(); |
|
87 |
}; |
|
88 |
|
|
89 |
const getColumnSearchProps = (dataIndex: string, searchLabel: string) => ({ |
|
90 |
// @ts-ignore |
|
91 |
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( |
|
92 |
<div style={{ padding: 8 }}> |
|
93 |
<Input |
|
94 |
placeholder={`Vyhledat ${searchLabel}`} |
|
95 |
value={selectedKeys[0]} |
|
96 |
onChange={(e) => |
|
97 |
setSelectedKeys(e.target.value ? [e.target.value] : []) |
|
98 |
} |
|
99 |
onPressEnter={() => handleSearch(selectedKeys, confirm)} |
|
100 |
style={{ marginBottom: 8, display: 'block' }} |
|
101 |
/> |
|
102 |
<Space> |
|
103 |
<Button |
|
104 |
type="primary" |
|
105 |
onClick={() => handleSearch(selectedKeys, confirm)} |
|
106 |
icon={<SearchOutlined />} |
|
107 |
style={{ width: 90 }} |
|
108 |
> |
|
109 |
Hledat |
|
110 |
</Button> |
|
111 |
<Button |
|
112 |
onClick={() => handleReset(clearFilters)} |
|
113 |
style={{ width: 90 }} |
|
114 |
> |
|
115 |
Smazat |
|
116 |
</Button> |
|
117 |
</Space> |
|
118 |
</div> |
|
119 |
), |
|
120 |
filterIcon: (filtered: any) => ( |
|
121 |
<SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} /> |
|
122 |
), |
|
123 |
onFilter: (value: string, record: { [x: string]: { toString: () => string } }) => |
|
124 |
record[dataIndex] |
|
125 |
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
|
126 |
: '', |
|
127 |
}); |
|
89 |
const menu = (id: string) => ( |
|
90 |
<Menu> |
|
91 |
<Menu.Item |
|
92 |
icon={<EditOutlined />} |
|
93 |
key={'edit'} |
|
94 |
onClick={() => showEditModal(id)} |
|
95 |
> |
|
96 |
Upravit |
|
97 |
</Menu.Item> |
|
98 |
<Menu.Item icon={<LockOutlined />} onClick={() => showEditPasswordModal(id)}> |
|
99 |
Změnit Heslo |
|
100 |
</Menu.Item> |
|
101 |
<Menu.Item icon={<DeleteOutlined />} onClick={() => deleteUser(id)}> |
|
102 |
Smazat |
|
103 |
</Menu.Item> |
|
104 |
</Menu> |
|
105 |
); |
|
128 | 106 |
|
129 | 107 |
const columns = [ |
130 | 108 |
{ |
... | ... | |
132 | 110 |
dataIndex: 'name', |
133 | 111 |
key: 'name', |
134 | 112 |
...getColumnSearchProps('name', 'jméno'), |
113 |
// @ts-ignore |
|
114 |
sorter: (a, b) => a.name.localeCompare(b.name), |
|
135 | 115 |
}, |
136 | 116 |
{ |
137 | 117 |
title: 'Příjmení', |
138 | 118 |
dataIndex: 'surname', |
139 | 119 |
key: 'surname', |
140 | 120 |
...getColumnSearchProps('surname', 'příjmení'), |
121 |
// @ts-ignore |
|
122 |
sorter: (a, b) => a.surname.localeCompare(b.surname), |
|
141 | 123 |
}, |
142 | 124 |
{ |
143 | 125 |
title: 'Uživatelské jméno', |
144 | 126 |
dataIndex: 'username', |
145 | 127 |
key: 'username', |
146 | 128 |
...getColumnSearchProps('username', 'uživatelské jméno'), |
129 |
// @ts-ignore |
|
130 |
sorter: (a, b) => a.username.localeCompare(b.username), |
|
131 |
}, |
|
132 |
{ |
|
133 |
title: 'Role', |
|
134 |
dataIndex: 'role', |
|
135 |
key: 'role', |
|
136 |
filters: [ |
|
137 |
{ |
|
138 |
text: 'Administrátor', |
|
139 |
value: 'ADMINISTRATOR', |
|
140 |
}, |
|
141 |
{ |
|
142 |
text: 'Anotátor', |
|
143 |
value: 'ANNOTATOR', |
|
144 |
}, |
|
145 |
], |
|
146 |
// @ts-ignore |
|
147 |
onFilter: (value, record) => record.role.indexOf(value) === 0, |
|
148 |
// @ts-ignore |
|
149 |
sorter: (a, b) => a.username.localeCompare(b.username), |
|
150 |
render: (role: string) => { |
|
151 |
let label = ''; |
|
152 |
if (role === 'ANNOTATOR') { |
|
153 |
label = 'Anotátor'; |
|
154 |
} |
|
155 |
if (role === 'ADMINISTRATOR') { |
|
156 |
label = 'Administrátor'; |
|
157 |
} |
|
158 |
return label; |
|
159 |
}, |
|
147 | 160 |
}, |
148 | 161 |
{ |
149 | 162 |
title: 'Přiřazeno dokumentů', |
... | ... | |
169 | 182 |
title: '', |
170 | 183 |
key: 'operations', |
171 | 184 |
dataIndex: 'id', |
172 |
align: 'center' as 'center', |
|
185 |
align: 'right' as 'right', |
|
186 |
width: '150px', |
|
173 | 187 |
// @ts-ignore |
174 | 188 |
render: (id) => ( |
175 | 189 |
<div> |
176 | 190 |
<Space size={'middle'}> |
177 |
<Button onClick={() => showEditModal(id)}>Upravit</Button>
|
|
178 |
<Button onClick={() => deleteUser(id)}>Smazat</Button>
|
|
191 |
{/*{menu(id)}*/}
|
|
192 |
{<Dropdown.Button overlay={menu(id)} placement="bottom" />}
|
|
179 | 193 |
</Space> |
180 | 194 |
</div> |
181 | 195 |
), |
... | ... | |
193 | 207 |
</Button> |
194 | 208 |
{addVisible && <AddUserModal onCancel={hideModals} />} |
195 | 209 |
{editVisible && <EditUserModal userInfo={user} onCancel={hideModals} />} |
210 |
{passwordEditVisible && ( |
|
211 |
<EditUserPasswordModal userInfo={user} onCancel={hideModals} /> |
|
212 |
)} |
|
196 | 213 |
{docsVisible && <UserDocumentsModal userId={user.id} onCancel={hideModals} />} |
197 | 214 |
|
198 | 215 |
<Table |
199 |
locale={{ |
|
200 |
triggerDesc: 'Seřadit sestupně', |
|
201 |
triggerAsc: 'Seřadit vzestupně', |
|
202 |
cancelSort: 'Vypnout řazení', |
|
203 |
}} |
|
216 |
locale={{ ...getLocaleProps() }} |
|
204 | 217 |
// @ts-ignore |
205 | 218 |
columns={columns} |
206 | 219 |
dataSource={users} |
207 |
scroll={{ y: 500 }}
|
|
220 |
scroll={{ y: 800 }}
|
|
208 | 221 |
/> |
209 | 222 |
</MainLayout> |
210 | 223 |
); |
webapp/utils/tableUtils.tsx | ||
---|---|---|
1 |
import { Button, Input, Space } from 'antd'; |
|
2 |
import { SearchOutlined } from '@ant-design/icons'; |
|
3 |
import React from 'react'; |
|
4 |
import { style } from 'dom-helpers'; |
|
5 |
|
|
6 |
export const getLocaleProps = () => ({ |
|
7 |
filterSearchPlaceholder: 'Hledat uživatele', |
|
8 |
triggerDesc: 'Seřadit sestupně', |
|
9 |
triggerAsc: 'Seřadit vzestupně', |
|
10 |
cancelSort: 'Vypnout řazení', |
|
11 |
}); |
|
12 |
|
|
13 |
const handleSearch = ( |
|
14 |
selectedKeys: React.SetStateAction<string>[], |
|
15 |
confirm: () => void |
|
16 |
) => { |
|
17 |
confirm(); |
|
18 |
}; |
|
19 |
|
|
20 |
const handleReset = (clearFilters: () => void) => { |
|
21 |
clearFilters(); |
|
22 |
}; |
|
23 |
|
|
24 |
export const getColumnSearchProps = (dataIndex: string, searchLabel: string) => ({ |
|
25 |
// @ts-ignore |
|
26 |
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( |
|
27 |
<div style={{ padding: 8 }}> |
|
28 |
<Input |
|
29 |
placeholder={`Vyhledat ${searchLabel}`} |
|
30 |
value={selectedKeys[0]} |
|
31 |
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])} |
|
32 |
onPressEnter={() => handleSearch(selectedKeys, confirm)} |
|
33 |
style={{ marginBottom: 8, display: 'block' }} |
|
34 |
/> |
|
35 |
<Space> |
|
36 |
<Button |
|
37 |
type="primary" |
|
38 |
onClick={() => handleSearch(selectedKeys, confirm)} |
|
39 |
icon={<SearchOutlined />} |
|
40 |
style={{ width: 90 }} |
|
41 |
> |
|
42 |
Hledat |
|
43 |
</Button> |
|
44 |
<Button onClick={() => handleReset(clearFilters)} style={{ width: 90 }}> |
|
45 |
Smazat |
|
46 |
</Button> |
|
47 |
</Space> |
|
48 |
</div> |
|
49 |
), |
|
50 |
filterIcon: (filtered: any) => ( |
|
51 |
<SearchOutlined |
|
52 |
style={{ color: filtered ? '#1890ff' : '#757575', fontSize: '140%' }} |
|
53 |
/> |
|
54 |
), |
|
55 |
onFilter: (value: string, record: { [x: string]: { toString: () => string } }) => |
|
56 |
record[dataIndex] |
|
57 |
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
|
58 |
: '', |
|
59 |
}); |
Také k dispozici: Unified diff
Refactoring after code review