@campxdev/shared 3.1.7 → 3.1.9
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
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
|
+
import RefreshIcon from '@mui/icons-material/Refresh'
|
|
1
2
|
import {
|
|
2
3
|
Timeline,
|
|
4
|
+
TimelineConnector,
|
|
3
5
|
TimelineContent,
|
|
4
6
|
TimelineItem,
|
|
5
7
|
TimelineSeparator,
|
|
6
8
|
} from '@mui/lab'
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Box,
|
|
11
|
+
CircularProgress,
|
|
12
|
+
IconButton,
|
|
13
|
+
SxProps,
|
|
14
|
+
Typography,
|
|
15
|
+
} from '@mui/material'
|
|
8
16
|
import moment from 'moment'
|
|
9
|
-
import {
|
|
17
|
+
import { useEffect, useRef } from 'react'
|
|
10
18
|
import { useInfiniteQuery } from 'react-query'
|
|
11
19
|
import { NoDataIllustration } from '..'
|
|
12
20
|
import axios from '../../config/axios'
|
|
13
21
|
import { useErrorModal } from '../ErrorModalWrapper/ErrorModalWrapper'
|
|
14
22
|
import Spinner from '../Spinner'
|
|
15
23
|
import Table from '../Tables/BasicTable/Table'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
import { StyledAvatar, StyledCircleIcon, StyledTimeLineDot } from './Styles'
|
|
25
|
+
|
|
26
|
+
interface ActivityLogItem {
|
|
27
|
+
timestamp: string
|
|
28
|
+
message: string
|
|
29
|
+
userName: string
|
|
30
|
+
action: string
|
|
31
|
+
typeId: number
|
|
32
|
+
type: string
|
|
33
|
+
tenantId: string
|
|
34
|
+
institutionId: number
|
|
35
|
+
userId: string
|
|
36
|
+
cursor: string
|
|
37
|
+
}
|
|
22
38
|
|
|
23
39
|
interface Props {
|
|
24
40
|
endPoint: string
|
|
@@ -26,6 +42,7 @@ interface Props {
|
|
|
26
42
|
enableTitle?: boolean
|
|
27
43
|
params: any
|
|
28
44
|
sx?: SxProps
|
|
45
|
+
reloadKey?: string | number
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
export default function ActivityLog({
|
|
@@ -34,31 +51,64 @@ export default function ActivityLog({
|
|
|
34
51
|
enableTitle,
|
|
35
52
|
params,
|
|
36
53
|
sx,
|
|
54
|
+
reloadKey,
|
|
37
55
|
}: Props) {
|
|
38
56
|
const errorModal = useErrorModal()
|
|
39
|
-
const
|
|
57
|
+
const timestamp = useRef(Date.now())
|
|
58
|
+
const queryKey = [
|
|
59
|
+
'activities',
|
|
60
|
+
endPoint,
|
|
61
|
+
JSON.stringify(params),
|
|
62
|
+
reloadKey ?? timestamp.current,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const fetchActivities = async ({ pageParam = null }) => {
|
|
40
66
|
try {
|
|
41
|
-
const
|
|
42
|
-
|
|
67
|
+
const queryParams = { ...params }
|
|
68
|
+
if (pageParam) {
|
|
69
|
+
queryParams.cursor = pageParam
|
|
70
|
+
}
|
|
71
|
+
const response = await axios.get(endPoint, { params: queryParams })
|
|
72
|
+
return response?.data || []
|
|
43
73
|
} catch (error) {
|
|
44
74
|
// eslint-disable-next-line no-console
|
|
45
|
-
console.
|
|
75
|
+
console.error('Error fetching activities:', error)
|
|
46
76
|
errorModal({ error: error })
|
|
77
|
+
return []
|
|
47
78
|
}
|
|
48
79
|
}
|
|
49
80
|
|
|
50
|
-
const {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
const {
|
|
82
|
+
data,
|
|
83
|
+
fetchNextPage,
|
|
84
|
+
hasNextPage,
|
|
85
|
+
isFetchingNextPage,
|
|
86
|
+
isLoading,
|
|
87
|
+
refetch,
|
|
88
|
+
} = useInfiniteQuery(queryKey, fetchActivities, {
|
|
89
|
+
getNextPageParam: (lastPage: ActivityLogItem[]) => {
|
|
90
|
+
return lastPage?.length ? lastPage[lastPage.length - 1].cursor : null
|
|
91
|
+
},
|
|
92
|
+
refetchOnWindowFocus: false,
|
|
93
|
+
cacheTime: 0,
|
|
94
|
+
})
|
|
56
95
|
|
|
57
|
-
const
|
|
96
|
+
const activitiesData = data?.pages?.flatMap((page) => page) || []
|
|
58
97
|
|
|
59
|
-
if (isLoading)
|
|
98
|
+
if (isLoading) {
|
|
99
|
+
return (
|
|
100
|
+
<Box
|
|
101
|
+
display="flex"
|
|
102
|
+
justifyContent="center"
|
|
103
|
+
alignItems="center"
|
|
104
|
+
minHeight="200px"
|
|
105
|
+
>
|
|
106
|
+
<Spinner />
|
|
107
|
+
</Box>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
60
110
|
|
|
61
|
-
if (
|
|
111
|
+
if (!activitiesData?.length) {
|
|
62
112
|
return (
|
|
63
113
|
<NoDataIllustration
|
|
64
114
|
imageSrc=""
|
|
@@ -67,7 +117,7 @@ export default function ActivityLog({
|
|
|
67
117
|
variant="h6"
|
|
68
118
|
style={{ textAlign: 'center', marginTop: '20px' }}
|
|
69
119
|
>
|
|
70
|
-
|
|
120
|
+
No activity logs found
|
|
71
121
|
</Typography>
|
|
72
122
|
}
|
|
73
123
|
/>
|
|
@@ -77,37 +127,40 @@ export default function ActivityLog({
|
|
|
77
127
|
return (
|
|
78
128
|
<Box>
|
|
79
129
|
{enableTitle && (
|
|
80
|
-
<Typography
|
|
81
|
-
variant="h1"
|
|
82
|
-
sx={{ fontSize: '18px', fontWeight: 700, margin: '20px' }}
|
|
83
|
-
>
|
|
84
|
-
Activity Log
|
|
85
|
-
</Typography>
|
|
86
|
-
)}
|
|
87
|
-
|
|
88
|
-
{!tableView ? (
|
|
89
130
|
<Box
|
|
90
131
|
sx={{
|
|
91
|
-
|
|
92
|
-
'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
height: '380px',
|
|
132
|
+
display: 'flex',
|
|
133
|
+
justifyContent: 'space-between',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
margin: '20px',
|
|
96
136
|
}}
|
|
97
137
|
>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
<Typography variant="h1" sx={{ fontSize: '18px', fontWeight: 700 }}>
|
|
139
|
+
Activity Logs
|
|
140
|
+
</Typography>
|
|
141
|
+
<IconButton
|
|
142
|
+
size="small"
|
|
143
|
+
onClick={() => refetch()}
|
|
144
|
+
sx={{ '&:hover': { backgroundColor: 'rgba(0, 0, 0, 0.04)' } }}
|
|
145
|
+
>
|
|
146
|
+
<RefreshIcon fontSize="small" />
|
|
147
|
+
</IconButton>
|
|
105
148
|
</Box>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{!tableView ? (
|
|
152
|
+
<TimeLineComponent
|
|
153
|
+
activitiesData={activitiesData}
|
|
154
|
+
fetchNextPage={fetchNextPage}
|
|
155
|
+
hasNextPage={hasNextPage}
|
|
156
|
+
isFetchingNextPage={isFetchingNextPage}
|
|
157
|
+
sx={sx}
|
|
158
|
+
/>
|
|
106
159
|
) : (
|
|
107
160
|
<Box sx={{ margin: '0 25px' }}>
|
|
108
161
|
<Table
|
|
109
|
-
columns={
|
|
110
|
-
dataSource={
|
|
162
|
+
columns={tableColumns}
|
|
163
|
+
dataSource={activitiesData}
|
|
111
164
|
loading={isLoading}
|
|
112
165
|
/>
|
|
113
166
|
</Box>
|
|
@@ -116,12 +169,13 @@ export default function ActivityLog({
|
|
|
116
169
|
)
|
|
117
170
|
}
|
|
118
171
|
|
|
119
|
-
|
|
172
|
+
// Table columns for table view
|
|
173
|
+
const tableColumns = [
|
|
120
174
|
{
|
|
121
175
|
title: 'Date',
|
|
122
176
|
dataIndex: 'timestamp',
|
|
123
177
|
key: 'timestamp',
|
|
124
|
-
render: (timestamp) =>
|
|
178
|
+
render: (timestamp: string) =>
|
|
125
179
|
moment.utc(timestamp)?.local().format('DD MMM YYYY - hh:mm A'),
|
|
126
180
|
},
|
|
127
181
|
{
|
|
@@ -138,131 +192,165 @@ const columns = [
|
|
|
138
192
|
title: 'Message',
|
|
139
193
|
dataIndex: 'message',
|
|
140
194
|
key: 'message',
|
|
141
|
-
render: (message) => (
|
|
195
|
+
render: (message: string) => (
|
|
142
196
|
<Typography sx={{ fontSize: '16px' }} variant="subtitle1">
|
|
143
|
-
{message
|
|
144
|
-
index % 2 === 0 ? (
|
|
145
|
-
<span key={index}>{text}</span>
|
|
146
|
-
) : (
|
|
147
|
-
<Typography key={index} sx={{ display: 'inline', fontWeight: 900 }}>
|
|
148
|
-
{text}
|
|
149
|
-
</Typography>
|
|
150
|
-
),
|
|
151
|
-
)}
|
|
197
|
+
{renderMessageWithBold(message)}
|
|
152
198
|
</Typography>
|
|
153
199
|
),
|
|
154
200
|
},
|
|
155
201
|
]
|
|
156
202
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
// Helper: Render message with bold text (text within single quotes)
|
|
204
|
+
const renderMessageWithBold = (message: string) => {
|
|
205
|
+
return message?.split("'")?.map((text: string, index: number) =>
|
|
206
|
+
index % 2 === 0 ? (
|
|
207
|
+
<span key={index}>{text}</span>
|
|
208
|
+
) : (
|
|
209
|
+
<Typography key={index} component="span" sx={{ fontWeight: 900 }}>
|
|
210
|
+
{text}
|
|
211
|
+
</Typography>
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Timeline Component with Infinite Scroll
|
|
217
|
+
interface TimeLineComponentProps {
|
|
218
|
+
activitiesData: ActivityLogItem[]
|
|
219
|
+
fetchNextPage: () => void
|
|
220
|
+
hasNextPage: boolean | undefined
|
|
221
|
+
isFetchingNextPage: boolean
|
|
222
|
+
sx?: SxProps
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const TimeLineComponent = ({
|
|
226
|
+
activitiesData,
|
|
161
227
|
fetchNextPage,
|
|
228
|
+
hasNextPage,
|
|
229
|
+
isFetchingNextPage,
|
|
162
230
|
sx,
|
|
163
|
-
}) => {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
231
|
+
}: TimeLineComponentProps) => {
|
|
232
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
|
233
|
+
const loadMoreTriggerRef = useRef<HTMLDivElement>(null)
|
|
234
|
+
|
|
235
|
+
// Intersection observer for infinite scroll
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const trigger = loadMoreTriggerRef.current
|
|
238
|
+
const container = scrollContainerRef.current
|
|
239
|
+
|
|
240
|
+
if (!trigger || !container) return
|
|
241
|
+
|
|
242
|
+
const observer = new IntersectionObserver(
|
|
243
|
+
(entries) => {
|
|
244
|
+
const [entry] = entries
|
|
245
|
+
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
|
|
246
|
+
fetchNextPage()
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
root: container,
|
|
251
|
+
rootMargin: '100px',
|
|
252
|
+
threshold: 0.1,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
observer.observe(trigger)
|
|
257
|
+
|
|
258
|
+
return () => {
|
|
259
|
+
observer.disconnect()
|
|
260
|
+
}
|
|
261
|
+
}, [fetchNextPage, hasNextPage, isFetchingNextPage])
|
|
167
262
|
|
|
168
263
|
return (
|
|
169
|
-
<
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
264
|
+
<Box
|
|
265
|
+
ref={scrollContainerRef}
|
|
266
|
+
sx={{
|
|
267
|
+
overflowY: 'auto',
|
|
268
|
+
maxHeight: '600px',
|
|
269
|
+
'&::-webkit-scrollbar': {
|
|
270
|
+
display: 'none',
|
|
271
|
+
},
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
<Timeline sx={{ padding: 0, margin: 0 }}>
|
|
275
|
+
{activitiesData.map((item, index) => (
|
|
276
|
+
<Box
|
|
277
|
+
key={`${item.cursor}-${index}`}
|
|
278
|
+
ref={
|
|
279
|
+
activitiesData.length - 1 === index ? loadMoreTriggerRef : null
|
|
280
|
+
}
|
|
281
|
+
sx={{ maxWidth: '550px', ...sx }}
|
|
182
282
|
>
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<
|
|
208
|
-
{
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
) : (
|
|
214
|
-
<Typography
|
|
215
|
-
key={index}
|
|
216
|
-
sx={{ display: 'inline', fontWeight: 900 }}
|
|
217
|
-
>
|
|
218
|
-
{text}
|
|
219
|
-
</Typography>
|
|
220
|
-
),
|
|
221
|
-
)}
|
|
222
|
-
</Typography>
|
|
223
|
-
<Typography
|
|
224
|
-
style={{
|
|
225
|
-
display: 'flex',
|
|
226
|
-
alignItems: 'center',
|
|
227
|
-
marginTop: '8px',
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
<StyledAvatar>
|
|
231
|
-
{item?.userName?.charAt(0)?.toUpperCase()}
|
|
232
|
-
</StyledAvatar>
|
|
233
|
-
<Typography sx={{ fontSize: '13px', fontWeight: 900 }}>
|
|
234
|
-
{item?.userName}
|
|
283
|
+
<TimelineItem
|
|
284
|
+
sx={{
|
|
285
|
+
margin: 2,
|
|
286
|
+
padding: 0,
|
|
287
|
+
'&:before': { display: 'none' },
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<TimelineSeparator>
|
|
291
|
+
<StyledTimeLineDot>
|
|
292
|
+
<StyledCircleIcon />
|
|
293
|
+
</StyledTimeLineDot>
|
|
294
|
+
{index < activitiesData.length - 1 && (
|
|
295
|
+
<Box
|
|
296
|
+
sx={{
|
|
297
|
+
width: '1px',
|
|
298
|
+
height: '115%',
|
|
299
|
+
bgcolor: '#12121233',
|
|
300
|
+
position: 'absolute',
|
|
301
|
+
top: '25px',
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</TimelineSeparator>
|
|
306
|
+
<TimelineContent sx={{ padding: '6px 8px' }}>
|
|
307
|
+
<Box>
|
|
308
|
+
<Typography variant="subtitle2" sx={{ fontSize: '14px' }}>
|
|
309
|
+
{moment
|
|
310
|
+
.utc(item.timestamp)
|
|
311
|
+
.local()
|
|
312
|
+
.format('DD MMM YYYY - hh:mm A')}
|
|
235
313
|
</Typography>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
314
|
+
<Typography sx={{ fontSize: '16px' }} variant="subtitle1">
|
|
315
|
+
{item.message?.split('\n')?.map((line: string, lineIdx: number) => (
|
|
316
|
+
<Box key={lineIdx} component="div">
|
|
317
|
+
{renderMessageWithBold(line)}
|
|
318
|
+
</Box>
|
|
319
|
+
))}
|
|
320
|
+
</Typography>
|
|
321
|
+
<Typography
|
|
322
|
+
style={{
|
|
323
|
+
display: 'flex',
|
|
324
|
+
alignItems: 'center',
|
|
325
|
+
marginTop: '8px',
|
|
326
|
+
}}
|
|
327
|
+
>
|
|
328
|
+
<StyledAvatar>
|
|
329
|
+
{item.userName?.charAt(0)?.toUpperCase()}
|
|
330
|
+
</StyledAvatar>
|
|
331
|
+
<Typography sx={{ fontSize: '13px', fontWeight: 900 }}>
|
|
332
|
+
{item.userName}
|
|
333
|
+
</Typography>
|
|
334
|
+
</Typography>
|
|
335
|
+
</Box>
|
|
336
|
+
</TimelineContent>
|
|
337
|
+
</TimelineItem>
|
|
338
|
+
</Box>
|
|
339
|
+
))}
|
|
340
|
+
</Timeline>
|
|
255
341
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
342
|
+
{/* Loading indicator */}
|
|
343
|
+
{isFetchingNextPage && (
|
|
344
|
+
<Box
|
|
345
|
+
sx={{
|
|
346
|
+
display: 'flex',
|
|
347
|
+
justifyContent: 'center',
|
|
348
|
+
padding: 2,
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
<CircularProgress size={20} />
|
|
352
|
+
</Box>
|
|
353
|
+
)}
|
|
354
|
+
</Box>
|
|
265
355
|
)
|
|
266
|
-
|
|
267
|
-
return handleObserver
|
|
268
356
|
}
|
package/src/config/axios.ts
CHANGED
|
@@ -25,6 +25,7 @@ const workspaceApiMapping: Record<string, string> = {
|
|
|
25
25
|
'hostel-admin-workspace': '/hostel-admin-api',
|
|
26
26
|
'transport-coordinator-workspace': '/transport-coordinator-api',
|
|
27
27
|
'employee-workspace': '/employee-api',
|
|
28
|
+
'librarian-workspace': '/librarian-api',
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export const formatParams = (params) => {
|