Skip to content

Commit

Permalink
feat(notifications): enable auto page loading and sorting (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
oyo authored Jan 10, 2024
1 parent 93210f9 commit 74249ea
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 23 deletions.
30 changes: 22 additions & 8 deletions src/components/pages/NotificationCenter/NotificationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,27 @@ const NotificationContent = ({
const NotificationConfig = ({ item }: { item: CXNotificationContent }) => {
switch (item.typeId) {
case NotificationType.APP_SUBSCRIPTION_ACTIVATION:
return <NotificationContent item={item} navlinks={['usermanagement']} />
return (
<NotificationContent item={item} navlinks={[PAGES.USER_MANAGEMENT]} />
)
case NotificationType.WELCOME:
return <NotificationContent item={item} navlinks={['home']} />
return <NotificationContent item={item} navlinks={[PAGES.HOME]} />
case NotificationType.WELCOME_APP_MARKETPLACE:
return <NotificationContent item={item} navlinks={['appmarketplace']} />
return (
<NotificationContent item={item} navlinks={[PAGES.APP_MARKETPLACE]} />
)
case NotificationType.WELCOME_CONNECTOR_REGISTRATION:
return <NotificationContent item={item} navlinks={['technicalsetup']} />
return (
<NotificationContent item={item} navlinks={[PAGES.TECHNICAL_SETUP]} />
)
case NotificationType.WELCOME_USE_CASES:
return <NotificationContent item={item} navlinks={['usecase']} />
return <NotificationContent item={item} navlinks={[PAGES.USE_CASE]} />
case NotificationType.WELCOME_SERVICE_PROVIDER:
return (
<NotificationContent item={item} navlinks={['servicemarketplace']} />
<NotificationContent
item={item}
navlinks={[PAGES.SERVICE_MARKETPLACE]}
/>
)
case NotificationType.APP_SUBSCRIPTION_REQUEST:
return (
Expand Down Expand Up @@ -237,9 +246,14 @@ export default function NotificationItem({
const dispatch = useDispatch()
const [userRead, setUserRead] = useState<boolean>(false)

const setRead = async (id: string, value: boolean) => {
item = {
...item,
contentParsed: JSON.parse(item.content ?? '{}'),
}

const setRead = async (id: string, flag: boolean) => {
try {
await setNotificationRead({ id, flag: value })
await setNotificationRead({ id, flag })
} catch (error: unknown) {
console.log(error)
}
Expand Down
50 changes: 38 additions & 12 deletions src/components/pages/NotificationCenter/NotificationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

import { type CXNotificationContent } from 'features/notification/types'
import {
type NotificationFetchType,
type CXNotificationContent,
} from 'features/notification/types'
import NotificationItem from './NotificationItem'
import dayjs from 'dayjs'
import isToday from 'dayjs/plugin/isToday'
Expand All @@ -28,29 +31,52 @@ import { useGetNotificationsQuery } from 'features/notification/apiSlice'
import './Notifications.scss'
import { useSelector } from 'react-redux'
import { notificationFetchSelector } from 'features/notification/slice'
import NoItems from '../NoItems'
import NotificationPager from './NotificationPager'

dayjs.extend(isToday)
dayjs.extend(isYesterday)
dayjs.extend(relativeTime)

const NotificationGroup = ({ items }: { items: CXNotificationContent[] }) => {
const NotificationItems = ({ items }: { items: CXNotificationContent[] }) => {
return (
<ul className="group">
{items.length > 0 ? (
items.map((item: CXNotificationContent) => (
<NotificationItem key={item.id} item={item} />
))
) : (
<NoItems />
)}
{items.map((item: CXNotificationContent) => (
<NotificationItem key={item.id} item={item} />
))}
</ul>
)
}

const NotificationGroup = ({
fetchArgs,
page,
}: {
fetchArgs: NotificationFetchType
page: number
}) => {
const { data } = useGetNotificationsQuery({
...fetchArgs,
page,
})
return (
<ul className="group">
<NotificationItems items={data?.content ?? []} />
</ul>
)
}

export default function NotificationList() {
const fetchArgs = useSelector(notificationFetchSelector)
const { data } = useGetNotificationsQuery(fetchArgs)

return <NotificationGroup items={data?.content ?? []} />
return (
<>
{fetchArgs &&
new Array(fetchArgs.page + 1)
.fill(0)
.map((_, i) => (
<NotificationGroup fetchArgs={fetchArgs} key={i} page={i} />
))}
{fetchArgs && <NotificationPager />}
</>
)
}
65 changes: 65 additions & 0 deletions src/components/pages/NotificationCenter/NotificationPager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/********************************************************************************
* Copyright (c) 2021, 2024 BMW Group AG
* Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

import { Box } from '@mui/material'
import { CircleProgress } from '@catena-x/portal-shared-components'
import { notificationFetchSelector, setPage } from 'features/notification/slice'
import { useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import useOnScreen from 'utils/useOnScreen'

export default function NotificationPager() {
const fetchArgs = useSelector(notificationFetchSelector)
const dispatch = useDispatch()
const ref = useRef<HTMLDivElement>(null)
const isVisible = useOnScreen(ref)

const triggerLoad = () => {
setTimeout(() => dispatch(setPage(fetchArgs.page + 1)), 700)
return true
}

return (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: 100,
}}
>
{' '}
{isVisible && (
<CircleProgress
size={40}
step={1}
interval={0.1}
colorVariant={'primary'}
variant={'indeterminate'}
thickness={8}
/>
)}
<div style={{ marginTop: 200 }} ref={ref}>
{isVisible && triggerLoad()}
</div>
</Box>
)
}
50 changes: 48 additions & 2 deletions src/components/pages/NotificationCenter/NotificationSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,64 @@
********************************************************************************/

import DebouncedSearchInput from 'components/shared/basic/Input/DebouncedSearchInput'
import { useDispatch } from 'react-redux'
import { setSearch } from 'features/notification/slice'
import { useDispatch, useSelector } from 'react-redux'
import {
notificationFetchSelector,
setOrder,
setSearch,
} from 'features/notification/slice'
import { useTranslation } from 'react-i18next'
import { SortOption } from '@catena-x/portal-shared-components'
import SortImage from 'components/shared/frame/SortImage'
import { NotificationSortingType } from 'features/notification/types'
import { useState } from 'react'

export default function NotificationSearch() {
const [showOrder, setShowOrder] = useState<boolean>(false)
const order = useSelector(notificationFetchSelector).args.sorting
const dispatch = useDispatch()
const { t } = useTranslation('notification')

const sortOptions = [
{
label: t('sortOptions.new'),
value: NotificationSortingType.DateDesc,
},
{
label: t('sortOptions.oldest'),
value: NotificationSortingType.DateAsc,
},
{
label: t('sortOptions.unread'),
value: NotificationSortingType.ReadStatusAsc,
},
]

return (
<div className="searchContainer">
<DebouncedSearchInput
debounceTime={500}
onSearch={(expr: string) => dispatch(setSearch(expr))}
/>
<div>
<SortImage
onClick={() => {
setShowOrder(true)
}}
selected={showOrder}
/>
<div className="sortSection">
<SortOption
show={showOrder}
selectedOption={order}
setSortOption={(value: string) => {
dispatch(setOrder(value as NotificationSortingType))
setShowOrder(false)
}}
sortOptions={sortOptions}
/>
</div>
</div>
</div>
)
}
5 changes: 4 additions & 1 deletion src/features/notification/apiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
type CXNotification,
type CXNotificationMeta,
type NotificationFetchType,
NotificationSortingType,
} from './types'

export interface NotificationArgsProps {
Expand All @@ -51,7 +52,7 @@ export const apiSlice = createApi({
}),
getNotifications: builder.query<CXNotification, NotificationFetchType>({
query: (fetchArgs) =>
`/api/notification?page=${0}&size=${20}${
`/api/notification?page=${fetchArgs?.page ?? 0}&size=${10}${
fetchArgs?.args?.notificationTopic &&
fetchArgs?.args?.notificationTopic !== NOTIFICATION_TOPIC.ALL
? `&notificationTopicId=${fetchArgs?.args?.notificationTopic}`
Expand All @@ -60,6 +61,8 @@ export const apiSlice = createApi({
fetchArgs?.args?.searchTypeIds
?.map((typeId: NotificationType) => `&searchTypeIds=${typeId}`)
.join('') ?? ''
}&sorting=${
fetchArgs?.args?.sorting ?? NotificationSortingType.DateDesc
}`,
}),
setNotificationRead: builder.mutation<void, NotificationReadType>({
Expand Down
11 changes: 11 additions & 0 deletions src/features/notification/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ export const slice = createSlice({
...state,
fetch: payload.initialNotificationState,
}),
setPage: (state, action: PayloadAction<number>) => ({
...state,
fetch: {
...state.fetch,
page: action.payload,
},
}),
setOrder: (state, action: PayloadAction<NotificationSortingType>) => ({
...state,
fetch: {
...state.fetch,
page: 0,
args: {
...state.fetch.args,
sorting: action.payload,
Expand All @@ -61,6 +69,7 @@ export const slice = createSlice({
...state,
fetch: {
...state.fetch,
page: 0,
args: {
...state.fetch.args,
searchTypeIds: I18nService.searchNotifications(action.payload),
Expand All @@ -71,6 +80,7 @@ export const slice = createSlice({
...state,
fetch: {
...state.fetch,
page: 0,
args: {
...state.fetch.args,
notificationTopic: action.payload,
Expand All @@ -95,6 +105,7 @@ export const {
setTopic,
setSearch,
setOrder,
setPage,
} = slice.actions

export default slice
43 changes: 43 additions & 0 deletions src/utils/useOnScreen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/********************************************************************************
* Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

import { type RefObject, useEffect, useMemo, useState } from 'react'

export default function useOnScreen(ref: RefObject<HTMLElement>) {
const [isIntersecting, setIsIntersecting] = useState<boolean>(false)

const observer = useMemo(
() =>
new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting)
}),
[ref]
)

useEffect(() => {
if (!ref.current) return
observer.observe(ref.current)
return () => {
observer.disconnect()
}
}, [])

return isIntersecting
}

0 comments on commit 74249ea

Please sign in to comment.