import { Add } from '@mui/icons-material'
import {
Box,
Button,
Chip,
Input,
Modal,
ModalDialog,
ModalOverflow,
Option,
Select,
Textarea,
Typography,
} from '@mui/joy'
import { FormControl } from '@mui/material'
import * as chrono from 'chrono-node'
import moment from 'moment'
import { useContext, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { UserContext } from '../../contexts/UserContext'
import { useCreateChore } from '../../queries/ChoreQueries'
import { useAllUsers } from '../../queries/UserQueries'
import useDebounce from '../../utils/Debounce'
import { isPlusAccount } from '../../utils/Helpers'
import { useLabels } from '../Labels/LabelQueries'
import SmartTaskTitleInput from '../TestView/SmartTaskTitleInput'
import { parseLabels, parsePriority, parseRepeatV2 } from './CustomParsers'
import LearnMoreButton from './LearnMore'
import SubTasks from './SubTask'
const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
const { data: allUsers, isLoading: isAllUserLoading } = useAllUsers()
const createChoreMutation = useCreateChore()
const { userProfile } = useContext(UserContext)
const navigate = useNavigate()
const [taskText, setTaskText] = useState('')
const debounceParsing = useDebounce(taskText, 30)
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 [assignedTo, setAssignedTo] = useState(userProfile?.id)
const [assignees, setAssignees] = useState([])
const [labelsV2, setLabelsV2] = useState([])
const [frequency, setFrequency] = useState(null)
const [frequencyHumanReadable, setFrequencyHumanReadable] = useState(null)
const [subTasks, setSubTasks] = useState(null)
const [hasDescription, setHasDescription] = useState(false)
const [hasSubTasks, setHasSubTasks] = 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])
useEffect(() => {
if (!isModalOpen || userLabelsLoading || isAllUserLoading) {
return
}
processText(taskText)
}, [taskText, userLabelsLoading, isAllUserLoading])
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([])
}
const handleSubmit = () => {
createChore()
handleCloseModal()
setTaskText('')
}
const handleTextChange = e => {
if (!e.target.value) {
setTaskText('')
setDueDate(null)
setFrequency(null)
setFrequencyHumanReadable(null)
setPriority(0)
return
}
setTaskText(e.target.value)
}
const processText = 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
}
// const assignees = parseAssignees(sentence)
// if (assignees.result) {
// cleanedSentence = assignees.cleanedSentence
// set
// }
const parsedDueDate = chrono.parse(sentence, new Date(), {
forwardDate: true,
})
if (parsedDueDate[0]?.index > -1) {
setDueDate(
moment(parsedDueDate[0].start.date()).format('YYYY-MM-DDTHH:mm:ss'),
)
cleanedSentence = cleanedSentence.replace(parsedDueDate[0].text, '')
}
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 parsedDueDate = chrono.parse(sentence, new Date(), {
forwardDate: true,
})
if (parsedDueDate[0]?.index > -1) {
setDueDate(
moment(parsedDueDate[0].start.date()).format('YYYY-MM-DDTHH:mm:ss'),
)
}
}
setTaskText(sentence)
setTaskTitle(cleanedSentence.trim())
const rendered = renderText(
sentence,
repeat.highlight,
priority.highlight,
labels.highlight,
parsedDueDate && parsedDueDate[0]
? {
start: parsedDueDate[0].index,
end: parsedDueDate[0].index + parsedDueDate[0].text.length,
text: parsedDueDate[0].text,
}
: null,
)
setRenderedParts(rendered)
}
const renderText = (
sentence,
repeatHighlight,
priorityHighlight,
labelsHighlight,
dueDateHighlight,
) => {
const parts = []
let lastIndex = 0
// Combine all highlight ranges and sort them by their start index
const allHighlights = []
if (repeatHighlight) {
repeatHighlight.forEach(h => allHighlights.push({ ...h, type: 'repeat' }))
}
if (priorityHighlight) {
priorityHighlight.forEach(h =>
allHighlights.push({ ...h, type: 'priority' }),
)
}
if (labelsHighlight) {
labelsHighlight.forEach(h => allHighlights.push({ ...h, type: 'label' }))
}
if (dueDateHighlight) {
allHighlights.push({ ...dueDateHighlight, type: 'dueDate' })
}
allHighlights.sort((a, b) => a.start - b.start)
for (const highlight of allHighlights) {
// Add the text before the highlight
if (highlight.start > lastIndex) {
parts.push(sentence.substring(lastIndex, highlight.start))
}
// 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
parts.push(
{sentence.substring(highlight.start, highlight.end)}
,
)
// 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) {
parts.push(sentence.substring(lastIndex))
}
return parts
}
const createChore = () => {
const chore = {
name: taskTitle,
assignees: [{ userId: userProfile.id }],
dueDate: dueDate ? new Date(dueDate).toISOString() : null,
assignedTo: userProfile.id,
assignStrategy: 'random',
isRolling: false,
notification: false,
description: description || null,
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()) {
chore.notification = true
chore.notificationMetadata = { dueDate: true }
}
}
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 || isAllUserLoading) {
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 : [],
},
'!': ['P1', 'P2', 'P3', 'P4'],
'@': {
// value: 'userId',
// display: 'displayName',
// options: allUsers ? allUsers : [],
value: 'id',
display: 'name',
options: [
{ id: userProfile.id, name: userProfile.displayName },
],
},
}}
/>
{/*
Title:
setTaskTitle(e.target.value)}
sx={{ width: '100%', fontSize: '16px' }}
/>
*/}
{!hasDescription && (
}
variant='plain'
size='sm'
onClick={() => setHasDescription(true)}
>
Description
)}
{!hasSubTasks && (
}
variant='plain'
size='sm'
onClick={() => setHasSubTasks(true)}
>
Subtasks
)}
{!dueDate && (
}
variant='plain'
size='sm'
onClick={() => {
setDueDate(
moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'),
)
}}
>
Due Date
)}
{hasDescription && (
Description:
)}
{hasSubTasks && (
Subtasks:
)}
Priority
{dueDate && (
Due Date
setDueDate(e.target.value)}
sx={{ width: '100%', fontSize: '16px' }}
/>
)}
Assignee
Frequency
)
}
export default TaskInput