308 lines
8.4 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 { AuthContext } from '../context/AuthContext'
import { getModelByName } from '../../../database/ObjectModels'
import { useNavigate } from 'react-router-dom'
const { Text } = Typography
const NoteItem = ({ note }) => {
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
})
}
return (
<Card
key={note._id}
size='small'
style={{
backgroundColor: note.noteType.color + '26',
textAlign: 'left'
}}
>
<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: 0 }} />
<Flex wrap gap={'small'}>
<Dropdown
menu={{ items: dropdownItems }}
trigger={['hover']}
placement='bottomLeft'
>
<Button size='small'>Actions</Button>
</Dropdown>
<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>
<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'}>
<Button
icon={<InfoIcon />}
type='text'
size='small'
onClick={() => {
navigate(infoAction.url(note._id))
}}
/>
<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>
</Card>
)
}
NoteItem.propTypes = {
note: PropTypes.object.isRequired
}
export default NoteItem