import { Add, EditNotifications } from '@mui/icons-material' import { Box, Button, Chip, Input, Modal, ModalDialog, ModalOverflow, Option, Select, Typography, } from '@mui/joy' import { FormControl } from '@mui/material' import * as chrono from 'chrono-node' import moment from 'moment' import { useCallback, useEffect, useRef, useState } from 'react' import { useCreateChore } from '../../queries/ChoreQueries' import { useCircleMembers, useUserProfile } from '../../queries/UserQueries' import { isPlusAccount } from '../../utils/Helpers' import { useLabels } from '../Labels/LabelQueries' import { parseDueDate, parseLabels, parsePriority, parseRepeatV2, } from './CustomParsers' import SmartTaskTitleInput from './SmartTaskTitleInput' import NotificationTemplate from '../../components/NotificationTemplate' import LearnMoreButton from './LearnMore' import RichTextEditor from './RichTextEditor' import SubTasks from './SubTask' const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => { const { data: userLabels, isLoading: userLabelsLoading } = useLabels() const { data: circleMembers, isLoading: isCircleMembersLoading } = useCircleMembers() const createChoreMutation = useCreateChore() const { data: userProfile } = useUserProfile() const [taskText, setTaskText] = useState('') const [taskTitle, setTaskTitle] = useState('') const [renderedParts, setRenderedParts] = useState([]) const textareaRef = useRef(null) const mainInputRef = useRef(null) const [priority, setPriority] = useState(0) const [dueDate, setDueDate] = useState(null) const [description, setDescription] = useState(null) const [assignees, setAssignees] = useState([]) const [labelsV2, setLabelsV2] = useState([]) const [frequency, setFrequency] = useState(null) const [notificationMetadata, setNotificationMetadata] = useState({ templates: [], }) const [frequencyHumanReadable, setFrequencyHumanReadable] = useState(null) const [subTasks, setSubTasks] = useState(null) const [hasDescription, setHasDescription] = useState(false) const [hasSubTasks, setHasSubTasks] = useState(false) const [hasNotifications, setHasNotifications] = useState(false) useEffect(() => { if (isModalOpen && textareaRef.current) { textareaRef.current.focus() textareaRef.current.selectionStart = textareaRef.current.value?.length textareaRef.current.selectionEnd = textareaRef.current.value?.length } }, [isModalOpen]) useEffect(() => { if (autoFocus > 0 && mainInputRef.current) { mainInputRef.current.focus() mainInputRef.current.selectionStart = mainInputRef.current.value?.length mainInputRef.current.selectionEnd = mainInputRef.current.value?.length } }, [autoFocus]) const renderHighlightedSentence = useCallback( ( sentence, repeatHighlight, priorityHighlight, labelsHighlight, dueDateHighlight, ) => { const parts = [] let lastIndex = 0 let plainText = '' // Combine all highlight ranges and sort them by their start index const allHighlights = [] if (repeatHighlight) { repeatHighlight.forEach(h => allHighlights.push({ ...h, type: 'repeat', priority: 40 }), ) } if (priorityHighlight) { priorityHighlight.forEach(h => allHighlights.push({ ...h, type: 'priority', priority: 30 }), ) } if (labelsHighlight) { labelsHighlight.forEach(h => allHighlights.push({ ...h, type: 'label', priority: 20 }), ) } if (dueDateHighlight) { allHighlights.push({ ...dueDateHighlight, type: 'dueDate', priority: 10, }) } allHighlights.sort((a, b) => a.start - b.start) const resolvedHighlights = [] for (let i = 0; i < allHighlights.length; i++) { const current = allHighlights[i] const previous = resolvedHighlights[resolvedHighlights.length - 1] if (previous && current.start < previous.end) { if (current.priority > previous.priority) { resolvedHighlights.pop() resolvedHighlights.push(current) } } else { // No overlap, add the current highlight resolvedHighlights.push(current) } } for (const highlight of resolvedHighlights) { // Add the text before the highlight if (highlight.start > lastIndex) { const textBefore = sentence.substring(lastIndex, highlight.start) parts.push(textBefore) plainText += textBefore } // Determine the class name based on the highlight type let className = '' switch (highlight.type) { case 'repeat': className = 'highlight-repeat' break case 'priority': className = 'highlight-priority' break case 'label': className = 'highlight-label' break case 'dueDate': className = 'highlight-date' break default: break } // Add the highlighted span const highlightedText = sentence.substring( highlight.start, highlight.end, ) parts.push( {highlightedText} , ) // Update the last index to the end of the current highlight lastIndex = highlight.end } // Add any remaining text after the last highlight if (lastIndex < sentence.length) { const remainingText = sentence.substring(lastIndex) parts.push(remainingText) plainText += remainingText } return { parts, plainText, } }, [], ) const processText = useCallback( sentence => { let cleanedSentence = sentence const priority = parsePriority(sentence) if (priority.result) setPriority(priority.result) cleanedSentence = priority.cleanedSentence const labels = parseLabels(sentence, userLabels) if (labels.result) { cleanedSentence = labels.cleanedSentence setLabelsV2(labels.result) } const repeat = parseRepeatV2(sentence) if (repeat.result) { setFrequency(repeat.result) setFrequencyHumanReadable(repeat.name) cleanedSentence = repeat.cleanedSentence } // Parse assignees using circle members // const circleMembersList = circleMembers?.res || [] // const assigneesForParsing = circleMembersList.map(member => ({ // userId: member.userId, // username: // member.username || // member.displayName?.toLowerCase().replace(/\s+/g, ''), // displayName: member.displayName, // name: member.displayName, // id: member.userId, // })) // const assigneesResult = parseAssignees(sentence, assigneesForParsing) // if (assigneesResult.result) { // cleanedSentence = assigneesResult.cleanedSentence // setAssignees( // assigneesResult.result.map(assignee => ({ // userId: assignee.userId, // })), // ) // } else { // setAssignees([ // { // userId: userProfile.id, // }, // ]) // } // Parse due date const dueDateParsed = parseDueDate(sentence, chrono) let dueDateHighlight = null if (dueDateParsed.result) { setDueDate(moment(dueDateParsed.result).format('YYYY-MM-DDTHH:mm:ss')) cleanedSentence = dueDateParsed.cleanedSentence dueDateHighlight = dueDateParsed.highlight[0] } if (repeat.result) { // if repeat has result the cleaned sentence will remove the date related info which mean // we need to reparse the date again to get the correct due date: const dueDateParsedAgain = parseDueDate(sentence, chrono) if (dueDateParsedAgain.result) { setDueDate( moment(dueDateParsedAgain.result).format('YYYY-MM-DDTHH:mm:ss'), ) } } setTaskText(sentence) setTaskTitle(cleanedSentence.trim()) const { parts, plainText } = renderHighlightedSentence( sentence, repeat.highlight, priority.highlight, labels.highlight, dueDateHighlight, ) setRenderedParts(parts) setTaskTitle(plainText) }, [userLabels, renderHighlightedSentence], ) useEffect(() => { if (!isModalOpen || userLabelsLoading || isCircleMembersLoading) { return } processText(taskText) }, [ taskText, userLabelsLoading, isCircleMembersLoading, isModalOpen, processText, ]) const handleEnterPressed = () => { createChore() } const handleCloseModal = forceRefetch => { onClose(forceRefetch) setTaskText('') setTaskTitle('') setDueDate(null) setFrequency(null) setFrequencyHumanReadable(null) setPriority(0) setHasDescription(false) setDescription(null) setSubTasks(null) setHasSubTasks(false) setLabelsV2([]) setAssignees([]) } const handleSubmit = () => { console.log('Submitting task:', isPlusAccount(userProfile)) // createChore() // handleCloseModal() // setTaskText('') } const createChore = () => { const chore = { name: taskTitle, assignees: assignees.length > 0 ? assignees : [{ userId: userProfile.id }], dueDate: dueDate ? new Date(dueDate).toISOString() : null, assignedTo: assignees.length > 0 ? assignees[0].userId : userProfile.id, assignStrategy: 'random', isRolling: false, labelsV2: labelsV2, priority: priority ? Number(priority) : 0, status: 0, frequencyType: 'once', frequencyMetadata: {}, notificationMetadata: {}, subTasks: subTasks?.length > 0 ? subTasks : null, } if (frequency) { chore.frequencyType = frequency.frequencyType chore.frequencyMetadata = frequency.frequencyMetadata chore.frequency = frequency.frequency if (isPlusAccount(userProfile)) { chore.notification = true chore.notificationMetadata = notificationMetadata } } if (!frequency && dueDate) { // use dueDate converted to UTC: chore.nextDueDate = new Date(dueDate).toUTCString() } createChoreMutation .mutateAsync(chore) .then(resp => { resp.json().then(data => { if (resp.status !== 200) { console.error('Error creating chore:', data) return } else { onChoreUpdate({ ...chore, id: data.res, nextDueDate: chore.dueDate, }) handleCloseModal(false) } }) }) .catch(error => { if (error?.queued) { handleCloseModal(true) } }) } if (userLabelsLoading || isCircleMembersLoading) { return <> } return ( Create new task Experimental Feature Task in a sentence: This feature lets you create a task simply by typing a sentence. It attempt parses the sentence to identify the task's due date, priority, and frequency. Examples:
  • Priority:For highest priority any of the following keyword P1, Urgent,{' '} Important, or ASAP. For lower priorities, use P2, P3, or P4 .
  • Due date: Specify dates with phrases like tomorrow, next week,{' '} Monday, or August 1st at 12pm.
  • Frequency: Set recurring tasks with terms like daily, weekly,{' '} monthly, yearly, or patterns such as{' '} every Tuesday and Thursday.
  • } />
    { setTaskText(text) }} customRenderer={renderedParts} onEnterPressed={handleEnterPressed} suggestions={{ '#': { value: 'id', display: 'name', options: userLabels ? userLabels : [], }, '!': { value: 'id', display: 'name', options: [ { id: '1', name: 'P1' }, { id: '2', name: 'P2' }, { id: '3', name: 'P3' }, { id: '4', name: 'P4' }, ], }, '@': { value: 'userId', display: 'displayName', options: circleMembers?.res || [], }, }} />
    {/* Title: setTaskTitle(e.target.value)} sx={{ width: '100%', fontSize: '16px' }} /> */} {!hasDescription && ( )} {!hasSubTasks && ( )} {!dueDate && ( )} {!hasNotifications && dueDate && ( )} {hasDescription && ( Description:
    )} {hasSubTasks && ( Subtasks: )} Priority {dueDate && ( Due Date setDueDate(e.target.value)} sx={{ width: '100%', fontSize: '16px' }} /> )} {/* Assignees {assignees.length > 0 ? ( assignees.map((assignee, index) => ( {assignee.displayName || assignee.username} )) ) : ( {userProfile.displayName} )} */} {hasNotifications && dueDate && ( Notification Schedule { const newNotificaitonMetadata = { ...notificationMetadata, templates: metadata.notifications, } setNotificationMetadata(newNotificaitonMetadata) }} value={notificationMetadata} showTimeline={false} /> )}
    ) } export default TaskInput