@campxdev/shared 1.11.5 → 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 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.5",
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": "^5.35.1",
77
- "@typescript-eslint/parser": "^5.35.1",
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": "^4.8.4"
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,5 @@
1
+ import { lazy } from 'react'
2
+
3
+ const DatabaseConfiguration = lazy(() => import('./DatabaseConfiguration'))
4
+
5
+ export { DatabaseConfiguration }
@@ -0,0 +1,6 @@
1
+ import axios from '../../config/axios'
2
+ const ENDPOINT = '/hrms/punch-logs-database-config'
3
+
4
+ export const getPunchLogsDatabaseConfigByTenant = async () => {
5
+ return axios.get(`${ENDPOINT}/tenant-config`).then((res) => res.data)
6
+ }
@@ -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
- // modalType: props.modalType ?? 'drawer',
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
- <DrawerTemplate
44
- handleClose={handleClose}
45
- modal={modal}
46
- setModal={setModal}
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)
@@ -118,7 +118,7 @@ export default function ReportHeader({
118
118
  src={
119
119
  reportHeaderImageUrl ||
120
120
  current?.reportHeader?.url ||
121
- masterInstitution.reportHeader?.url
121
+ masterInstitution?.reportHeader?.url
122
122
  }
123
123
  />
124
124
  </>
@@ -142,19 +142,23 @@ export default function ReportHeader({
142
142
  sx={recognitionDetailsSx}
143
143
  >
144
144
  {recognitionDetailsText ||
145
- current.recognitionDetails ||
146
- masterInstitution.recognitionDetails}
145
+ current?.recognitionDetails ||
146
+ masterInstitution?.recognitionDetails ||
147
+ ''}
147
148
  </Typography>
148
149
  )}
149
150
  {!hideAddress && (
150
151
  <Typography variant={addressVariant} sx={addressSx}>
151
- {addressText || current.address || masterInstitution.address}
152
+ {addressText ||
153
+ current?.address ||
154
+ masterInstitution?.address ||
155
+ ''}
152
156
  </Typography>
153
157
  )}
154
158
  {!hidePhone && (
155
159
  <Typography variant={phoneVariant} sx={phoneSx}>
156
160
  {'Phone: ' +
157
- (phoneText || current.phone || masterInstitution.phone)}
161
+ (phoneText || current?.phone || masterInstitution?.phone)}
158
162
  </Typography>
159
163
  )}
160
164
  </>
@@ -168,8 +172,8 @@ export default function ReportHeader({
168
172
  />
169
173
  )}
170
174
  {typographyList &&
171
- typographyList.map((s) => (
172
- <Typography variant={s.variant || 'body1'} sx={s.sx}>
175
+ typographyList.map((s, index) => (
176
+ <Typography key={index} variant={s.variant || 'body1'} sx={s.sx}>
173
177
  {s.text}
174
178
  </Typography>
175
179
  ))}
@@ -1,8 +1,7 @@
1
- import { Done, Close, WarningAmberRounded } from '@mui/icons-material'
1
+ import { Close, Done, WarningAmberRounded } from '@mui/icons-material'
2
2
  import { Box } from '@mui/material'
3
- import { Slide, ToastContainer as ReactToastContainer } from 'react-toastify'
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 FreshChatButton from '../components/Layout/Header/HeaderActions/FreshChatButton'
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 />