@campxdev/shared 1.11.6 → 1.11.7
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/exports.ts +4 -0
- package/package.json +4 -4
- package/src/assets/images/animation.gif +0 -0
- package/src/assets/images/index.ts +4 -3
- package/src/components/DatabaseConfiguration/DatabaseConfiguration.tsx +28 -0
- package/src/components/DatabaseConfiguration/DatabaseConfigurationForm.tsx +87 -0
- package/src/components/DatabaseConfiguration/components/AddConnectionDrawerButton.tsx +30 -0
- package/src/components/DatabaseConfiguration/components/ConnectionCard.tsx +79 -0
- package/src/components/DatabaseConfiguration/index.ts +5 -0
- package/src/components/DatabaseConfiguration/service.ts +6 -0
- package/src/components/DatabaseConfiguration/styles.ts +30 -0
- package/src/components/DrawerWrapper/DialogTemplate.tsx +58 -0
- package/src/components/DrawerWrapper/DrawerWrapper.tsx +23 -7
- package/src/components/DrawerWrapper/ErrorTemplate.tsx +77 -0
- package/src/components/ErrorModal.tsx +88 -0
- package/src/components/ErrorModalWrapper/ErrorModalTemplate.tsx +73 -0
- package/src/components/ErrorModalWrapper/ErrorModalWrapper.tsx +57 -0
- package/src/components/ToastContainer/ToastContainer.tsx +2 -3
- package/src/constants/UIConstants.ts +49 -0
- package/src/contexts/Providers.tsx +3 -3
package/exports.ts
CHANGED
|
@@ -9,6 +9,10 @@ export {
|
|
|
9
9
|
default as DialogProvider,
|
|
10
10
|
useModal,
|
|
11
11
|
} from './src/components/DrawerWrapper/DrawerWrapper'
|
|
12
|
+
export {
|
|
13
|
+
default as ErrorModalProvider,
|
|
14
|
+
useErrorModal,
|
|
15
|
+
} from './src/components/ErrorModalWrapper/ErrorModalWrapper'
|
|
12
16
|
export { default as ConfirmContextProvider } from './src/components/PopupConfirm/ConfirmContextProvider'
|
|
13
17
|
|
|
14
18
|
export { default as Providers } from './src/contexts/Providers'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campxdev/shared",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.7",
|
|
4
4
|
"main": "./exports.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "react-scripts start",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@types/react": "^18.0.25",
|
|
74
74
|
"@types/react-flatpickr": "^3.8.8",
|
|
75
75
|
"@types/react-helmet": "^6.1.6",
|
|
76
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
77
|
-
"@typescript-eslint/parser": "^
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
77
|
+
"@typescript-eslint/parser": "^6.7.4",
|
|
78
78
|
"eslint": "^8.23.0",
|
|
79
79
|
"eslint-config-prettier": "^8.5.0",
|
|
80
80
|
"eslint-import-resolver-typescript": "^3.5.0",
|
|
@@ -85,6 +85,6 @@
|
|
|
85
85
|
"prettier": "^2.5.0",
|
|
86
86
|
"react-scripts": "^5.0.1",
|
|
87
87
|
"storybook-addon-react-router-v6": "^0.2.1",
|
|
88
|
-
"typescript": "^
|
|
88
|
+
"typescript": "^5.2.2"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
Binary file
|
|
@@ -5,13 +5,14 @@ const internalserver = require('./500.png')
|
|
|
5
5
|
const unauth = require('./401.png')
|
|
6
6
|
const nointernet = require('./nointernet.png')
|
|
7
7
|
const campxLogoPrimary = require('./campx_logo__full_primary.png')
|
|
8
|
-
|
|
8
|
+
const animatedImage = require('./animation.gif')
|
|
9
9
|
export {
|
|
10
|
+
animatedImage,
|
|
10
11
|
avatarImage,
|
|
11
12
|
campxLogoPrimary,
|
|
13
|
+
internalserver,
|
|
14
|
+
nointernet,
|
|
12
15
|
pagenotfound,
|
|
13
16
|
permissiondenied,
|
|
14
|
-
internalserver,
|
|
15
17
|
unauth,
|
|
16
|
-
nointernet,
|
|
17
18
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Stack } from '@mui/material'
|
|
2
|
+
import { useQuery } from 'react-query'
|
|
3
|
+
import PageHeader from '../PageHeader'
|
|
4
|
+
import Spinner from '../Spinner'
|
|
5
|
+
import { AddConnectionDrawerButton } from './components/AddConnectionDrawerButton'
|
|
6
|
+
import { ConnectionCard } from './components/ConnectionCard'
|
|
7
|
+
import { getPunchLogsDatabaseConfigByTenant } from './service'
|
|
8
|
+
|
|
9
|
+
const DatabaseConfiguration = ({ type }) => {
|
|
10
|
+
const { data: punchLogsDatabaseConfig, isLoading } = useQuery(
|
|
11
|
+
'punch-logs-database-config',
|
|
12
|
+
getPunchLogsDatabaseConfigByTenant,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if (isLoading) return <Spinner />
|
|
16
|
+
return (
|
|
17
|
+
<Stack>
|
|
18
|
+
<PageHeader title="Punch Logs Database Configuration" />
|
|
19
|
+
{!punchLogsDatabaseConfig ? (
|
|
20
|
+
<AddConnectionDrawerButton type={type} />
|
|
21
|
+
) : (
|
|
22
|
+
<ConnectionCard data={punchLogsDatabaseConfig} />
|
|
23
|
+
)}
|
|
24
|
+
</Stack>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default DatabaseConfiguration
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as yup from 'yup'
|
|
2
|
+
import { generateLabelValueOptions } from '../../constants'
|
|
3
|
+
import Form from '../Form/Form'
|
|
4
|
+
import { Field } from '../Form/RenderForm'
|
|
5
|
+
|
|
6
|
+
const fields: Field[] = [
|
|
7
|
+
{
|
|
8
|
+
name: 'host',
|
|
9
|
+
render: 'FormTextField',
|
|
10
|
+
required: true,
|
|
11
|
+
label: 'Host',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'username',
|
|
15
|
+
render: 'FormTextField',
|
|
16
|
+
required: true,
|
|
17
|
+
label: 'Username',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'password',
|
|
21
|
+
render: 'FormTextField',
|
|
22
|
+
required: true,
|
|
23
|
+
label: 'Password',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'port',
|
|
27
|
+
render: 'FormTextField',
|
|
28
|
+
required: true,
|
|
29
|
+
label: 'Port',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'database',
|
|
33
|
+
render: 'FormTextField',
|
|
34
|
+
required: true,
|
|
35
|
+
label: 'Database',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'timezone',
|
|
39
|
+
render: 'FormMultiSelect',
|
|
40
|
+
elementProps: {
|
|
41
|
+
multiple: false,
|
|
42
|
+
},
|
|
43
|
+
validation: yup.object().required('Select a Timezone'),
|
|
44
|
+
required: true,
|
|
45
|
+
label: 'Timezone',
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
export const DatabaseConfigurationForm = ({
|
|
50
|
+
data,
|
|
51
|
+
close,
|
|
52
|
+
successToastMessage,
|
|
53
|
+
type,
|
|
54
|
+
}) => {
|
|
55
|
+
const timezones = Intl.supportedValuesOf('timeZone')
|
|
56
|
+
return (
|
|
57
|
+
<Form
|
|
58
|
+
onCancel={close}
|
|
59
|
+
endPoint="/hrms/punch-logs-database-config"
|
|
60
|
+
fields={fields}
|
|
61
|
+
cols={1}
|
|
62
|
+
refetchKey="punch-logs-database-config"
|
|
63
|
+
dropdowns={{ timezone: generateLabelValueOptions(timezones) }}
|
|
64
|
+
submitBtn={{
|
|
65
|
+
label: 'Save',
|
|
66
|
+
}}
|
|
67
|
+
onSubmit={(originalFormData) => {
|
|
68
|
+
return {
|
|
69
|
+
...originalFormData,
|
|
70
|
+
timezone: originalFormData?.timezone?.value,
|
|
71
|
+
type,
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
defaultValues={{
|
|
75
|
+
...data,
|
|
76
|
+
timezone: {
|
|
77
|
+
label: data?.timezone,
|
|
78
|
+
value: data?.timezone,
|
|
79
|
+
},
|
|
80
|
+
}}
|
|
81
|
+
successToastMessage={successToastMessage}
|
|
82
|
+
onSuccess={() => {
|
|
83
|
+
close()
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Add } from '@mui/icons-material'
|
|
2
|
+
import { Typography } from '@mui/material'
|
|
3
|
+
import { DrawerButton } from '../../ModalButtons'
|
|
4
|
+
import { DatabaseConfigurationForm } from '../DatabaseConfigurationForm'
|
|
5
|
+
import { StyledSelectAttachmentsContainer } from '../styles'
|
|
6
|
+
|
|
7
|
+
export const AddConnectionDrawerButton = ({ type }) => {
|
|
8
|
+
return (
|
|
9
|
+
<DrawerButton
|
|
10
|
+
key={'1'}
|
|
11
|
+
anchor={({ open }) => (
|
|
12
|
+
<StyledSelectAttachmentsContainer onClick={open}>
|
|
13
|
+
<Add />
|
|
14
|
+
<Typography sx={{ margin: '0px 10px', opacity: '0.6' }}>
|
|
15
|
+
Add Database Configuration
|
|
16
|
+
</Typography>
|
|
17
|
+
</StyledSelectAttachmentsContainer>
|
|
18
|
+
)}
|
|
19
|
+
content={({ close }) => (
|
|
20
|
+
<DatabaseConfigurationForm
|
|
21
|
+
type={type}
|
|
22
|
+
data={null}
|
|
23
|
+
close={close}
|
|
24
|
+
successToastMessage="Created Database Configuration"
|
|
25
|
+
/>
|
|
26
|
+
)}
|
|
27
|
+
title="Add Database Configuration"
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Cached } from '@mui/icons-material'
|
|
2
|
+
import { Box, CardActions, IconButton, Typography } from '@mui/material'
|
|
3
|
+
import { toast } from 'react-toastify'
|
|
4
|
+
import axios from '../../../config/axios'
|
|
5
|
+
import { EditButton } from '../../IconButtons'
|
|
6
|
+
import { DrawerButton } from '../../ModalButtons'
|
|
7
|
+
import { DatabaseConfigurationForm } from '../DatabaseConfigurationForm'
|
|
8
|
+
import { StyledCard, StyledCardContent } from '../styles'
|
|
9
|
+
|
|
10
|
+
export const ConnectionCard = ({ data }) => {
|
|
11
|
+
const handleReconnect = async () => {
|
|
12
|
+
const response: string = await axios.post(
|
|
13
|
+
'hrms/punch-logs-database-config/connect',
|
|
14
|
+
{},
|
|
15
|
+
)
|
|
16
|
+
if (response) {
|
|
17
|
+
window.location.reload()
|
|
18
|
+
}
|
|
19
|
+
toast.success(response)
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<StyledCard>
|
|
24
|
+
<StyledCardContent>
|
|
25
|
+
<Column label={'Host'} value={data?.host} />
|
|
26
|
+
<Column label={'Username'} value={data?.username} />
|
|
27
|
+
<Column label={'Password'} value={data?.password} />
|
|
28
|
+
<Column label={'Port'} value={data?.port} />
|
|
29
|
+
<Column label={'Database'} value={data?.database} />
|
|
30
|
+
<Column label={'Timezone'} value={data?.timezone} />
|
|
31
|
+
<Column
|
|
32
|
+
label={'Status'}
|
|
33
|
+
value={data?.connectionStatus}
|
|
34
|
+
color={data?.connectionStatus == 'Connected' ? 'green' : 'red'}
|
|
35
|
+
/>
|
|
36
|
+
</StyledCardContent>
|
|
37
|
+
<CardActions>
|
|
38
|
+
<DrawerButton
|
|
39
|
+
key={'1'}
|
|
40
|
+
anchor={({ open }) => <EditButton onClick={open} />}
|
|
41
|
+
content={({ close }) => (
|
|
42
|
+
<DatabaseConfigurationForm
|
|
43
|
+
data={data}
|
|
44
|
+
close={close}
|
|
45
|
+
successToastMessage="Updated Database Configuration"
|
|
46
|
+
type={data.type}
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
49
|
+
title="Add Database Configuration"
|
|
50
|
+
/>
|
|
51
|
+
<IconButton onClick={handleReconnect}>
|
|
52
|
+
<Cached sx={{ '&:hover': { color: 'green' } }} />
|
|
53
|
+
</IconButton>
|
|
54
|
+
</CardActions>
|
|
55
|
+
</StyledCard>
|
|
56
|
+
</>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Column = ({
|
|
61
|
+
label,
|
|
62
|
+
value,
|
|
63
|
+
color = '#000000',
|
|
64
|
+
}: {
|
|
65
|
+
label: string
|
|
66
|
+
value: string
|
|
67
|
+
color?: string
|
|
68
|
+
}) => {
|
|
69
|
+
return (
|
|
70
|
+
<Box>
|
|
71
|
+
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
|
|
72
|
+
{label}
|
|
73
|
+
</Typography>
|
|
74
|
+
<Typography variant="h5" component="div" color={color}>
|
|
75
|
+
{value}
|
|
76
|
+
</Typography>
|
|
77
|
+
</Box>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Card, CardContent, FormLabel, styled } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
export const StyledSelectAttachmentsContainer = styled(FormLabel)({
|
|
4
|
+
border: '1px dashed #0000001A',
|
|
5
|
+
margin: '20px',
|
|
6
|
+
display: 'flex',
|
|
7
|
+
justifyContent: 'center',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
borderRadius: '10px',
|
|
10
|
+
cursor: 'pointer',
|
|
11
|
+
height: '148px',
|
|
12
|
+
gap: '10px',
|
|
13
|
+
'&:hover': {
|
|
14
|
+
backgroundColor: '#EFEFEF',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const StyledCard = styled(Card)({
|
|
19
|
+
display: 'flex',
|
|
20
|
+
margin: '20px',
|
|
21
|
+
justifyContent: 'center',
|
|
22
|
+
gap: '60px',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const StyledCardContent = styled(CardContent)({
|
|
26
|
+
display: 'flex',
|
|
27
|
+
margin: '20px',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
gap: '30px',
|
|
30
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Close } from '@mui/icons-material'
|
|
2
|
+
import {
|
|
3
|
+
alpha,
|
|
4
|
+
Box,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
IconButton,
|
|
8
|
+
styled,
|
|
9
|
+
Typography,
|
|
10
|
+
} from '@mui/material'
|
|
11
|
+
|
|
12
|
+
const StyledDialogHeader = styled(Box)(({ theme }) => ({
|
|
13
|
+
height: '64px',
|
|
14
|
+
backgroundColor: alpha(theme.palette.text.secondary, 0.1),
|
|
15
|
+
display: 'flex',
|
|
16
|
+
justifyContent: 'space-between',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
padding: '0.6rem 1rem',
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
export default function DialogTemplate({
|
|
22
|
+
handleClose,
|
|
23
|
+
modal,
|
|
24
|
+
setModal,
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<Dialog
|
|
29
|
+
sx={{
|
|
30
|
+
zIndex: 500,
|
|
31
|
+
minHeight: '70vh',
|
|
32
|
+
'& .MuiDialog-container': {
|
|
33
|
+
'& .MuiPaper-root': {
|
|
34
|
+
maxWidth: '1420px',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}}
|
|
38
|
+
onClose={handleClose}
|
|
39
|
+
open={modal.open}
|
|
40
|
+
{...props}
|
|
41
|
+
PaperProps={{
|
|
42
|
+
sx: {
|
|
43
|
+
borderRadius: '10px',
|
|
44
|
+
},
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<StyledDialogHeader>
|
|
48
|
+
<Typography fontWeight={600}>{modal.title}</Typography>
|
|
49
|
+
<IconButton onClick={() => setModal({ ...modal, open: false })}>
|
|
50
|
+
<Close />
|
|
51
|
+
</IconButton>
|
|
52
|
+
</StyledDialogHeader>
|
|
53
|
+
<DialogContent sx={{ padding: '0' }}>
|
|
54
|
+
<>{modal.content({ close: handleClose })}</>
|
|
55
|
+
</DialogContent>
|
|
56
|
+
</Dialog>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createContext, useContext, useState } from 'react'
|
|
2
|
+
import DialogTemplate from './DialogTemplate'
|
|
2
3
|
import { DrawerTemplate } from './DrawerTemplate'
|
|
4
|
+
import ErrorTemplate from './ErrorTemplate'
|
|
3
5
|
|
|
4
6
|
interface IProps {
|
|
5
7
|
title: string
|
|
6
|
-
modalType?: 'dialog' | 'drawer'
|
|
8
|
+
modalType?: 'dialog' | 'drawer' | 'error'
|
|
7
9
|
content: ({ close }: { close: () => void }) => any
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -32,7 +34,7 @@ export default function DialogProvider({ children }) {
|
|
|
32
34
|
open: true,
|
|
33
35
|
title: props.title,
|
|
34
36
|
content: props.content,
|
|
35
|
-
|
|
37
|
+
modalType: props.modalType ?? 'drawer',
|
|
36
38
|
})
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -40,11 +42,25 @@ export default function DialogProvider({ children }) {
|
|
|
40
42
|
<DialogContext.Provider value={showModal}>
|
|
41
43
|
{children}
|
|
42
44
|
<>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
{modal.modalType == 'drawer' ? (
|
|
46
|
+
<DrawerTemplate
|
|
47
|
+
handleClose={handleClose}
|
|
48
|
+
modal={modal}
|
|
49
|
+
setModal={setModal}
|
|
50
|
+
/>
|
|
51
|
+
) : modal.modalType == 'dialog' ? (
|
|
52
|
+
<DialogTemplate
|
|
53
|
+
handleClose={handleClose}
|
|
54
|
+
modal={modal}
|
|
55
|
+
setModal={setModal}
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<ErrorTemplate
|
|
59
|
+
handleClose={handleClose}
|
|
60
|
+
modal={modal}
|
|
61
|
+
setModal={setModal}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
48
64
|
</>
|
|
49
65
|
</DialogContext.Provider>
|
|
50
66
|
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Box, Button, Modal, Typography, styled } from '@mui/material'
|
|
2
|
+
import { animatedImage } from '../../assets/images'
|
|
3
|
+
|
|
4
|
+
const style = {
|
|
5
|
+
position: 'absolute' as 'absolute',
|
|
6
|
+
top: '50%',
|
|
7
|
+
left: '50%',
|
|
8
|
+
transform: 'translate(-50%, -50%)',
|
|
9
|
+
bgcolor: 'background.paper',
|
|
10
|
+
boxShadow: 24,
|
|
11
|
+
width: '700px',
|
|
12
|
+
borderRadius: '10px',
|
|
13
|
+
display: 'flex',
|
|
14
|
+
gap: '15px',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
padding: '20px',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const StyledImage = styled(Box)(({ theme }) => ({
|
|
20
|
+
width: '154px',
|
|
21
|
+
margin: 'auto',
|
|
22
|
+
'> img': {
|
|
23
|
+
width: '100%',
|
|
24
|
+
height: '120px',
|
|
25
|
+
objectFit: 'contain',
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
const MessageContainer = styled(Box)(({ theme }) => ({
|
|
30
|
+
maxHeight: '60vh',
|
|
31
|
+
padding: '15px',
|
|
32
|
+
overflowY: 'auto',
|
|
33
|
+
'&::-webkit-scrollbar': {
|
|
34
|
+
width: '0.5em',
|
|
35
|
+
height: '0.5em',
|
|
36
|
+
},
|
|
37
|
+
'&::-webkit-scrollbar-thumb': {
|
|
38
|
+
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
|
39
|
+
borderRadius: '3px',
|
|
40
|
+
'&:hover': {
|
|
41
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
export default function ErrorTemplate({
|
|
47
|
+
handleClose,
|
|
48
|
+
modal,
|
|
49
|
+
setModal,
|
|
50
|
+
...props
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<Modal open={modal.open} onClose={handleClose}>
|
|
54
|
+
<Box sx={style}>
|
|
55
|
+
<StyledImage>
|
|
56
|
+
<img src={animatedImage} alt="Error Image" />
|
|
57
|
+
</StyledImage>
|
|
58
|
+
|
|
59
|
+
<MessageContainer>
|
|
60
|
+
<Typography
|
|
61
|
+
variant="h6"
|
|
62
|
+
sx={{ textAlign: 'center', lineHeight: 1.7 }}
|
|
63
|
+
>
|
|
64
|
+
{modal.content({ close: handleClose })}
|
|
65
|
+
</Typography>
|
|
66
|
+
</MessageContainer>
|
|
67
|
+
<Button
|
|
68
|
+
variant="outlined"
|
|
69
|
+
onClick={handleClose}
|
|
70
|
+
sx={{ height: '40px', margin: 'auto', width: '200px' }}
|
|
71
|
+
>
|
|
72
|
+
OK
|
|
73
|
+
</Button>
|
|
74
|
+
</Box>
|
|
75
|
+
</Modal>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Box, Button, Modal, Typography, styled } from '@mui/material'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { animatedImage } from '../assets/images'
|
|
4
|
+
|
|
5
|
+
const style = {
|
|
6
|
+
position: 'absolute' as 'absolute',
|
|
7
|
+
top: '50%',
|
|
8
|
+
left: '50%',
|
|
9
|
+
transform: 'translate(-50%, -50%)',
|
|
10
|
+
bgcolor: 'background.paper',
|
|
11
|
+
boxShadow: 24,
|
|
12
|
+
width: '700px',
|
|
13
|
+
borderRadius: '10px',
|
|
14
|
+
display: 'flex',
|
|
15
|
+
gap: '15px',
|
|
16
|
+
flexDirection: 'column',
|
|
17
|
+
padding: '20px',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const StyledImage = styled(Box)(({ theme }) => ({
|
|
21
|
+
width: '154px',
|
|
22
|
+
margin: 'auto',
|
|
23
|
+
'> img': {
|
|
24
|
+
width: '100%',
|
|
25
|
+
height: '120px',
|
|
26
|
+
objectFit: 'contain',
|
|
27
|
+
},
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const MessageContainer = styled(Box)(({ theme }) => ({
|
|
31
|
+
maxHeight: '60vh',
|
|
32
|
+
padding: '15px',
|
|
33
|
+
overflowY: 'auto',
|
|
34
|
+
'&::-webkit-scrollbar': {
|
|
35
|
+
width: '0.5em',
|
|
36
|
+
height: '0.5em',
|
|
37
|
+
},
|
|
38
|
+
'&::-webkit-scrollbar-thumb': {
|
|
39
|
+
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
|
40
|
+
borderRadius: '3px',
|
|
41
|
+
'&:hover': {
|
|
42
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
export default function ErrorModal({
|
|
48
|
+
errorMessage,
|
|
49
|
+
...props
|
|
50
|
+
}: {
|
|
51
|
+
errorMessage: string
|
|
52
|
+
}) {
|
|
53
|
+
const [open, setOpen] = useState(false)
|
|
54
|
+
|
|
55
|
+
const handleClose = () => {
|
|
56
|
+
setOpen((prev) => !prev)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setOpen(true)
|
|
61
|
+
}, [])
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Modal open={open} onClose={handleClose}>
|
|
65
|
+
<Box sx={style}>
|
|
66
|
+
<StyledImage>
|
|
67
|
+
<img src={animatedImage} alt="Error Image" />
|
|
68
|
+
</StyledImage>
|
|
69
|
+
|
|
70
|
+
<MessageContainer>
|
|
71
|
+
<Typography
|
|
72
|
+
variant="h6"
|
|
73
|
+
sx={{ textAlign: 'center', lineHeight: 1.7 }}
|
|
74
|
+
>
|
|
75
|
+
{errorMessage}
|
|
76
|
+
</Typography>
|
|
77
|
+
</MessageContainer>
|
|
78
|
+
<Button
|
|
79
|
+
variant="outlined"
|
|
80
|
+
onClick={handleClose}
|
|
81
|
+
sx={{ height: '40px', margin: 'auto', width: '200px' }}
|
|
82
|
+
>
|
|
83
|
+
OK
|
|
84
|
+
</Button>
|
|
85
|
+
</Box>
|
|
86
|
+
</Modal>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Box, Button, Modal, styled, Typography } from '@mui/material'
|
|
2
|
+
import { animatedImage } from '../../assets/images'
|
|
3
|
+
|
|
4
|
+
const style = {
|
|
5
|
+
position: 'absolute' as 'absolute',
|
|
6
|
+
top: '50%',
|
|
7
|
+
left: '50%',
|
|
8
|
+
transform: 'translate(-50%, -50%)',
|
|
9
|
+
bgcolor: 'background.paper',
|
|
10
|
+
boxShadow: 24,
|
|
11
|
+
width: '700px',
|
|
12
|
+
borderRadius: '10px',
|
|
13
|
+
display: 'flex',
|
|
14
|
+
gap: '15px',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
padding: '20px',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const StyledImage = styled(Box)(({ theme }) => ({
|
|
20
|
+
width: '154px',
|
|
21
|
+
margin: 'auto',
|
|
22
|
+
'> img': {
|
|
23
|
+
width: '100%',
|
|
24
|
+
height: '120px',
|
|
25
|
+
objectFit: 'contain',
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
const MessageContainer = styled(Box)(({ theme }) => ({
|
|
30
|
+
maxHeight: '60vh',
|
|
31
|
+
padding: '15px',
|
|
32
|
+
overflowY: 'auto',
|
|
33
|
+
'&::-webkit-scrollbar': {
|
|
34
|
+
width: '0.5em',
|
|
35
|
+
height: '0.5em',
|
|
36
|
+
},
|
|
37
|
+
'&::-webkit-scrollbar-thumb': {
|
|
38
|
+
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
|
39
|
+
borderRadius: '3px',
|
|
40
|
+
'&:hover': {
|
|
41
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
export const ErrorModalTemplate = ({ handleClose, modal, setModal }) => {
|
|
47
|
+
return (
|
|
48
|
+
<Modal open={modal.open} onClose={handleClose}>
|
|
49
|
+
<Box sx={style}>
|
|
50
|
+
<StyledImage>
|
|
51
|
+
<img src={animatedImage} alt="Error Image" />
|
|
52
|
+
</StyledImage>
|
|
53
|
+
|
|
54
|
+
<MessageContainer>
|
|
55
|
+
<Typography
|
|
56
|
+
variant="h6"
|
|
57
|
+
sx={{ textAlign: 'center', lineHeight: 1.7 }}
|
|
58
|
+
>
|
|
59
|
+
{modal?.errorMessage}
|
|
60
|
+
</Typography>
|
|
61
|
+
</MessageContainer>
|
|
62
|
+
{modal.content && modal.content({ close: handleClose })}
|
|
63
|
+
<Button
|
|
64
|
+
variant="outlined"
|
|
65
|
+
onClick={() => setModal({ ...modal, open: false })}
|
|
66
|
+
sx={{ height: '40px', margin: 'auto', width: '200px' }}
|
|
67
|
+
>
|
|
68
|
+
Close
|
|
69
|
+
</Button>
|
|
70
|
+
</Box>
|
|
71
|
+
</Modal>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createContext, useContext, useState } from 'react'
|
|
2
|
+
import { ErrorModalTemplate } from './ErrorModalTemplate'
|
|
3
|
+
|
|
4
|
+
interface IProps {
|
|
5
|
+
content?: ({ close }: { close: () => void }) => any
|
|
6
|
+
error?: any
|
|
7
|
+
fallBack?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type ContextType = (props: IProps) => void
|
|
11
|
+
|
|
12
|
+
const ErrorModalContext = createContext<ContextType>(null)
|
|
13
|
+
|
|
14
|
+
export default function ErrorModalProvider({ children }) {
|
|
15
|
+
const [modal, setModal] = useState({
|
|
16
|
+
open: false,
|
|
17
|
+
content: ({ close }) => null,
|
|
18
|
+
errorMessage: null,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const handleClose = () => {
|
|
22
|
+
setModal({
|
|
23
|
+
...modal,
|
|
24
|
+
open: false,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const showModal = (props) => {
|
|
29
|
+
const fallbackMessage = props.fallBack ?? 'Something went wrong.'
|
|
30
|
+
const errorMessage =
|
|
31
|
+
typeof props.error?.response?.data?.message !== 'string'
|
|
32
|
+
? props.error?.response?.data?.message?.join('\n') ?? fallbackMessage
|
|
33
|
+
: props.error?.response?.data?.message
|
|
34
|
+
|
|
35
|
+
setModal({
|
|
36
|
+
...modal,
|
|
37
|
+
open: true,
|
|
38
|
+
content: props.content,
|
|
39
|
+
errorMessage: errorMessage,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<ErrorModalContext.Provider value={showModal}>
|
|
45
|
+
{children}
|
|
46
|
+
<>
|
|
47
|
+
<ErrorModalTemplate
|
|
48
|
+
handleClose={handleClose}
|
|
49
|
+
modal={modal}
|
|
50
|
+
setModal={setModal}
|
|
51
|
+
/>
|
|
52
|
+
</>
|
|
53
|
+
</ErrorModalContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const useErrorModal = () => useContext(ErrorModalContext)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Close, Done, WarningAmberRounded } from '@mui/icons-material'
|
|
2
2
|
import { Box } from '@mui/material'
|
|
3
|
-
import {
|
|
3
|
+
import { ToastContainer as ReactToastContainer, Slide } from 'react-toastify'
|
|
4
4
|
import 'react-toastify/dist/ReactToastify.css'
|
|
5
|
-
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded'
|
|
6
5
|
|
|
7
6
|
export default function ToastContainer() {
|
|
8
7
|
return (
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ReactNode } from "react"
|
|
2
|
+
|
|
1
3
|
export const batchOptions = Array.from({ length: 12 }, (_, i) => {
|
|
2
4
|
return `${2012 + i} - ${2012 + i + 1}`
|
|
3
5
|
}).reverse()
|
|
@@ -126,3 +128,50 @@ export const ProfileApplicationType = [
|
|
|
126
128
|
value: 'hrms',
|
|
127
129
|
},
|
|
128
130
|
]
|
|
131
|
+
|
|
132
|
+
export interface Option {
|
|
133
|
+
label: string | ReactNode
|
|
134
|
+
value: any
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface GenerateLabelValueProps {
|
|
138
|
+
labelKey?: string
|
|
139
|
+
valueKey?: string
|
|
140
|
+
useIndexAsValue?: boolean
|
|
141
|
+
startFromOne?: boolean
|
|
142
|
+
showAll?: boolean
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const generateLabelValueOptions = (
|
|
146
|
+
options: any[],
|
|
147
|
+
{
|
|
148
|
+
labelKey,
|
|
149
|
+
valueKey,
|
|
150
|
+
useIndexAsValue,
|
|
151
|
+
startFromOne,
|
|
152
|
+
showAll,
|
|
153
|
+
}: GenerateLabelValueProps = {
|
|
154
|
+
labelKey: null,
|
|
155
|
+
valueKey: null,
|
|
156
|
+
useIndexAsValue: false,
|
|
157
|
+
startFromOne: false,
|
|
158
|
+
showAll: false,
|
|
159
|
+
},
|
|
160
|
+
): Option[] => {
|
|
161
|
+
let result
|
|
162
|
+
if (labelKey == null) {
|
|
163
|
+
result = options.map((option, index) => ({
|
|
164
|
+
label: option[0].toUpperCase() + option.slice(1),
|
|
165
|
+
value: useIndexAsValue ? (startFromOne ? index + 1 : index) : option,
|
|
166
|
+
}))
|
|
167
|
+
} else {
|
|
168
|
+
result = options.map((option) => ({
|
|
169
|
+
label: option[labelKey],
|
|
170
|
+
value: option[valueKey],
|
|
171
|
+
}))
|
|
172
|
+
}
|
|
173
|
+
if (showAll) {
|
|
174
|
+
result.unshift({ label: 'All', value: 'all' })
|
|
175
|
+
}
|
|
176
|
+
return result
|
|
177
|
+
}
|
|
@@ -8,9 +8,9 @@ import { ReactNode, useEffect } from 'react'
|
|
|
8
8
|
import { ToastContainer } from '../components'
|
|
9
9
|
import DialogProvider from '../components/DrawerWrapper/DrawerWrapper'
|
|
10
10
|
import GlobalNetworkLoadingIndicator from '../components/ErrorBoundary/GlobalNetworkLoadingIndicator'
|
|
11
|
-
import
|
|
12
|
-
import RootModal from './RootModal'
|
|
11
|
+
import ErrorModalProvider from '../components/ErrorModalWrapper/ErrorModalWrapper'
|
|
13
12
|
import { isDevelopment } from '../constants'
|
|
13
|
+
import RootModal from './RootModal'
|
|
14
14
|
|
|
15
15
|
export const campxTenantKey = Cookies.get('campx_tenant')
|
|
16
16
|
export const urlTenantKey = window.location.pathname.split('/')[1]
|
|
@@ -60,7 +60,7 @@ export default function Providers({ children }: { children: ReactNode }) {
|
|
|
60
60
|
<MuiThemeProvider>
|
|
61
61
|
<ConfirmContextProvider>
|
|
62
62
|
<DialogProvider>
|
|
63
|
-
{children}
|
|
63
|
+
<ErrorModalProvider>{children}</ErrorModalProvider>
|
|
64
64
|
<GlobalNetworkLoadingIndicator />
|
|
65
65
|
<RootModal />
|
|
66
66
|
<ToastContainer />
|