farmcontrol-ui/src/components/Dashboard/common/UserNotifierToggle.jsx
Tom Butcher 2622fae555
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Implemented notifications.
2026-03-01 01:42:27 +00:00

233 lines
6.7 KiB
JavaScript

import PropTypes from 'prop-types'
import { useState, useEffect, useContext } from 'react'
import { Button, message, Popover, Typography, Space, Flex } from 'antd'
import { UserOutlined } from '@ant-design/icons'
import BellIcon from '../../Icons/BellIcon'
import NewMailIcon from '../../Icons/NewMailIcon'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import { LoadingOutlined } from '@ant-design/icons'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
const { Text } = Typography
const UserNotifierToggle = ({
type,
objectData,
disabled = false,
...buttonProps
}) => {
const {
toggleUserNotifier,
editUserNotifier,
fetchUserNotifiersForObject,
fetchAllUserNotifiersForObject
} = useContext(ApiServerContext)
const { userProfile } = useContext(AuthContext)
const [isNotifying, setIsNotifying] = useState(false)
const [loading, setLoading] = useState(false)
const [initialLoad, setInitialLoad] = useState(true)
const [allNotifiers, setAllNotifiers] = useState([])
const [popoverOpen, setPopoverOpen] = useState(false)
const [popoverLoading, setPopoverLoading] = useState(false)
const [emailTogglingId, setEmailTogglingId] = useState(null)
const objectId = objectData?._id
useEffect(() => {
const loadNotifierState = async () => {
if (!objectId || !type) return
setInitialLoad(true)
try {
const { data } = await fetchUserNotifiersForObject(objectId, type)
setIsNotifying(data?.length > 0)
} catch (error) {
console.error('Error fetching user notifier state:', error)
} finally {
setInitialLoad(false)
}
}
loadNotifierState()
}, [objectId, type, fetchUserNotifiersForObject])
useEffect(() => {
const loadAllNotifiers = async () => {
if (!objectId || !type || !popoverOpen) return
setPopoverLoading(true)
try {
const { data } = await fetchAllUserNotifiersForObject(objectId, type)
setAllNotifiers(data || [])
} catch (error) {
console.error('Error fetching all user notifiers:', error)
setAllNotifiers([])
} finally {
setPopoverLoading(false)
}
}
loadAllNotifiers()
}, [objectId, type, popoverOpen, fetchAllUserNotifiersForObject])
const handleClick = async () => {
if (!objectId || !type || loading) return
setLoading(true)
try {
const enabled = await toggleUserNotifier(objectId, type)
setIsNotifying(enabled)
if (popoverOpen) {
const { data } = await fetchAllUserNotifiersForObject(objectId, type)
setAllNotifiers(data || [])
}
message.success(
enabled
? 'Notifications enabled for this object'
: 'Notifications disabled for this object'
)
} catch (error) {
console.error('Error toggling user notifier:', error)
message.error('Failed to update notifications')
} finally {
setLoading(false)
}
}
const getUserDisplayName = (user) => {
if (!user) return 'Unknown'
return (
user.name ||
user.username ||
`${user.firstName || ''} ${user.lastName || ''}`.trim() ||
user.email ||
'Unknown'
)
}
const isCurrentUser = (user) => user?._id === userProfile?._id
const handleEmailToggle = async (item) => {
if (!isCurrentUser(item.user) || emailTogglingId) return
setEmailTogglingId(item._id)
const newEmail = !item.email
try {
const result = await editUserNotifier(item._id, { email: newEmail })
if (result) {
setAllNotifiers((prev) =>
prev.map((n) =>
n._id === item._id ? { ...n, email: result.email ?? newEmail } : n
)
)
message.success(
(result.email ?? newEmail)
? 'Email notifications enabled'
: 'Email notifications disabled'
)
}
} catch (error) {
console.error('Error toggling email:', error)
message.error('Failed to update email notifications')
} finally {
setEmailTogglingId(null)
}
}
const popoverContent = (
<Flex
vertical
justify='center'
style={{ minWidth: 240, minHeight: 25 }}
gap={'4px'}
>
{popoverLoading ? (
<Space size={'small'}>
<LoadingOutlined />
<Text style={{ margin: 0 }}>Loading, please wait...</Text>
</Space>
) : allNotifiers.length === 0 ? (
<Space size={'small'}>
<Text style={{ margin: 0 }} type='secondary'>
<InfoCircleIcon />
</Text>
<Text style={{ margin: 0 }} type='secondary'>
No users subscribed.
</Text>
</Space>
) : (
<>
{[...allNotifiers]
.sort(
(a, b) =>
(isCurrentUser(b.user) ? 1 : 0) -
(isCurrentUser(a.user) ? 1 : 0)
)
.map((item) => (
<Flex key={item._id} justify='space-between' align='center'>
<Flex align='center' gap={'6px'}>
<UserOutlined />
<Text>{getUserDisplayName(item.user)}</Text>
{isCurrentUser(item.user) && (
<Text type='secondary'> (you)</Text>
)}
</Flex>
<Space size={'small'}>
<Button
type='text'
icon={
<NewMailIcon
style={{
color: item.email ? 'var(--color-primary)' : undefined
}}
/>
}
size='small'
disabled={!isCurrentUser(item.user)}
loading={emailTogglingId === item._id}
onClick={() => handleEmailToggle(item)}
/>
</Space>
</Flex>
))}
</>
)}
</Flex>
)
return (
<Popover
content={popoverContent}
title={null}
trigger='hover'
placement='bottomLeft'
arrow={false}
open={popoverOpen}
onOpenChange={setPopoverOpen}
styles={{ body: { padding: '10px 15px' } }}
>
<Button
{...buttonProps}
icon={
<BellIcon
style={{
color: isNotifying ? 'var(--color-warning)' : undefined
}}
/>
}
disabled={disabled || loading || initialLoad}
loading={loading || !objectId || !type}
onClick={handleClick}
/>
</Popover>
)
}
UserNotifierToggle.propTypes = {
type: PropTypes.string.isRequired,
objectData: PropTypes.object.isRequired,
disabled: PropTypes.bool
}
export default UserNotifierToggle