@campxdev/shared 1.11.29 → 1.11.30
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 +4 -3
- package/src/components/ActivityLog/ActivityLog.tsx +179 -115
- package/src/components/ActivityLog/Styles.tsx +11 -2
- package/src/components/Input/SearchSingleSelect.tsx +1 -1
- package/src/components/Layout/Header/AppHeader.tsx +15 -6
- package/src/components/Layout/Header/HeaderActions/HeaderActions.tsx +3 -0
- package/src/components/Layout/Header/HeaderActions/UserBox.tsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@campxdev/shared",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.30",
|
|
4
4
|
"main": "./exports.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "react-scripts start",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"lint:fix": "eslint --fix \"./src/**/*.{js,jsx,ts,tsx,json}\"",
|
|
14
14
|
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc",
|
|
15
15
|
"storybook": "start-storybook -p 6006 -s public",
|
|
16
|
-
"build-storybook": "
|
|
16
|
+
"build-storybook": "cross-env NODE_OPTIONS=--openssl-legacy-provider build-storybook"
|
|
17
17
|
},
|
|
18
18
|
"browserslist": {
|
|
19
19
|
"production": [
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"storybook-addon-react-router-v6": "^0.2.1"
|
|
92
92
|
},
|
|
93
93
|
"resolutions": {
|
|
94
|
-
"react-dev-utils/fork-ts-checker-webpack-plugin": "^6.5.3"
|
|
94
|
+
"react-dev-utils/fork-ts-checker-webpack-plugin": "^6.5.3",
|
|
95
|
+
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0"
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -1,145 +1,209 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Timeline,
|
|
3
|
-
TimelineConnector,
|
|
4
3
|
TimelineContent,
|
|
5
4
|
TimelineItem,
|
|
6
5
|
TimelineSeparator,
|
|
7
6
|
} from '@mui/lab'
|
|
8
7
|
import { Box, SxProps, Typography } from '@mui/material'
|
|
9
|
-
import
|
|
10
|
-
import
|
|
8
|
+
import axios from 'axios'
|
|
9
|
+
import { useCallback, useRef } from 'react'
|
|
10
|
+
import { useInfiniteQuery } from 'react-query'
|
|
11
11
|
import { useErrorModal } from '../ErrorModalWrapper/ErrorModalWrapper'
|
|
12
|
-
import { PageContent } from '../PageContent'
|
|
13
|
-
import PageHeader from '../PageHeader'
|
|
14
12
|
import Spinner from '../Spinner'
|
|
15
13
|
import Table from '../Tables/BasicTable/Table'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
import {
|
|
15
|
+
StyledAvatar,
|
|
16
|
+
StyledCircleIcon,
|
|
17
|
+
StyledSpinnerBox,
|
|
18
|
+
StyledTimeLineDot,
|
|
19
|
+
} from './Styles'
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
message: string
|
|
27
|
-
userName: string
|
|
21
|
+
interface Props {
|
|
22
|
+
endPoint: string
|
|
23
|
+
tableView: boolean
|
|
24
|
+
sx?: SxProps
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
activities,
|
|
32
|
-
endPoint,
|
|
33
|
-
tableView,
|
|
34
|
-
title,
|
|
35
|
-
sx,
|
|
36
|
-
}: {
|
|
37
|
-
activities: Activity[]
|
|
38
|
-
sx?: SxProps
|
|
39
|
-
endPoint?: string
|
|
40
|
-
title?: string
|
|
41
|
-
tableView: boolean
|
|
42
|
-
}) => {
|
|
27
|
+
export default function ActivityLog({ endPoint, tableView, sx }: Props) {
|
|
43
28
|
const errorModal = useErrorModal()
|
|
44
|
-
const
|
|
45
|
-
|
|
29
|
+
const fetchActivities = async ({ pageParam = 0 }) => {
|
|
30
|
+
try {
|
|
31
|
+
const response = await axios.get(endPoint, {
|
|
32
|
+
params: {
|
|
33
|
+
limit: 4,
|
|
34
|
+
skip: pageParam,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
return response.data.posts
|
|
38
|
+
} catch (error) {
|
|
46
39
|
// eslint-disable-next-line no-console
|
|
47
40
|
console.log(error)
|
|
48
41
|
errorModal({ error: error })
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
if (isLoading) {
|
|
53
|
-
return <Spinner />
|
|
42
|
+
}
|
|
54
43
|
}
|
|
55
44
|
|
|
45
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
|
|
46
|
+
useInfiniteQuery('activities', fetchActivities, {
|
|
47
|
+
getNextPageParam: (lastPage) => {
|
|
48
|
+
return lastPage.length ? lastPage[lastPage.length - 1].id : null
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const activitesData = data ? data.pages.flatMap((page) => page) : []
|
|
53
|
+
|
|
54
|
+
if (isLoading) return <Spinner />
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Box>
|
|
58
|
+
<Typography
|
|
59
|
+
variant="h1"
|
|
60
|
+
sx={{ fontSize: '18px', fontWeight: 700, margin: '20px' }}
|
|
61
|
+
>
|
|
62
|
+
Activity Log
|
|
63
|
+
</Typography>
|
|
64
|
+
|
|
65
|
+
{!tableView ? (
|
|
66
|
+
<Box
|
|
67
|
+
sx={{
|
|
68
|
+
overflowY: 'scroll',
|
|
69
|
+
'&::-webkit-scrollbar': {
|
|
70
|
+
display: 'none',
|
|
71
|
+
},
|
|
72
|
+
height: '380px',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<TimeLineComponent
|
|
76
|
+
activitesData={activitesData}
|
|
77
|
+
fetchNextPage={fetchNextPage}
|
|
78
|
+
isFetchingNextPage={isFetchingNextPage}
|
|
79
|
+
hasNextPage={hasNextPage}
|
|
80
|
+
sx={sx}
|
|
81
|
+
/>
|
|
82
|
+
</Box>
|
|
83
|
+
) : (
|
|
84
|
+
<Box sx={{ margin: '0 25px' }}>
|
|
85
|
+
<Table
|
|
86
|
+
columns={columns}
|
|
87
|
+
dataSource={activitesData}
|
|
88
|
+
loading={isLoading}
|
|
89
|
+
/>
|
|
90
|
+
</Box>
|
|
91
|
+
)}
|
|
92
|
+
</Box>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const columns = [
|
|
97
|
+
{ title: 'Date', dataIndex: 'date', key: 'date' },
|
|
98
|
+
{ title: 'Time', dataIndex: 'time', key: 'time' },
|
|
99
|
+
{ title: 'Message', dataIndex: 'message', key: 'message' },
|
|
100
|
+
{ title: 'User', dataIndex: 'userName', key: 'userName' },
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
export const TimeLineComponent = ({
|
|
104
|
+
activitesData,
|
|
105
|
+
isFetchingNextPage,
|
|
106
|
+
hasNextPage,
|
|
107
|
+
fetchNextPage,
|
|
108
|
+
sx,
|
|
109
|
+
}) => {
|
|
110
|
+
const lastItemRef = useIntersectionObserver<HTMLElement>(() => {
|
|
111
|
+
if (!isFetchingNextPage && hasNextPage) fetchNextPage()
|
|
112
|
+
})
|
|
113
|
+
|
|
56
114
|
return (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
115
|
+
<Timeline sx={{ padding: 0, margin: 0 }}>
|
|
116
|
+
{activitesData.map((item, index, items) => (
|
|
117
|
+
<Box
|
|
118
|
+
key={index}
|
|
119
|
+
ref={items.length - 1 === index ? lastItemRef : null}
|
|
120
|
+
sx={{ maxWidth: '550px', ...sx }}
|
|
121
|
+
>
|
|
122
|
+
<TimelineItem
|
|
62
123
|
sx={{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
margin: 2,
|
|
125
|
+
padding: 0,
|
|
126
|
+
'&:before': { display: 'none' },
|
|
66
127
|
}}
|
|
67
128
|
>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
129
|
+
<TimelineSeparator>
|
|
130
|
+
<StyledTimeLineDot>
|
|
131
|
+
<StyledCircleIcon />
|
|
132
|
+
</StyledTimeLineDot>
|
|
133
|
+
{index < activitesData.length - 1 && (
|
|
134
|
+
<Box
|
|
72
135
|
sx={{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
136
|
+
width: '1px',
|
|
137
|
+
height: '110px',
|
|
138
|
+
bgcolor: '#12121233',
|
|
139
|
+
position: 'absolute',
|
|
140
|
+
top: '25px',
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
</TimelineSeparator>
|
|
145
|
+
<TimelineContent sx={{ padding: '6px 10px' }}>
|
|
146
|
+
<Box>
|
|
147
|
+
<Typography variant="subtitle2" sx={{ fontSize: '14px' }}>
|
|
148
|
+
{`${Math.floor(Math.random() * 30) + 1} ${
|
|
149
|
+
Math.random() > 0.5 ? 'June' : 'July'
|
|
150
|
+
}, 2023`}{' '}
|
|
151
|
+
-{' '}
|
|
152
|
+
{`${Math.floor(Math.random() * 12) + 1}:${
|
|
153
|
+
Math.floor(Math.random() * 60) + 1
|
|
154
|
+
} ${Math.random() > 0.5 ? 'AM' : 'PM'}`}
|
|
155
|
+
{/* {item.date} - {item.time} */}
|
|
156
|
+
</Typography>
|
|
157
|
+
<Typography sx={{ fontSize: '16px' }} variant="body1">
|
|
158
|
+
{item.title}
|
|
159
|
+
{/* {item.message} */}
|
|
160
|
+
</Typography>
|
|
161
|
+
<Typography
|
|
162
|
+
style={{
|
|
163
|
+
display: 'flex',
|
|
164
|
+
alignItems: 'center',
|
|
165
|
+
marginTop: '8px',
|
|
76
166
|
}}
|
|
77
167
|
>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<StyledAvatar />
|
|
104
|
-
<Typography sx={{ fontSize: '13px', fontWeight: 900 }}>
|
|
105
|
-
{item.userName}
|
|
106
|
-
</Typography>
|
|
107
|
-
</Typography>
|
|
108
|
-
</Box>
|
|
109
|
-
</TimelineContent>
|
|
110
|
-
</TimelineItem>
|
|
111
|
-
))}
|
|
112
|
-
</Timeline>
|
|
113
|
-
</Box>
|
|
114
|
-
) : (
|
|
115
|
-
<Table columns={columns} dataSource={activities ?? []} />
|
|
116
|
-
)}
|
|
117
|
-
</PageContent>
|
|
118
|
-
</>
|
|
168
|
+
<StyledAvatar>
|
|
169
|
+
{'John Doe'
|
|
170
|
+
.split(' ')
|
|
171
|
+
.map((n) => n[0])
|
|
172
|
+
.join('')
|
|
173
|
+
.toUpperCase()}
|
|
174
|
+
</StyledAvatar>
|
|
175
|
+
<Typography sx={{ fontSize: '13px', fontWeight: 900 }}>
|
|
176
|
+
{`John Doe ${item.id}`}
|
|
177
|
+
{/* {item.userName} */}
|
|
178
|
+
</Typography>
|
|
179
|
+
</Typography>
|
|
180
|
+
</Box>
|
|
181
|
+
</TimelineContent>
|
|
182
|
+
</TimelineItem>
|
|
183
|
+
{isFetchingNextPage && index === items.length - 1 && hasNextPage && (
|
|
184
|
+
<StyledSpinnerBox>
|
|
185
|
+
<Spinner />
|
|
186
|
+
</StyledSpinnerBox>
|
|
187
|
+
)}
|
|
188
|
+
</Box>
|
|
189
|
+
))}
|
|
190
|
+
</Timeline>
|
|
119
191
|
)
|
|
120
192
|
}
|
|
121
193
|
|
|
122
|
-
|
|
194
|
+
function useIntersectionObserver<T extends HTMLElement>(callback: () => void) {
|
|
195
|
+
const observer = useRef<IntersectionObserver | null>(null)
|
|
123
196
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dataIndex: 'message',
|
|
138
|
-
key: 'message',
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
title: 'User',
|
|
142
|
-
dataIndex: 'userName',
|
|
143
|
-
key: 'userName',
|
|
144
|
-
},
|
|
145
|
-
]
|
|
197
|
+
const handleObserver = useCallback(
|
|
198
|
+
(node: T) => {
|
|
199
|
+
if (observer.current) observer.current.disconnect()
|
|
200
|
+
observer.current = new IntersectionObserver((entries) => {
|
|
201
|
+
if (entries[0].isIntersecting) callback()
|
|
202
|
+
})
|
|
203
|
+
if (node) observer.current.observe(node)
|
|
204
|
+
},
|
|
205
|
+
[callback],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return handleObserver
|
|
209
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import CircleRoundedIcon from '@mui/icons-material/CircleRounded'
|
|
2
2
|
import { TimelineDot } from '@mui/lab'
|
|
3
|
-
import { Avatar, styled } from '@mui/material'
|
|
3
|
+
import { Avatar, Box, styled } from '@mui/material'
|
|
4
4
|
|
|
5
5
|
export const StyledTimeLineDot = styled(TimelineDot)({
|
|
6
6
|
width: '20px',
|
|
@@ -21,8 +21,17 @@ export const StyledCircleIcon = styled(CircleRoundedIcon)({
|
|
|
21
21
|
height: '7px',
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
export const StyledAvatar = styled(Avatar)({
|
|
24
|
+
export const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
|
25
25
|
width: 30,
|
|
26
26
|
height: 30,
|
|
27
27
|
marginRight: '8px',
|
|
28
|
+
backgroundColor: theme.palette.primary.main,
|
|
29
|
+
fontSize: '14px',
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
export const StyledSpinnerBox = styled(Box)({
|
|
33
|
+
display: 'flex',
|
|
34
|
+
justifyContent: 'center',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
padding: '20px',
|
|
28
37
|
})
|
|
@@ -87,7 +87,7 @@ export const SearchSingleSelect = ({ options, value, onChange }: Props) => {
|
|
|
87
87
|
<TextField
|
|
88
88
|
inputRef={(input) => {
|
|
89
89
|
if (input && value) {
|
|
90
|
-
input.style.width = `${value.label
|
|
90
|
+
input.style.width = `${value.label?.length * 9}px`
|
|
91
91
|
}
|
|
92
92
|
}}
|
|
93
93
|
InputProps={{
|
|
@@ -38,6 +38,7 @@ interface AppHeaderProps {
|
|
|
38
38
|
clientLogo: string
|
|
39
39
|
fullName: string
|
|
40
40
|
profileUrl?: string
|
|
41
|
+
showMenu?: boolean
|
|
41
42
|
actions?: ReactNode[]
|
|
42
43
|
userBoxActions: {
|
|
43
44
|
label: ReactNode
|
|
@@ -45,23 +46,30 @@ interface AppHeaderProps {
|
|
|
45
46
|
onClick: any
|
|
46
47
|
}[]
|
|
47
48
|
customHeaderActions?: ReactNode
|
|
49
|
+
headerSx?: any
|
|
50
|
+
imageSx?: any
|
|
51
|
+
profileSx?: any
|
|
48
52
|
cogWheelMenu?: { label: string; path: string; openNewTab?: boolean }[]
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export default function AppHeader({
|
|
52
56
|
clientLogo = imageMap.campx,
|
|
57
|
+
showMenu = true,
|
|
53
58
|
fullName,
|
|
54
59
|
userBoxActions = [],
|
|
55
60
|
cogWheelMenu = [],
|
|
56
61
|
customHeaderActions,
|
|
57
62
|
profileUrl,
|
|
58
63
|
actions = [],
|
|
64
|
+
headerSx = {},
|
|
65
|
+
profileSx = {},
|
|
66
|
+
imageSx = {},
|
|
59
67
|
}: AppHeaderProps) {
|
|
60
68
|
return (
|
|
61
|
-
<StyledHeader>
|
|
69
|
+
<StyledHeader sx={headerSx}>
|
|
62
70
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
63
|
-
<AppsMenu />
|
|
64
|
-
<AppLogo clientLogo={clientLogo} />
|
|
71
|
+
{showMenu && <AppsMenu />}
|
|
72
|
+
<AppLogo clientLogo={clientLogo} imageSx={imageSx} />
|
|
65
73
|
</Box>
|
|
66
74
|
<Box className="actions">
|
|
67
75
|
{customHeaderActions ? (
|
|
@@ -73,6 +81,7 @@ export default function AppHeader({
|
|
|
73
81
|
userBoxActions={userBoxActions}
|
|
74
82
|
profileUrl={profileUrl}
|
|
75
83
|
actions={actions}
|
|
84
|
+
profileSx={profileSx}
|
|
76
85
|
/>
|
|
77
86
|
)}
|
|
78
87
|
</Box>
|
|
@@ -80,16 +89,16 @@ export default function AppHeader({
|
|
|
80
89
|
)
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
const AppLogo = ({ clientLogo }) => {
|
|
92
|
+
const AppLogo = ({ clientLogo, imageSx }) => {
|
|
84
93
|
const originSubdomain = window.location.host.split('.')?.slice(-3)[0] ?? 'ums'
|
|
85
94
|
const currentApp =
|
|
86
95
|
applications.find((item) => item.domainName === originSubdomain)
|
|
87
|
-
?.domainName ?? '
|
|
96
|
+
?.domainName ?? 'admin'
|
|
88
97
|
|
|
89
98
|
return (
|
|
90
99
|
<StyledRouterLink to={'/'}>
|
|
91
100
|
<StyledLogosWrapper>
|
|
92
|
-
<StyledImageWrapper>
|
|
101
|
+
<StyledImageWrapper sx={imageSx}>
|
|
93
102
|
<img src={imageMap[currentApp]} />
|
|
94
103
|
</StyledImageWrapper>
|
|
95
104
|
<Box
|
|
@@ -18,12 +18,14 @@ export default function HeaderActions({
|
|
|
18
18
|
userBoxActions,
|
|
19
19
|
profileUrl = '',
|
|
20
20
|
actions = [],
|
|
21
|
+
profileSx = {},
|
|
21
22
|
}: {
|
|
22
23
|
cogWheelMenu?: any[]
|
|
23
24
|
fullName: string
|
|
24
25
|
userBoxActions: IMenuItemProps[] | []
|
|
25
26
|
profileUrl: string
|
|
26
27
|
actions?: ReactNode[]
|
|
28
|
+
profileSx?: any
|
|
27
29
|
}) {
|
|
28
30
|
const navigate = useNavigate()
|
|
29
31
|
return (
|
|
@@ -46,6 +48,7 @@ export default function HeaderActions({
|
|
|
46
48
|
fullName={fullName}
|
|
47
49
|
actions={userBoxActions}
|
|
48
50
|
profileUrl={profileUrl}
|
|
51
|
+
profileSx={profileSx}
|
|
49
52
|
/>
|
|
50
53
|
</Stack>
|
|
51
54
|
</Stack>
|
|
@@ -27,17 +27,19 @@ export default function UserBox({
|
|
|
27
27
|
actions,
|
|
28
28
|
customActions = [],
|
|
29
29
|
profileUrl,
|
|
30
|
+
profileSx = {},
|
|
30
31
|
}: {
|
|
31
32
|
fullName: string
|
|
32
33
|
actions: IMenuItemProps[] | []
|
|
33
34
|
customActions?: IMenuItemProps[] | []
|
|
34
35
|
profileUrl?: string
|
|
36
|
+
profileSx?: any
|
|
35
37
|
}) {
|
|
36
38
|
const navigate = useNavigate()
|
|
37
39
|
return (
|
|
38
40
|
<DropDownButton
|
|
39
41
|
anchor={({ open }) => (
|
|
40
|
-
<StyledAvatar src={profileUrl ?? ''} onClick={open}>
|
|
42
|
+
<StyledAvatar src={profileUrl ?? ''} onClick={open} sx={profileSx}>
|
|
41
43
|
{getStartingLetters(fullName)}
|
|
42
44
|
</StyledAvatar>
|
|
43
45
|
)}
|