@campxdev/shared 1.8.4 → 1.8.5-0.alpha-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/package.json +1 -1
- package/src/components/ApplicationProfile/ApplicationProfile.tsx +342 -0
- package/src/components/ApplicationProfile/Service.ts +68 -0
- package/src/components/ApplicationProfile/UserProfileRelation.tsx +174 -0
- package/src/components/ApplicationProfile/index.tsx +1 -0
- package/src/components/DropDownButton/DropdownMenuItem.tsx +12 -0
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +8 -3
- package/src/components/ErrorBoundary/ErrorFallback.tsx +7 -2
- package/src/components/FloatingContainer.tsx +1 -1
- package/src/components/HookForm/MultiSelect.tsx +8 -0
- package/src/components/Input/DatePicker.tsx +11 -0
- package/src/components/Input/MultiSelect.tsx +11 -0
- package/src/components/Layout/Header/AppHeader.tsx +2 -1
- package/src/components/Layout/Header/AppsMenu.tsx +51 -23
- package/src/components/Layout/Header/HeaderActions/FreshChatButton.tsx +61 -0
- package/src/components/Layout/Header/HeaderActions/FreshDeskHelpButton.tsx +36 -7
- package/src/components/Layout/Header/HeaderActions/HeaderActions.tsx +2 -0
- package/src/components/Layout/Header/SchoolSwitch/SchoolSwitch.tsx +33 -0
- package/src/components/Layout/Header/applications.ts +8 -9
- package/src/components/Layout/Header/assets/chat_with_us.png +0 -0
- package/src/components/Layout/Header/assets/index.ts +2 -0
- package/src/components/Layout/Helmet.tsx +91 -32
- package/src/components/ModalButtons/PopoverButton.tsx +99 -0
- package/src/components/Tabs/TabsContainer.tsx +3 -0
- package/src/components/index.ts +4 -0
- package/src/config/axios.ts +8 -2
- package/src/constants/UIConstants.ts +27 -0
- package/src/contexts/LoginFormProvider.tsx +2 -7
- package/src/contexts/Providers.tsx +3 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAuth.ts +58 -2
- package/src/hooks/useExternalScript.ts +38 -0
- package/src/hooks/useFilters.ts +6 -3
- package/src/shared-state/PermissionsStore.ts +937 -130
- package/src/theme/muiTheme.ts +6 -4
- package/src/theme/theme.d.ts +2 -0
- package/tsconfig.json +19 -19
package/package.json
CHANGED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Avatar,
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
CircularProgress,
|
|
6
|
+
InputAdornment,
|
|
7
|
+
Typography,
|
|
8
|
+
styled,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import { useEffect, useState } from 'react'
|
|
11
|
+
import { useMutation, useQuery } from 'react-query'
|
|
12
|
+
import { toast } from 'react-toastify'
|
|
13
|
+
import { useImmer } from 'use-immer'
|
|
14
|
+
import UserProfileRelation from './UserProfileRelation'
|
|
15
|
+
import {
|
|
16
|
+
defaultFilterObj,
|
|
17
|
+
fetchApplicationUsers,
|
|
18
|
+
fetchProfiles,
|
|
19
|
+
removeUserApplicationProfile,
|
|
20
|
+
updateUserApplicationProfile,
|
|
21
|
+
} from './Service'
|
|
22
|
+
import useConfirm from '../PopupConfirm/useConfirm'
|
|
23
|
+
import Spinner from '../Spinner'
|
|
24
|
+
import PageHeader from '../PageHeader'
|
|
25
|
+
import { PageContent } from '../PageContent'
|
|
26
|
+
|
|
27
|
+
import { SingleSelect } from '../Input'
|
|
28
|
+
import ActionButton from '../ActionButton'
|
|
29
|
+
import { axiosErrorToast } from '../../config/axios'
|
|
30
|
+
import { DialogButton } from '../ModalButtons'
|
|
31
|
+
import SearchBar from '../FilterComponents/SearchBar'
|
|
32
|
+
import Table from '../Tables/BasicTable/Table'
|
|
33
|
+
import { ValidateAccess } from '../../permissions'
|
|
34
|
+
import { Permission, PermissionsStore } from '../../shared-state'
|
|
35
|
+
|
|
36
|
+
interface ApplicationProfileProps {
|
|
37
|
+
application: 'exams' | 'square' | 'payments' | 'enroll_x' | 'hostels'
|
|
38
|
+
title: string
|
|
39
|
+
permissions?: {
|
|
40
|
+
add: string
|
|
41
|
+
edit: string
|
|
42
|
+
view: string
|
|
43
|
+
delete: string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function ApplicationProfile({
|
|
47
|
+
application = 'exams',
|
|
48
|
+
title,
|
|
49
|
+
permissions,
|
|
50
|
+
}: ApplicationProfileProps) {
|
|
51
|
+
const { isConfirmed } = useConfirm()
|
|
52
|
+
const [filters, setFilters] = useImmer(defaultFilterObj)
|
|
53
|
+
const { data, isLoading, refetch } = useQuery(
|
|
54
|
+
['application-users', ...Object.keys(filters)?.map((key) => filters[key])],
|
|
55
|
+
() => fetchApplicationUsers({ application, ...filters }),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const { data: profiles, isLoading: profilesLoading } = useQuery(
|
|
59
|
+
'profiles',
|
|
60
|
+
() => fetchProfiles({ application }),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const { mutate, isLoading: removingUserProfile } = useMutation(
|
|
64
|
+
removeUserApplicationProfile,
|
|
65
|
+
{
|
|
66
|
+
onSuccess: (res) => {
|
|
67
|
+
refetch()
|
|
68
|
+
toast.success('User profile removed from application')
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const handleRemove = async (data) => {
|
|
74
|
+
const confirmed = await isConfirmed(
|
|
75
|
+
'Are you sure you want to remove the user profile?',
|
|
76
|
+
)
|
|
77
|
+
if (!confirmed) return
|
|
78
|
+
mutate({ userId: data.id, application: application })
|
|
79
|
+
}
|
|
80
|
+
const columns = [
|
|
81
|
+
{
|
|
82
|
+
title: 'User',
|
|
83
|
+
key: '',
|
|
84
|
+
dataIndex: '',
|
|
85
|
+
render: (_, row) => <UserComponent userData={row} />,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: 'Profile',
|
|
89
|
+
key: '',
|
|
90
|
+
dataIndex: '',
|
|
91
|
+
render: (_, row) => (
|
|
92
|
+
<RenderProfileDropDown
|
|
93
|
+
profiles={profiles?.profiles}
|
|
94
|
+
data={row}
|
|
95
|
+
application={application}
|
|
96
|
+
refetchFn={refetch}
|
|
97
|
+
permissions={permissions}
|
|
98
|
+
/>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: 'Actions',
|
|
103
|
+
key: '',
|
|
104
|
+
dataIndex: '',
|
|
105
|
+
render: (_, row) => (
|
|
106
|
+
<ValidateAccess
|
|
107
|
+
accessKey={
|
|
108
|
+
permissions
|
|
109
|
+
? Permission[permissions.delete]
|
|
110
|
+
: Permission.CAN_DASHBOARD_VIEW
|
|
111
|
+
}
|
|
112
|
+
>
|
|
113
|
+
<Button
|
|
114
|
+
variant="text"
|
|
115
|
+
onClick={() => handleRemove(row)}
|
|
116
|
+
sx={{ padding: '0px', margin: '0px' }}
|
|
117
|
+
>
|
|
118
|
+
Remove
|
|
119
|
+
</Button>
|
|
120
|
+
</ValidateAccess>
|
|
121
|
+
),
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
const handleLimitChange = (value: number) => {
|
|
125
|
+
setFilters((s) => {
|
|
126
|
+
s.limit = value
|
|
127
|
+
s.offset = 0
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const handlePagination = (value: number) => {
|
|
132
|
+
setFilters((s) => {
|
|
133
|
+
s.offset = value * s.limit - s.limit
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
if (profilesLoading) {
|
|
137
|
+
return <Spinner />
|
|
138
|
+
}
|
|
139
|
+
return (
|
|
140
|
+
<>
|
|
141
|
+
<PageHeader
|
|
142
|
+
title={title}
|
|
143
|
+
actions={[
|
|
144
|
+
<ValidateAccess
|
|
145
|
+
key={0}
|
|
146
|
+
accessKey={
|
|
147
|
+
permissions
|
|
148
|
+
? Permission[permissions.add]
|
|
149
|
+
: Permission.CAN_DASHBOARD_VIEW
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<DialogButton
|
|
153
|
+
key={0}
|
|
154
|
+
title={'Add User Profile Relation'}
|
|
155
|
+
anchor={({ open }) => (
|
|
156
|
+
<ActionButton onClick={open}>
|
|
157
|
+
Add User Profile Relation
|
|
158
|
+
</ActionButton>
|
|
159
|
+
)}
|
|
160
|
+
content={({ close }) => (
|
|
161
|
+
<UserProfileRelation
|
|
162
|
+
close={close}
|
|
163
|
+
application={application}
|
|
164
|
+
profiles={profiles?.profiles}
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
/>
|
|
168
|
+
</ValidateAccess>,
|
|
169
|
+
]}
|
|
170
|
+
/>
|
|
171
|
+
<PageContent sx={{ marginTop: '25px' }}>
|
|
172
|
+
<StyledTableContainer>
|
|
173
|
+
<SearchBar
|
|
174
|
+
onSearch={(value) => {
|
|
175
|
+
setFilters((s) => {
|
|
176
|
+
s.search = value
|
|
177
|
+
})
|
|
178
|
+
}}
|
|
179
|
+
textFieldProps={{
|
|
180
|
+
placeholder: 'Search by Name',
|
|
181
|
+
title: 'Search by Name',
|
|
182
|
+
sx: { width: '300px', marginBottom: '25px' },
|
|
183
|
+
size: 'small',
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
<Table
|
|
187
|
+
columns={columns}
|
|
188
|
+
dataSource={data?.users ?? []}
|
|
189
|
+
loading={isLoading || removingUserProfile}
|
|
190
|
+
pagination={{
|
|
191
|
+
limit: filters.limit,
|
|
192
|
+
onChangeLimit: handleLimitChange,
|
|
193
|
+
onChange: handlePagination,
|
|
194
|
+
totalCount: data?.count,
|
|
195
|
+
page: filters.offset / filters.limit,
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</StyledTableContainer>
|
|
199
|
+
</PageContent>
|
|
200
|
+
</>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
export default ApplicationProfile
|
|
204
|
+
|
|
205
|
+
export const RenderProfileDropDown = ({
|
|
206
|
+
profiles,
|
|
207
|
+
data,
|
|
208
|
+
refetchFn,
|
|
209
|
+
application,
|
|
210
|
+
permissions,
|
|
211
|
+
}) => {
|
|
212
|
+
const permissionState = PermissionsStore.useState((s) => s)
|
|
213
|
+
const CanEdit = permissions
|
|
214
|
+
? permissionState.permissions[Permission[permissions.edit]]
|
|
215
|
+
: permissionState.permissions['can_dashboard_view']
|
|
216
|
+
const [state, setState] = useState({
|
|
217
|
+
userId: data.id,
|
|
218
|
+
profileId: data.profiles[0].id,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
setState((pre) => ({
|
|
223
|
+
userId: data.id,
|
|
224
|
+
profileId: data.profiles[0].id,
|
|
225
|
+
}))
|
|
226
|
+
}, [data])
|
|
227
|
+
const { mutate, isLoading } = useMutation(updateUserApplicationProfile, {
|
|
228
|
+
onSuccess: (res) => {
|
|
229
|
+
refetchFn()
|
|
230
|
+
toast.success('User application profile updated successfully')
|
|
231
|
+
},
|
|
232
|
+
onError: (err) => {
|
|
233
|
+
// eslint-disable-next-line no-console
|
|
234
|
+
console.log(err)
|
|
235
|
+
axiosErrorToast(err)
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
const handleChange = (e) => {
|
|
239
|
+
setState({
|
|
240
|
+
userId: data.id,
|
|
241
|
+
profileId: e.target.value,
|
|
242
|
+
})
|
|
243
|
+
mutate({
|
|
244
|
+
userId: state.userId,
|
|
245
|
+
profileIds: [e.target.value],
|
|
246
|
+
application: application,
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
<StyledDropDownContainer>
|
|
252
|
+
<SingleSelect
|
|
253
|
+
label={'Profile'}
|
|
254
|
+
name={'profile'}
|
|
255
|
+
value={state?.profileId}
|
|
256
|
+
onChange={(e) => {
|
|
257
|
+
handleChange(e)
|
|
258
|
+
}}
|
|
259
|
+
disabled={!CanEdit}
|
|
260
|
+
endAdornment={
|
|
261
|
+
isLoading && (
|
|
262
|
+
<InputAdornment position="end">
|
|
263
|
+
<CircularProgress
|
|
264
|
+
size={20}
|
|
265
|
+
color={'secondary'}
|
|
266
|
+
sx={{ marginRight: '10px' }}
|
|
267
|
+
/>
|
|
268
|
+
</InputAdornment>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
options={
|
|
272
|
+
profiles?.map((profile) => ({
|
|
273
|
+
label: profile.name,
|
|
274
|
+
value: profile.id,
|
|
275
|
+
})) ?? []
|
|
276
|
+
}
|
|
277
|
+
/>
|
|
278
|
+
</StyledDropDownContainer>
|
|
279
|
+
</>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export const UserComponent = ({ userData }) => {
|
|
284
|
+
return (
|
|
285
|
+
<>
|
|
286
|
+
<Box
|
|
287
|
+
sx={{
|
|
288
|
+
width: '100%',
|
|
289
|
+
display: 'flex',
|
|
290
|
+
gap: '10px',
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
justifyContent: 'flex-start',
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<Avatar alt={userData?.fullName} />
|
|
296
|
+
<Typography variant="subtitle1">{userData?.fullName}</Typography>
|
|
297
|
+
</Box>
|
|
298
|
+
</>
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
export const StyledTableContainer = styled(Box)(({ theme }) => ({
|
|
302
|
+
width: '70%',
|
|
303
|
+
margin: 'auto',
|
|
304
|
+
'& .MuiTableHead-root': {
|
|
305
|
+
border: '1px solid white',
|
|
306
|
+
borderBottom: theme.borders.grayLight,
|
|
307
|
+
borderWidth: '2px',
|
|
308
|
+
|
|
309
|
+
backgroundColor: 'white',
|
|
310
|
+
'& th': {
|
|
311
|
+
textAlign: 'left',
|
|
312
|
+
padding: '5px',
|
|
313
|
+
},
|
|
314
|
+
'& .MuiBox-root': {
|
|
315
|
+
color: '#121212b3',
|
|
316
|
+
fontSize: '164x',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
'& tbody': {
|
|
320
|
+
border: '1px solid white',
|
|
321
|
+
borderBottom: theme.borders.grayLight,
|
|
322
|
+
borderWidth: '2px',
|
|
323
|
+
'& tr': {
|
|
324
|
+
border: '1px solid white',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
'& td': {
|
|
328
|
+
textAlign: 'center',
|
|
329
|
+
border: '1px solid white',
|
|
330
|
+
padding: '15px 0px',
|
|
331
|
+
},
|
|
332
|
+
}))
|
|
333
|
+
|
|
334
|
+
export const StyledDropDownContainer = styled(Box)(({ theme }) => ({
|
|
335
|
+
width: '200px',
|
|
336
|
+
'& .MuiTypography-root': {
|
|
337
|
+
display: 'none',
|
|
338
|
+
},
|
|
339
|
+
'& .MuiInputBase-root': {
|
|
340
|
+
backgroundColor: '#f5f6f8',
|
|
341
|
+
},
|
|
342
|
+
}))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as yup from 'yup'
|
|
2
|
+
import axios from '../../config/axios'
|
|
3
|
+
export const defaultFilterObj = {
|
|
4
|
+
search: null,
|
|
5
|
+
limit: 10,
|
|
6
|
+
offset: 0,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const userProfileSchema = yup.object().shape({
|
|
10
|
+
userId: yup
|
|
11
|
+
.object()
|
|
12
|
+
.shape({
|
|
13
|
+
label: yup.string().required('User is required'),
|
|
14
|
+
value: yup.string().required('User is required'),
|
|
15
|
+
})
|
|
16
|
+
.required('User is required'),
|
|
17
|
+
profileIds: yup.string().required('Profile is required'),
|
|
18
|
+
})
|
|
19
|
+
export const fetchUsers = (params) => {
|
|
20
|
+
return axios
|
|
21
|
+
.get(`/square/profile-permissions/new-users`, {
|
|
22
|
+
params: params,
|
|
23
|
+
})
|
|
24
|
+
.then((res) => res.data)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const fetchApplicationUsers = (params) => {
|
|
28
|
+
return axios
|
|
29
|
+
.get(`/square/profile-permissions/application-users`, {
|
|
30
|
+
params: params,
|
|
31
|
+
})
|
|
32
|
+
.then((res) => res.data)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const fetchProfiles = (application) => {
|
|
36
|
+
return axios
|
|
37
|
+
.get(`/square/profiles`, {
|
|
38
|
+
params: application,
|
|
39
|
+
})
|
|
40
|
+
.then((res) => res.data)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ApplicationUserProfile {
|
|
44
|
+
application: string
|
|
45
|
+
userId: number
|
|
46
|
+
profileIds: number[]
|
|
47
|
+
}
|
|
48
|
+
export const createApplicationUserProfile = (
|
|
49
|
+
postBody: ApplicationUserProfile,
|
|
50
|
+
) => {
|
|
51
|
+
return axios
|
|
52
|
+
.post(`/square/profile-permissions/add-user`, postBody)
|
|
53
|
+
.then((res) => res.data)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const removeUserApplicationProfile = (postBody) => {
|
|
57
|
+
return axios
|
|
58
|
+
.post(`/square/profile-permissions/remove-user`, postBody)
|
|
59
|
+
.then((res) => res.data)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const updateUserApplicationProfile = (
|
|
63
|
+
postBody: ApplicationUserProfile,
|
|
64
|
+
) => {
|
|
65
|
+
return axios
|
|
66
|
+
.put(`/square/profile-permissions/edit-user-permissions`, postBody)
|
|
67
|
+
.then((res) => res.data)
|
|
68
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { yupResolver } from '@hookform/resolvers/yup'
|
|
2
|
+
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
CircularProgress,
|
|
5
|
+
Popper,
|
|
6
|
+
Stack,
|
|
7
|
+
styled,
|
|
8
|
+
} from '@mui/material'
|
|
9
|
+
import { useState } from 'react'
|
|
10
|
+
import { Controller, useForm } from 'react-hook-form'
|
|
11
|
+
import { useMutation, useQueryClient } from 'react-query'
|
|
12
|
+
import { toast } from 'react-toastify'
|
|
13
|
+
import {
|
|
14
|
+
createApplicationUserProfile,
|
|
15
|
+
fetchUsers,
|
|
16
|
+
userProfileSchema,
|
|
17
|
+
} from './Service'
|
|
18
|
+
import { useImmer } from 'use-immer'
|
|
19
|
+
import { axiosErrorToast } from '../../config/axios'
|
|
20
|
+
import { FormMultiSelect, FormSingleSelect } from '../HookForm'
|
|
21
|
+
import ActionButton from '../ActionButton'
|
|
22
|
+
interface UserProps {
|
|
23
|
+
options: {
|
|
24
|
+
label: any
|
|
25
|
+
value: any
|
|
26
|
+
}[]
|
|
27
|
+
inputValue: string
|
|
28
|
+
}
|
|
29
|
+
function UserProfileRelation({ close, application, profiles }) {
|
|
30
|
+
const [state, setState] = useImmer<UserProps>({
|
|
31
|
+
options: [],
|
|
32
|
+
inputValue: '',
|
|
33
|
+
})
|
|
34
|
+
const queryClient = useQueryClient()
|
|
35
|
+
|
|
36
|
+
const { control, handleSubmit, formState } = useForm({
|
|
37
|
+
defaultValues: {},
|
|
38
|
+
resolver: yupResolver(userProfileSchema),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const { mutate, isLoading: creatingUserProfile } = useMutation(
|
|
42
|
+
createApplicationUserProfile,
|
|
43
|
+
{
|
|
44
|
+
onSuccess: (res) => {
|
|
45
|
+
queryClient.invalidateQueries(`application-users`)
|
|
46
|
+
toast.success('User profile added to application successfully')
|
|
47
|
+
close()
|
|
48
|
+
},
|
|
49
|
+
onError: (err) => {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(err)
|
|
52
|
+
axiosErrorToast(err)
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const { mutate: fetchUsersFn, isLoading: gettingUsers } = useMutation(
|
|
58
|
+
fetchUsers,
|
|
59
|
+
{
|
|
60
|
+
onSuccess: (res) => {
|
|
61
|
+
setState((s) => {
|
|
62
|
+
s.options = res.users?.map((item) => ({
|
|
63
|
+
label: item.fullName,
|
|
64
|
+
value: item.id,
|
|
65
|
+
}))
|
|
66
|
+
})
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
const onSubmit = (formData) => {
|
|
71
|
+
mutate({
|
|
72
|
+
application: application,
|
|
73
|
+
profileIds: [+formData.profileIds],
|
|
74
|
+
userId: +formData.userId?.value,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
const onError = (err) => {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log(err)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleInputChange = (e) => {
|
|
83
|
+
if (e) {
|
|
84
|
+
setState((s) => {
|
|
85
|
+
s.inputValue = e.target.value
|
|
86
|
+
})
|
|
87
|
+
if (e.target.value.length > 3) {
|
|
88
|
+
fetchUsersFn({
|
|
89
|
+
application: application,
|
|
90
|
+
search: e.target.value,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<form onSubmit={handleSubmit(onSubmit, onError)}>
|
|
99
|
+
<Stack gap={4} sx={{ padding: '10px' }}>
|
|
100
|
+
<FormMultiSelect
|
|
101
|
+
multiple={false}
|
|
102
|
+
label={'User'}
|
|
103
|
+
name={'userId'}
|
|
104
|
+
onInputChange={handleInputChange}
|
|
105
|
+
loading={gettingUsers}
|
|
106
|
+
control={control}
|
|
107
|
+
options={state.options ?? []}
|
|
108
|
+
required
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<FormSingleSelect
|
|
112
|
+
label={'Profile'}
|
|
113
|
+
name={'profileIds'}
|
|
114
|
+
control={control}
|
|
115
|
+
options={
|
|
116
|
+
profiles?.map((profile) => ({
|
|
117
|
+
label: profile.name,
|
|
118
|
+
value: profile.id,
|
|
119
|
+
})) ?? []
|
|
120
|
+
}
|
|
121
|
+
required
|
|
122
|
+
/>
|
|
123
|
+
<Stack gap={2} direction={'row'} sx={{ justifyContent: 'flex-end' }}>
|
|
124
|
+
<ActionButton variant="outlined" onClick={close}>
|
|
125
|
+
Cancel
|
|
126
|
+
</ActionButton>
|
|
127
|
+
<ActionButton type="submit" loading={creatingUserProfile}>
|
|
128
|
+
Confirm
|
|
129
|
+
</ActionButton>
|
|
130
|
+
</Stack>
|
|
131
|
+
</Stack>
|
|
132
|
+
</form>
|
|
133
|
+
</>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
export default UserProfileRelation
|
|
137
|
+
|
|
138
|
+
const StyledPopper = styled(Popper)(({ theme }) => ({
|
|
139
|
+
'& .MuiPaper-root': {
|
|
140
|
+
borderRadius: '10px',
|
|
141
|
+
borderTopRightRadius: 0,
|
|
142
|
+
borderTopLeftRadius: 0,
|
|
143
|
+
boxShadow: '0px 4px 16px #0000000F',
|
|
144
|
+
marginTop: '1px',
|
|
145
|
+
'& .MuiAutocomplete-listbox': {
|
|
146
|
+
minWidth: '240px',
|
|
147
|
+
padding: '10px',
|
|
148
|
+
'& .MuiAutocomplete-option': {
|
|
149
|
+
padding: '10px',
|
|
150
|
+
background: 'none',
|
|
151
|
+
'&.Mui-focused': {
|
|
152
|
+
background: 'none',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
'& .MuiCheckbox-root': {
|
|
156
|
+
padding: 0,
|
|
157
|
+
marginRight: '10px',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
'&::-webkit-scrollbar': {
|
|
161
|
+
width: '0.5em',
|
|
162
|
+
height: '0.5em',
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
'&::-webkit-scrollbar-thumb': {
|
|
166
|
+
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
|
167
|
+
borderRadius: '3px',
|
|
168
|
+
|
|
169
|
+
'&:hover': {
|
|
170
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./ApplicationProfile";
|
|
@@ -9,6 +9,8 @@ import { ReactNode } from 'react'
|
|
|
9
9
|
import { Link } from 'react-router-dom'
|
|
10
10
|
import { DrawerButtonProps } from '../ModalButtons/DrawerButton'
|
|
11
11
|
import { StyledMenuItem } from './styles'
|
|
12
|
+
import { ValidateAccess } from '../../permissions'
|
|
13
|
+
import { Permission } from '../../shared-state'
|
|
12
14
|
|
|
13
15
|
const StyledLink = styled(Link)(({ theme }) => ({
|
|
14
16
|
display: 'block',
|
|
@@ -53,6 +55,7 @@ export type IMenuItemProps = Omit<MenuItemButtonProps, 'onClick'> & {
|
|
|
53
55
|
onClick?: () => void
|
|
54
56
|
dialogProps?: Omit<DialogProps, 'open'>
|
|
55
57
|
drawerProps?: Omit<DrawerProps, 'open'>
|
|
58
|
+
permissionKey?: Permission
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export interface IRenderMenuItemProps extends IMenuItemProps {
|
|
@@ -70,6 +73,7 @@ export const RenderMenuItem = ({
|
|
|
70
73
|
link,
|
|
71
74
|
drawerProps,
|
|
72
75
|
dialogProps,
|
|
76
|
+
permissionKey,
|
|
73
77
|
...props
|
|
74
78
|
}: IRenderMenuItemProps) => {
|
|
75
79
|
const renderMenuItem: Record<MenuItemType, ReactNode> = {
|
|
@@ -106,5 +110,13 @@ export const RenderMenuItem = ({
|
|
|
106
110
|
</StyledLink>
|
|
107
111
|
),
|
|
108
112
|
}
|
|
113
|
+
|
|
114
|
+
if (permissionKey)
|
|
115
|
+
return (
|
|
116
|
+
<ValidateAccess accessKey={permissionKey}>
|
|
117
|
+
{renderMenuItem[actionType]}
|
|
118
|
+
</ValidateAccess>
|
|
119
|
+
)
|
|
120
|
+
|
|
109
121
|
return <>{renderMenuItem[actionType]}</>
|
|
110
122
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { ReactNode } from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import { ReactElement, ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ErrorBoundary as ReactErrorBoundary,
|
|
4
|
+
FallbackProps,
|
|
5
|
+
} from 'react-error-boundary'
|
|
3
6
|
import { QueryErrorResetBoundary } from 'react-query'
|
|
4
7
|
import { useLocation } from 'react-router-dom'
|
|
5
8
|
import ErrorFallback from './ErrorFallback'
|
|
@@ -7,9 +10,11 @@ import ErrorFallback from './ErrorFallback'
|
|
|
7
10
|
export default function ErrorBoundary({
|
|
8
11
|
children,
|
|
9
12
|
resetKey,
|
|
13
|
+
customFallback,
|
|
10
14
|
}: {
|
|
11
15
|
children: ReactNode
|
|
12
16
|
resetKey?: string
|
|
17
|
+
customFallback?: (props: FallbackProps) => JSX.Element
|
|
13
18
|
}) {
|
|
14
19
|
const location = useLocation()
|
|
15
20
|
return (
|
|
@@ -17,7 +22,7 @@ export default function ErrorBoundary({
|
|
|
17
22
|
{({ reset }) => (
|
|
18
23
|
<ReactErrorBoundary
|
|
19
24
|
key={resetKey ?? location?.pathname}
|
|
20
|
-
fallbackRender={ErrorFallback}
|
|
25
|
+
fallbackRender={customFallback ?? ErrorFallback}
|
|
21
26
|
onReset={reset}
|
|
22
27
|
>
|
|
23
28
|
{children}
|
|
@@ -14,6 +14,7 @@ import { PermissionsStore } from '../../shared-state'
|
|
|
14
14
|
import axios from '../../config/axios'
|
|
15
15
|
|
|
16
16
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
17
|
+
height: '60px',
|
|
17
18
|
border: `1px solid ${theme.palette.error.main}`,
|
|
18
19
|
display: 'flex',
|
|
19
20
|
alignItems: 'center',
|
|
@@ -66,7 +67,7 @@ export default function ErrorFallback({ error, resetErrorBoundary }) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
return (
|
|
69
|
-
<Box sx={{ marginTop: '16px', padding: '
|
|
70
|
+
<Box sx={{ marginTop: '16px', padding: '20px' }}>
|
|
70
71
|
<StyledAlert severity="error">
|
|
71
72
|
{error?.response?.data?.message ?? error?.message}
|
|
72
73
|
<Button
|
|
@@ -139,7 +140,11 @@ export function InternalServer({ resetBoundary, error }) {
|
|
|
139
140
|
Internal Server Error.
|
|
140
141
|
</Typography>
|
|
141
142
|
<Typography variant="body1">
|
|
142
|
-
{error?.response?.data?.message
|
|
143
|
+
{error?.response?.data?.message
|
|
144
|
+
? typeof error?.response?.data?.message === 'string'
|
|
145
|
+
? error?.response?.data?.message
|
|
146
|
+
: JSON.stringify(error?.response?.data?.message)
|
|
147
|
+
: 'Internal Server Error'}
|
|
143
148
|
</Typography>
|
|
144
149
|
<Button
|
|
145
150
|
size="small"
|