All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
351 lines
9.5 KiB
JavaScript
351 lines
9.5 KiB
JavaScript
import { useState, useContext, useCallback, useEffect, useRef } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import {
|
|
Card,
|
|
Button,
|
|
Space,
|
|
Typography,
|
|
Flex,
|
|
Tag,
|
|
Dropdown,
|
|
Divider,
|
|
Modal
|
|
} from 'antd'
|
|
import { CaretLeftFilled, LoadingOutlined } from '@ant-design/icons'
|
|
import PlusIcon from '../../Icons/PlusIcon'
|
|
import BinIcon from '../../Icons/BinIcon'
|
|
import PersonIcon from '../../Icons/PersonIcon'
|
|
import TimeDisplay from './TimeDisplay'
|
|
import MarkdownDisplay from './MarkdownDisplay'
|
|
import IdDisplay from './IdDisplay'
|
|
import MissingPlaceholder from './MissingPlaceholder'
|
|
import NewNote from '../Management/Notes/NewNote'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
|
import UserNotifierToggle from './UserNotifierToggle'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
import { getModelByName } from '../../../database/ObjectModels'
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
const { Text } = Typography
|
|
|
|
const NoteItem = ({
|
|
note,
|
|
showCard = true,
|
|
showCreatedAt = true,
|
|
showChildNotes = true,
|
|
showActions = true,
|
|
showInfo = true,
|
|
largeSpacing = false
|
|
}) => {
|
|
const [childNotes, setChildNotes] = useState([])
|
|
const noteModel = getModelByName('note')
|
|
const infoAction = noteModel.actions.filter(
|
|
(action) => action.name == 'info'
|
|
)[0]
|
|
const InfoIcon = infoAction.icon
|
|
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
|
const [deleteNoteOpen, setDeleteNoteOpen] = useState(false)
|
|
const [deleteNoteLoading, setDeleteNoteLoading] = useState(false)
|
|
const [childNotesLoading, setChildNotesLoading] = useState(false)
|
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
const subscribeToObjectTypeUpdatesRef = useRef(null)
|
|
const navigate = useNavigate()
|
|
|
|
const {
|
|
deleteObject,
|
|
fetchObjects,
|
|
subscribeToObjectTypeUpdates,
|
|
connected
|
|
} = useContext(ApiServerContext)
|
|
const { userProfile, token } = useContext(AuthContext)
|
|
|
|
let transformValue = 'rotate(0deg)'
|
|
if (isExpanded) {
|
|
transformValue = 'rotate(-90deg)'
|
|
}
|
|
|
|
const handleNoteExpand = useCallback(async () => {
|
|
setChildNotesLoading(true)
|
|
try {
|
|
const childNotesData = await fetchObjects('note', {
|
|
filter: { 'parent._id': note._id }
|
|
})
|
|
setChildNotes(childNotesData.data)
|
|
} catch (error) {
|
|
console.error('Error fetching child notes:', error)
|
|
} finally {
|
|
setChildNotesLoading(false)
|
|
}
|
|
}, [note._id, fetchObjects])
|
|
|
|
const toggleExpand = async () => {
|
|
if (isExpanded == false) {
|
|
await handleNoteExpand()
|
|
setIsExpanded(true)
|
|
} else {
|
|
setChildNotes([])
|
|
setIsExpanded(false)
|
|
}
|
|
}
|
|
|
|
const handleDeleteNote = async () => {
|
|
if (token != null) {
|
|
setDeleteNoteLoading(true)
|
|
await deleteObject(note._id, 'note')
|
|
setDeleteNoteOpen(false)
|
|
setDeleteNoteLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
|
|
if (isExpanded == true) {
|
|
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
|
'note',
|
|
(noteData) => {
|
|
if (noteData.parent._id == note._id) {
|
|
if (isExpanded == true) {
|
|
handleNoteExpand()
|
|
}
|
|
}
|
|
}
|
|
)
|
|
} else {
|
|
if (subscribeToObjectTypeUpdatesRef.current) {
|
|
subscribeToObjectTypeUpdatesRef.current()
|
|
subscribeToObjectTypeUpdatesRef.current = null
|
|
}
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
|
|
subscribeToObjectTypeUpdatesRef.current()
|
|
subscribeToObjectTypeUpdatesRef.current = null
|
|
}
|
|
}
|
|
}, [
|
|
subscribeToObjectTypeUpdates,
|
|
connected,
|
|
handleNoteExpand,
|
|
isExpanded,
|
|
note._id
|
|
])
|
|
|
|
// Check if the current user can delete this note
|
|
const canDeleteNote = userProfile && userProfile._id === note.user._id
|
|
|
|
const dropdownItems = [
|
|
{
|
|
key: 'new',
|
|
icon: <PlusIcon />,
|
|
label: 'New Note',
|
|
onClick: () => {
|
|
setNewNoteOpen(true)
|
|
}
|
|
}
|
|
]
|
|
|
|
// Only add delete option if user owns the note
|
|
if (canDeleteNote) {
|
|
dropdownItems.push({
|
|
key: 'delete',
|
|
label: 'Delete Note',
|
|
icon: <BinIcon />,
|
|
onClick: () => {
|
|
setDeleteNoteOpen(true)
|
|
},
|
|
danger: true
|
|
})
|
|
}
|
|
|
|
const noteItem = (
|
|
<>
|
|
<Flex vertical gap={'small'}>
|
|
<Flex gap={'middle'} align='start'>
|
|
<Space>
|
|
<PersonIcon />
|
|
<Text style={{ whiteSpace: 'nowrap' }}>{note.user.name}:</Text>
|
|
</Space>
|
|
<div style={{ marginBottom: '4px' }}>
|
|
<MarkdownDisplay content={note.content} />
|
|
</div>
|
|
</Flex>
|
|
<Divider style={{ margin: largeSpacing ? '10px 0' : 0 }} />
|
|
<Flex wrap gap={'small'}>
|
|
{showActions && (
|
|
<>
|
|
<Dropdown
|
|
menu={{ items: dropdownItems }}
|
|
trigger={['hover']}
|
|
placement='bottomLeft'
|
|
>
|
|
<Button size='small'>Actions</Button>
|
|
</Dropdown>
|
|
<UserNotifierToggle
|
|
type='note'
|
|
size='small'
|
|
objectData={note}
|
|
disabled={false}
|
|
/>
|
|
</>
|
|
)}
|
|
<Space size={'small'} style={{ marginRight: 8 }}>
|
|
<Text type='secondary'>Type:</Text>
|
|
<Tag color={note.noteType.color} style={{ margin: 0 }}>
|
|
{note.noteType.name}
|
|
</Tag>
|
|
</Space>
|
|
<Space size={'small'} style={{ marginRight: 8 }}>
|
|
<Text type='secondary'>User ID:</Text>
|
|
<IdDisplay
|
|
longId={false}
|
|
id={note.user._id}
|
|
type={'user'}
|
|
showHyperlink={true}
|
|
/>
|
|
</Space>
|
|
{showCreatedAt && (
|
|
<Space size={'small'} style={{ marginRight: 8 }}>
|
|
<Text type='secondary'>Created At:</Text>
|
|
<TimeDisplay dateTime={note.createdAt} showSince={true} />
|
|
</Space>
|
|
)}
|
|
<Flex style={{ flexGrow: 1 }} justify='end'>
|
|
<Space size={'small'}>
|
|
{showInfo && (
|
|
<Button
|
|
icon={<InfoIcon />}
|
|
type='text'
|
|
size='small'
|
|
onClick={() => {
|
|
navigate(infoAction.url(note._id))
|
|
}}
|
|
/>
|
|
)}
|
|
{showChildNotes && (
|
|
<Button
|
|
icon={
|
|
childNotesLoading ? (
|
|
<LoadingOutlined />
|
|
) : (
|
|
<CaretLeftFilled />
|
|
)
|
|
}
|
|
size='small'
|
|
type='text'
|
|
loading={childNotesLoading}
|
|
disabled={childNotesLoading}
|
|
style={{
|
|
transform: transformValue,
|
|
transition: 'transform 0.2s ease'
|
|
}}
|
|
onClick={toggleExpand}
|
|
/>
|
|
)}
|
|
</Space>
|
|
</Flex>
|
|
</Flex>
|
|
{isExpanded && (
|
|
<Flex vertical gap={'small'} style={{ flexGrow: 1 }}>
|
|
{childNotes.length > 0 ? (
|
|
childNotes.map((childNote) => (
|
|
<NoteItem key={childNote._id} note={childNote} />
|
|
))
|
|
) : (
|
|
<MissingPlaceholder message={'No child notes.'} />
|
|
)}
|
|
</Flex>
|
|
)}
|
|
<Flex vertical gap={'middle'}></Flex>
|
|
</Flex>
|
|
<Modal
|
|
open={newNoteOpen}
|
|
onCancel={() => {
|
|
setNewNoteOpen(false)
|
|
}}
|
|
width={800}
|
|
closeIcon={false}
|
|
destroyOnHidden={true}
|
|
footer={null}
|
|
>
|
|
<NewNote
|
|
onOk={() => {
|
|
setNewNoteOpen(false)
|
|
}}
|
|
defaultValues={{
|
|
parent: { _id: note._id },
|
|
parentType: 'note'
|
|
}}
|
|
/>
|
|
</Modal>
|
|
<Modal
|
|
open={deleteNoteOpen}
|
|
title={
|
|
<Space size={'middle'}>
|
|
<ExclamationOctagonIcon />
|
|
Confirm Delete
|
|
</Space>
|
|
}
|
|
okText='Delete'
|
|
cancelText='Cancel'
|
|
okType='danger'
|
|
closable={false}
|
|
centered
|
|
maskClosable={false}
|
|
footer={[
|
|
<Button
|
|
key='cancel'
|
|
onClick={() => {
|
|
setDeleteNoteOpen(false)
|
|
}}
|
|
disabled={deleteNoteLoading}
|
|
>
|
|
Cancel
|
|
</Button>,
|
|
<Button
|
|
key='delete'
|
|
type='primary'
|
|
danger
|
|
onClick={handleDeleteNote}
|
|
loading={deleteNoteLoading}
|
|
disabled={deleteNoteLoading}
|
|
>
|
|
Delete
|
|
</Button>
|
|
]}
|
|
>
|
|
<Text>Are you sure you want to delete this note?</Text>
|
|
</Modal>
|
|
</>
|
|
)
|
|
|
|
return showCard ? (
|
|
<Card
|
|
key={note._id}
|
|
size='small'
|
|
style={{
|
|
backgroundColor: note.noteType.color + '26',
|
|
textAlign: 'left'
|
|
}}
|
|
>
|
|
{noteItem}
|
|
</Card>
|
|
) : (
|
|
noteItem
|
|
)
|
|
}
|
|
|
|
NoteItem.propTypes = {
|
|
note: PropTypes.object.isRequired,
|
|
showCard: PropTypes.bool,
|
|
showCreatedAt: PropTypes.bool,
|
|
showChildNotes: PropTypes.bool,
|
|
showActions: PropTypes.bool,
|
|
largeSpacing: PropTypes.bool,
|
|
showInfo: PropTypes.bool
|
|
}
|
|
|
|
export default NoteItem
|